File: SkinScriptSyntax.stx

package info (click to toggle)
zope-zpatterns 0.4.3p2-2
  • links: PTS
  • area: main
  • in suites: woody
  • size: 476 kB
  • ctags: 814
  • sloc: python: 2,817; ansic: 310; makefile: 52; sh: 39
file content (680 lines) | stat: -rw-r--r-- 30,323 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
The SkinScript Language Reference


 Basic Concepts and Syntax

  If you're familiar with ZPatterns, you know that DataSkins get their
  attributes and behavior from AttributeProviders and RuleAgents,
  respectively.  Providers and Agents (known as DataPlugIns) can be
  written directly in Python, but it is much easier to write them in
  SkinScript.  SkinScript lets you define an individual attribute
  provider or rule agent in a single declarative statement, using
  standard Zope tools such as SQL Methods, DTML Methods, Python
  Methods, and so on to do the actual data retrieval or storage.  In
  effect, SkinScript is a "glue language" which lets you define the
  linkages between a DataSkin and the methods you want to implement
  its "skeleton" of data storage and triggered behavior.

  In most languages, the order that statements appear in makes a
  difference to the results you get, and SkinScript is no exception.
  Although each SkinScript statement defines a seperate and
  independent data plug-in, the ordering makes a difference in how
  they will be used by DataSkins.  When a DataSkin needs to perform
  an operation (such as retrieving an attribute), it asks its
  DataManager for a list of suitable DataPlugIns.  This list is
  ordered according to the original order of plug-ins as listed on the
  Data Plug-ins tab of the Racks or Customizers involved.  And the
  declarations of a SkinScript Method are treated as though they were
  individual plug-ins appearing in place of the SkinScript Method in
  that list.

  In other words, SkinScript is literally a language for defining
  data plug-ins.  A SkinScript *script* consists of a series of
  *declarations*, seperated by whitespace.  Each declaration is
  compiled into a single data plug-in.  SkinScript is a
  keyword-driven language, is case-sensitive, and is not whitespace
  sensitive, even for Python expressions contained within
  declarations.  All whitespace which occurs outside of quoted strings
  is treated as though it were a single space.  (Incidentally, this
  means that the usual Python rules about where you can put linebreaks
  in expressions do not apply.  You can write the expression '1+2'
  split across three lines, if you so desire.)  Comments are marked as
  in Python, using a '#' symbol to mean that the rest of the current
  line is a comment.  Comments are treated as simple whitespace.



 Declarations

  Declarations are the basic building block of SkinScript.
  Declarations are compiled into AttributeProviders or RuleAgents, one
  per declaration.  Some declarations provide attribute values:

   - 'INITIALIZE OBJECT WITH' *assignmentlist*

   - 'WITH [QUERY]' *expression* 'COMPUTE' *nameorassignlist* [OTHERWISE LET assignmentlist] [DEPENDENT ON dependencies]

   - 'WITH SELF COMPUTE' *assignmentlist*

  Some handle attribute storage:

   - [WHEN eventspec] 'STORE' *attributelist* 'USING' *expression* [ 'SAVING' *mementolist* ]

   - 'STORE' *attributelist* 'IN SELF'


  And others can call an expression upon transaction commit:

   - 'WHEN' *eventspec* 'CALL' *expression* [ 'SAVING' *mementolist* ]


  Each declaration has its own parameters, but there are certain
  conventions which are followed across most kinds of declarations.


 Declaration Parameters

  *expression* -- A DTML-style Python expression.  As with DTML, the
  "_" object is available for access to Python built-ins and library
  functions.  Please see the section below on "Variables and Functions
  Available in Expressions" for details on how names other than "_"
  are looked up in SkinScript expressions.

  *assignmentlist* -- A comma-seperated list of assignments in the
  form *attributename* = *expression*, similar to passing keyword
  arguments to a function or method.

  *nameorassignlist* -- Similar to *assignmentlist*, but with a
  special shorthand for the case where *attributename* and
  *expression* are the same (e.g. 'foo=foo').  To make such an
  assignment, you can replace *attributename* '=' *expression* with
  just *attributename*.  So 'foo=bar,baz=baz,bada=bing' can be
  simplified to 'foo=bar,baz,bada=bing' if a clause's syntax allows a
  *nameorassignmentlist*.

  *mementolist* -- A *mementolist* is syntactically identical to a
  *nameorassignlist*, but has a different context and purpose.  A
  *mementolist* is used to save old values of expressions for later
  comparison purposes when a (sub)transaction commits, and appears
  only in 'SAVING' clauses.  Expression variables are looked up in the
  context of the DataSkin whose snapshot is being taken.  So in the
  clause 'SAVING bar,foo=baz' the DataSkin's 'bar' attribute will be
  saved as OLD['bar'], and its 'baz' attribute will be saved as
  OLD['foo'].

   A *mementolist* is computed only once per (sub)transaction for a
   given DataSkin.  The first time the DataSkin is changed in a
   (sub)transaction, the 'SAVING' clause is executed for all
   declarations that have one, even if the declaration never ends up
   firing.

  *eventspec* -- A clause of the form 'OBJECT ADDED, CHANGED,
  DELETED', where the 'ADDED', 'CHANGED' and 'DELETED' keywords may be
  used in any combination.  For example, 'OBJECT ADDED,DELETED' and
  'OBJECT CHANGED' are both valid *eventspec* clauses.  Please see
  HowTriggersWork for details on how ZPatterns events are interpreted
  by SkinScript.
  
  *attributelist* -- A comma-seperated list of attribute names, to
  which the declaration will be applied.  An asterisk ("*") may be
  used as a wildcard attribute name, meaning that the declaration will
  be applicable for any attribute name.  Remember, however, that
  declarations associated with specific names will take precedence
  over wildcard declarations, even if the wildcard declaration comes
  before the specific declaration and would match the name being
  looked up.

  *dependencies* -- A comma-seperated list of attribute names which,
  when changed, will cause the dependent attributes to be recalculated
  on their next access.  Wildcards cannot be used, only actual
  attribute names.



 Variables and Functions Available in Expressions

  Names used in an expression are usually looked up from the
  attributes of individual SkinScript statement and its acquisition
  parents (e.g. the Rack or Customizer which it's contained in, and so
  on up the line).  One exception to this rule is in the 'COMPUTE'
  clause of a WITH/COMPUTE declaration, where names are first looked
  up in the 'RESULT' object returned by the 'WITH' clause.  The other
  exception is in 'SAVING' clauses, where names are looked up in the
  context of the DataSkin whose snapshot is being taken.

  The following variable names and functions are provided by
  SkinScript for use in expressions.

  * Generally available:

   'self' -- The DataSkin instance which the declaration is being
   applied to at the time the expression is being called.

   'NOT_FOUND' -- A special value which causes an attribute to be
   non-existent, if this is the value provided.  In a WITH/COMPUTE
   declaration, if the 'WITH' expression returns 'NOT_FOUND', the
   'COMPUTE' clause is ignored, and the 'OTHERWISE LET' clause is
   activated if one exists.

  * Available for WITH/COMPUTE declarations:

   'RESULT' -- The result of the 'WITH' expression in a WITH/COMPUTE
   declaration.  Available only from expressions in the 'COMPUTE'
   clause.  This variable is placed atop the DTML namespace stack
   during execution of the 'COMPUTE' clause, so just referring to a
   name in a 'COMPUTE' expression will look up that name in the
   'RESULT' object first.  Only if it is not found there, will the
   search continue to the declaration and its acquisition context.

   'ATTRIBUTE_NAME' -- A string containing the name of the attribute
   which the DataSkin is currently trying to retrieve.  Available in
   both the 'WITH' and 'COMPUTE' clauses.

  * Available for WHEN/STORE and WHEN/CALL declarations:

   'TRIGGER_EVENT' -- A string, either "ADD", "CHANGE", or "DELETE",
   denoting the type of event which caused the expression to be
   executed.

   'OLD["name"]' -- 'OLD' is a mapping object containing the values
   saved by the "SAVING mementolist" clause of a WHEN/STORE or
   WHEN/CALL declaration.

   'HAS_CHANGED("name")' -- returns a true value if the attribute
   named "name" has been changed or deleted during the current
   (sub)transaction.

   'CHANGED_ATTRS()' -- returns a list of the names of attributes
   which have been changed or deleted during the current
   (sub)transaction.

   'ORIGINAL["name"]' -- returns the value of the attribute named
   "name" before it was changed or deleted, or the 'NOT_FOUND' value
   if the attribute did not exist before this (sub)transaction.  A
   'KeyError' will be raised if "name" was not changed/deleted, so
   use 'HAS_CHANGED()' to check before using this.




 INITIALIZE OBJECT WITH *assignmentlist*

  What It's For

   Providing default attribute values to newly created objects.

  How It Works

   This declaration assigns the specified attribute values to the
   object when it is created.  Due to limitations of the built-in Zope
   event model, it is not possible to unequivocally determine when
   DataSkins outside of Racks are created, so this declaration
   applies only to objects created inside Racks.  The
   *assignmentlist* is executed in context of the DataSkin, so later
   assignments can reference the values of earlier assignments.

  Examples

   - Constant values::

     INITIALIZE OBJECT WITH foo=1, bar='baz', degrees=360,
       radians = degrees / (180 / _.math.pi)

   - Defaults from properties in acquisition context::

     INITIALIZE OBJECT WITH foo=myfoo, bar=mybar




 WITH [ QUERY ] *expression* COMPUTE *nameorassignlist* [ OTHERWISE LET *assignmentlist* ] [ DEPENDENT ON *dependencies* ]

  What It's For

    Providing readable values for computed attributes and/or
    attributes which are loaded from an external data source (e.g. via
    an LDAP or SQL query).  WITH/COMPUTE statements minimize redundant
    calculations by determining some attribute values **before** they
    are actually needed, as soon as one of a set of related values are
    needed.  This is especially important where database retrievals
    are involved.  It would be very bad to have to issue an SQL query
    for each attribute access, even if the result was then cached.  So
    a WITH/COMPUTE statement executes the expensive *expression* once,
    and then caches the attributes computed by the *nameorassignlist*.
    Notice, by the way, that this means you can do performance tuning
    by changing the grouping of attributes in your WITH/COMPUTE
    statements, so that computations which are very expensive and
    infrequently used are placed in standalone statements, and so on.
    And if you're using data from outer-joined SQL tables, you may
    even decide to split them into seperate queries (one per table)
    and corresponding WITH/COMPUTE statements, if some of the tables'
    data is rarely used.

  How It Works

   When an attribute defined in the "nameorassignlist" is requested,
   call "expression".  If the result of expression (available as
   'RESULT' in the "nameorassignlist" expressions) is not 'NOT_FOUND',
   compute all attributes from nameorassignlist, in the order they are
   listed, caching the results for the remainder of the
   (sub)transaction (unless later reset by a change to a 'DEPENDENT
   ON' attribute).

   If the 'RESULT' is 'NOT_FOUND', the search for the attribute value
   falls through to the next declaration (or attribute provider if the
   SkinScript is finished).  If there is an 'OTHERWISE LET' clause,
   the assignments given there are computed and cached for the
   remainder of the (sub)transaction.  (This is to prevent repeated
   execution of "expression" that will only result in further
   failures.)

   The optional 'QUERY' keyword states that "expression" is a query
   method such as an LDAP or SQL query that returns a sequence of
   objects.  When the 'QUERY' keyword is used, the declaration
   automatically takes the first item of the sequence to be used as
   'RESULT', unless the sequence is empty, in which case it behaves as
   though the 'RESULT' were 'NOT_FOUND'.

   The optional 'DEPENDENT ON' clause declares that the computed
   attributes depend on the listed attributes for their values, and
   that if any of the 'DEPENDENT ON' attributes are overwritten, the
   cached values should be reset.  (And thus recomputed the next time
   they are accessed.)  So, if for example you had a ZODB-stored field
   which was a key to be looked up in an RDBMS, listing the key field
   in the 'DEPENDENT ON' clause would ensure that any access to the
   RDBMS-stored fields after a change in key would return data from
   the correct record.  (Note, however, that the attributes are *only*
   reset if a 'DEPENDENT ON' attribute is changed by being *written
   to*.  A change in the value calculated or retrieved by another
   statement or provider has no effect on the attribute cache.)

  Examples

   - Simple SQL query.  'SomeSQLMethod' is an SQL Method (located
   somewhere in the acquisition context of this SkinScript method)
   that takes 'someparam' as a parameter to return a row by primary
   key, and returns a result which contains the fields 'foo', 'bar',
   and 'baz', which we want to use as attributes of the same names::

    WITH QUERY SomeSQLMethod(someparam=self.id) COMPUTE foo,bar,baz
    
    
   - SQL query with renaming and computations.  Again, notice how the
   'WITH' expression is computed in the acquisition context of the
   SkinScript method itself, but the 'COMPUTE' expressions are in the
   context of the result of the 'WITH' expression::

    WITH QUERY GetRoomData(roomname=self.id) COMPUTE
      labor_cost=labor_rate*labor_hours, material_cost=materials,
      height=roomdim1, width=roomdim2


   - A simple computed attribute.  The formula will be computed the
   first time the DataSkin needs to know its 'total_cost' attribute
   in a given (sub)transaction, and again if the 'labor_cost' or
   'material_cost' attributes are written to::

    WITH self.labor_cost + self.material_cost COMPUTE total_cost = RESULT
    DEPENDENT ON labor_cost,material_cost

   - Interdependent computed attributes.  The first time that 'area',
   'square_yards', or 'price_per_sq_yd' is asked for in a
   (sub)transaction, all three will be computed (and recomputed if the
   non-calculated attributes are written to)::

    WITH self.width * self.height COMPUTE
      area = RESULT,
      square_yards = self.area / 9,
      price_per_sq_yd = self.total_cost / self.square_yards

    # We have to list 'real' attributes, not intermediate values!
    DEPENDENT ON  width,height,labor_cost,material_cost

   - Object re-mapping; convert data from some object in another part
   of the application.  Notice that we can even save a reference to
   the original object so we can call methods on it later to save our
   changed attributes back into it...  (And of course, we can also
   seperate more expensive and less-often used attribute computations
   into other WITH self.original_object COMPUTE statements)::

    WITH SomeSpecialist.getItem(self.id) COMPUTE
      my_foo=its_foo, my_bar=its_bar, original_object=RESULT




 WITH SELF COMPUTE *nameorassignlist* [ DEPENDENT ON *dependencies* ]

  What It's For

    A shorthand way of defining computed attributes.  This variant
    form of WITH/COMPUTE simply treats 'self' as 'RESULT', placing it
    on the namespace stack for the execution of the
    *nameorassignlist*.

  How It Works

   When an attribute defined in the "nameorassignlist" is requested,
   compute all attributes from nameorassignlist, in the order they are
   listed, caching the results for the remainder of the
   (sub)transaction (unless reset by a change to a 'DEPENDENT ON'
   attribute).  The DataSkin itself is placed on the namespace stack
   during execution of the *nameorassignlist*, so expressions can
   refer to other attributes without having to prefix them with
   'self.'.  'WITH SELF COMPUTE' executes slightly faster than an
   otherwise equivalent 'WITH self COMPUTE' statement, and a lot
   faster than repeating 'self.foo', 'self.bar', and so on in an
   expression.

   The optional 'DEPENDENT ON' clause declares that the computed
   attributes depend on the listed attributes for their values, and
   that if any of the 'DEPENDENT ON' attributes are overwritten, the
   cached values should be reset.  (And thus recomputed the next time
   they are accessed.)  This is helpful for ensuring that
   calculation-based attributes always reflect the object's current
   state.  Note, however, that the attributes are *only* reset if a
   'DEPENDENT ON' attribute is changed by being *written to*.  A
   change in the value calculated or retrieved by another statement or
   provider has no effect on the attribute cache.


  Examples

   - Simple computed attribute. The formula will be computed the first
   time the DataSkin needs to know its 'total_cost' attribute in a
   given (sub)transaction.  Notice how much shorter this is than the
   equivalent example shown under the general WITH/COMPUTE examples.
   It will also execute more quickly, although by an utterly
   insignificant amount::

    WITH SELF COMPUTE total_cost = labor_cost+material_cost
    DEPENDENT ON labor_cost,material_cost


   - Interdependent computed attributes.  Again, it's much less
   verbose, and a hair faster than its generic WITH/COMPUTE cousin::

    WITH SELF COMPUTE
      area            = width * height,
      square_yards    = area / 9,
      price_per_sq_yd = total_cost / square_yards

    # We have to list 'real' attributes, not intermediate values!
    DEPENDENT ON  width,height,labor_cost,material_cost



 [ WHEN *eventspec* ] STORE *attributelist* USING *expression* [ SAVING *mementolist* ]

  What It's For

   Storing/updating attributes in an external data source.  The
   *attributelist* can contain an asterisk ('*') to mean "all
   attributes".

  How It Works

   When an attribute listed in "attributelist" is changed/deleted, the
   new value is cached in the DataSkin until a (sub)transaction
   commit occurs.  At that time, "expression" is called, with the
   'OLD[]' variable containing the values saved by "mementolist", if
   specified.  (See the section above on "Declaration Parameters" for
   more details on how memento lists work.)

   If the optional "WHEN eventspec" clause is used, "expression" will
   only be called if the eventspec matches the object-level event.
   (Note that this means that if you use the WHEN clause, you will
   need declarations for each possible event for each attribute to
   ensure that the attributes will be saved under all possible
   circumstances.)

   A STORE/USING declaration is compiled into a single Data Plug-in
   with two functions: to act as an attribute setter, and to act as a
   rule agent.  The attribute setter part simply caches changes to
   attributes until (sub)transaction commit time, when the rule agent
   part becomes active.  The rule agent part executes the 'USING'
   expression if and only if one of the 'STORE' attributes have been
   changed, and the WHEN clause (if any) is applicable to the
   situation as of commit time.  Note that the seperation of these
   parts means that the USING expression can and will be called **even
   if** more than one declaration (or other Attribute Provider)
   handles storage for the same attributes!  This is actually quite
   useful when you have data that you want updated in more than one
   back-end database, but it can be surprising if you expect
   first-come, only-served behavior as is the case for attribute
   getters.

   There is one interesting side effect, however, if a wildcard ('*')
   is used in the 'STORE' list.  Since the '*' matches **any**
   attribute, the 'USING' expression will be called at subtransaction
   commit as long as **any** attribute has changed and the 'WHEN'
   clause is applicable.  Again, this is a bit different from
   attribute getters and other attribute setters, where '*'
   effectively means "any attribute that hasn't already been claimed
   by another provider."

  Examples

   - Context-dependent method-driven storage reusing already-written
   methods.  In this example, we use different methods for each
   situation, which we assume are already-written and which we do not
   want to change for our specific situation.  Notice that for the
   'ADD' and 'DELETE' events we use 'WHEN ... CALL' declarations,
   because we want the widget data to be added or deleted regardless
   of whether the 'foo' or 'bar' attributes have specifically
   changed::

     WHEN OBJECT ADDED CALL 
       AddWidget(widget_id=self.id, foo_field=self.foo, wbar=self.bar)

     WHEN OBJECT CHANGED STORE foo,bar USING 
       UpdateMethod(widget_id=self.id, foo_field=self.foo, wbar=self.bar)

     WHEN OBJECT DELETED CALL DeleteWidget(widget_id=self.id)


   - Object remapping.  This example shows how to create a "virtual"
   object whose attributes 'my_foo' and 'my_bar' are mapped to/from
   another object from a different specialist ('SomeSpecialist')
   using the same id value, but whose fields are named 'its_foo' and
   'its_bar'.  The example works by maintaining an 'original_object'
   attribute which contains the object from the other specialist.
   This is actually a pretty simple remapping example; a single
   SkinScript script might contain multiple remappings like this to
   combine parts of different objects into a whole.  Also, this
   example assumes that we are creating an object in 'SomeSpecialist'
   for every DataSkin created, while common real-life uses will often
   have the 'original_object' created on demand for an existing
   DataSkin, or conversely, have the DataSkin created on demand for
   an existing 'original_object'::

     # Handle the case where we just got created...
     INITIALIZE OBJECT WITH original_object=SomeSpecialist.newItem(self.id)

     # ...and the case where we already existed
     WITH SomeSpecialist.getItem(self.id) COMPUTE original_object=RESULT

     # Map the other object's names to mine...
     WITH self.original_object COMPUTE my_foo=its_foo, my_bar=its_bar

     # ...and mine to its
     WHEN OBJECT ADDED,CHANGED STORE my_foo, my_bar USING
       self.original_object.mange_changeProperties(
         its_foo=self.my_foo, its_bar=self.my_bar
       )

     # Last, but not least, get rid of the other guy when I'm deleted
     WHEN OBJECT DELETED CALL self.original_object.manage_delete()


   - Complex storage mapping using a memento.  This example
   illustrates mapping a more complex kind of attribute into data
   storage.  'DelKeywords' and 'AddKeywords' are methods (in the
   SkinScript method's acquisition context) that delete or add
   keyword records in some (unspecified) kind of database, given an
   item id and a list of keywords.  The example calls a method on the
   DataSkin called 'getKeywords()' to extract keywords from the
   'description' attribute.  If the 'description' attribute is changed
   during the (sub)transaction, at commit time the external database
   will be updated.  For illustrative purposes, appropriate WHEN/CALL
   declarations are included to ensure that adds and deletes are
   properly handled.  Notice that the DELETED declaration uses the
   value for the keywords which existed at the **start** of the
   (sub)transaction, since it could theoretically have changed before
   the object ended up being deleted::

     WHEN OBJECT CHANGED STORE description USING
       DelKeywords(item=self.id, kwlist=OLD['kw']),
       AddKeywords(item=self.id, kwlist=self.getKeywords(self.description))
     SAVING
       kw=getKeywords(description)

     WHEN OBJECT ADDED CALL 
       AddKeywords(item=self.id,kwlist=getKeywords(self.description))

     WHEN OBJECT DELETED CALL
       DelKeywords(item=self.id,kwlist=OLD['kw'])
     SAVING
       kw=getKeywords(description)



 STORE *attributelist* IN SELF

  What It's For

    Specifying attributes to be stored persistently within the object.

  How It Works

   When an attribute listed in "attributelist" is changed/deleted, it
   will be stored directly in the object as a persistent attribute.
   Of course, this will only work if the object itself is stored in
   the ZODB, and no previous declarations or AttributeProviders
   declared storage for the attribute.  "attributelist" can be just an
   asterisk ('*') to indicate that all attributes not stored in some
   other way should be stored persistently.

  Examples

   - Simple storage.  The attributes 'foo', 'bar', and 'baz' will be
   stored persistently in the ZODB.  (Note that if the DataSkin is
   stored in a Rack which is not using persistent storage, this will
   not work, and data will silently be lost.)

     STORE foo, bar, baz IN SELF

   - Wildcard.  Any attribute which is not explicitly claimed for
   storage by another attribute provider/declaration will be stored
   persistently, assuming that there is not another wildcard attribute
   setter which appears earlier than this declaration in precedence
   order.

     STORE * IN SELF


 WHEN *eventspec* CALL *expression* [ SAVING *mementolist* ]

  What It's For

   Calling an expression at (sub)transaction commit if any of the
   specified events have occurred.

  How It Works

   At (sub)transaction commit time, if one of the events specified in
   "eventspec" occurred, "expression" is called, with the 'OLD[]'
   variable containing the values saved by "mementolist", if
   specified.

  Examples

   - E-mail notification upon object add.  'EMailToManager' is a DTML
   document or method in the acquisition context of the SkinScript
   method, which expects a 'new_item' parameter containing the object
   which the notification is supposed to be about.  Note that if this
   snippet were used in a Customizer, the 'ADD' event can occur when
   an object is moved into the area covered by that Customizer.  This
   would mean notifications would be sent for objects which were not
   necessarily newly created::

     WHEN OBJECT ADDED CALL EMailToManager(_.None,_,new_item=self)

   - Catalog example #1.  This is an interesting way of doing
   automatic cataloging/re-cataloging of objects without having them
   be CatalogAware.  Note that the order of declarations here is very
   important: when an object is changed, 'uncatalog_object' will be
   called before 'catalog_object'.  'SomeCatalog', of course, is a
   catalog somewhere in the SkinScript method's acquisition context::

     WHEN OBJECT CHANGED,DELETED CALL SomeCatalog.uncatalog_object(self.absolute_url(1))
     WHEN OBJECT ADDED,CHANGED CALL SomeCatalog.catalog_object(self,self.absolute_url(1))

   - Complex event alert.  This example triggers if there is a 5% or
   greater change in the ROI of an investment, based on any of the
   input factors having changed (e.g. cashflow amounts or dates).  It
   works by saving the old result of the DataSkin's 'computeROI()'
   method right before any change is made to the DataSkin's
   attributes.  The 'CALL' expression is called at (sub)transaction
   commit if any changes have been made to the object.  That
   expression compares the new value of 'computeROI()' with the old
   value as a percentage change, and if it's greater than 5%, calls
   the 'SendROIAlert()' function which is somewhere in the SkinScript
   method's acquisition context::

     WHEN OBJECT CHANGED CALL
       (abs(self.computeROI()-OLD['ROI'])/OLD['ROI'] > .05 ) 
        and SendROIAlert(investment=self)
     SAVING 
       ROI=computeROI()

   - Catalog example #2.  The previous catalog example recatalogs the
   object if *any* attribute is changed, even if it is simply set back
   to what it was in the first place.  While future versions of
   ZCatalog may optimize this case, we can do it ourselves with a bit
   more complex SkinScript.  This example will only recatalog the
   object if attributes 'foo' or 'bar' were set or the result of
   method 'baz' has changed::

     WHEN OBJECT ADDED CALL SomeCatalog.catalog_object(self,self.absolute_url(1))
     WHEN OBJECT DELETED CALL SomeCatalog.uncatalog_object(self.absolute_url(1))

     WHEN OBJECT CHANGED CALL
      (HAS_CHANGED('foo') or HAS_CHANGED('bar') or self.baz()<>OLD['baz']) and
      (  SomeCatalog.uncatalog_object(self.absolute_url(1)),
         SomeCatalog.catalog_object(self,self.absolute_url(1))
       )
     SAVING baz=baz()

   - Generic method-driven storage with custom method.  In this
   example, it is assumed that 'SomeMethod' is a DTML, SQL, or Python
   method that expects to get a DataSkin and save its attributes in
   an external database.  The method must be smart enough to handle an
   'ADD', 'CHANGE', or 'DELETE' event, and takes a parameter called
   'what_happened' to tell it which one happened.  In the case of an
   SQL method, it's pretty straightforward to write DTML that
   generates an appropriate 'INSERT', 'UPDATE', or 'DELETE' statement
   according to which event happened.  By using the 'HAS_CHANGED'
   function, 'SomeMethod' can even generate an optimal 'UPDATE'
   statement that only changes fields that have actually changed.  As
   you can see, this example puts the bulk of the processing burden on
   'SomeMethod', which has to be written specifically for the
   situation.  This may be optimal, however, if you are going to have
   to write the methods anyway::

     WHEN OBJECT ADDED,CHANGED,DELETED CALL
       SomeMethod(object=self, what_happened=TRIGGER_EVENT, has_changed=HAS_CHANGED)


 Language Keywords

  The following keywords are SkinScript reserved words and cannot be
  used as attribute names or appear in expressions.  If you must
  access an attribute with one of these names in an expression, you
  must use '_["name"]' syntax, as is sometimes used in DTML expressions
  to access otherwise inaccessible names.

  Keywords: 'ADDED CALL CHANGED COMPUTE DELETED DEPENDENT IN INCLUDE
  INITIALIZE LET OBJECT ON OTHERWISE QUERY SAVING SELF SLOT STORE
  USING WHEN WITH'