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