Added more tests for conditionals. Need to fix linking in POD. - Intermediate commit!
[p5sagit/Excel-Template.git] / lib / Excel / Template / Context.pm
1 package Excel::Template::Context;
2
3 use strict;
4
5 BEGIN {
6     use vars qw(@ISA);
7     @ISA = qw(Excel::Template::Base);
8
9     use Excel::Template::Base;
10 }
11
12 use Excel::Template::Format;
13
14 # This is a helper object. It is not instantiated by the user, nor does it
15 # represent an XML object. Rather, every container will use this object to
16 # maintain the context for its children.
17
18 my %isAbsolute = map { $_ => ~~1 } qw(
19     ROW
20     COL
21 );
22
23 sub new
24 {
25     my $class = shift;
26     my $self = $class->SUPER::new(@_);
27
28     $self->{ACTIVE_WORKSHEET} = undef;
29     $self->{ACTIVE_FORMAT}    = Excel::Template::Format->blank_format($self);
30     $self->{WORKSHEET_NAMES}  = undef;
31
32     $self->{__MARKS} = {};
33
34     # Removed NAME_MAP until I figure out what the heck it's for
35     for (qw( STACK PARAM_MAP ))
36     {
37         next if defined $self->{$_} && ref $self->{$_} eq 'ARRAY';
38         $self->{$_} = [];
39     }
40
41     $self->{$_} = 0 for keys %isAbsolute;
42
43     return $self;
44 }
45
46 sub use_unicode { $_[0]->{UNICODE} && 1 }
47
48 sub _find_param_in_map
49 {
50     my $self = shift;
51     my ($map, $param, $depth) = @_;
52     $param = uc $param;
53     $depth ||= 0;
54
55     my ($val, $found);
56     for my $map (reverse @{$self->{$map}})
57     {
58         next unless exists $map->{$param};
59         $depth--, next if $depth;
60
61         $found = ~~1;
62         $val = $map->{$param};
63         last;
64     }
65
66     die "Parameter '$param' not found\n"
67         if !$found && $self->{DIE_ON_NO_PARAM};
68
69     return $val;
70 }
71
72 sub param
73 {
74     my $self = shift;
75     $self->_find_param_in_map(
76         'PARAM_MAP',
77         @_,
78     );
79 }
80
81 #sub named_param
82 #{
83 #    my $self = shift;
84 #    $self->_find_param_in_map(
85 #        'NAME_MAP',
86 #        @_,
87 #    );
88 #}
89
90 sub resolve
91 {
92     my $self = shift;
93     my ($obj, $key, $depth) = @_;
94     $key = uc $key;
95     $depth ||= 0;
96
97     my $obj_val = $obj->{$key};
98
99     $obj_val = $self->param($1)
100         if $obj_val =~ /^\$(\S+)$/o;
101
102 #GGG Remove this once NAME_MAP is working
103 #    $obj_val = $self->named_param($1)
104 #        if $obj_val =~ /^\\(\S+)$/o;
105
106 #GGG Does this adequately test values to make sure they're legal??
107     # A value is defined as:
108     #    1) An optional operator (+, -, *, or /)
109     #    2) A decimal number
110
111 #GGG Convert this to use //x
112     my ($op, $val) = $obj_val =~ m/^\s*([\+\*\/\-])?\s*([\d.]*\d)\s*$/oi;
113
114     # Unless it's a relative value, we have what we came for.
115     return $obj_val unless $op;
116
117     my $prev_val = $isAbsolute{$key}
118         ? $self->{$key}
119         : $self->get($obj, $key, $depth + 1);
120
121     return $obj_val unless defined $prev_val;
122     return $prev_val unless defined $obj_val;
123
124     # Prevent divide-by-zero issues.
125     return $val if $op eq '/' and $val == 0;
126
127     my $new_val;
128     for ($op)
129     {
130         /^\+$/ && do { $new_val = ($prev_val + $val); last; };
131         /^\-$/ && do { $new_val = ($prev_val - $val); last; };
132         /^\*$/ && do { $new_val = ($prev_val * $val); last; };
133         /^\/$/ && do { $new_val = ($prev_val / $val); last; };
134
135         die "Unknown operator '$op' in arithmetic resolve\n";
136     }
137
138     return $new_val if defined $new_val;
139     return;
140 }
141
142 sub enter_scope
143 {
144     my $self = shift;
145     my ($obj) = @_;
146
147     push @{$self->{STACK}}, $obj;
148
149     for my $key (keys %isAbsolute)
150     {
151         next unless exists $obj->{$key};
152         $self->{$key} = $self->resolve($obj, $key);
153     }
154
155     return ~~1;
156 }
157
158 sub exit_scope
159 {
160     my $self = shift;
161     my ($obj, $no_delta) = @_;
162
163     unless ($no_delta)
164     {
165         my $deltas = $obj->deltas($self);
166         $self->{$_} += $deltas->{$_} for keys %$deltas;
167     }
168
169     pop @{$self->{STACK}};
170
171     return ~~1;
172 }
173
174 sub get
175 {
176     my $self = shift;
177     my ($dummy, $key, $depth) = @_;
178     $depth ||= 0;
179     $key = uc $key;
180
181     return unless @{$self->{STACK}};
182
183     my $obj = $self->{STACK}[-1];
184
185     return $self->{$key} if $isAbsolute{$key};
186
187     my $val = undef;
188     my $this_depth = $depth;
189     foreach my $e (reverse @{$self->{STACK}})
190     {
191         next unless exists $e->{$key};
192         next if $this_depth-- > 0;
193
194         $val = $self->resolve($e, $key, $depth);
195         last;
196     }
197
198     $val = $self->{$key} unless defined $val;
199     return $val unless defined $val;
200
201     return $self->param($1, $depth) if $val =~ /^\$(\S+)$/o;
202
203     return $val;
204 }
205
206 sub active_format
207 {
208     my $self = shift;
209     
210     $self->{ACTIVE_FORMAT} = $_[0]
211         if @_;
212
213     $self->{ACTIVE_FORMAT};
214 }
215
216 sub new_worksheet
217 {
218     my $self = shift;
219     my ($worksheet) = @_;
220
221     $self->{ROW} = $self->{COL} = 0;
222     $self->{REFERENCES} = {};
223
224     my $name = $self->get( $worksheet, 'NAME' );
225
226     if ( defined $name && length $name )
227     {
228         if ( exists $self->{WORKSHEET_NAMES}{$name} )
229         {
230             $name = '';
231         }
232         else
233         {
234             $self->{WORKSHEET_NAMES}{$name} = undef;
235         }
236     }
237     else
238     {
239         $name = '';
240     }
241
242     return $self->active_worksheet(
243         $self->{XLS}->add_worksheet( $name ),
244     );
245 }
246
247 sub mark
248 {
249     my $self = shift;
250
251     if ( @_ > 1 )
252     {
253         my %args = @_;
254
255         @{$self->{__MARKS}}{keys %args} = values %args;
256     }
257
258     return $self->{__MARKS}{$_[0]}
259 }
260
261 sub active_worksheet
262 {
263     my $self = shift;
264     
265     $self->{ACTIVE_WORKSHEET} = $_[0]
266         if @_;
267
268     $self->{ACTIVE_WORKSHEET};
269 }
270
271 sub add_reference
272 {
273     my $self = shift;
274     my ($ref, $row, $col) = @_;
275
276     $self->{REFERENCES}{$ref} ||= [];
277
278     push @{$self->{REFERENCES}{$ref}}, [ $row, $col ];
279
280     return ~~1;
281 }
282
283 sub get_all_references
284 {
285     my $self = shift;
286     my $ref = uc shift;
287
288     $self->{REFERENCES}{$ref} ||= [];
289
290     return @{ $self->{REFERENCES}{$ref} };
291 }
292
293 sub get_last_reference
294 {
295     my $self = shift;
296     my $ref = uc shift;
297
298     $self->{REFERENCES}{$ref} ||= [];
299
300     return @{ $self->{REFERENCES}{$ref}[-1] };
301 }
302
303 1;
304 __END__
305
306 =head1 NAME
307
308 Excel::Template::Context
309
310 =head1 PURPOSE
311
312 This is a helper node that provides the global context for the nodes do their processing within. It provides attribute scoping, parameter resolution, and other very nice things.
313
314 Documentation is provided for if you wish to subclass another node.
315
316 =head1 NODE NAME
317
318 None
319
320 =head1 INHERITANCE
321
322 None
323
324 =head1 ATTRIBUTES
325
326 None
327
328 =head1 CHILDREN
329
330 None
331
332 =head1 AFFECTS
333
334 Everything
335
336 =head1 DEPENDENCIES
337
338 None
339
340 =head1 METHODS
341
342 =head2 active_format
343
344 =head2 active_worksheet
345
346 =head2 add_reference
347
348 =head2 get
349
350 =head2 get_all_references
351
352 =head2 get_last_reference
353
354 =head2 mark
355
356 =head2 new_worksheet
357
358 =head2 param
359
360 =head2 use_unicode
361
362 =head1 AUTHOR
363
364 Rob Kinyon (rob.kinyon@gmail.com)
365
366 =head1 SEE ALSO
367
368 =cut