blob: 6098357afea81957c52bd1b48b89e4c96ded1803 [file] [log] [blame]
rjw6c1fd8f2022-11-30 14:33:01 +08001package Spreadsheet::WriteExcel::Formula;
2
3###############################################################################
4#
5# Formula - A class for generating Excel formulas.
6#
7#
8# Used in conjunction with Spreadsheet::WriteExcel
9#
10# Copyright 2000-2010, John McNamara, jmcnamara@cpan.org
11#
12# Documentation after __END__
13#
14
15use Exporter;
16use strict;
17use Carp;
18
19
20
21
22
23
24use vars qw($VERSION @ISA);
25@ISA = qw(Exporter);
26
27$VERSION = '2.37';
28
29###############################################################################
30#
31# Class data.
32#
33my $parser;
34my %ptg;
35my %functions;
36
37
38###############################################################################
39#
40# For debugging.
41#
42my $_debug = 0;
43
44
45###############################################################################
46#
47# new()
48#
49# Constructor
50#
51sub new {
52
53 my $class = $_[0];
54
55 my $self = {
56 _byte_order => $_[1],
57 _workbook => "",
58 _ext_sheets => {},
59 _ext_refs => {},
60 _ext_ref_count => 0,
61 _ext_names => {},
62 };
63
64 bless $self, $class;
65 return $self;
66}
67
68
69###############################################################################
70#
71# _init_parser()
72#
73# There is a small overhead involved in generating the parser. Therefore, the
74# initialisation is delayed until a formula is required.
75# TODO: use a pre-compiled grammar.
76#
77# Porters take note, a recursive descent parser isn't mandatory. A future
78# version of this module may use a YACC based parser instead.
79#
80sub _init_parser {
81
82 my $self = shift;
83
84 # Delay loading Parse::RecDescent to reduce the module dependencies.
85 eval { require Parse::RecDescent };
86 die "The Parse::RecDescent module must be installed in order ".
87 "to write an Excel formula\n" if $@;
88
89 $self->_initialize_hashes();
90
91 # The parsing grammar.
92 #
93 # TODO: Add support for international versions of Excel
94 #
95 $parser = Parse::RecDescent->new(<<'EndGrammar');
96
97 expr: list
98
99 # Match arg lists such as SUM(1,2, 3)
100 list: <leftop: addition ',' addition>
101 { [ $item[1], '_arg', scalar @{$item[1]} ] }
102
103 addition: <leftop: multiplication add_op multiplication>
104
105 # TODO: The add_op operators don't have equal precedence.
106 add_op: add | sub | concat
107 | eq | ne | le | ge | lt | gt # Order is important
108
109 add: '+' { 'ptgAdd' }
110 sub: '-' { 'ptgSub' }
111 concat: '&' { 'ptgConcat' }
112 eq: '=' { 'ptgEQ' }
113 ne: '<>' { 'ptgNE' }
114 le: '<=' { 'ptgLE' }
115 ge: '>=' { 'ptgGE' }
116 lt: '<' { 'ptgLT' }
117 gt: '>' { 'ptgGT' }
118
119
120 multiplication: <leftop: exponention mult_op exponention>
121
122 mult_op: mult | div
123 mult: '*' { 'ptgMul' }
124 div: '/' { 'ptgDiv' }
125
126 # Left associative (apparently)
127 exponention: <leftop: factor exp_op factor>
128
129 exp_op: '^' { 'ptgPower' }
130
131 factor: number # Order is important
132 | string
133 | range2d
134 | range3d
135 | true
136 | false
137 | ref2d
138 | ref3d
139 | function
140 | name
141 | '(' expr ')' { [$item[2], 'ptgParen'] }
142
143 # Match a string.
144 # Regex by merlyn. See http://www.perlmonks.org/index.pl?node_id=330280
145 #
146 string: /"([^"]|"")*"/ #" For editors
147 { [ '_str', $item[1]] }
148
149 # Match float or integer
150 number: /([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?/
151 { ['_num', $item[1]] }
152
153 # Note: The highest column values is IV. The following regexes match
154 # up to IZ. Out of range values are caught in the code.
155 #
156 # Note: sheetnames with whitespace, commas, or parentheses must be in
157 # single quotes. Applies to ref3d and range3d
158 #
159
160 # Match A1, $A1, A$1 or $A$1.
161 ref2d: /\$?[A-I]?[A-Z]\$?\d+/
162 { ['_ref2d', $item[1]] }
163
164 # Match an external sheet reference: Sheet1!A1 or 'Sheet (1)'!A1
165 ref3d: /[^!(,]+!\$?[A-I]?[A-Z]\$?\d+/
166 { ['_ref3d', $item[1]] }
167 | /'[^']+'!\$?[A-I]?[A-Z]\$?\d+/
168 { ['_ref3d', $item[1]] }
169
170 # Match A1:C5, $A1:$C5 or A:C etc.
171 range2d: /\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/
172 { ['_range2d', $item[1]] }
173
174 # Match an external sheet range. 'Sheet 1:Sheet 2'!B2:C5
175 range3d: /[^!(,]+!\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/
176 { ['_range3d', $item[1]] }
177 | /'[^']+'!\$?[A-I]?[A-Z]\$?(\d+)?:\$?[A-I]?[A-Z]\$?(\d+)?/
178 { ['_range3d', $item[1]] }
179
180 # Match a function name.
181 function: /[A-Z0-9À-Ü_.]+/ '()'
182 { ['_func', $item[1]] }
183 | /[A-Z0-9À-Ü_.]+/ '(' expr ')'
184 { ['_class', $item[1], $item[3], '_func', $item[1]] }
185 | /[A-Z0-9À-Ü_.]+/ '(' list ')'
186 { ['_class', $item[1], $item[3], '_func', $item[1]] }
187
188 # Match a defined name.
189 name: /[A-Za-z_]\w+/
190 { ['_name', $item[1]] }
191
192 # Boolean values.
193 true: 'TRUE' { [ 'ptgBool', 1 ] }
194
195 false: 'FALSE' { [ 'ptgBool', 0 ] }
196
197EndGrammar
198
199print "Init_parser.\n\n" if $_debug;
200}
201
202
203
204###############################################################################
205#
206# parse_formula()
207#
208# Takes a textual description of a formula and returns a RPN encoded byte
209# string.
210#
211sub parse_formula {
212
213 my $self= shift;
214
215 # Initialise the parser if this is the first call
216 $self->_init_parser() if not defined $parser;
217
218 my $formula = shift @_;
219 my $tokens;
220
221 print $formula, "\n" if $_debug;
222
223 # Build the parse tree for the formula
224 my $parsetree =$parser->expr($formula);
225
226 # Check if parsing worked.
227 if (defined $parsetree) {
228 my @tokens = $self->_reverse_tree(@$parsetree);
229
230 # Add a volatile token if the formula contains a volatile function.
231 # This must be the first token in the list
232 #
233 unshift @tokens, '_vol' if $self->_check_volatile(@tokens);
234
235 # The return value depends on which Worksheet.pm method is the caller
236 if (wantarray) {
237 # Parse formula to see if it throws any errors and then
238 # return raw tokens to Worksheet::store_formula()
239 #
240 $self->parse_tokens(@tokens);
241 return @tokens;
242 }
243 else{
244 # Return byte stream to Worksheet::write_formula()
245 return $self->parse_tokens(@tokens);
246 }
247 }
248 else {
249 die "Couldn't parse formula: =$formula\n";
250 }
251}
252
253
254###############################################################################
255#
256# parse_tokens()
257#
258# Convert each token or token pair to its Excel 'ptg' equivalent.
259#
260sub parse_tokens {
261
262 my $self = shift;
263 my $parse_str = '';
264 my $last_type = '';
265 my $modifier = '';
266 my $num_args = 0;
267 my $class = 0;
268 my @class = 1;
269 my @tokens = @_;
270
271
272 # A note about the class modifiers used below. In general the class,
273 # "reference" or "value", of a function is applied to all of its operands.
274 # However, in certain circumstances the operands can have mixed classes,
275 # e.g. =VLOOKUP with external references. These will eventually be dealt
276 # with by the parser. However, as a workaround the class type of a token
277 # can be changed via the repeat_formula interface. Thus, a _ref2d token can
278 # be changed by the user to _ref2dA or _ref2dR to change its token class.
279 #
280 while (@_) {
281 my $token = shift @_;
282
283 if ($token eq '_arg') {
284 $num_args = shift @_;
285 }
286 elsif ($token eq '_class') {
287 $token = shift @_;
288 $class = $functions{$token}[2];
289 # If $class is undef then it means that the function isn't valid.
290 die "Unknown function $token() in formula\n" unless defined $class;
291 push @class, $class;
292 }
293 elsif ($token eq '_vol') {
294 $parse_str .= $self->_convert_volatile();
295 }
296 elsif ($token eq 'ptgBool') {
297 $token = shift @_;
298 $parse_str .= $self->_convert_bool($token);
299 }
300 elsif ($token eq '_num') {
301 $token = shift @_;
302 $parse_str .= $self->_convert_number($token);
303 }
304 elsif ($token eq '_str') {
305 $token = shift @_;
306 $parse_str .= $self->_convert_string($token);
307 }
308 elsif ($token =~ /^_ref2d/) {
309 ($modifier = $token) =~ s/_ref2d//;
310 $class = $class[-1];
311 $class = 0 if $modifier eq 'R';
312 $class = 1 if $modifier eq 'V';
313 $token = shift @_;
314 $parse_str .= $self->_convert_ref2d($token, $class);
315 }
316 elsif ($token =~ /^_ref3d/) {
317 ($modifier = $token) =~ s/_ref3d//;
318 $class = $class[-1];
319 $class = 0 if $modifier eq 'R';
320 $class = 1 if $modifier eq 'V';
321 $token = shift @_;
322 $parse_str .= $self->_convert_ref3d($token, $class);
323 }
324 elsif ($token =~ /^_range2d/) {
325 ($modifier = $token) =~ s/_range2d//;
326 $class = $class[-1];
327 $class = 0 if $modifier eq 'R';
328 $class = 1 if $modifier eq 'V';
329 $token = shift @_;
330 $parse_str .= $self->_convert_range2d($token, $class);
331 }
332 elsif ($token =~ /^_range3d/) {
333 ($modifier = $token) =~ s/_range3d//;
334 $class = $class[-1];
335 $class = 0 if $modifier eq 'R';
336 $class = 1 if $modifier eq 'V';
337 $token = shift @_;
338 $parse_str .= $self->_convert_range3d($token, $class);
339 }
340 elsif ($token =~ /^_name/) {
341 ($modifier = $token) =~ s/_name//;
342 $class = $class[-1];
343 $class = 0 if $modifier eq 'R';
344 $class = 1 if $modifier eq 'V';
345 $token = shift @_;
346 $parse_str .= $self->_convert_name($token, $class);
347 }
348 elsif ($token eq '_func') {
349 $token = shift @_;
350 $parse_str .= $self->_convert_function($token, $num_args);
351 pop @class;
352 $num_args = 0; # Reset after use
353 }
354 elsif (exists $ptg{$token}) {
355 $parse_str .= pack("C", $ptg{$token});
356 }
357 else {
358 # Unrecognised token
359 return undef;
360 }
361 }
362
363
364 if ($_debug) {
365 print join(" ", map { sprintf "%02X", $_ } unpack("C*",$parse_str));
366 print "\n\n";
367 print join(" ", @tokens), "\n\n";
368 }
369
370 return $parse_str;
371}
372
373
374###############################################################################
375#
376# _reverse_tree()
377#
378# This function descends recursively through the parse tree. At each level it
379# swaps the order of an operator followed by an operand.
380# For example, 1+2*3 would be converted in the following sequence:
381# 1 + 2 * 3
382# 1 + (2 * 3)
383# 1 + (2 3 *)
384# 1 (2 3 *) +
385# 1 2 3 * +
386#
387sub _reverse_tree
388{
389 my $self = shift;
390
391 my @tokens;
392 my @expression = @_;
393 my @stack;
394
395 while (@expression) {
396 my $token = shift @expression;
397
398 # If the token is an operator swap it with the following operand
399 if ( $token eq 'ptgAdd' ||
400 $token eq 'ptgSub' ||
401 $token eq 'ptgConcat' ||
402 $token eq 'ptgMul' ||
403 $token eq 'ptgDiv' ||
404 $token eq 'ptgPower' ||
405 $token eq 'ptgEQ' ||
406 $token eq 'ptgNE' ||
407 $token eq 'ptgLE' ||
408 $token eq 'ptgGE' ||
409 $token eq 'ptgLT' ||
410 $token eq 'ptgGT')
411 {
412 my $operand = shift @expression;
413 push @stack, $operand;
414 }
415
416 push @stack, $token;
417 }
418
419 # Recurse through the parse tree
420 foreach my $token (@stack) {
421 if (ref($token)) {
422 push @tokens, $self->_reverse_tree(@$token);
423 }
424 else {
425 push @tokens, $token;
426 }
427 }
428
429 return @tokens;
430}
431
432
433###############################################################################
434#
435# _check_volatile()
436#
437# Check if the formula contains a volatile function, i.e. a function that must
438# be recalculated each time a cell is updated. These formulas require a ptgAttr
439# with the volatile flag set as the first token in the parsed expression.
440#
441# Examples of volatile functions: RAND(), NOW(), TODAY()
442#
443sub _check_volatile {
444
445 my $self = shift;
446 my @tokens = @_;
447 my $volatile = 0;
448
449 for my $i (0..@tokens-1) {
450 # If the next token is a function check if it is volatile.
451 if ($tokens[$i] eq '_func' and $functions{$tokens[$i+1]}[3]) {
452 $volatile = 1;
453 last;
454 }
455 }
456
457 return $volatile;
458}
459
460
461###############################################################################
462#
463# _convert_volatile()
464#
465# Convert _vol to a ptgAttr tag formatted to indicate that the formula contains
466# a volatile function. See _check_volatile()
467#
468sub _convert_volatile {
469
470 my $self = shift;
471
472 # Set bitFattrSemi flag to indicate volatile function, "w" is set to zero.
473 return pack("CCv", $ptg{ptgAttr}, 0x1, 0x0);
474}
475
476
477###############################################################################
478#
479# _convert_bool()
480#
481# Convert a boolean token to ptgBool
482#
483sub _convert_bool {
484
485 my $self = shift;
486 my $bool = shift;
487
488 return pack("CC", $ptg{ptgBool}, $bool);
489}
490
491
492###############################################################################
493#
494# _convert_number()
495#
496# Convert a number token to ptgInt or ptgNum
497#
498sub _convert_number {
499
500 my $self = shift;
501 my $num = shift;
502
503 # Integer in the range 0..2**16-1
504 if (($num =~ /^\d+$/) && ($num <= 65535)) {
505 return pack("Cv", $ptg{ptgInt}, $num);
506 }
507 else { # A float
508 $num = pack("d", $num);
509 $num = reverse $num if $self->{_byte_order};
510 return pack("C", $ptg{ptgNum}) . $num;
511 }
512}
513
514
515###############################################################################
516#
517# _convert_string()
518#
519# Convert a string to a ptg Str.
520#
521sub _convert_string {
522
523 my $self = shift;
524 my $str = shift;
525 my $encoding = 0;
526
527 $str =~ s/^"//; # Remove leading "
528 $str =~ s/"$//; # Remove trailing "
529 $str =~ s/""/"/g; # Substitute Excel's escaped double quote "" for "
530
531 my $length = length($str);
532
533 # Handle utf8 strings in perl 5.8.
534 if ($] >= 5.008) {
535 require Encode;
536
537 if (Encode::is_utf8($str)) {
538 $str = Encode::encode("UTF-16LE", $str);
539 $encoding = 1;
540 }
541 }
542
543 die "String in formula has more than 255 chars\n" if $length > 255;
544
545 return pack("CCC", $ptg{ptgStr}, $length, $encoding) . $str;
546}
547
548
549###############################################################################
550#
551# _convert_ref2d()
552#
553# Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
554#
555sub _convert_ref2d {
556
557 my $self = shift;
558 my $cell = shift;
559 my $class = shift;
560 my $ptgRef;
561
562 # Convert the cell reference
563 my ($row, $col) = $self->_cell_to_packed_rowcol($cell);
564
565 # The ptg value depends on the class of the ptg.
566 if ($class == 0) {
567 $ptgRef = pack("C", $ptg{ptgRef});
568 }
569 elsif ($class == 1) {
570 $ptgRef = pack("C", $ptg{ptgRefV});
571 }
572 elsif ($class == 2) {
573 $ptgRef = pack("C", $ptg{ptgRefA});
574 }
575 else{
576 die "Unknown function class in formula\n";
577 }
578
579 return $ptgRef . $row . $col;
580}
581
582
583###############################################################################
584#
585# _convert_ref3d
586#
587# Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
588# ptgRef3dV.
589#
590sub _convert_ref3d {
591
592 my $self = shift;
593 my $token = shift;
594 my $class = shift;
595 my $ptgRef;
596
597 # Split the ref at the ! symbol
598 my ($ext_ref, $cell) = split '!', $token;
599
600 # Convert the external reference part
601 $ext_ref = $self->_pack_ext_ref($ext_ref);
602
603 # Convert the cell reference part
604 my ($row, $col) = $self->_cell_to_packed_rowcol($cell);
605
606 # The ptg value depends on the class of the ptg.
607 if ($class == 0) {
608 $ptgRef = pack("C", $ptg{ptgRef3d});
609 }
610 elsif ($class == 1) {
611 $ptgRef = pack("C", $ptg{ptgRef3dV});
612 }
613 elsif ($class == 2) {
614 $ptgRef = pack("C", $ptg{ptgRef3dA});
615 }
616 else{
617 die "Unknown function class in formula\n";
618 }
619
620 return $ptgRef . $ext_ref. $row . $col;
621}
622
623
624###############################################################################
625#
626# _convert_range2d()
627#
628# Convert an Excel range such as A1:D4 or A:D to a ptgRefV.
629#
630sub _convert_range2d {
631
632 my $self = shift;
633 my $range = shift;
634 my $class = shift;
635 my $ptgArea;
636
637 # Split the range into 2 cell refs
638 my ($cell1, $cell2) = split ':', $range;
639
640 # A range such as A:D is equivalent to A1:D65536, so add rows as required
641 $cell1 .= '1' if $cell1 !~ /\d/;
642 $cell2 .= '65536' if $cell2 !~ /\d/;
643
644 # Convert the cell references
645 my ($row1, $col1) = $self->_cell_to_packed_rowcol($cell1);
646 my ($row2, $col2) = $self->_cell_to_packed_rowcol($cell2);
647
648 # The ptg value depends on the class of the ptg.
649 if ($class == 0) {
650 $ptgArea = pack("C", $ptg{ptgArea});
651 }
652 elsif ($class == 1) {
653 $ptgArea = pack("C", $ptg{ptgAreaV});
654 }
655 elsif ($class == 2) {
656 $ptgArea = pack("C", $ptg{ptgAreaA});
657 }
658 else{
659 die "Unknown function class in formula\n";
660 }
661
662 return $ptgArea . $row1 . $row2 . $col1. $col2;
663}
664
665
666###############################################################################
667#
668# _convert_range3d
669#
670# Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
671# a ptgArea3dV.
672#
673sub _convert_range3d {
674
675 my $self = shift;
676 my $token = shift;
677 my $class = shift;
678 my $ptgArea;
679
680 # Split the ref at the ! symbol
681 my ($ext_ref, $range) = split '!', $token;
682
683 # Convert the external reference part
684 $ext_ref = $self->_pack_ext_ref($ext_ref);
685
686 # Split the range into 2 cell refs
687 my ($cell1, $cell2) = split ':', $range;
688
689 # A range such as A:D is equivalent to A1:D65536, so add rows as required
690 $cell1 .= '1' if $cell1 !~ /\d/;
691 $cell2 .= '65536' if $cell2 !~ /\d/;
692
693 # Convert the cell references
694 my ($row1, $col1) = $self->_cell_to_packed_rowcol($cell1);
695 my ($row2, $col2) = $self->_cell_to_packed_rowcol($cell2);
696
697 # The ptg value depends on the class of the ptg.
698 if ($class == 0) {
699 $ptgArea = pack("C", $ptg{ptgArea3d});
700 }
701 elsif ($class == 1) {
702 $ptgArea = pack("C", $ptg{ptgArea3dV});
703 }
704 elsif ($class == 2) {
705 $ptgArea = pack("C", $ptg{ptgArea3dA});
706 }
707 else{
708 die "Unknown function class in formula\n";
709 }
710
711 return $ptgArea . $ext_ref . $row1 . $row2 . $col1. $col2;
712}
713
714
715###############################################################################
716#
717# _pack_ext_ref()
718#
719# Convert the sheet name part of an external reference, for example "Sheet1" or
720# "Sheet1:Sheet2", to a packed structure.
721#
722sub _pack_ext_ref {
723
724 my $self = shift;
725 my $ext_ref = shift;
726 my $sheet1;
727 my $sheet2;
728
729 $ext_ref =~ s/^'//; # Remove leading ' if any.
730 $ext_ref =~ s/'$//; # Remove trailing ' if any.
731
732 # Check if there is a sheet range eg., Sheet1:Sheet2.
733 if ($ext_ref =~ /:/) {
734 ($sheet1, $sheet2) = split ':', $ext_ref;
735
736 $sheet1 = $self->_get_sheet_index($sheet1);
737 $sheet2 = $self->_get_sheet_index($sheet2);
738
739 # Reverse max and min sheet numbers if necessary
740 if ($sheet1 > $sheet2) {
741 ($sheet1, $sheet2) = ($sheet2, $sheet1);
742 }
743 }
744 else {
745 # Single sheet name only.
746 ($sheet1, $sheet2) = ($ext_ref, $ext_ref);
747
748 $sheet1 = $self->_get_sheet_index($sheet1);
749 $sheet2 = $sheet1;
750 }
751
752 my $key = "$sheet1:$sheet2";
753 my $index;
754
755 if (exists $self->{_ext_refs}->{$key}) {
756 $index = $self->{_ext_refs}->{$key};
757 }
758 else {
759 $index = $self->{_ext_ref_count};
760 $self->{_ext_refs}->{$key} = $index;
761 $self->{_ext_ref_count}++;
762 }
763
764 return pack("v",$index);
765}
766
767
768###############################################################################
769#
770# _get_sheet_index()
771#
772# Look up the index that corresponds to an external sheet name. The hash of
773# sheet names is updated by the add_worksheet() method of the Workbook class.
774#
775sub _get_sheet_index {
776
777 my $self = shift;
778 my $sheet_name = shift;
779
780 # Handle utf8 sheetnames in perl 5.8.
781 if ($] >= 5.008) {
782 require Encode;
783
784 if (Encode::is_utf8($sheet_name)) {
785 $sheet_name = Encode::encode("UTF-16BE", $sheet_name);
786 }
787 }
788
789
790 if (not exists $self->{_ext_sheets}->{$sheet_name}) {
791 die "Unknown sheet name $sheet_name in formula\n";
792 }
793 else {
794 return $self->{_ext_sheets}->{$sheet_name};
795 }
796}
797
798
799###############################################################################
800#
801# set_ext_sheets()
802#
803# This semi-public method is used to update the hash of sheet names. It is
804# updated by the add_worksheet() method of the Workbook class.
805#
806sub set_ext_sheets {
807
808 my $self = shift;
809 my $worksheet = shift;
810 my $index = shift;
811
812 # The _ext_sheets hash is used to translate between worksheet names
813 # and their index
814 $self->{_ext_sheets}->{$worksheet} = $index;
815
816}
817
818
819###############################################################################
820#
821# get_ext_sheets()
822#
823# This semi-public method is used to get the worksheet references that were
824# used in formulas for inclusion in the EXTERNSHEET Workbook record.
825#
826sub get_ext_sheets {
827
828 my $self = shift;
829
830 return %{$self->{_ext_refs}};
831}
832
833
834###############################################################################
835#
836# get_ext_ref_count()
837#
838# This semi-public method is used to update the hash of sheet names. It is
839# updated by the add_worksheet() method of the Workbook class.
840#
841sub get_ext_ref_count {
842
843 my $self = shift;
844
845 return $self->{_ext_ref_count};
846}
847
848
849###############################################################################
850#
851# _get_name_index()
852#
853# Look up the index that corresponds to an external defined name. The hash of
854# defined names is updated by the define_name() method in the Workbook class.
855#
856sub _get_name_index {
857
858 my $self = shift;
859 my $name = shift;
860
861 if (not exists $self->{_ext_names}->{$name}) {
862 die "Unknown defined name $name in formula\n";
863 }
864 else {
865 return $self->{_ext_names}->{$name};
866 }
867}
868
869
870###############################################################################
871#
872# set_ext_name()
873#
874# This semi-public method is used to update the hash of defined names.
875#
876sub set_ext_name {
877
878 my $self = shift;
879 my $name = shift;
880 my $index = shift;
881
882 $self->{_ext_names}->{$name} = $index;
883}
884
885
886###############################################################################
887#
888# _convert_function()
889#
890# Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
891# args that it takes.
892#
893sub _convert_function {
894
895 my $self = shift;
896 my $token = shift;
897 my $num_args = shift;
898
899 die "Unknown function $token() in formula\n"
900 unless defined $functions{$token}[0];
901
902 my $args = $functions{$token}[1];
903
904 # Fixed number of args eg. TIME($i,$j,$k).
905 if ($args >= 0) {
906 # Check that the number of args is valid.
907 if ($args != $num_args) {
908 die "Incorrect number of arguments for $token() in formula\n";
909 }
910 else {
911 return pack("Cv", $ptg{ptgFuncV}, $functions{$token}[0]);
912 }
913 }
914
915 # Variable number of args eg. SUM($i,$j,$k, ..).
916 if ($args == -1) {
917 return pack "CCv", $ptg{ptgFuncVarV}, $num_args, $functions{$token}[0];
918 }
919}
920
921
922###############################################################################
923#
924# _convert_name()
925#
926# Convert a symbolic name into a name reference.
927#
928sub _convert_name {
929
930 my $self = shift;
931 my $name = shift;
932 my $class = shift;
933
934 my $ptgName;
935
936 my $name_index = $self->_get_name_index($name);
937
938 # The ptg value depends on the class of the ptg.
939 if ($class == 0) {
940 $ptgName = $ptg{ptgName};
941 }
942 elsif ($class == 1) {
943 $ptgName = $ptg{ptgNameV};
944 }
945 elsif ($class == 2) {
946 $ptgName = $ptg{ptgNameA};
947 }
948
949
950 return pack 'CV', $ptgName, $name_index;
951}
952
953
954###############################################################################
955#
956# _cell_to_rowcol($cell_ref)
957#
958# Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
959# indexed row and column number. Also returns two boolean values to indicate
960# whether the row or column are relative references.
961# TODO use function in Utility.pm
962#
963sub _cell_to_rowcol {
964
965 my $self = shift;
966 my $cell = shift;
967
968 $cell =~ /(\$?)([A-I]?[A-Z])(\$?)(\d+)/;
969
970 my $col_rel = $1 eq "" ? 1 : 0;
971 my $col = $2;
972 my $row_rel = $3 eq "" ? 1 : 0;
973 my $row = $4;
974
975 # Convert base26 column string to a number.
976 # All your Base are belong to us.
977 my @chars = split //, $col;
978 my $expn = 0;
979 $col = 0;
980
981 while (@chars) {
982 my $char = pop(@chars); # LS char first
983 $col += (ord($char) - ord('A') + 1) * (26**$expn);
984 $expn++;
985 }
986
987 # Convert 1-index to zero-index
988 $row--;
989 $col--;
990
991 return $row, $col, $row_rel, $col_rel;
992}
993
994
995###############################################################################
996#
997# _cell_to_packed_rowcol($row, $col, $row_rel, $col_rel)
998#
999# pack() row and column into the required 3 byte format.
1000#
1001sub _cell_to_packed_rowcol {
1002
1003 use integer; # Avoid << shift bug in Perl 5.6.0 on HP-UX
1004
1005 my $self = shift;
1006 my $cell = shift;
1007
1008 my ($row, $col, $row_rel, $col_rel) = $self->_cell_to_rowcol($cell);
1009
1010 die "Column $cell greater than IV in formula\n" if $col >= 256;
1011 die "Row $cell greater than 65536 in formula\n" if $row >= 65536;
1012
1013 # Set the high bits to indicate if row or col are relative.
1014 $col |= $col_rel << 14;
1015 $col |= $row_rel << 15;
1016
1017 $row = pack('v', $row);
1018 $col = pack('v', $col);
1019
1020 return ($row, $col);
1021}
1022
1023
1024###############################################################################
1025#
1026# _initialize_hashes()
1027#
1028sub _initialize_hashes {
1029
1030 # The Excel ptg indices
1031 %ptg = (
1032 'ptgExp' => 0x01,
1033 'ptgTbl' => 0x02,
1034 'ptgAdd' => 0x03,
1035 'ptgSub' => 0x04,
1036 'ptgMul' => 0x05,
1037 'ptgDiv' => 0x06,
1038 'ptgPower' => 0x07,
1039 'ptgConcat' => 0x08,
1040 'ptgLT' => 0x09,
1041 'ptgLE' => 0x0A,
1042 'ptgEQ' => 0x0B,
1043 'ptgGE' => 0x0C,
1044 'ptgGT' => 0x0D,
1045 'ptgNE' => 0x0E,
1046 'ptgIsect' => 0x0F,
1047 'ptgUnion' => 0x10,
1048 'ptgRange' => 0x11,
1049 'ptgUplus' => 0x12,
1050 'ptgUminus' => 0x13,
1051 'ptgPercent' => 0x14,
1052 'ptgParen' => 0x15,
1053 'ptgMissArg' => 0x16,
1054 'ptgStr' => 0x17,
1055 'ptgAttr' => 0x19,
1056 'ptgSheet' => 0x1A,
1057 'ptgEndSheet' => 0x1B,
1058 'ptgErr' => 0x1C,
1059 'ptgBool' => 0x1D,
1060 'ptgInt' => 0x1E,
1061 'ptgNum' => 0x1F,
1062 'ptgArray' => 0x20,
1063 'ptgFunc' => 0x21,
1064 'ptgFuncVar' => 0x22,
1065 'ptgName' => 0x23,
1066 'ptgRef' => 0x24,
1067 'ptgArea' => 0x25,
1068 'ptgMemArea' => 0x26,
1069 'ptgMemErr' => 0x27,
1070 'ptgMemNoMem' => 0x28,
1071 'ptgMemFunc' => 0x29,
1072 'ptgRefErr' => 0x2A,
1073 'ptgAreaErr' => 0x2B,
1074 'ptgRefN' => 0x2C,
1075 'ptgAreaN' => 0x2D,
1076 'ptgMemAreaN' => 0x2E,
1077 'ptgMemNoMemN' => 0x2F,
1078 'ptgNameX' => 0x39,
1079 'ptgRef3d' => 0x3A,
1080 'ptgArea3d' => 0x3B,
1081 'ptgRefErr3d' => 0x3C,
1082 'ptgAreaErr3d' => 0x3D,
1083 'ptgArrayV' => 0x40,
1084 'ptgFuncV' => 0x41,
1085 'ptgFuncVarV' => 0x42,
1086 'ptgNameV' => 0x43,
1087 'ptgRefV' => 0x44,
1088 'ptgAreaV' => 0x45,
1089 'ptgMemAreaV' => 0x46,
1090 'ptgMemErrV' => 0x47,
1091 'ptgMemNoMemV' => 0x48,
1092 'ptgMemFuncV' => 0x49,
1093 'ptgRefErrV' => 0x4A,
1094 'ptgAreaErrV' => 0x4B,
1095 'ptgRefNV' => 0x4C,
1096 'ptgAreaNV' => 0x4D,
1097 'ptgMemAreaNV' => 0x4E,
1098 'ptgMemNoMemN' => 0x4F,
1099 'ptgFuncCEV' => 0x58,
1100 'ptgNameXV' => 0x59,
1101 'ptgRef3dV' => 0x5A,
1102 'ptgArea3dV' => 0x5B,
1103 'ptgRefErr3dV' => 0x5C,
1104 'ptgAreaErr3d' => 0x5D,
1105 'ptgArrayA' => 0x60,
1106 'ptgFuncA' => 0x61,
1107 'ptgFuncVarA' => 0x62,
1108 'ptgNameA' => 0x63,
1109 'ptgRefA' => 0x64,
1110 'ptgAreaA' => 0x65,
1111 'ptgMemAreaA' => 0x66,
1112 'ptgMemErrA' => 0x67,
1113 'ptgMemNoMemA' => 0x68,
1114 'ptgMemFuncA' => 0x69,
1115 'ptgRefErrA' => 0x6A,
1116 'ptgAreaErrA' => 0x6B,
1117 'ptgRefNA' => 0x6C,
1118 'ptgAreaNA' => 0x6D,
1119 'ptgMemAreaNA' => 0x6E,
1120 'ptgMemNoMemN' => 0x6F,
1121 'ptgFuncCEA' => 0x78,
1122 'ptgNameXA' => 0x79,
1123 'ptgRef3dA' => 0x7A,
1124 'ptgArea3dA' => 0x7B,
1125 'ptgRefErr3dA' => 0x7C,
1126 'ptgAreaErr3d' => 0x7D,
1127 );
1128
1129 # Thanks to Michael Meeks and Gnumeric for the initial arg values.
1130 #
1131 # The following hash was generated by "function_locale.pl" in the distro.
1132 # Refer to function_locale.pl for non-English function names.
1133 #
1134 # The array elements are as follow:
1135 # ptg: The Excel function ptg code.
1136 # args: The number of arguments that the function takes:
1137 # >=0 is a fixed number of arguments.
1138 # -1 is a variable number of arguments.
1139 # class: The reference, value or array class of the function args.
1140 # vol: The function is volatile.
1141 #
1142 %functions = (
1143 # ptg args class vol
1144 'COUNT' => [ 0, -1, 0, 0 ],
1145 'IF' => [ 1, -1, 1, 0 ],
1146 'ISNA' => [ 2, 1, 1, 0 ],
1147 'ISERROR' => [ 3, 1, 1, 0 ],
1148 'SUM' => [ 4, -1, 0, 0 ],
1149 'AVERAGE' => [ 5, -1, 0, 0 ],
1150 'MIN' => [ 6, -1, 0, 0 ],
1151 'MAX' => [ 7, -1, 0, 0 ],
1152 'ROW' => [ 8, -1, 0, 0 ],
1153 'COLUMN' => [ 9, -1, 0, 0 ],
1154 'NA' => [ 10, 0, 0, 0 ],
1155 'NPV' => [ 11, -1, 1, 0 ],
1156 'STDEV' => [ 12, -1, 0, 0 ],
1157 'DOLLAR' => [ 13, -1, 1, 0 ],
1158 'FIXED' => [ 14, -1, 1, 0 ],
1159 'SIN' => [ 15, 1, 1, 0 ],
1160 'COS' => [ 16, 1, 1, 0 ],
1161 'TAN' => [ 17, 1, 1, 0 ],
1162 'ATAN' => [ 18, 1, 1, 0 ],
1163 'PI' => [ 19, 0, 1, 0 ],
1164 'SQRT' => [ 20, 1, 1, 0 ],
1165 'EXP' => [ 21, 1, 1, 0 ],
1166 'LN' => [ 22, 1, 1, 0 ],
1167 'LOG10' => [ 23, 1, 1, 0 ],
1168 'ABS' => [ 24, 1, 1, 0 ],
1169 'INT' => [ 25, 1, 1, 0 ],
1170 'SIGN' => [ 26, 1, 1, 0 ],
1171 'ROUND' => [ 27, 2, 1, 0 ],
1172 'LOOKUP' => [ 28, -1, 0, 0 ],
1173 'INDEX' => [ 29, -1, 0, 1 ],
1174 'REPT' => [ 30, 2, 1, 0 ],
1175 'MID' => [ 31, 3, 1, 0 ],
1176 'LEN' => [ 32, 1, 1, 0 ],
1177 'VALUE' => [ 33, 1, 1, 0 ],
1178 'TRUE' => [ 34, 0, 1, 0 ],
1179 'FALSE' => [ 35, 0, 1, 0 ],
1180 'AND' => [ 36, -1, 1, 0 ],
1181 'OR' => [ 37, -1, 1, 0 ],
1182 'NOT' => [ 38, 1, 1, 0 ],
1183 'MOD' => [ 39, 2, 1, 0 ],
1184 'DCOUNT' => [ 40, 3, 0, 0 ],
1185 'DSUM' => [ 41, 3, 0, 0 ],
1186 'DAVERAGE' => [ 42, 3, 0, 0 ],
1187 'DMIN' => [ 43, 3, 0, 0 ],
1188 'DMAX' => [ 44, 3, 0, 0 ],
1189 'DSTDEV' => [ 45, 3, 0, 0 ],
1190 'VAR' => [ 46, -1, 0, 0 ],
1191 'DVAR' => [ 47, 3, 0, 0 ],
1192 'TEXT' => [ 48, 2, 1, 0 ],
1193 'LINEST' => [ 49, -1, 0, 0 ],
1194 'TREND' => [ 50, -1, 0, 0 ],
1195 'LOGEST' => [ 51, -1, 0, 0 ],
1196 'GROWTH' => [ 52, -1, 0, 0 ],
1197 'PV' => [ 56, -1, 1, 0 ],
1198 'FV' => [ 57, -1, 1, 0 ],
1199 'NPER' => [ 58, -1, 1, 0 ],
1200 'PMT' => [ 59, -1, 1, 0 ],
1201 'RATE' => [ 60, -1, 1, 0 ],
1202 'MIRR' => [ 61, 3, 0, 0 ],
1203 'IRR' => [ 62, -1, 0, 0 ],
1204 'RAND' => [ 63, 0, 1, 1 ],
1205 'MATCH' => [ 64, -1, 0, 0 ],
1206 'DATE' => [ 65, 3, 1, 0 ],
1207 'TIME' => [ 66, 3, 1, 0 ],
1208 'DAY' => [ 67, 1, 1, 0 ],
1209 'MONTH' => [ 68, 1, 1, 0 ],
1210 'YEAR' => [ 69, 1, 1, 0 ],
1211 'WEEKDAY' => [ 70, -1, 1, 0 ],
1212 'HOUR' => [ 71, 1, 1, 0 ],
1213 'MINUTE' => [ 72, 1, 1, 0 ],
1214 'SECOND' => [ 73, 1, 1, 0 ],
1215 'NOW' => [ 74, 0, 1, 1 ],
1216 'AREAS' => [ 75, 1, 0, 1 ],
1217 'ROWS' => [ 76, 1, 0, 1 ],
1218 'COLUMNS' => [ 77, 1, 0, 1 ],
1219 'OFFSET' => [ 78, -1, 0, 1 ],
1220 'SEARCH' => [ 82, -1, 1, 0 ],
1221 'TRANSPOSE' => [ 83, 1, 1, 0 ],
1222 'TYPE' => [ 86, 1, 1, 0 ],
1223 'ATAN2' => [ 97, 2, 1, 0 ],
1224 'ASIN' => [ 98, 1, 1, 0 ],
1225 'ACOS' => [ 99, 1, 1, 0 ],
1226 'CHOOSE' => [ 100, -1, 1, 0 ],
1227 'HLOOKUP' => [ 101, -1, 0, 0 ],
1228 'VLOOKUP' => [ 102, -1, 0, 0 ],
1229 'ISREF' => [ 105, 1, 0, 0 ],
1230 'LOG' => [ 109, -1, 1, 0 ],
1231 'CHAR' => [ 111, 1, 1, 0 ],
1232 'LOWER' => [ 112, 1, 1, 0 ],
1233 'UPPER' => [ 113, 1, 1, 0 ],
1234 'PROPER' => [ 114, 1, 1, 0 ],
1235 'LEFT' => [ 115, -1, 1, 0 ],
1236 'RIGHT' => [ 116, -1, 1, 0 ],
1237 'EXACT' => [ 117, 2, 1, 0 ],
1238 'TRIM' => [ 118, 1, 1, 0 ],
1239 'REPLACE' => [ 119, 4, 1, 0 ],
1240 'SUBSTITUTE' => [ 120, -1, 1, 0 ],
1241 'CODE' => [ 121, 1, 1, 0 ],
1242 'FIND' => [ 124, -1, 1, 0 ],
1243 'CELL' => [ 125, -1, 0, 1 ],
1244 'ISERR' => [ 126, 1, 1, 0 ],
1245 'ISTEXT' => [ 127, 1, 1, 0 ],
1246 'ISNUMBER' => [ 128, 1, 1, 0 ],
1247 'ISBLANK' => [ 129, 1, 1, 0 ],
1248 'T' => [ 130, 1, 0, 0 ],
1249 'N' => [ 131, 1, 0, 0 ],
1250 'DATEVALUE' => [ 140, 1, 1, 0 ],
1251 'TIMEVALUE' => [ 141, 1, 1, 0 ],
1252 'SLN' => [ 142, 3, 1, 0 ],
1253 'SYD' => [ 143, 4, 1, 0 ],
1254 'DDB' => [ 144, -1, 1, 0 ],
1255 'INDIRECT' => [ 148, -1, 1, 1 ],
1256 'CALL' => [ 150, -1, 1, 0 ],
1257 'CLEAN' => [ 162, 1, 1, 0 ],
1258 'MDETERM' => [ 163, 1, 2, 0 ],
1259 'MINVERSE' => [ 164, 1, 2, 0 ],
1260 'MMULT' => [ 165, 2, 2, 0 ],
1261 'IPMT' => [ 167, -1, 1, 0 ],
1262 'PPMT' => [ 168, -1, 1, 0 ],
1263 'COUNTA' => [ 169, -1, 0, 0 ],
1264 'PRODUCT' => [ 183, -1, 0, 0 ],
1265 'FACT' => [ 184, 1, 1, 0 ],
1266 'DPRODUCT' => [ 189, 3, 0, 0 ],
1267 'ISNONTEXT' => [ 190, 1, 1, 0 ],
1268 'STDEVP' => [ 193, -1, 0, 0 ],
1269 'VARP' => [ 194, -1, 0, 0 ],
1270 'DSTDEVP' => [ 195, 3, 0, 0 ],
1271 'DVARP' => [ 196, 3, 0, 0 ],
1272 'TRUNC' => [ 197, -1, 1, 0 ],
1273 'ISLOGICAL' => [ 198, 1, 1, 0 ],
1274 'DCOUNTA' => [ 199, 3, 0, 0 ],
1275 'ROUNDUP' => [ 212, 2, 1, 0 ],
1276 'ROUNDDOWN' => [ 213, 2, 1, 0 ],
1277 'RANK' => [ 216, -1, 0, 0 ],
1278 'ADDRESS' => [ 219, -1, 1, 0 ],
1279 'DAYS360' => [ 220, -1, 1, 0 ],
1280 'TODAY' => [ 221, 0, 1, 1 ],
1281 'VDB' => [ 222, -1, 1, 0 ],
1282 'MEDIAN' => [ 227, -1, 0, 0 ],
1283 'SUMPRODUCT' => [ 228, -1, 2, 0 ],
1284 'SINH' => [ 229, 1, 1, 0 ],
1285 'COSH' => [ 230, 1, 1, 0 ],
1286 'TANH' => [ 231, 1, 1, 0 ],
1287 'ASINH' => [ 232, 1, 1, 0 ],
1288 'ACOSH' => [ 233, 1, 1, 0 ],
1289 'ATANH' => [ 234, 1, 1, 0 ],
1290 'DGET' => [ 235, 3, 0, 0 ],
1291 'INFO' => [ 244, 1, 1, 1 ],
1292 'DB' => [ 247, -1, 1, 0 ],
1293 'FREQUENCY' => [ 252, 2, 0, 0 ],
1294 'ERROR.TYPE' => [ 261, 1, 1, 0 ],
1295 'REGISTER.ID' => [ 267, -1, 1, 0 ],
1296 'AVEDEV' => [ 269, -1, 0, 0 ],
1297 'BETADIST' => [ 270, -1, 1, 0 ],
1298 'GAMMALN' => [ 271, 1, 1, 0 ],
1299 'BETAINV' => [ 272, -1, 1, 0 ],
1300 'BINOMDIST' => [ 273, 4, 1, 0 ],
1301 'CHIDIST' => [ 274, 2, 1, 0 ],
1302 'CHIINV' => [ 275, 2, 1, 0 ],
1303 'COMBIN' => [ 276, 2, 1, 0 ],
1304 'CONFIDENCE' => [ 277, 3, 1, 0 ],
1305 'CRITBINOM' => [ 278, 3, 1, 0 ],
1306 'EVEN' => [ 279, 1, 1, 0 ],
1307 'EXPONDIST' => [ 280, 3, 1, 0 ],
1308 'FDIST' => [ 281, 3, 1, 0 ],
1309 'FINV' => [ 282, 3, 1, 0 ],
1310 'FISHER' => [ 283, 1, 1, 0 ],
1311 'FISHERINV' => [ 284, 1, 1, 0 ],
1312 'FLOOR' => [ 285, 2, 1, 0 ],
1313 'GAMMADIST' => [ 286, 4, 1, 0 ],
1314 'GAMMAINV' => [ 287, 3, 1, 0 ],
1315 'CEILING' => [ 288, 2, 1, 0 ],
1316 'HYPGEOMDIST' => [ 289, 4, 1, 0 ],
1317 'LOGNORMDIST' => [ 290, 3, 1, 0 ],
1318 'LOGINV' => [ 291, 3, 1, 0 ],
1319 'NEGBINOMDIST' => [ 292, 3, 1, 0 ],
1320 'NORMDIST' => [ 293, 4, 1, 0 ],
1321 'NORMSDIST' => [ 294, 1, 1, 0 ],
1322 'NORMINV' => [ 295, 3, 1, 0 ],
1323 'NORMSINV' => [ 296, 1, 1, 0 ],
1324 'STANDARDIZE' => [ 297, 3, 1, 0 ],
1325 'ODD' => [ 298, 1, 1, 0 ],
1326 'PERMUT' => [ 299, 2, 1, 0 ],
1327 'POISSON' => [ 300, 3, 1, 0 ],
1328 'TDIST' => [ 301, 3, 1, 0 ],
1329 'WEIBULL' => [ 302, 4, 1, 0 ],
1330 'SUMXMY2' => [ 303, 2, 2, 0 ],
1331 'SUMX2MY2' => [ 304, 2, 2, 0 ],
1332 'SUMX2PY2' => [ 305, 2, 2, 0 ],
1333 'CHITEST' => [ 306, 2, 2, 0 ],
1334 'CORREL' => [ 307, 2, 2, 0 ],
1335 'COVAR' => [ 308, 2, 2, 0 ],
1336 'FORECAST' => [ 309, 3, 2, 0 ],
1337 'FTEST' => [ 310, 2, 2, 0 ],
1338 'INTERCEPT' => [ 311, 2, 2, 0 ],
1339 'PEARSON' => [ 312, 2, 2, 0 ],
1340 'RSQ' => [ 313, 2, 2, 0 ],
1341 'STEYX' => [ 314, 2, 2, 0 ],
1342 'SLOPE' => [ 315, 2, 2, 0 ],
1343 'TTEST' => [ 316, 4, 2, 0 ],
1344 'PROB' => [ 317, -1, 2, 0 ],
1345 'DEVSQ' => [ 318, -1, 0, 0 ],
1346 'GEOMEAN' => [ 319, -1, 0, 0 ],
1347 'HARMEAN' => [ 320, -1, 0, 0 ],
1348 'SUMSQ' => [ 321, -1, 0, 0 ],
1349 'KURT' => [ 322, -1, 0, 0 ],
1350 'SKEW' => [ 323, -1, 0, 0 ],
1351 'ZTEST' => [ 324, -1, 0, 0 ],
1352 'LARGE' => [ 325, 2, 0, 0 ],
1353 'SMALL' => [ 326, 2, 0, 0 ],
1354 'QUARTILE' => [ 327, 2, 0, 0 ],
1355 'PERCENTILE' => [ 328, 2, 0, 0 ],
1356 'PERCENTRANK' => [ 329, -1, 0, 0 ],
1357 'MODE' => [ 330, -1, 2, 0 ],
1358 'TRIMMEAN' => [ 331, 2, 0, 0 ],
1359 'TINV' => [ 332, 2, 1, 0 ],
1360 'CONCATENATE' => [ 336, -1, 1, 0 ],
1361 'POWER' => [ 337, 2, 1, 0 ],
1362 'RADIANS' => [ 342, 1, 1, 0 ],
1363 'DEGREES' => [ 343, 1, 1, 0 ],
1364 'SUBTOTAL' => [ 344, -1, 0, 0 ],
1365 'SUMIF' => [ 345, -1, 0, 0 ],
1366 'COUNTIF' => [ 346, 2, 0, 0 ],
1367 'COUNTBLANK' => [ 347, 1, 0, 0 ],
1368 'ROMAN' => [ 354, -1, 1, 0 ],
1369 );
1370
1371}
1372
1373
1374
1375
13761;
1377
1378
1379__END__
1380
1381
1382=head1 NAME
1383
1384Formula - A class for generating Excel formulas
1385
1386=head1 SYNOPSIS
1387
1388See the documentation for Spreadsheet::WriteExcel
1389
1390=head1 DESCRIPTION
1391
1392This module is used by Spreadsheet::WriteExcel. You do not need to use it directly.
1393
1394
1395=head1 NOTES
1396
1397The following notes are to help developers and maintainers understand the sequence of operation. They are also intended as a pro-memoria for the author. ;-)
1398
1399Spreadsheet::WriteExcel::Formula converts a textual representation of a formula into the pre-parsed binary format that Excel uses to store formulas. For example C<1+2*3> is stored as follows: C<1E 01 00 1E 02 00 1E 03 00 05 03>.
1400
1401This string is comprised of operators and operands arranged in a reverse-Polish format. The meaning of the tokens in the above example is shown in the following table:
1402
1403 Token Name Value
1404 1E ptgInt 0001 (stored as 01 00)
1405 1E ptgInt 0002 (stored as 02 00)
1406 1E ptgInt 0003 (stored as 03 00)
1407 05 ptgMul
1408 03 ptgAdd
1409
1410The tokens and token names are defined in the "Excel Developer's Kit" from Microsoft Press. C<ptg> stands for Parse ThinG (as in "That lexer can't grok it, it's a parse thang.")
1411
1412In general the tokens fall into two categories: operators such as C<ptgMul> and operands such as C<ptgInt>. When the formula is evaluated by Excel the operand tokens push values onto a stack. The operator tokens then pop the required number of operands off of the stack, perform an operation and push the resulting value back onto the stack. This methodology is similar to the basic operation of a reverse-Polish (RPN) calculator.
1413
1414Spreadsheet::WriteExcel::Formula parses a formula using a C<Parse::RecDescent> parser (at a later stage it may use a C<Parse::Yapp> parser or C<Parse::FastDescent>).
1415
1416The parser converts the textual representation of a formula into a parse tree. Thus, C<1+2*3> is converted into something like the following, C<e> stands for expression:
1417
1418 e
1419 / | \
1420 1 + e
1421 / | \
1422 2 * 3
1423
1424
1425The function C<_reverse_tree()> recurses down through this structure swapping the order of operators followed by operands to produce a reverse-Polish tree. In other words the formula is converted from in-fix notation to post-fix. Following the above example the resulting tree would look like this:
1426
1427
1428 e
1429 / | \
1430 1 e +
1431 / | \
1432 2 3 *
1433
1434The result of the recursion is a single array of tokens. In our example the simplified form would look like the following:
1435
1436 (1, 2, 3, *, +)
1437
1438The actual return value contains some additional information to help in the secondary parsing stage:
1439
1440 (_num, 1, _num, 2, _num, 3, ptgMul, ptgAdd, _arg, 1)
1441
1442The additional tokens are:
1443
1444 Token Meaning
1445 _num The next token is a number
1446 _str The next token is a string
1447 _ref2d The next token is a 2d cell reference
1448 _ref3d The next token is a 3d cell reference
1449 _range2d The next token is a 2d range
1450 _range3d The next token is a 3d range
1451 _func The next token is a function
1452 _arg The next token is the number of args for a function
1453 _class The next token is a function name
1454 _vol The formula contains a voltile function
1455
1456The C<_arg> token is generated for all lists but is only used for functions that take a variable number of arguments.
1457
1458The C<_class> token indicates the start of the arguments to a function. This allows the post-processor to decide the "class" of the ref and range arguments that the function takes. The class can be reference, value or array. Since function calls can be nested, the class variable is stored on a stack in the C<@class> array. The class of the ref or range is then read as the top element of the stack C<$class[-1]>. When a C<_func> is read it pops the class value.
1459
1460Certain Excel functions such as RAND() and NOW() are designated as volatile and must be recalculated by Excel every time that a cell is updated. Any formulas that contain one of these functions has a specially formatted C<ptgAttr> tag prepended to it to indicate that it is volatile.
1461
1462A secondary parsing stage is carried out by C<parse_tokens()> which converts these tokens into a binary string. For the C<1+2*3> example this would give:
1463
1464 1E 01 00 1E 02 00 1E 03 00 05 03
1465
1466This two-pass method could probably have been reduced to a single pass through the C<Parse::RecDescent> parser. However, it was easier to develop and debug this way.
1467
1468The token values and formula values are stored in the C<%ptg> and C<%functions> hashes. These hashes and the parser object C<$parser> are exposed as global data. This breaks the OO encapsulation, but means that they can be shared by several instances of Spreadsheet::WriteExcel called from the same program.
1469
1470Non-English function names can be added to the C<%functions> hash using the C<function_locale.pl> program in the C<examples> directory of the distro. The supported languages are: German, French, Spanish, Portuguese, Dutch, Finnish, Italian and Swedish. These languages are not added by default because there are conflicts between functions names in different languages.
1471
1472The parser is initialised by C<_init_parser()>. The initialisation is delayed until the first formula is parsed. This eliminates the overhead of generating the parser in programs that are not processing formulas. (The parser should really be pre-compiled, this is to-do when the grammar stabilises).
1473
1474
1475
1476
1477=head1 AUTHOR
1478
1479John McNamara jmcnamara@cpan.org
1480
1481=head1 COPYRIGHT
1482
1483© MM-MMX, John McNamara.
1484
1485All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.