Added new tests, fixed a bug, and cleaned up more code: Intermediate check-in
[p5sagit/Excel-Template.git] / lib / Excel / Template / Context.pm
CommitLineData
d0eafc11 1package Excel::Template::Context;
2
3use strict;
4
5BEGIN {
6 use vars qw(@ISA);
7 @ISA = qw(Excel::Template::Base);
8
9 use Excel::Template::Base;
10}
11
12use 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
8c63e224 18my %isAbsolute = map { $_ => ~~1 } qw(
d0eafc11 19 ROW
20 COL
21);
22
23sub 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);
37513eae 30 $self->{WORKSHEET_NAMES} = undef;
d0eafc11 31
e976988f 32 # Removed NAME_MAP until I figure out what the heck it's for
33 for (qw( STACK PARAM_MAP ))
c11fa570 34 {
d01e4722 35 next if defined $self->{$_} && ref $self->{$_} eq 'ARRAY';
c11fa570 36 $self->{$_} = [];
37 }
d0eafc11 38
39 $self->{$_} = 0 for keys %isAbsolute;
40
41 return $self;
42}
43
8c63e224 44sub use_unicode { $_[0]->{UNICODE} && 1 }
45
d0eafc11 46sub _find_param_in_map
47{
48 my $self = shift;
49 my ($map, $param, $depth) = @_;
50 $param = uc $param;
51 $depth ||= 0;
52
37513eae 53 my ($val, $found);
d0eafc11 54 for my $map (reverse @{$self->{$map}})
55 {
56 next unless exists $map->{$param};
57 $depth--, next if $depth;
58
8c63e224 59 $found = ~~1;
d0eafc11 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
70sub param
71{
72 my $self = shift;
73 $self->_find_param_in_map(
74 'PARAM_MAP',
75 @_,
76 );
77}
78
e976988f 79#sub named_param
80#{
81# my $self = shift;
82# $self->_find_param_in_map(
83# 'NAME_MAP',
84# @_,
85# );
86#}
d0eafc11 87
88sub 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
140sub 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
8c63e224 153 return ~~1;
d0eafc11 154}
155
156sub 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
8c63e224 169 return ~~1;
d0eafc11 170}
171
172sub 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
204sub active_format
205{
206 my $self = shift;
207
208 $self->{ACTIVE_FORMAT} = $_[0]
209 if @_;
210
211 $self->{ACTIVE_FORMAT};
212}
213
214sub new_worksheet
215{
216 my $self = shift;
37513eae 217 my ($worksheet) = @_;
d0eafc11 218
219 $self->{ROW} = $self->{COL} = 0;
37513eae 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 }
d0eafc11 239
240 $self->active_worksheet(
37513eae 241 $self->{XLS}->add_worksheet( $name ),
d0eafc11 242 );
243}
244
245sub active_worksheet
246{
247 my $self = shift;
248
249 $self->{ACTIVE_WORKSHEET} = $_[0]
250 if @_;
251
252 $self->{ACTIVE_WORKSHEET};
253}
254
37513eae 255sub 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
8c63e224 264 return ~~1;
37513eae 265}
266
267sub 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
277sub 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
d0eafc11 2871;
288__END__
289
290=head1 NAME
291
292Excel::Template::Context
293
294=head1 PURPOSE
295
c11fa570 296This 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
298Documentation is provided for if you wish to subclass another node.
299
d0eafc11 300=head1 NODE NAME
301
c11fa570 302None
303
d0eafc11 304=head1 INHERITANCE
305
c11fa570 306None
307
d0eafc11 308=head1 ATTRIBUTES
309
c11fa570 310None
311
d0eafc11 312=head1 CHILDREN
313
c11fa570 314None
315
d0eafc11 316=head1 AFFECTS
317
c11fa570 318Everything
319
d0eafc11 320=head1 DEPENDENCIES
321
c11fa570 322None
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
d0eafc11 345
346=head1 AUTHOR
347
c09684ff 348Rob Kinyon (rob.kinyon@gmail.com)
d0eafc11 349
350=head1 SEE ALSO
351
352=cut