@@ -327,57 +327,20 @@ public static function stringFromColumnIndex($columnIndex)
327327 }
328328
329329 /**
330- * Extract all cell references in range.
330+ * Extract all cell references in range, which may be comprised of multiple cell ranges .
331331 *
332332 * @param string $pRange Range (e.g. A1 or A1:C10 or A1:E10 A20:E25)
333333 *
334334 * @return array Array containing single cell references
335335 */
336336 public static function extractAllCellReferencesInRange ($ pRange )
337337 {
338- // Returnvalue
339338 $ returnValue = [];
340339
341340 // Explode spaces
342- $ cellBlocks = explode ( ' ' , str_replace ( ' $ ' , '' , strtoupper ( $ pRange)) );
341+ $ cellBlocks = self :: getCellBlocksFromRangeString ( $ pRange );
343342 foreach ($ cellBlocks as $ cellBlock ) {
344- // Single cell?
345- if (!self ::coordinateIsRange ($ cellBlock )) {
346- $ returnValue [] = $ cellBlock ;
347-
348- continue ;
349- }
350-
351- // Range...
352- $ ranges = self ::splitRange ($ cellBlock );
353- foreach ($ ranges as $ range ) {
354- // Single cell?
355- if (!isset ($ range [1 ])) {
356- $ returnValue [] = $ range [0 ];
357-
358- continue ;
359- }
360-
361- // Range...
362- list ($ rangeStart , $ rangeEnd ) = $ range ;
363- sscanf ($ rangeStart , '%[A-Z]%d ' , $ startCol , $ startRow );
364- sscanf ($ rangeEnd , '%[A-Z]%d ' , $ endCol , $ endRow );
365- ++$ endCol ;
366-
367- // Current data
368- $ currentCol = $ startCol ;
369- $ currentRow = $ startRow ;
370-
371- // Loop cells
372- while ($ currentCol != $ endCol ) {
373- while ($ currentRow <= $ endRow ) {
374- $ returnValue [] = $ currentCol . $ currentRow ;
375- ++$ currentRow ;
376- }
377- ++$ currentCol ;
378- $ currentRow = $ startRow ;
379- }
380- }
343+ $ returnValue = array_merge ($ returnValue , static ::getReferencesForCellBlock ($ cellBlock ));
381344 }
382345
383346 // Sort the result by column and row
@@ -392,6 +355,72 @@ public static function extractAllCellReferencesInRange($pRange)
392355 return array_values ($ sortKeys );
393356 }
394357
358+ /**
359+ * Get all cell references for an individual cell block.
360+ *
361+ * @param string $cellBlock A cell range e.g. A4:B5
362+ *
363+ * @throws Exception
364+ *
365+ * @return array All individual cells in that range
366+ */
367+ private static function getReferencesForCellBlock ($ cellBlock )
368+ {
369+ $ returnValue = [];
370+
371+ // Single cell?
372+ if (!self ::coordinateIsRange ($ cellBlock )) {
373+ return (array ) $ cellBlock ;
374+ }
375+
376+ // Range...
377+ $ ranges = self ::splitRange ($ cellBlock );
378+ foreach ($ ranges as $ range ) {
379+ // Single cell?
380+ if (!isset ($ range [1 ])) {
381+ $ returnValue [] = $ range [0 ];
382+
383+ continue ;
384+ }
385+
386+ // Range...
387+ list ($ rangeStart , $ rangeEnd ) = $ range ;
388+ list ($ startCol , $ startRow ) = static ::extractColumnAndRow ($ rangeStart );
389+ list ($ endCol , $ endRow ) = static ::extractColumnAndRow ($ rangeEnd );
390+ ++$ endCol ;
391+
392+ // Current data
393+ $ currentCol = $ startCol ;
394+ $ currentRow = $ startRow ;
395+
396+ static ::validateRange ($ cellBlock , $ startCol , $ endCol , $ currentRow , $ endRow );
397+
398+ // Loop cells
399+ while ($ currentCol < $ endCol ) {
400+ while ($ currentRow <= $ endRow ) {
401+ $ returnValue [] = $ currentCol . $ currentRow ;
402+ ++$ currentRow ;
403+ }
404+ ++$ currentCol ;
405+ $ currentRow = $ startRow ;
406+ }
407+ }
408+
409+ return $ returnValue ;
410+ }
411+
412+ /**
413+ * Extract the column and row from a cell reference in the format [$column, $row].
414+ *
415+ * @param string $cell
416+ *
417+ * @return array
418+ */
419+ private static function extractColumnAndRow ($ cell )
420+ {
421+ return sscanf ($ cell , '%[A-Z]%d ' );
422+ }
423+
395424 /**
396425 * Convert an associative array of single cell coordinates to values to an associative array
397426 * of cell ranges to values. Only adjacent cell coordinates with the same
@@ -477,4 +506,35 @@ public static function mergeRangesInCollection(array $pCoordCollection)
477506
478507 return $ mergedCoordCollection ;
479508 }
509+
510+ /**
511+ * Get the individual cell blocks from a range string, splitting by space and removing any $ characters.
512+ *
513+ * @param string $pRange
514+ *
515+ * @return string[]
516+ */
517+ private static function getCellBlocksFromRangeString ($ pRange )
518+ {
519+ return explode (' ' , str_replace ('$ ' , '' , strtoupper ($ pRange )));
520+ }
521+
522+ /**
523+ * Check that the given range is valid, i.e. that the start column and row are not greater than the end column and
524+ * row.
525+ *
526+ * @param string $cellBlock The original range, for displaying a meaningful error message
527+ * @param string $startCol
528+ * @param string $endCol
529+ * @param int $currentRow
530+ * @param int $endRow
531+ *
532+ * @throws Exception
533+ */
534+ private static function validateRange ($ cellBlock , $ startCol , $ endCol , $ currentRow , $ endRow )
535+ {
536+ if ($ startCol >= $ endCol || $ currentRow > $ endRow ) {
537+ throw new Exception ('Invalid range: " ' . $ cellBlock . '" ' );
538+ }
539+ }
480540}
0 commit comments