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