File Coverage

lib/Google/RestApi/SheetsApi4/Range.pm
Criterion Covered Total %
statement 225 311 72.3
branch 47 72 65.2
condition 26 37 70.2
subroutine 38 52 73.0
pod 25 34 73.5
total 361 506 71.3


line stmt bran cond sub pod time code
1              
2             # some private subroutines here are called by RangeGroup, so think of RangeGroup as
3             # a friend of Range. the routines RangeGroup calls are commented thusly:
4             # "private range routine called here!"
5              
6             # there are different ways to specify ranges. this came about by merging different
7             # ideas from different spreadsheet implementations.
8              
9             # column:
10             # A ====> A:A
11             # A:A ====> A:A
12             # [A] ====> A:A
13             # [[A]] ====> A:A
14             # [1] ====> A:A
15             # [[1]] ====> A:A
16             # {col => A} ====> A:A
17             # [{col => A}] ====> A:A
18             # {col => 1} ====> A:A
19             # [{col => 1}] ====> A:A
20             # a partial column is still considered a column
21             # A5:A10 ====> A5:A10
22              
23             # row:
24             # 1 ====> 1:1
25             # 1:1 ====> 1:1
26             # [undef, 1] ====> 1:1
27             # [[undef, 1]] ====> 1:1
28             # 0's are 'valid' in that they are ignored.
29             # [0, 1] ====> 1:1
30             # [[0, 1]] ====> 1:1
31             # {row => 1} ====> 1:1
32             # [{row => 1}] ====> 1:1
33             # a partial row is still considered a row
34             # D1:1 ====> D1:1
35              
36             # partial rows/columns:
37             # [[A, 5], [A]] ====> A5:A
38             # [{col => A, row => 5}, {col => A}] ====> A5:A
39             # [[A], [A, 5]] ====> A:A5
40             # [{col => A}, {col => A, row => 5}] ====> A:A5
41             # [[5, 1], [undef, 1]] ====> E1:1
42             # [[5, 1], [0, 1]] ====> E1:1
43             # [{col => 5, row => 1}, {row => 1}] ====> E1:1
44              
45             # single cell:
46             # A1 ====> A1
47             # [A, 1] ====> A1
48             # [[A, 1]] ====> A1
49             # [1, 1] ====> A1
50             # [[1, 1]] ====> A1
51             # {col => A, row => 1} ====> A1
52             # [{col => A, row => 1}] ====> A1
53             # {col => 1, row => 1} ====> A1
54             # [{col => 1, row => 1}] ====> A1
55              
56             # same cell twice gets reduced to single cell:
57             # A1:A1 ====> A1
58             # these only get reduced using the factory method.
59             # [[A, 1], [A, 1]] ====> A1
60             # [[1, 1], [1, 1]] ====> A1
61             # [{col => A, row => 1}, {col => A, row => 1}] ====> A1
62             # [{col => 1, row => 1}, {col => 1, row => 1}] ====> A1
63              
64             # general ranges:
65             # A1:B2 ====> A1:B2
66             # [[1,1],[2,2]] ====> A1:B2
67             # [[A,1],[B,2]] ====> A1:B2
68             # [{col => 1, row => 1}, {col => 2, row => 2}] ====> A1:B2
69             # [{col => A, row => 1}, {col => B, row => 2}] ====> A1:B2
70              
71             # mixing is ok:
72             # [A1, [2, 2]] ====> A1:B2
73             # [{col => 1, row => 1}, [2, 2]] ====> A1:B2
74             # [{col => 1. row => 1}, B2] ====> A1:B2
75              
76             # bad ranges:
77             # should be able to support this but makes range routine
78             # too complex, and the simple workaround is to just A1:B2.
79             # [A1, B2]
80              
81             our $VERSION = '1.0.2';
82              
83             use Google::RestApi::Setup;
84 1     1   596  
  1         3  
  1         7  
85             use Carp qw( confess );
86 1     1   19193 use List::Util qw( max );
  1         2  
  1         46  
87 1     1   8 use Readonly;
  1         2  
  1         50  
88 1     1   495 use Scalar::Util qw( looks_like_number );
  1         3596  
  1         49  
89 1     1   6 use Try::Tiny qw( try catch );
  1         2  
  1         48  
90 1     1   5  
  1         2  
  1         41  
91             use experimental qw( switch );
92 1     1   501  
  1         3136  
  1         5  
93             use aliased 'Google::RestApi::SheetsApi4::Range::Col';
94 1     1   88 use aliased 'Google::RestApi::SheetsApi4::Range::Row';
  1         2  
  1         7  
95 1     1   85 use aliased 'Google::RestApi::SheetsApi4::Range::Cell';
  1         3  
  1         3  
96 1     1   83 use aliased 'Google::RestApi::SheetsApi4::Range::Iterator';
  1         3  
  1         4  
97 1     1   146  
  1         2  
  1         4  
98             use parent 'Google::RestApi::SheetsApi4::Request::Spreadsheet::Worksheet::Range';
99 1     1   144  
  1         3  
  1         8  
