source: project/sqlite3-tinyclos/tags/1.2.3/sqlite3-tinyclos.html @ 4991

Last change on this file since 4991 was 4991, checked in by Thomas Chust, 13 years ago

[sqlite3-tinyclos] Conversion of repository directory layout, step 1

File size: 21.9 KB
Line 
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2<!-- Generated by eggdoc Revision: 1.20  -->
3<html>
4<head>
5<title>Eggs Unlimited - sqlite3-tinyclos</title><style type="text/css"> <!--
6      CODE {
7            color: #666666;
8          }
9/*   DT.definition EM { font-weight: bold; font-style: normal; } */
10
11     DT.definition { 
12                   background: #eee;
13                   color: black;
14                   padding: 0.2em 1em 0.2em 0.7em;
15                   margin-left: 0.2em;
16border: 1px solid #bbc;
17                   font-family: "Andale Mono", monospace;
18                   /* font-size: 1.2em; */
19                   
20                 }
21     DD {
22                   margin-top: 0.8em;
23                   margin-bottom: 0.8em;
24     }
25     DIV.subsection {
26                    border-top: 1px solid #448;
27                    padding-left: 1em;
28                    margin-bottom: 1.2em;
29     }
30     DIV.subsubsection {
31                    border-top: 1px dotted #99c;
32                    /* border-left: 1px solid #99c; */
33                    padding-left: 1em;
34                    margin-bottom: 1.2em;
35     }
36     DIV.subsubsubsection {
37                    border-top: 1px solid #ddf;
38                    padding-left: 1em;
39                    margin-bottom: 1.2em;
40     }
41
42         DIV.section {
43                 margin-bottom: 1.5em;
44         }
45         a:link {
46                 color: #336;
47         }
48         a:visited { color: #666; }
49         a:active  { color: #966; }
50         a:hover   { color: #669; }
51         body { margin: 0; padding: 0; background: #fff; color: #000; font: 9pt "Lucida Grande", "Verdana", sans-serif; }
52         H2 {
53                 background: #336;
54                 color: #fff;
55                 padding-top: 0.5em;
56                 padding-bottom: 0.5em;
57                 padding-left: 16px;
58                 margin: 0 0 1em 0;
59        }
60        UL LI {
61                list-style: none;
62        }
63        TT {
64                font-family: "Andale Mono", monospace;
65                /* font-size: 1.2em; */
66        }
67        H3 {
68                color: #113;
69                margin-bottom: 0.5em;
70        }
71        H4, H5, H6 {
72                color: #113;
73                margin-bottom: 1.0em;
74        }
75        H5 {
76                font-weight: normal;
77                font-style: italic;
78                font-size: 100%;
79                margin-top: 1.2em;
80        }
81        H6 {
82                font-weight: bold;
83                font-size: 85%;
84                margin-top: 1.2em;
85        }
86     DIV#eggheader {
87         text-align: center;
88                 float: right;
89                 margin-right: 2em;
90     }
91     DIV#header IMG {
92            /* display: block; margin-left: auto; margin-right: auto;  */
93            /* float: right; */
94            border: none;  /* firefox */
95     }
96     DIV#footer {
97                background: #bbd;
98                padding: 0.7em ;
99                border-top: 1px solid #cce;
100     }
101     DIV#footer hr {
102                display: none;
103     }
104     DIV#footer a {
105                float: left;
106     }
107     DIV#revision-history {
108         float: right;
109     }
110     
111     DIV#body {
112                 margin: 1em 1em 1em 16px;
113         }
114
115     DIV#examples PRE {
116       background: #eef;
117       padding: 0.1em;
118       border: 1px solid #aac;
119     }
120     PRE#license, DIV#examples PRE {
121       padding: 0.5em;
122     }
123     DIV#examples PRE {
124       /* font-size: 85%; */
125     }
126     PRE { font-family: "Andale Mono", monospace; }
127     TABLE {
128       background: #eef;
129       padding: 0.2em;
130       border: 1px solid #aac;
131       border-collapse: collapse;
132       width: 100%;
133     }
134     TABLE.symbol-table TD.symbol {
135          width: 15em;
136          font-family: "Andale Mono", monospace;
137          /* font-size: 1.2em; */
138     }
139     TH {
140       text-align: left;
141       border-bottom: 1px solid #aac;
142       padding: 0.25em 0.5em 0.25em 0.5em;
143     } 
144     TD { padding: 0.25em 0.5em 0.25em 0.5em; }
145     --></style></head>
146<body>
147<div id="header">
148<h2>sqlite3-tinyclos</h2>
149<div id="eggheader"><a href="index.html">
150<img src="egg.jpg" alt="[Picture of an egg]" /></a></div></div>
151<div id="body">
152<div class="section">
153<h3>Description</h3>
154<p>A bridge between persistent storage in SQLite3 tables and TinyCLOS objects.</p></div>
155<div class="section">
156<h3>Author</h3><a href="http://www.chust.org/">Thomas Chust</a></div>
157<div class="section">
158<h3>Version</h3>
159<ul>
160<li>1.2.3 Fixed compiler flags to pull in tinyclos</li>
161<li>1.2.2 Alternative superclass for generated classes added</li>
162<li>1.2.0 Superclasses of generated classes can be specified arbitrarily</li>
163<li>1.1.0 Facility to prevent creation of certain getter and/or setter methods</li>
164<li>1.0.0 Initial release</li></ul></div>
165<div class="section">
166<h3>Usage</h3><tt>(require-extension sqlite3-tinyclos)</tt></div>
167<div class="section">
168<h3>Download</h3><a href="sqlite3-tinyclos.egg">sqlite3-tinyclos.egg</a></div>
169<div class="section">
170<h3>Requires</h3>
171<ul>
172<li>sqlite3</li></ul></div>
173<div class="section">
174<h3>Documentation</h3>
175<p>This egg is intended to quickly establish an object oriented interface to data in an SQLite3 database. The interface you are provided with is rather basic, but easily extensible.</p>
176<p>In the simplest case you just create the database with its schema of tables and then call <tt>sqlite3:define-stored-object-class</tt> once for each table you want to access through this interface. See the example below for a first impression.</p>
177<div class="subsection">
178<h4>The class generator procedure</h4>
179<dl>
180<dt class="definition"><strong>procedure:</strong> (sqlite3:define-stored-object-class (db &lt;sqlite3:database&gt;) (table &lt;string&gt;) #!key prefix name symbol add-super supers slots) =&gt; &lt;void&gt;</dt>
181<dd>
182<p>This procedure creates a TinyCLOS class of metaclass <tt>&lt;sqlite3:stored-object-class&gt;</tt> and superclasses <tt>supers</tt>, defaulting to a list containing only <tt>&lt;sqlite3:stored-object&gt;</tt>. The new class can be instantiated to access data stored in the <tt>table</tt> of the given SQLite3 <tt>db</tt>.</p>
183<p>Additional value slots for the instances can be obtained by specifying a list as keyword parameter <tt>slots</tt>.</p>
184<p>The list specified for <tt>supers</tt> can be chosen arbitrarily but it should probably contain either <tt>&lt;sqlite3:stored-object&gt;</tt> itself or a subclass of it. To just add superclasses in addition to the default one you may want to use <tt>add-super</tt> keyword parameters, which are prepended in sequence to the list specified by <tt>supers</tt> or the default list.</p>
185<p>The prefix referred to in the following paragraphs is either taken from the corresponding keyword argument or, if no such argument is given, is assumed to be the empty string.</p>
186<p>The generated class has the name given as a keyword argument or, if no such argument is present, a name composed of the prefix and the table name stripped of any trailing 's' characters.</p>
187<p>The generated class is assigned to the global variable specified by <tt>symbol</tt> or, if no such argument is given, the symbol composed of an opening angle bracket, the name of the class and a closing angle bracket.</p>
188<p>For all columns in the table that are not part of the primary key, two accessor methods for retrieving and setting them are defined, with names computed by <tt>sqlite3:field-name-&gt;getter-symbol</tt> and <tt>sqlite3:field-name-&gt;setter-symbol</tt> respectively. If a list is passed in the keyword argument <tt>no-getter-or-setter</tt> and it contains the name of a database field, no getter or setter is created for this field. Likewise no getter is generated for columns mentioned in <tt>no-getter</tt> and no setter is generated for those mentioned in <tt>no-setter</tt>.</p></dd></dl></div>
189<div class="subsection">
190<h4>Generated getter and setter methods</h4>
191<dl>
192<dt class="definition"><strong>method:</strong> (&lt;prefix&gt;&lt;name&gt; (self &lt;subclass of sqlite3:stored-object&gt;)) =&gt; &lt;top&gt;
193<br /><strong>method:</strong> (&lt;prefix&gt;&lt;name&gt;? (self &lt;subclass of sqlite3:stored-object&gt;)) =&gt; &lt;boolean&gt;</dt>
194<dd>
195<p>These methods defined by <tt>sqlite3:define-stored-object-class</tt> retrieve the value of a regular or boolean field in the database respectively. For boolean fields, whose names start with <tt>is_</tt> in the database, an automatic conversion from <tt>NULL</tt> or <tt>0</tt> to <tt>#f</tt> and anything else to <tt>#t</tt> is performed.</p></dd>
196<dt class="definition"><strong>method:</strong> (&lt;prefix&gt;set-&lt;name&gt;! (self &lt;subclass of sqlite3:stored-object&gt;) (value &lt;top&gt;)) =&gt; &lt;void&gt;</dt>
197<dd>
198<p>These methods defined by <tt>sqlite3:define-stored-object-class</tt> set the value of a field in the database. For boolean fields, whose names start with <tt>is_</tt> in the database, an automatic conversion from <tt>#f</tt> to <tt>0</tt> and anything else to <tt>1</tt> is performed.</p></dd></dl></div>
199<div class="subsection">
200<h4>Metaclasses and classes</h4>
201<dl>
202<dt class="definition"><strong>class:</strong> &lt;sqlite3:stored-object-class&gt;</dt>
203<dd>
204<p>The metaclass for classes generated by <tt>sqlite3:define-stored-object-class</tt>. It provides its class instances with facilities to store the database handle and analyze the schema.</p>
205<p>In particular, instances of this class contain <tt>db</tt>, <tt>table</tt>, <tt>pk</tt> and <tt>fields</tt> slots. Of these <tt>db</tt> and <tt>table</tt> must be set when instantiating (which is automatically done by <tt>sqlite3:define-stored-object-class</tt>), while <tt>pk</tt> and <tt>fields</tt> are computed by the <tt>initialize</tt> method. All these fields should be read with the accessors described below.</p></dd>
206<dt class="definition"><strong>class:</strong> &lt;sqlite3:stored-object&gt;</dt>
207<dd>
208<p>The common base class of classes generated by <tt>sqlite3:define-stored-object-class</tt>. It provides general facilities for row level data access.</p>
209<p>In particular, instances of this class contain a <tt>pk</tt> slot storing the current values of the primary key columns for this object. The contents of this slot should be retrieved and set using the accessors described below. When creating an object of class <tt>&lt;sqlite3:stored-object&gt;</tt>, the <tt>pk</tt> slot can either be set with the usual TinyCLOS initialization argument syntax or by specifying exactly all the primary key values as initialization arguments.</p>
210<p>The <tt>initialize</tt> method for this class also checks whether a row with the specified primary key already exists in the database using <tt>sqlite3:in-store?</tt> and inserts such a row using <tt>sqlite3:create-in-store!</tt> if this is not the case.</p></dd>
211<dt class="definition"><strong>class:</strong> &lt;sqlite3:stored-object/automatic-integer-pk&gt;</dt>
212<dd>
213<p>This subclass of <tt>&lt;sqlite3:stored-object&gt;</tt> has a modified <tt>sqlite3:in-store?</tt> method that automatically allocates a new primary key one larger than the largest one in use if the object was created with an empty primary key. This means that objects created by a simple <tt>(make &lt;class&gt;)</tt> call, where <tt>&lt;class&gt;</tt> is a subclass of <tt>&lt;sqlite3:stored-object/automatic-integer-pk&gt;</tt>, will immediately be entered into the database with a new unique id.</p>
214<p><em>Note that this class can only be used sensibly with tables that have a single integer primary key. Also note that instances of this class should only be created with proper exclusive locks on the database in place.</em></p></dd></dl></div>
215<div class="subsection">
216<h4>Methods</h4>
217<dl>
218<p>The following methods are common to the standard metaclass and class:</p>
219<dt class="definition"><strong>method:</strong> (sqlite3:db (self &lt;sqlite3:stored-object-class&gt;)) =&gt; &lt;sqlite3:database&gt;
220<br /><strong>method:</strong> (sqlite3:db (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;sqlite3:database&gt;</dt>
221<dd>
222<p>Given a stored object class or a stored object instance this method returns the database connection that object belongs to.</p></dd>
223<dt class="definition"><strong>method:</strong> (sqlite3:table (self &lt;sqlite3:stored-object-class&gt;)) =&gt; &lt;string&gt;
224<br /><strong>method:</strong> (sqlite3:table (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;string&gt;</dt>
225<dd>
226<p>Given a stored object class or a stored object instance this method returns the name of the database table that holds the instances of the class or the object respectively.</p></dd>
227<dt class="definition"><strong>method:</strong> (sqlite3:pk (self &lt;sqlite3:stored-object-class&gt;)) =&gt; &lt;list of string&gt;
228<br /><strong>method:</strong> (sqlite3:pk (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;list of string&gt;</dt>
229<dd>
230<p>Given a stored object class this method returns a list of column names that hold the primary key for objects of this class.</p>
231<p>Given a stored object instance this method returns the list of primary key values for this object corresponding in sequence to the list of primary key columns.</p></dd>
232<dt class="definition"><strong>method:</strong> (sqlite3:pk/select (self &lt;sqlite3:stored-object-class&gt;)) =&gt; &lt;string&gt;
233<br /><strong>method:</strong> (sqlite3:pk/select (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;string&gt;</dt>
234<dd>
235<p>Given a stored object class or a stored object instance this method returns a comma separated list of primary key column names.</p></dd>
236<dt class="definition"><strong>method:</strong> (sqlite3:pk/update (self &lt;sqlite3:stored-object-class&gt;)) =&gt; &lt;string&gt;
237<br /><strong>method:</strong> (sqlite3:pk/update (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;string&gt;</dt>
238<dd>
239<p>Given a stored object class or a stored object instance this method returns a comma separated list of tokens in the form <tt>primary_key_column_name = ?</tt>.</p></dd>
240<dt class="definition"><strong>method:</strong> (sqlite3:pk/where (self &lt;sqlite3:stored-object-class&gt;)) =&gt; &lt;string&gt;
241<br /><strong>method:</strong> (sqlite3:pk/where (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;string&gt;</dt>
242<dd>
243<p>Given a stored object class or a stored object instance this method returns a list of tokens in the form <tt>primary_key_column_name = ?</tt>, separated by the word <tt>AND</tt>.</p></dd>
244<dt class="definition"><strong>method:</strong> (sqlite3:fields (self &lt;sqlite3:stored-object-class&gt;)) =&gt; &lt;list of string&gt;
245<br /><strong>method:</strong> (sqlite3:fields (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;list of string&gt;</dt>
246<dd>
247<p>Given a stored object class or a stored object instance this method returns a list of column names which are not part of the primary key.</p></dd></dl>
248<dl>
249<p>The following methods are available for objects of class <tt>&lt;sqlite3:stored-object&gt;</tt>:</p>
250<dt class="definition"><strong>method:</strong> (sqlite3:set-pk! (self &lt;sqlite3:stored-object&gt;) . new-pk) =&gt; &lt;void&gt;</dt>
251<dd>
252<p>Changes the value of the primary key columns for the given object to the new given values.</p></dd>
253<dt class="definition"><strong>method:</strong> (sqlite3:in-store? (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;boolean&gt;</dt>
254<dd>
255<p>Checks whether a row with the currently set primary key column values (still) exists in the database.</p></dd>
256<dt class="definition"><strong>method:</strong> (sqlite3:create-in-store! (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;boolean&gt;</dt>
257<dd>
258<p>Creates a row with the currently set primary key column values and default values for all other columns if no such row already exists. Returns <tt>#t</tt> or <tt>#f</tt> if a row was inserted or not.</p>
259<p>This method may return <tt>#f</tt> if a row with the given primary key already exists, but it may also return <tt>#f</tt> if the database imposes some constraints on the values of further rows that is not fulfilled by the default values. You should generally either not create tables with such constraints for use with this egg or you should override <tt>sqlite3:create-in-store!</tt> for your stored object classes because it is called from <tt>initialize</tt> if an object of class <tt>&lt;sqlite3:stored-object&gt;</tt> is created with a not yet existing primary key.</p></dd>
260<dt class="definition"><strong>method:</strong> (sqlite3:remove-from-store! (self &lt;sqlite3:stored-object&gt;)) =&gt; &lt;boolean&gt;</dt>
261<dd>
262<p>Removes the row with the currently stored primary key column values from the database and returns <tt>#t</tt> or returns <tt>#f</tt> and does nothing if no such row exists.</p></dd></dl>
263<dl>
264<p>The following two methods should seldomly be needed, as specific field accessors are generated automatically by <tt>sqlite3:define-stored-object-class</tt>:</p>
265<dt class="definition"><strong>method:</strong> (sqlite3:get-stored-property (self &lt;sqlite3:stored-object&gt;) (name &lt;string&gt;)) =&gt; &lt;top&gt;</dt>
266<dd>
267<p>Retrieves the value stored in column <tt>name</tt> from the table apropriate for this object where the primary key columns have the values currently stored in this object.</p></dd>
268<dt class="definition"><strong>method:</strong> (sqlite3:set-stored-property! (self &lt;sqlite3:stored-object&gt;) (name &lt;string&gt;) (value &lt;top&gt;)) =&gt; &lt;void&gt;</dt>
269<dd>
270<p>Sets the column <tt>name</tt> to the given <tt>value</tt> in the table apropriate for this object where the primary key columns have the values stored in this object.</p></dd></dl></div>
271<div class="subsection">
272<h4>Helper procedures</h4>
273<dl>
274<dt class="definition"><strong>procedure:</strong> (sqlite3:field-name-&gt;getter-symbol (name &lt;string&gt;) #!optional ((prefix &lt;string|symbol&gt;) &quot;&quot;)) =&gt; &lt;symbol&gt;</dt>
275<dd>
276<p>Given the name of a database column this procedure returns a mangled version of the name with underscores replaced by dashes and the prefix <tt>is-</tt> replaced by a trailing question mark. Thus the return value is a scheme symbol in the form <tt>&lt;prefix&gt;&lt;name&gt;[?]</tt>.</p>
277<p>This procedure is internally used by <tt>sqlite3:define-stored-object-class</tt> to compute the names of getter methods.</p></dd>
278<dt class="definition"><strong>procedure:</strong> (sqlite3:field-name-&gt;setter-symbol (name &lt;string&gt;) #!optional ((prefix &lt;string|symbol&gt;) &quot;&quot;)) =&gt; &lt;symbol&gt;</dt>
279<dd>
280<p>Given the name of a database column this procedure returns a mangled version of the name with underscores replaced by dashes and the prefix <tt>is-</tt> stripped. In addition to the given prefix, <tt>set-</tt> is prepended to the result and an exclamation mark is appended. Thus the return value is a scheme symbol in the form <tt>&lt;prefix&gt;set-&lt;name&gt;!</tt>.</p>
281<p>This procedure is internally used by <tt>sqlite3:define-stored-object-class</tt> to compute the names of setter methods.</p></dd></dl></div>
282<div class="section">
283<h3>Examples</h3>
284<div id="examples"><pre>
285<i><font color="#B22222">;;;; sqlite3-tinyclos-demo.scm
286</font></i><i><font color="#B22222">;;;; Small demonstration of the capabilities of sqlite3-tinyclos
287</font></i>
288<i><font color="#B22222">;;; load extensions
289</font></i>(require-extension
290  sqlite3 tinyclos sqlite3-tinyclos)
291
292<i><font color="#B22222">;;; create a database
293</font></i>(<b><font color="#A020F0">define</font></b> <b><font color="#0000FF">db</font></b>
294  (sqlite3:open <font color="#BC8F8F"><b>"test.db"</b></font>))
295
296(sqlite3:exec db
297  <font color="#BC8F8F"><b>"CREATE TABLE eggs(
298                  name TEXT NOT NULL, license TEXT, is_module INTEGER NOT NULL DEFAULT 0,
299                  PRIMARY KEY(name), CHECK(is_module IN (0, 1)));"</b></font>)
300
301<i><font color="#B22222">;;; define the object class
302</font></i>(sqlite3:define-stored-object-class db <font color="#BC8F8F"><b>"eggs"</b></font>)
303
304<i><font color="#B22222">;;; add some data
305</font></i>(<b><font color="#A020F0">let</font></b> ((egg (make &lt;egg&gt; <font color="#BC8F8F"><b>"sqlite3"</b></font>)))
306  <i><font color="#B22222">;; now the entry should already exist
307</font></i>  (print (sqlite3:in-store? egg))
308  <i><font color="#B22222">;; let's add some data
309</font></i>  (set-license! egg <font color="#BC8F8F"><b>"BSD"</b></font>)
310  <i><font color="#B22222">;; and read it back
311</font></i>  (print (license egg))
312  <i><font color="#B22222">;; other fields are filled with default data
313</font></i>  (print (module? egg))
314  <i><font color="#B22222">;; changing the primary key is possible as well
315</font></i>  (sqlite3:set-pk! egg <font color="#BC8F8F"><b>"foo"</b></font>)
316  (print (sqlite3:pk egg))
317  <i><font color="#B22222">;; which does not change data, of course
318</font></i>  (print (license egg))
319  <i><font color="#B22222">;; and we can also delete the entry
320</font></i>  (print (sqlite3:remove-from-store! egg))
321  <i><font color="#B22222">;; then it is gone
322</font></i>  (print (sqlite3:in-store? egg)))
323
324<i><font color="#B22222">;;;; vim:set shiftwidth=2 softtabstop=2: ;;;;
325</font></i></pre></div></div>
326<div class="section">
327<h3>License</h3>
328<pre id="license">Copyright (c) 2006, Thomas Chust &lt;chust@web.de&gt;.  All rights reserved.
329
330Redistribution and use in source and binary forms, with or without
331modification, are permitted provided that the following conditions are met:
332
333  Redistributions of source code must retain the above copyright notice,
334  this list of conditions and the following disclaimer. Redistributions in
335  binary form must reproduce the above copyright notice, this list of
336  conditions and the following disclaimer in the documentation and/or
337  other materials provided with the distribution. Neither the name of the
338  author nor the names of its contributors may be used to endorse or
339  promote products derived from this software without specific prior
340  written permission.
341
342THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS &quot;AS
343IS&quot; AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
344THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
345PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
346CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
347EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
348PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
349PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
350LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
351NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
352SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.</pre></div></div></div>
353<div id="footer">
354<hr /><a href="index.html">&lt; Egg index</a>
355<div id="revision-history">$Revision$ $Date$</div>&nbsp;</div></body></html>
Note: See TracBrowser for help on using the repository browser.