100             Readonly::Scalar our $RANGE_EXPANDED => 1;
101              
102             # this routine returns the best fitting object for the range specified.
103             # A5:A10 will return a Col object. A5:J5 will return a Row object. Etc.
104             # this grew organically into something more complicated that it was worth,
105             # but i got it working reliably so, whatever...
106             my %original_range_args = @_;
107             my $original_range = $original_range_args{range};
108 90     90 0 431  
109 90         330 state $check = multisig(
110             compile_named(
111 90         258 worksheet => HasApi,
112             range => RangeCol,
113             dim => DimColRow, { optional => 1 }, # is switched to 'col'.
114             ),
115             compile_named(
116             worksheet => HasApi,
117             range => RangeRow,
118             dim => DimColRow, { optional => 1 }, # is switched to 'row'
119             ),
120             compile_named(
121             worksheet => HasApi,
122             range => RangeCell,
123             dim => DimColRow, { optional => 1 }, # doesn't matter for cells.
124             ),
125             compile_named(
126             worksheet => HasApi,
127             range => RangeAny,
128             dim => DimColRow, { default => 'row' },
129             ),
130             compile_named(
131             worksheet => HasApi,
132             range => RangeNamed,
133             dim => DimColRow, { default => 'row' },
134             ),
135             );
136             my @range_args = $check->(@_);
137             my $range_args = $range_args[0]; # no idea why it returns an arrayref pointer to a hashref.
138 90         9340 my $range = $range_args->{range};
139 90         11021
140 90         374 # be careful here, recursive.
141             given (${^TYPE_PARAMS_MULTISIG}) {
142             when (0) { return Col->new(%$range_args); }
143 90         195 when (1) { return Row->new(%$range_args); }
144 90         367 when (2) { return Cell->new(%$range_args); }
  11         160  
145 79         199  
  12         152  
146 67         203 # if we've translated a range, it now may be a better fit for one of the above.
  43         638  
147             when (3) {
148             # convert the range to A1:A1 format and redrive the factory routine to see
149 24         80 # if it ends up being a col, row, or cell range.
150             if ($range ne $original_range) {
151             DEBUG(sprintf("Range '%s' converted to '$range'", flatten_range($original_range)));
152 23 100       90 # resolve cells by collapsing A1:A1 to just A1. also A:A and 1:1 will be
153 9         93 # properly resolved to cols/rows.
154             my ($start, $end) = split(':', $range);
155             $range = $start if $end && $start eq $end;
156 9         129 return factory(%$range_args); ##### recursion
157 9 50 66     56 }
158 9         65 # we're already in A1:A1 format so just create a new range object.
159             return __PACKAGE__->new(%$range_args);
160             }
161 14         143  
162             # range could be a named range or a column/row header. we have to resolve the
163             # range first, then see what the best fit will be above.
164             when (4) {
165             my $worksheet = $range_args->{worksheet};
166 1         3  
167 1         3 my $named = $range;
168             $range = $worksheet->resolve_header_range($named);
169 1         3 if ($range) {
170 1         6 $range = factory(%$range_args, range => $range); ##### recursion
171 1 50       4 $range->{header_name} = $named;
172 0         0 return $range;
173 0         0 }
174 0         0  
175             $range = $worksheet->normalize_named($named)
176             or LOGDIE("Unable to resolve named range '$named'");
177 1 50       6 # we've resolved the name to A1 format, so redrive factory routine to
178             # generate a range object with the resolved range.
179             $range = factory(%$range_args, range => $range); ##### recursion
180             $range->{named} = $named;
181 1         10 return $range;
182 1         4 }
183 1         8 }
184              
185             LOGDIE("Unable to resolve '$range_args->{range}' to a range object");
186             }
187 0         0  
188             # this should not normally be called directly, everything should be created via
189             # the factory method above or more commonly via the 'worksheet::range*' methods.
190             my $class = shift;
191              
192             # TODO: sort start range and end range before storing.
193 220     220 1 627  
194             state $check = compile_named(
195             worksheet => HasMethods[qw(api worksheet_name)],
196             range => RangeAny,
197 220         520 dim => DimColRow, { default => 'row' },
198             );
199             my $self = $check->(@_);
200             $self->{dim} = dims_any($self->{dim}); # convert internally to COLUMN | ROW
201             DEBUG("New range " . flatten_range($self->{range}) . " has been created");
202 220         6313  
203 220         15652 return bless $self, $class;
204 220         905 }
205              
206 220         3586 my $self = shift;
207             my $range = $self->range();
208             DEBUG("Clearing range '$range'");
209             my %p = (
210 0     0 1 0 uri => "/values/$range:clear",
211 0         0 method => 'post',
212 0         0 );
213 0         0 my $response = $self->api(%p);
214             $self->clear_cached_values();
215             return $response;
216             }
217 0         0  
218 0         0  
219 0         0 my $self = shift;
220             $self->clear_cached_values();
221             return $self->values();
222 0     0 0 0 }
223              
224             # this gets or sets the values of a range immediately, no batch.
225 0     0 1 0 # if no values passed, gets them. if values passed, sets them.
226 0         0 my $self = shift;
227 0         0 my %p = @_;
228             $self->_send_values(%p) if defined $p{values};
229             return $self->_cache_range_values(%p)->{values};
230             }
231              
232             # immediately update the values for a range (no batch).
233 143     143 1 257 my $self = shift;
234 143         256  
235 143 100       404 state $check = compile_named(
236 143         405 values => ArrayRef[ArrayRef[Str]], { optional => 1 },
237             params => HashRef, { default => {} },
238             content => HashRef, { default => {} },
239             _extra_ => slurpy Any,
240             );
241 3     3   8 my $p = named_extra($check->(@_));
242            
243 3         14 my $range = $self->range();
244              
245             # since we're sending these values, fake a response from the
246             # api to store them in our cache.
247             # if includeValuesInResponse is sent in the params, then the
248             # response will replace the cache again.
249 3         2679 $self->_cache_range_values(
250             range => $range,
251 3         19 majorDimension => $self->dimension(),
252             values => $p->{values},
253             );
254              
255             $p->{content}->{range} = $range;
256             $p->{content}->{values} = delete $p->{values};
257             $p->{content}->{majorDimension} = $self->dimension();
258             $p->{params}->{valueInputOption} //= 'USER_ENTERED';
259             # $p->{params}->{includeValuesInResponse} = 1;
260             $p->{uri} = "/values/$range";
261 3         19 $p->{method} = 'put';
262              
263 3         10 my $update = $self->api(%$p);
264 3         16  
265 3         11 # this fakes out the batch response with this immediate response.
266 3   50     23 # batch response would be in an arrayref, so wrap it thusly.
267             return $self->values_response_from_api([ $update ]);
268 3         12 }
269 3         12  
270             # this stores the response to an update request, either batch or immediate:
271 3         17 # ---
272             # spreadsheetId: 1ky2czjhPArP71a6woeo_dxRr8gBOZZxGAPjOCXJvCwA
273             # updatedCells: 6
274             # updatedColumns: 3
275 3         28 # updatedRange: Sheet1!B3:D4
276             # updatedRows: 2
277             # if includeValuesInResponse was requested, the values are stashed
278             # from the updatedData response key.
279             # spreadsheet object will call this on a batch update response.
280             my $self = shift;
281              
282             state $check = compile(ArrayRef, { optional => 1 });
283             my ($updates) = $check->(@_);
284             return if !$updates;
285              
286             # shift off the next update from the batch api response. if this is
287             # getting called from the spreadsheet object, it means we have an
288             # update response to process.
289 34     34 0 66 my $update = shift @$updates;
290              
291 34         56 # updatedData is included if includeValuesInResponse query param was sent.
292 34         680 # updatedData:
293 34 50       455 # majorDimension: ROWS
294             # range: Sheet1!A1
295             # values:
296             # - - Fred
297             # if the data is included, then replace any cached values with this latest
298 34         73 # updated set of values.
299             $self->_cache_range_values(%{ delete $update->{updatedData} })
300             if $update->{updatedData};
301             $self->{values_response_from_api} = $update;
302              
303             return $self->values();
304             }
305              
306             # this returns the values and major dimension for a given range. it will store
307             # the values returned from an api fetch. it will store the values from a staged
308 0         0 # 'batch_values' call for later update from the api. it will store the values
309 34 50       101 # from an update if includeValuesInResponse was included.
310 34         67 # cache is in the format:
311             # majorDimension: ROWS
312 34         118 # range: Sheet1!A1 # only on an api return value.
313             # values:
314             # - - Fred
315             # if a range is included it's a flag that the dim and values are present from
316             # the returned api call.
317             my $self = shift;
318              
319             my %p = @_;
320             # if a range is included, assume this cache is coming from the api as a reply.
321             # this is to store the values for this range when includeValuesInResponse is
322             # added to the url or content on the original values call. you can replace the
323             # original values using the valueRenderOption to replace, say, formulas with their
324             # calculated value.
325             if ($p{range}) {
326             state $check = compile_named(
327 200     200   362 majorDimension => DimColRow,
328             range => StrMatch[qr/.+!/],
329 200         391 values => ArrayRef, { optional => 1 } # will not exist if values aren't set in the ss.
330             );
331             my $p = $check->(@_);
332              
333             # remove all quotes for comparison.
334             my $self_range = $self->range();
335 200 100       437 $self_range =~ s/'//g;
336 57         101 my $range = $p->{range};
337             $range =~ s/'//g;
338             my ($worksheet_name) = $range =~ /^(.+)!/;
339              
340             LOGDIE "Setting range data to worksheet name '$worksheet_name' that doesn't belong to this range: " . $self->worksheet_name()
341 57         2330 if $worksheet_name ne $self->worksheet_name();
342             # TODO: sometimes the api returns a range that is different from the one we sent to the api.
343             # a column A:A can be returned as A1:A1000 by the api, so have to come up with a way
344 57         2355 # of identifying when a returned range is a close enough match to the one we have.
345 57         245 #LOGDIE "Setting range data to '$range' which is not this range: " . $self_range
346 57         134 # if $range ne $self_range;
347 57         129 LOGDIE "Setting major dimention to '$p->{majorDimension}' that doesn't belong to this range: " . $self->dimention()
348 57         235 if $p->{majorDimension} ne $self->dimension();
349              
350 57 50       135 delete $p->{range}; # we only cache the values and dimensions.
351             $self->{cache_range_values} = $p;
352             DEBUG("Saved cached values for range " . $self->range());
353             }
354              
355             # if the value range was just stored (above) or was previously stored, return it.
356             return $self->{cache_range_values} if $self->{cache_range_values};
357             DEBUG("No local cache values exist for range " . $self->range() . ", checking for shared values");
358 57 50       175  
359             # used by iterators to use a 'parent' range to query the values for this 'child'
360 57         156 # range. this is to reduce network calls when iterating through a range.
361 57         137 my $shared = $self->{shared};
362 57         112 if ($shared && $shared->has_values()) {
363             my $dim = $shared->{cache_range_values}->{majorDimension};
364             my $values = $shared->{cache_range_values}->{values};
365              
366 200 100       999 my ($top, $left, $bottom, $right) = $self->offsets($shared);
367 52         157 my $data;
368             if ($dim =~ /^col/i) {
369             my @cols = @$values[$left..$right];
370             $_ = [ @$_[$top..$bottom] ] foreach (@cols);
371 52         307 $data = \@cols;
372 52 50 33     156 } else {
373 0         0 my @rows = @$values[$top..$bottom];
374 0         0 $_ = [ @$_[$left..$right] ] foreach (@rows);
375             $data = \@rows;
376 0         0 }
377 0         0  
378 0 0       0 # return a subsection of the cached value for the iterator.
379 0         0 DEBUG("Returning shared values for range " . $self->range());
380 0         0 return {
381 0         0 majorDimension => $dim,
382             values => $data,
383 0         0 };
384 0         0 }
385 0         0 DEBUG("No shared values exist for range " . $self->range());
386              
387             # no values are found for this range, go get them from the api immediately.
388             $p{uri} = sprintf("/values/%s", $self->range());
389 0         0 $p{params}->{majorDimension} = $self->dimension();
390             $self->{cache_range_values} = $self->api(%p);
391 0         0 DEBUG("Cached values loaded from api for range " . $self->range());
392              
393             $self->{cache_range_values}->{values} //= []; # in case there's nothing set in the ss.
394              
395 52         113 return $self->{cache_range_values};
396             }
397              
398 52         301 my $self = shift;
399 52         175 return $self->{cache_range_values} || ($self->{shared} && $self->{shared}->has_values());
400 52         268 }
401 52         203  
402             # for a given range, calculate the offsets from this range.
403 52   100     442 my $self = shift;
404              
405 52         266 state $check = compile(HasRange);
406             my ($other_range) = $check->(@_);
407              
408             my $range = $self->range_to_hash($RANGE_EXPANDED);
409 134     134 1 212 $other_range = $other_range->range_to_hash($RANGE_EXPANDED);
410 134   66     656  
411             my $top = $range->[0]->{row} - $other_range->[0]->{row};
412             my $left = $range->[0]->{col} - $other_range->[0]->{col};
413             my $bottom = $range->[1]->{row} - $other_range->[0]->{row};
414             my $right = $range->[1]->{col} - $other_range->[0]->{col};
415 0     0 1 0  
416             return ( $top, $left, $bottom, $right );
417 0         0 }
418 0         0  
419             my $self = shift;
420 0         0  
421 0         0 state $check = compile(HasRange);
422             my ($shared_range) = $check->(@_);
423 0         0 return if !$shared_range->is_other_inside($self);
424 0         0  
425 0         0 DEBUG(flatten_range($self) . " is sharing values with " . flatten_range($shared_range));
426 0         0 $self->{shared} = $shared_range;
427             return $shared_range;
428 0         0 }
429              
430             my $self = shift;
431              
432 22     22 1 41 state $check = compile(HasRange);
433             my ($inside_range) = $check->(@_);
434 22         47  
435 22         804 my $range = $self->range_to_hash($RANGE_EXPANDED);
436 22 50       283 $inside_range = $inside_range->range_to_hash($RANGE_EXPANDED);
437              
438 22         70 return 1 if
439 22         159 $range->[0]->{col} <= $inside_range->[0]->{col} &&
440 22         54 $range->[0]->{row} <= $inside_range->[0]->{row} &&
441             $range->[1]->{col} >= $inside_range->[1]->{col} &&
442             $range->[1]->{row} >= $inside_range->[1]->{row}
443             ;
444 10     10 1 58 return;
445             }
446 10         24  
447 10         686 # stage batch values get/set for later use by submit_values
448             my $self = shift;
449 10         154  
450 10         44 state $check = compile_named(
451             values => ArrayRef, { optional => 1 },
452             );
453             my $p = $check->(@_);
454              
455             if (defined $p->{values}) {
456             # since we're sending these values, fake a response from the
457 10 100 33     258 # api to store them in our cache.
      66        
      100        
458 2         16 # if includeValuesInResponse is sent in the params, then the
459             # response will replace the cache again.
460             $self->_cache_range_values(
461             range => $self->range(),
462             majorDimension => $self->dimension(),
463 62     62 1 100 values => $p->{values},
464             );
465 62         103 }
466              
467             return if !$self->{cache_range_values};
468 62         840  
469             return {
470 62 100       905 range => $self->range(),
471             %{ $self->{cache_range_values} },
472             };
473             }
474              
475             # tell the api to submit the batch values. the api will call back
476             # values_response_from_api with the results of the update.
477             my $self = shift;
478             $self->spreadsheet()->submit_values(ranges => [ $self ], @_);
479 31         82 return $self->values();
480             }
481              
482 62 50       149 my $self = shift;
483             return $self->spreadsheet()->submit_requests(ranges => [ $self ], @_);
484             }
485              
486 62         129 my $self = shift;
  62         372  
487              
488             state $check = compile_named(
489             values => ArrayRef->plus_coercions(Str, sub { [ $_ ] } ),
490             params => HashRef, { default => {} },
491             content => HashRef, { default => {} },
492             _extra_ => slurpy Any,
493 0     0 1 0 );
494 0         0 my $p = named_extra($check->(@_));
495 0         0  
496             my $range = $self->range();
497             $p->{content}->{range} = $range;
498             $p->{content}->{values} = delete $p->{values};
499 6     6 1 9049 $p->{content}->{majorDimension} = $self->dimension();
500 6         20 $p->{params}->{valueInputOption} //= 'USER_ENTERED';
501             $p->{uri} = "/values/$range:append";
502             $p->{method} = 'post';
503              
504 0     0 0 0 return $self->api(%$p);
505             }
506              
507 0     0   0 my $self = shift;
  0         0  
508             TRACE("Range external caller: " . $self->_caller_external());
509             TRACE("Range internal caller: " . $self->_caller_internal());
510             my $name = $self->worksheet_name();
511             return "'$name'!$self->{range}";
512 0         0 }
513              
514 0         0 # some staic calls...
515 0         0  
516 0         0 my ($package, $subroutine, $line, $i) = ('', '', 0);
517 0         0 do {
518 0   0     0 ($package, undef, $line, $subroutine) = caller(++$i);
519 0         0 } while($subroutine =~ m|range$|);
520 0         0 # not usually going to happen, but during testing we call
521             # range directly, so have to backtrack.
522 0         0 ($package, undef, $line, $subroutine) = caller(--$i)
523             if !$package;
524             return "$package:$line => $subroutine";
525             }
526 842     842 1 1604  
527 842         1940 my ($package, $subroutine, $line, $i) = ('', '', 0);
528 842         6631 do {
529 842         5161 ($package, undef, $line, $subroutine) = caller(++$i);
530 842         3548 } while($package && $package =~ m[^(Google::RestApi)]);
531             return "$package:$line => $subroutine";
532             }
533              
534             # taken from https://metacpan.org/source/DOUGW/Spreadsheet-ParseExcel-0.65/lib/Spreadsheet/ParseExcel/Utility.pm
535             state $check = compile(StrMatch[qr/^[A-Z]+$/]);
536 842     842   1586 my ($a) = $check->(@_);
537 842         1098  
538 1724         15839 my $result = 0;
539             my $incr = 0;
540             for (my $i = length($a); $i > 0 ; $i--) {
541             my $char = substr($a, $i - 1);
542 842 50       12148 my $curr += ord(lc($char)) - ord('a') + 1;
543             $curr *= $incr if ($incr);
544 842         3135 $result += $curr;
545             $incr += 26;
546             }
547              
548 842     842   1867 return $result;
549 842   66     1101 }
550 4143         60587  
551             my $cell = shift;
552 842         17048  
553             my ($col, $row) = $cell =~ /^([A-Z]*)(\d*)$/;
554             $col = _col_a2i($col) if $col;
555              
556             $col ||= 0;
557 337     337   493 $row ||= 0;
558 337         2473  
559             return [$col, $row];
560 337         4107 }
561 337         538  
562 337         905 # back to object calls...
563 337         728  
564 337         704 # returns [[col, row], [col, row]] for a full range.
565 337 50       573 # returns [col, row] for a col or col(a2:a) or row(a2:2).
566 337         449 # passing $RANGE_EXPANDED will always return the former.
567 337         629 my $self = shift;
568              
569             state $check = compile(Bool, { optional => 1 });
570 337         669 my ($expand_range) = $check->(@_);
571              
572             my $range = $self->range();
573             ($range) = $range =~ /!(.+)/;
574 365     365 0 591 $range or LOGDIE "Unable to convert range to array: ", $self->range();
575              
576 365         1241 my ($start_cell, $end_cell) = split(':', $range);
577 365 100       977 $end_cell = undef if defined $end_cell && $start_cell eq $end_cell;
578             $start_cell = cell_to_array($start_cell);
579 365   100     778 $end_cell = cell_to_array($end_cell) if $end_cell;
580 365   100     828
581             $end_cell = $start_cell if !$end_cell && $expand_range;
582 365         841  
583             return $end_cell ? [$start_cell, $end_cell] : $start_cell;
584             }
585              
586             # returns [{col =>, row =>}, {col =>, row => }] for an expanded range.
587             # returns {col =>, row =>} for a cell or col(a2:a) or row(a2:2).
588             # passing $RANGE_EXPANDED will always return the former.
589             my $self = shift;
590              
591 306     306 1 534 my $range = $self->range_to_array(@_);
592             $range = [ $range ] if !ref($range->[1]);
593 306         490 my @ranges = map { { col => $_->[0], row => $_->[1] } } @$range;
594 306         1837  
595             return $ranges[1] ? \@ranges : $ranges[0];
596 306         3584 }
597 306         1296  
598 306 50       686 my $self = shift;
599              
600 306         885 my $array = $self->range_to_array();
601 306 100 100     1071 $array = [$array, $array] if !ref($array->[1]);
602 306         601  
603 306 100       621 my %range = (
604             sheetId => $self->worksheet()->worksheet_id(),
605 306 100 100     1106 startColumnIndex => max($array->[0]->[0] - 1, 0),
606             startRowIndex => max($array->[0]->[1] - 1, 0),
607 306 100       932 endColumnIndex => $array->[1]->[0],
608             endRowIndex => $array->[1]->[1],
609             );
610             delete $range{endColumnIndex}
611             if $range{endColumnIndex} < $range{startColumnIndex};
612             delete $range{endRowIndex}
613             if $range{endRowIndex} < $range{startRowIndex};
614 190     190 1 297
615             return \%range;
616 190         496 }
617 190 100       608  
618 190         412 my $self = shift;
  312         1177  
619              
620 190 100       742 state $check = compile(DimColRow);
621             my ($dims) = $check->(@_);
622             $dims = dims_any($dims);
623              
624 98     98 1 210 my $array = $self->range_to_array();
625             $array = [$array, $array] if !ref($array->[1]);
626 98         266 my $start = $dims =~ /^col/i ? $array->[0]->[0] : $array->[0]->[1];
627 98 100       346 my $end = $dims =~ /^col/i ? $array->[1]->[0] : $array->[1]->[1];
628              
629 98         211 my %range = (
630             sheetId => $self->worksheet()->worksheet_id(),
631             startIndex => max($start - 1, 0),
632             endIndex => $end,
633             dimension => $dims,
634             );
635              
636             return \%range;
637 98 50       273 }
638              
639 98 50       192 my $self = shift;
640              
641 98         711 state $check = compile(Int, DimColRow);
642             my ($offset, $dim) = $check->(@_);
643              
644             # it's an a1:b2 range. col/row/cell handle this in the subclass.
645 0     0 1 0 my $range = $self->range_to_hash($RANGE_EXPANDED);
646             my $other_dim = $dim =~ /col/i ? 'row' : 'col';
647 0         0 # TODO: this is really brain dead, figure this out with a bit of arithmetic...
648 0         0 my @ranges = map {
649 0         0 my $outside = $_;
650             map {
651 0         0 my $inside = $_;
652 0 0       0 my $r = { $dim => $inside, $other_dim => $outside }; $r; # for some reason have to stage the ref to a scalar.
653 0 0       0 } ($range->[0]->{$other_dim} .. $range->[1]->{$other_dim});
654 0 0       0 } ($range->[0]->{$dim} .. $range->[1]->{$dim});
655              
656 0         0 my $offset_range = $ranges[$offset] or return;
657             my $new_cell = $self->worksheet()->range_cell($offset_range);
658             $new_cell->share_values($self);
659              
660             return $new_cell;
661             }
662              
663 0         0 # doesn't seem to be used anywhere internally.
664             my $self = shift;
665              
666             state $check = compile_named(
667 6     6 0 386 col => Int, { default => 0 },
668             row => Int, { default => 0 },
669 6         11 top => Int, { default => 0 },
670 6         1146 left => Int, { default => 0 },
671             bottom => Int, { default => 0 },
672             right => Int, { default => 0 },
673 6         143 );
674 6 100       22 my $p = $check->(@_);
675              
676             my $range = $self->range_to_hash();
677 12         18 if (ref($range) eq 'ARRAY') {
678             foreach my $dim (@$range) {
679 24         26 $dim->{$_} += $p->{$_} foreach (qw(col row));
680 24         42 }
  24         45  
681 12         32 $range->[0]->{row} += $p->{top};
682 6         28 $range->[0]->{col} += $p->{left};
683             $range->[1]->{row} += $p->{bottom};
684 6 100       23 $range->[1]->{col} += $p->{right};
685 4         11 } else {
686 4         15 $range->{$_} += $p->{$_} foreach (qw(col row));
687             $range->{row} += $p->{top};
688 4         35 $range->{col} += $p->{left};
689             $range->{row} += $p->{bottom};
690             $range->{col} += $p->{right};
691             }
692              
693 0     0 0 0 return (ref($self))->new(worksheet => $self->worksheet(), range => $range);
694             }
695 0         0  
696              
697             1;
698              
699              
700             =head1 NAME
701              
702             Google::RestApi::SheetsApi4::Range - Represents a range in a Worksheet.
703 0         0  
704             =head1 DESCRIPTION
705 0         0  
706 0 0       0 A Range object that represents a range in a remote spreadsheet. These are
707 0         0 normally short-lived objects used to set up and execute a remote action.
708 0         0 Keep in mind that the remote spreadsheet can be concurrently updated by
709             many people, so a compromise must always be reached between holding a copy
710 0         0 of the local cell values versus the number of network calls to the Google
711 0         0 API to keep the values current.
712 0         0  
713 0         0 A range can be specified in whatever way is most convenient.
714              
715 0         0 For ranges:
716 0         0  
717 0         0 * A1 notation: A1:B2
718 0         0 * Hash: [ { col => (A|1), row => 1 }, { col => (B|2), row => 2 } ]
719 0         0 * Array: [ [ (A|1), 1 ], [ (B|2), 2 ] ]
720              
721             For columns:
722 0         0  
723             * A1 notation: A or A:A
724             * Hash: { col => (A|1) }
725 0     0 0 0 * Array: [ A|1 ]
726 2     2 1 37  
727 55     55 1 118 For rows:
728 169     169 1 558  
729 1080     1080 1 3325 * A1 notation: 1 or 1:1
730 899     899 1 1984 * Hash: { row => 1 }
731 0     0 1 0 * Array: [ <false>, 1 ]
732 6     6 1 16  
733 0     0 1   For cells:
734 0     0 0    
735 0     0 1   * A1 notation: A1
736             * Hash: { col => (A|1), row => 1 }
737             * Array: [ (A|1), 1 ]
738              
739             See the description and synopsis at L<Google::RestApi::SheetsApi4>.
740              
741             =head1 NAVIGATION
742              
743             =over
744              
745             =item * L<Google::RestApi::SheetsApi4>
746              
747             =item * L<Google::RestApi::SheetsApi4::Spreadsheet>
748              
749             =item * L<Google::RestApi::SheetsApi4::Worksheet>
750              
751             =item * L<Google::RestApi::SheetsApi4::Range>
752              
753             =item * L<Google::RestApi::SheetsApi4::Range::All>
754              
755             =item * L<Google::RestApi::SheetsApi4::Range::Col>
756              
757             =item * L<Google::RestApi::SheetsApi4::Range::Row>
758              
759             =item * L<Google::RestApi::SheetsApi4::Range::Cell>
760              
761             =item * L<Google::RestApi::SheetsApi4::RangeGroup>
762              
763             =item * L<Google::RestApi::SheetsApi4::RangeGroup::Iterator>
764              
765             =item * L<Google::RestApi::SheetsApi4::RangeGroup::Tie>
766              
767             =item * L<Google::RestApi::SheetsApi4::RangeGroup::Tie::Iterator>
768              
769             =item * L<Google::RestApi::SheetsApi4::Request::Spreadsheet>
770              
771             =item * L<Google::RestApi::SheetsApi4::Request::Spreadsheet::Worksheet>
772              
773             =item * L<Google::RestApi::SheetsApi4::Request::Spreadsheet::Worksheet::Range>
774              
775             =back
776              
777             =head1 SUBROUTINES
778              
779             =over
780              
781             =item new(%args);
782              
783             Creates a new range object for the given worksheet.
784              
785             %args consists of:
786              
787             =over
788              
789             =item * C<worksheet> <Worksheet>: The parent Worksheet object for this range.
790              
791             =item * C<range> <range>: The range for this object, either A1 notation, hash {col => x, row => x} or array [col, row].
792              
793             =item * C<dim> <col|row>: The major dimension for this range, either 'col' or 'row', defaults to 'row'.
794              
795             =back
796              
797             You would not normally call this directly, you'd use Worksheet::range* methods to create the range object for you. It is recommended and safer to use
798             the Worksheet's methods to create ranges.
799              
800             =item api(%args);
801              
802             Calls the parent Worksheet's 'api' routine with the range added into the URI or content appropriately. This then get's passed to the
803             Spreadsheet's C<api> routine where the spreadsheet ID is tacked on to the URI. This then gets passed to the SheetsApi4's C<api>
804             routine where the Sheets endpoint is tacked on to the URI. This then gets passed to RestApi's api routine for actual execution.
805              
806             You would not normally call this directly unless you were making a Google API call not currently supported by this API framework.
807              
808             =item clear();
809              
810             Clears the values using Google API's 'A1:clear' call.
811              
812             =item refresh_values();
813              
814             Immediately refreshes and returns the values from the spreadsheet.
815              
816             =item values(values => <arrayref>, %args);
817              
818             Immediately gets or sets the values using Google API's 'get' or 'update'.
819              
820             values: The array ref of cells to update.
821              
822             'args' are passed to the SheetsApi4's 'api' routine so you may add
823             extra arguments to the 'params' (such as C<valueInputOption>) or 'content' as necessary.
824              
825             =item batch_values(values => <arrayref>);
826              
827             Gets or sets the queued batch values that will be sent to Google API
828             at a later time. Call 'submit_values' to send them later.
829              
830             values: The array ref of cells to update.
831              
832             =item submit_values(%args);
833              
834             Sends the previously queued batch values to Google API, if any.
835              
836             'args' are passed to the SheetsApi4's 'api' routine so you may add
837             extra arguments to the 'params' or 'content' as necessary.
838              
839             =item submit_requests(%args);
840              
841             Sends the previously queued requests (formatting, sheet properties etc)
842             to Google API, if any.
843              
844             'args' are passed to the SheetsApi4's 'api' routine so you may add
845             extra arguments to the 'params' or 'content' as necessary.
846              
847             =item normalize_named();
848              
849             If this is a 'named range', get the named range property from the
850             spreadsheet and set this object's real range that the named range
851             represents.
852              
853             =item named();
854              
855             Returns the 'named range' for this range, if any.
856              
857             =item range();
858              
859             Returns the A1 notation for this range.
860              
861             =item range_to_hash();
862              
863             Returns the hash representation for this range (e.g. {col => 1, row => 1}). Passing $RANGE_EXPANDED
864             will always return a double cell notations: [{col => 1, row => 1}, {col => 1, row => 1}].
865              
866             =item range_to_array();
867              
868             Returns the array representation for this range (e.g. [1, 1]). Passing $RANGE_EXPANDED
869             will always return a double cell notations: [[ 1, 1 ], [ 1, 1]].
870              
871             =item range_to_index();
872              
873             Returns the index hash representation for this range, used for formatting
874             requests etc. You would not normally need to call this yourself.
875              
876             =item range_to_dimension();
877              
878             Returns the dimension hash representation for this range, used for
879             insert requests etc. You would not normally need to call this yourself.
880              
881             =item offset(col => <int>, row => <int>, top => <int>, left => <int>, bottom => <int>, right => <int>);
882              
883             Returns a new range object offset from this range.
884              
885             col: Optionally offset the new range by this many columns.
886             row: Optionally offset the new range by this many rows.
887             top: Optionally offset the new range with the new top.
888             left: Optionally offset the new range with the new left.
889             bottom: Optionally offset the new range with the new bottom.
890             right: Optionally offset the new range with the new right.
891              
892             =item offsets(range<Range>);
893              
894             Returns the offsets of this range from the given range in an
895             array (top, left, bottom, right).
896              
897             =item iterator(%args);
898              
899             Returns an iterator for this range. Any 'args' are passed to the
900             'new' routine for the iterator.
901              
902             =item is_other_inside(range<Range>);
903              
904             Returns a true value if the given range fits entirely inside this range.
905              
906             =item share_values(range<Range>);
907              
908             Share the cell values between this range and the passed range.
909              
910             Normally ranges are short-lived, throw-away objects used to interact
911             with the Google API. Range objects work independently and don't
912             share any common storage of cells. No attempt is made to share a
913             common, local grid that mirrors what's in the remote spreadsheet.
914             The spreadsheet is a shared resource that can be updated concurrently
915             by multiple users, so attempting to mirror it locally is a waste of
916             time unless some kind of listener is provided that can be used to
917             syncronize the remote updates with the local copy (which Google doesn't).
918              
919             However, for some cases, like iterators, we can set up ranges to
920             share values between them for a short time in order to reduce the
921             number of network calls to the Google API while you iterate over
922             the cells. When the iterated cell values are updated, the parent
923             range's value is also updated.
924              
925             So far, this is the only use-case I can see for sharing the cell
926             grid between ranges.
927              
928             =item dimension();
929              
930             Returns this range's major dimension (col|row).
931              
932             =item has_values();
933              
934             Returns a true value if this range's values have been set either via
935             'values' or 'batch_values' routines.
936              
937             =item worksheet();
938              
939             Returns the parent Worksheet object.
940              
941             =item worksheet_name();
942              
943             Returns the parent Worksheet name.
944              
945             =item worksheet_id();
946              
947             Returns the parent Worksheet ID.
948              
949             =item spreadsheet();
950              
951             Returns the parent Spreadsheet object.
952              
953             =item spreadsheet_id();
954              
955             Returns the parent Spreadsheet ID.
956              
957             =back
958              
959             =head1 AUTHORS
960              
961             =over
962              
963             =item
964              
965             Robin Murray mvsjes@cpan.org
966              
967             =back
968              
969             =head1 COPYRIGHT
970              
971             Copyright (c) 2021, Robin Murray. All rights reserved.
972              
973             This program is free software; you may redistribute it and/or modify it under the same terms as Perl itself.