WebKeystone
Language Reference Manual
Version 2.1
WebKeystone was written to satisfy a need for a CGI-like
language that was easy to learn, easy to write in, and powerful
enough to do a variety of common CGI-script chores. It had to
be both tightly coupled with http requests and secure
enough so that applications written in it could be trusted
not only to not impinge on other applications and the system,
but also to not overuse system resources. It also needed to
be able to account for the resources that are used, so that
they could be both allocated and billed for correctly.
WebKeystone allows a site to give subscribers access to a
powerful scripting language with confidence in the security of the system
and the ability to bill for resources used.
The WebKeystone programming language is called "WebKeyscript".
WebKeyscript is a web scripting language based heavily upon the Python programming
language. It is
considerably simpler to use for web-based applications and has more security, but is not as
general purpose as Python. If the full features of Python are needed,
there is an escape mechanism that can be installed in order to
"hook" or "wrap" a Python routine. The wrapped Python routine
then has access to the full spectrum of system resources available
to a user.
WebKeyscript provides a secure and convenient operating environment.
It also provides access to persistent and temporary databases
that can be used to build applications such as shopping carts,
which need to preserve state in an Internet environment that was
designed to be stateless. It is our observation that most
applications can be built in WebKeystone alone without access
to other programs. This includes applications as complex as
full-scale bookstores based upon multiple catalogues and Web pages
of descriptions complete with a shopping cart that maintains state,
automatic billing, invoice calculations and mailed confirmations,
and a search engine.
All WebKeystone forms are dynamic and can be generated and displayed
automatically. We have also built other applications including
calculators, color selection devices, and Web-based reservation
systems. WebKeystone can be mixed transparently with Java,
JavaScript, and other Internet scripting languages.
The program portion of WebKeystone is private, and cannot be
seen by anyone except the owner of the WebKeystone forms and
the System Administrator. The entry points into the system are
controlled by the form owner. It is possible, using this
technology, to easily install private Web pages on public
Web servers.
WebKeystone is invoked from an HTML form as a CGI-script. As
mentioned previously, the WebKeystone programming language is
called "WebKeyscript". Any file that invokes WebKeystone is
called a "Startfile". There is a file that contains the script
that WebKeystone executes; this file is called the "Profile".
The result of executing the Profile is a returned HTML file
which is displayed to a user; this returned file is called an
"Endfile". We call the person who wrote a particular application,
or who owns the site where the application is run, the
"form owner". We call a person who accesses WebKeystone via
the World Wide Web a "user".
The Startfile resides in a directory tree. If this
is the mechanism used to invoke WebKeystone, then this particular
file is in a webserver addressable area. All other parts of the system may be
stored in a private area, away from this public area. We call the
Startfile's directory tree, which is available to the Web server
(such as apache), the "Conventional Web Space". There is another
directory tree used by a form owner, which is where Profiles,
Endfiles, and other dynamically altered WebKeystone files are
stored. We call this directory the "WebKeystone File Space".
There may be a sub-directory tree of the WebKeystone File Space
that is used to store database objects and flat files ; we call
this the "file directory". (The system can be configured so that all
of these directories reside in different areas; however our
convention, for security and ease of administration, is to
establish the WebKeystone File Space with the file directory as a
sub-directory in a directory tree that is separate from the
Conventional Web Space.) There is a file for each owner, called the
UserID file, where the configuration information is stored in an XML format.
Within this file it is possible to designate, on a per user basis, other areas in the
server's file structure that are accessible for other purposes.
<FORM METHOD="POST" ACTION="/wkstone/webkeystone.py">
On your system this name may be different; the correct
name is available from your System Administrator. It must
follow the "ACTION=" command and must be enclosed in double
quotes.
<A HREF="http://www.webkeystone.com/wkstone/webkeystone.py?
UserID=biz_marketing&Profile=lesson1-1.prof">Go Shopping!</A>
This statement declares that the WebKeystone program resides in:
"/wkstone/webkeystone.py"
The UserID name is:
"biz_marketing"
The Profile name is:
"lesson1-1.prof"
".../wkstone/wksi/aUserId/aProfile"
"aUserId" is some UserID and "aProfile" is some
Profile. The wksi mechanism allows the UserID and the Profile to be set as a part
of the URL.
<INPUT TYPE="HIDDEN" NAME="UserID" VALUE="biz_marketing">
Substitute your own UserID for "biz_marketing".
<INPUT TYPE="HIDDEN" NAME="Profile" VALUE="lesson1.prof">
Substitute your own Profile path and filename for
"lesson1.prof" (relative to your WebKeystone File Space
directory). The path may contain sub-directories
such as "/car/parts/batteries/neverready.HTML" but
may not contain references to directories above
the WebKeystone File Space directory
(ie, "../../stuff.HTML" will not work.) In addition, operating system
directory links have been disabled.
A second method of invoking a Profile is through the indirect
link mechanism:
NAME="Profile" VALUE="[shopping_cart:maIndex1.prof]
In this technique there are two UserIDs. One is given as the
UserID and the other is a linked ID, named explicitly using
the ":" mechanism in the Profile. In this example,
"shopping_cart" is the second UserID and is included in the
value setting of the Profile. The Profile maIndex1.prof in the
WebKeystone File Space owned by shopping_cart will be executed
if the permissions are correct. (In order for a user to use
this second mechanism, permission must have been granted.)
The name of the indirect UserID must appear in a list within
the direct user's UserID file. Such indirect accesses should
only be allowed by the System Administrator to trusted
applications which implement security.
<INPUT TYPE="TEXT" NAME="Email" SIZE="30">
Now you have a field called "Email" which is a variable you may
manipulate in your Profile. You could use that variable in the
"TO" line of your "MAIL" command to send a message to your user:
MAIL
SUBJECT 'My Subject'
FROM 'myEmailAddress@webkeystone.com'
TO Email
BODY 'MyMail.txt'
A list of all the form fields (variables) submitted in the form
is stored in a built-in list named CGI_FORM_KEYS.
RETURN "nextFile.html"
This returns the file named "nextFile.html" from the
WebKeystone File Space. This is an Endfile and is returned
with substitutions. The Endfile may also contain forms
that invoke WebKeystone. In fact, most complex applications
have many cross-linked forms. Endfiles are also frequently
Startfiles as well. The terms "Endfile" and "Startfile" are
functional and contextual and are not mutually exclusive.
Security is a major issue in all Internet applications. WebKeystone
was designed with security issues in mind. WebKeyscript
performs two critically different functions. The first function is an
extension of the system functions made available to a user. WebKeystone
performs many operations automatically that a CGI script has to do
procedurally. It also provides specialized functions and commands
unique to WebKeystone that make the processing of form data
quicker and more convenient and greatly reduce the development time
needed to create an interactive site. The second function that
WebKeystone performs is a limitation and restriction upon the system
environment in order to increase and enhance the security of the system.
eval, open, reload, __import__, getattr, setattr,
compile, raw_input, delattr, execfile, lambda
These are available in "hooked" programs if a form owner has
been granted access to these less secure facilities.
WebKeyscript is similar to Python, is written in Python, and is based
upon Python, but is NOT the same as Python. On one hand it provides
its own set of built-in commands and objects and functions. On the
other hand, it seriously limits and prohibits the use of many built-in
Python commands. This chapter documents
the commands WebKeyscript inherits, the commands WebKeyscript prohibits,
and the new commands WebKeyscript introduces.
There are four places where assignments to variables take place
in WebKeystone. The first is in forms in the Startfile. All
cgi-variables are declared in the forms. The second place is
during validation and conversion. Validations and conversions
are based upon suffixes of cgi-variables. During the process
of conversion new values may be assigned to these variables.
There are two places where the form writer can explicitly make
assignments to variables in WebKeystone. The first is in the
Profile and the second is in the Endfile. Assignments may
occur throughout the Profile; the Endfile may begin with an
optional header in which assignments can be evaluated. The
header is separated from the rest of the Endfile by
a line beginning with the two characters "#-". The header is
executed and stripped from the Endfile when it is invoked by the
RETURN statement. Any file in which substitutions may be performed
may contain assignments in a header. This includes files mailed
and specified in the BODY command or the ATTACH command. These
assignments are only performed if the file is loaded with
substitutions. This header feature was added in order to make
certain kinds of processing, in particular the display of lists,
convenient. It keeps the formatting for the list close to where
the list will be displayed, but hides this processing information
from users.
Let a = 'abcd1234xyz'
The "==" sign means is equal to. All of the following
statements with respect to "a" are true.
a[0] == 'a'
a[10] == 'z'
a[1:3] == 'bc'
a[1:] == 'bcd1234xyz'
a[:3] == 'abc'
a[1:-1] == 'bcd123xy'
a[:-3] == 'abcd1234'
a[-3:] == 'xyz'
The symbol "+" is used for concatenating strings. Using the
value for "a" above:
a[1:5] + a[-3:] == 'bcd1xyz'
Other operations are also possible and WebKeystone imports the
string component of Python which can be used with the prefix
"f.string.".
x = ['abc',77,['xyz']]
x is a list containing three items: the string 'abc', the integer
77, and the list containing one item ['xyz']. Similar to strings,
lists may be sliced and combined using the "+" symbol.
Examples:
x[0] == 'abc'
x[1] == 77
x[:2] == ['abc',77]
[x[0],x[2][0]] == ['abc','xyz']
x[2] + x[2] == ['xyz','xyz']
d = {'abc':1234, 7:'xyz'}
The first pair is indexed by a string and returns an integer.
The second pair is indexed by an integer and returns a string.
d['abc'] == 1234
d[7] == 'xyz'
Dictionaries may also contain arbitrary Python objects as values
and can be indexed with almost any Python object. In order
to create a list of the keys of a WebKeystone dictionary,
you can use the keys() method:
d.keys() == ['abc',7]
d.values() == [1234,'xyz']
d.items() == [('abc',1234),(7,'xyz')]
d.items() == [('abc',1234),(7,'xyz')]
are tuples. Tuples behave like lists for slicing and indexing,
but are immutable and cannot be added to or deleted from, nor
can their elements be changed. Tuples do not have as much use
in WebKeystone as lists do, but they can be created and they
may occur in certain special applications. Tuples are more
obscure in WebKeystone than they are in Python.
[
['kat','(777)-7777','DC']
['ned','8','NY']
|
|__>['pat','385-1111','PT']
| |
| |__>['jon','(206) 385-1111','PA']
| |
| |__>['snook','379-4444','SEQUIM']
| |
| |__>['ann','911','PC']
| | |
| | \__>['ruth','999-9999','LA']
| |
| |
| \__>['foofy','3779','PT']
|
|
\__>['mike','6','AC']
]
may be generated easily in a browser using
WebKeystone. It is even easier to generate list-based trees.
Trees and tables using graphics may also be generated with
WebKeystone. The tree above could have been created using the following
set of WebKeyscript commands:
ids = ['pat','jon','foofy','snook','ann','mike','kat','ned','ruth']
parents = ['ned','pat','pat','pat','pat','ned','other','other','ann']
phones =['385-1111','(206) 385-1111','3779','379-4444','911','6','(777)-7777','8','999-9999']
town =['PT','PA','PT','SEQUIM','PC','AC','DC','NY','LA']
t = tree([ids,parents,phones,town])
The id list above identifies WebKeystone tree-nodes.
In WebKeystone there is a one-to-one correspondence between tree-nodes and
unique-IDs. In the above example, "pat" is the name of a
tree-node and there must be only one tree-node that has
the unique-ID "pat". We then say the tree-node "pat" has
the parent tree-node "ned". If a tree-node has no parent
it is a root-node. If a tree-node has no children, it is
a leaf-node. All other tree-nodes are branch-nodes.
Arbitrary data may be associated with any tree-node.
In the above example, the lists "phones" and "town" are data
associated with each tree-node. The data associated with
a tree-node need not be unique.
Every tree-node in a tree in WebKeystone must have a
unique-ID which is used as a key. In the above example, the list named "ids" declares the
unique identifier for each tree-node. The list named "parents" identifies the
linkages between nodes. Note that the two tree members with the
keys "kat" and "ned" have "other" for a parent. Since "other" is
not in the list "ids" (which identifies all of the tree-nodes in the tree),
"kat" and "ned" are root nodes. This means
that what is really implemented by the tree class is a forest that
can contain more than one tree. Any node which identifies a parent
that is not in the unique-ID list is the root of a tree.
The next two lists, "phones" and "town", contain data. There is a
CAVEAT. The number of elements in the four lists must be identical.
The 'tree' will support any number of data lists greater than two.
The unique-ID list and the parent list are required. The other
lists are optional, and there can be any number, provided that
the length of the lists are the same. If users need to store
variable length data with a key, they can use a single list in
which each element has a variable length.
There are two classes associated with a tree:
the tree class and the tree-node class. The tree acts very
much like a dictionary.
If you access the tree using one of the ids, what is
returned is a tree-node instance. The tree-node instance
is not easily displayed, but the contents of the tree-node
can be accessed easily.
If we evaluate:
tree['pat'].vector
We get the list:
['pat', 'ned', '385-1111', 'PT']
If we evaluate:
tree['pat'].children
We get the list:
['jon', 'snook', 'ann', 'foofy']
If we evaluate:
tree['pat'].parent()
We get the list:
'ned'
If we evaluate:
id = tree['pat'].id()
We get the list:
'pat'
DISPLAYING A WEBKEYSTONE TREE
One of the biggest challenges in using
trees is displaying
their contents. A traditional way of doing this kind of display
is to write a recursive routine that will parse the tree and
generate the display. This can be done in WebKeystone, but
WebKeystone offers an easier, template based approach, as well
as the ability to use powerful "call-back" functions.
The text tree above was generated in a browser using the following
Profile commands:
itemmap1 = t.map(' ',' |',' \__',' |__','','>')
itemmap2 = t.map(' ',' |',' |',' |','\n')
branchmap = t.map( ' ',' |',' ',' |','\n')
tDict = {"_item":"%(itemmap2)s\n%(itemmap1)s['%(0)s','%(2)s','%(3)s']",+
"_tree":"[<pre>%()s</pre>]",+
"_branch":"%()s%(branchmap)s",+
"itemmap1":itemmap1,+
"itemmap2":itemmap2,+
"branchmap":branchmap+
}
And was displayed with the end-file tag:
%(t,tDict)s
It is much simpler to generate a display as a list of HTML lists.
The easiest kind of display can be generated with a simple template. One way is to use the substitute method of the tree.
The command:
x = t.substitute(_item="%(0)s - %(1)s - %(2)s - %(3)s<br>\n")
And the Endfile code:
<pre>
%(x)s
</pre>
Will generate the following display:
kat - other - (777)-7777 - DC
ned - other - 8 - NY
pat - ned - 385-1111 - PT
jon - pat - (206) 385-1111 - PA
snook - pat - 379-4444 - SEQUIM
ann - pat - 911 - PC
ruth - ann - 999-9999 - LA
foofy - pat - 3779 - PT
mike - ned - 6 - AC
Unfortunately, the above display does not encapsulate any of
the tree structure, simply the tree contents.
The following command will create a nicely indented list when
displayed in a browser with the "%(x)s" in the Endfile:
x = t.substitute(_item=" <li> %(0)s - %(2)s -%(3)s\n",+
_branch="<ul>\n%()s</ul>\n",+
_tree="<ul>\n%()s</ul>"+
)
The above creates the following HTML code:
<ul>
<li> kat - (777)-7777 -DC
<li> ned - 8 -NY
<ul>
<li> pat - 385-1111 -PT
<ul>
<li> jon - (206) 385-1111 -PA
<li> snook - 379-4444 -SEQUIM
<li> ann - 911 -PC
<ul>
<li> ruth - 999-9999 -LA
</ul>
<li> foofy - 3779 -PT
</ul>
<li> mike - 6 -AC
</ul>
</ul>
Note the use of the null substitution "%()s" in the "_branch" template
and the "_tree" template above. The null substitution is replaced with
all of the nodes in the tree that are part of that branch. In addition
all sub-branches or twigs are replaced.
_item=" <li> %(0)s - %(2)s -%(3)s\n"
Sets the template for each item in the tree. %(0)s is from the
tree-node vector and represents the item id. We do not use
%(1)s which is the key of the parent of the current tree-node.
%(2)s and %(3)s are from the data lists. The spaces and the
"\n" character are not needed; they are ignored by the browser and are there
simply to make the HTML code more easily read by humans.
_branch="<ul>\n%()s</ul>\n"
Again the "\n" characters are not needed. The
above informs the WebKeystone system that each branch should be surrounded
with the <ul> and </ul> tags. The null substitution
declaration "%()s" represents the contents of the branch, each of
which is an item and uses the _item template above.
_tree="<ul>\n%()s</ul>
Again the "\n" character is for readability of the output.
The null substitution "%()s" represents the whole tree.
The above tells us to surround the tree with the <ul>
and </ul> pair.
In the above we use the "substitute" command which generates
the text representation of the tree within the Profile code.
It is normally more convenient and quicker to represent
the templates for the conversion within a dictionary.
We can change the line:
x = t.substitute(_item=" <li> %(0)s - %(2)s -%(3)s\n",+
_branch="<ul>\n%()s</ul>\n",+
_tree="<ul>\n%()s</ul>"+
)
to be:
template = {"_item":" <li> %(0)s - %(2)s -%(3)s\n",+
"_branch":"<ul>\n%()s</ul>\n",+
"_tree":"<ul>\n%()s</ul>"+
}
And in the Endfile display the tree using the command:
%(t,template)s
Particularly when using the same template to display multiple
trees, this second technique is more efficient. We describe
other techniques using dictionary templates below.
ADVANCED TREE DISPLAY TECHNIQUES
Tree substitutions can be greatly extended with the use of
call-back functions. Each of the three templates that are passed
in a dictionary can be replaced with a call-back function. The
template dictionary represents a namespace. The call-back function
for _item and for _branch must accept exactly two arguments. The
first argument is the key for the node that is being processed. The
second argument is the tree object. This allows for an unlimited
number of different ways to render the tree.
The call-back function must be named and passed in the template
dictionary. In the example below a single call
back function is used in the "_branch" and the "_tree"
templates. Note that the call-back function must be passed in
the template dictionary so that it can be used in the "_branch"
and _tree templates.
The function space is declared in a DEFINE. The
function is given
the name "spc" and may be used in the templates with the %(spc)s
tag. The unique-ID of the current tree-node and a copy of the
current tree are then passed to the function. The evaluation of
this function replaces %(spc)s in the template. The following example is
somewhat contrived, although it does provide a different text
output.
To the Profile above that defines the tree, add:
DEFINE space,item,tre
EXITWITH len(tre.parents(item)) * " "
template = {"_item":"%(spc)s['%(0)s','%(1)s','%(2)s','%(3)s']\n",+
"_branch":"%(spc)s>>\n%()s%(spc)s<<\n",+
"_tree":"[<pre>%()s</pre>]",+
"spc":space
}
And display the tree with the above template using the tag:
%(t,template)s
This produces the result:
[
['kat','other','(777)-7777','DC']
['ned','other','8','NY']
>>
['pat','ned','385-1111','PT']
>>
['jon','pat','(206) 385-1111','PA']
['snook','pat','379-4444','SEQUIM']
['ann','pat','911','PC']
>>
['ruth','ann','999-9999','LA']
<<
['foofy','pat','3779','PT']
<<
['mike','ned','6','AC']
<<
]
In the DEFINE we use the method of the tree
"parents(<key>)". This returns a list of all the parents of a tree-node back to
the root. We have used the length of this list to calculate
the indentation.
The item passed to the call-back from the branch is
the unique-ID
of the tree-node containing the link. We have modified call-back
above to show this. Change the EXITWITH statement in the DEFINE
to be:
DEFINE space,item,tre
EXITWITH ("%5.5s" % item) + len(tre.parents(item)) * " "
Which produces the text:
[
kat ['kat','other','(777)-7777','DC']
ned ['ned','other','8','NY']
ned >>
pat ['pat','ned','385-1111','PT']
pat >>
jon ['jon','pat','(206) 385-1111','PA']
snook ['snook','pat','379-4444','SEQUIM']
ann ['ann','pat','911','PC']
ann >>
ruth ['ruth','ann','999-9999','LA']
ann <<
foofy ['foofy','pat','3779','PT']
pat <<
mike ['mike','ned','6','AC']
ned <<
]
It is also possible to produce a call-back function that is used in
the outermost "_tree" template. A call-back function of this sort
accepts only one value, the tree object. Call-backs that are used
in the "_item" and the "_branch" templates may not be used in
the "_tree" template.
The "_tree" template
need not be a string, but can simply be a call-back that accepts
two arguments as above, the unique-ID and the tree. Similarly
the "_branch" need not be a string, but can be simply a call-back
accepting a unique-ID and tree pair of arguments. Similarly the
"_tree" need not be a string, but may be a call-back accepting
a single argument.
THE map FUNCTION:
Although essentially any output format can be achieved by
combining the string templates and call-backs, sometimes it
is not efficient or easy to get the desired results this
way. Frequently, we may wish to format a tree using lines
and indentation such as was done in text in the first tree
in this section, which looked like this:
['kat','(777)-7777','DC']
['ned','8','NY']
|
|__>['pat','385-1111','PT']
| |
| |__>['jon','(206) 385-1111','PA']
| |
| |__>['snook','379-4444','SEQUIM']
| |
| |__>['ann','911','PC']
| | |
| | \__>['ruth','999-9999','LA']
| |
| |
| \__>['foofy','3779','PT']
|
|
\__>['mike','6','AC']
This was prepared using the "map" method of the tree which returns
a function that is suited to such displays. It
also makes it easy to produce nice tree displays in HTML and other
languages.
To use such a tree, include these lines in your
Profile:
mp1 = t.map(' ',' |',' \_',' |_')
tDict = {"_item":"%(mp1)s['%(0)s','%(2)s','%(3)s']\n",+
"_tree":"<pre>%()s</pre>",+
"mp1":mp1+
}
Display the above using the "%(t,tDict)s" tag:
['kat','(777)-7777','DC']
['ned','8','NY']
|_['pat','385-1111','PT']
| |_['jon','(206) 385-1111','PA']
| |_['snook','379-4444','SEQUIM']
| |_['ann','911','PC']
| | \_['ruth','999-9999','LA']
| \_['foofy','3779','PT']
\_['mike','6','AC']
The order-dependent arguments in the map function
produce text to be used for "space", for the vertical "bar" that connects
lines, for the "elbow" that marks the last child in a list of
children, and for the "tee" that is used for multiple children.
These could also have been named explicitly in the function,
in which case the order would be unimportant. The following
command to set the variable "mp1" is equivalent to the above:
mp1 = t.map(elbow=' \_',space=' ',bar=' |',tee=' |_')
The map function is not really necessary, but it makes displaying
trees in a somewhat standard manner easier. The map accepts
up to 7 arguments. All of the call-back functions accept two
arguments (the key and the tree) in a manner analogous to that
above.
The first argument to "map" is the "space" character
string or call-back function to use for spaces in front of an item in the tree. In
the above example this is a string of spaces.
The second argument to "map" is the "bar" character
string or call-back function that produces a bar. The character string is used to
connect children. In the example this is spaces followed by the vertical
bar.
The third argument to "map" is the "elbow" character
string or call-back that
produces the elbow at the end of a list of children. In the example
this is the character string of a backslash and an underline character.
The fourth argument to "map" is the "tee" character
string that precedes a
child in a list of children. It is used for children that are not
at the end of a list of children. In the example this is a vertical bar
followed by an underline.
The fifth character to "map" is the "pre" character
string or call-back
that always occurs on the left of new lines. The pre character string
is not used in the above example.
The sixth character to "map" is the "post" character
string or call-back
that always occurs on the right of new lines. The post character string
is not used in the above example.
The seventh and last argument to "map" is the "null"
character or call-back
that is used when none of the other characters generate a string. This
is used above to prevent spaces after root elements. It is normally
not needed and can be left as the default. The null character or
call-back will never be used if either the "pre" or "post" arguments
are non-empty.
MANIPULATING TREES:
There are a number of operations that can be performed
on a tree. In the following descriptions, <tree> is any instance of the tree
class. <tree-node> is any instance of the treeNode class. <key> is any
unique-ID of a node in the tree. <string>, <list>, <dictionary>, and
<int> are all types of variables.
BASIC FUNCTIONS:
len(<tree>)
The number of records in the tree.
<tree>[<key>]
Recover a <tree-node>. This is a dictionary-like interface.
`<tree>`
The tree is represented as string which is a list of lists.
<tree>.parents(<key>)
Returns a list of the keys of all ancestors with the root ancestor on the left.
<tree>.siblings(<key>)
Returns a list of the keys of all siblings, including self.
<tree>.children(<key>)
Returns a list of the keys of all children, including self.
<tree>.roots()
Returns a list of the roots of all the trees in the forest.
<tree>.insertAfter(<key>, <key>, <vector>):
Allows the ordered insertion of a newID. Order is
preserved. Note that the parent is not given. The new node is ALWAYS a
sibling node.
###Examples
<tree>.insertAfter('jon','joe',['385-2002','Hadlock'])
Note that we insert the data portion of the vector only! Neither the
unique-ID nor the parent unique-ID are included in the vector.
<tree>.insertBefore(<key>, <key>, <vector>):
Ordered insertion of a new id. See above insertAfter(...)
<tree>.has_key()
Standard dictionary-like interface.
<tree>.keys()
Standard dictionary-like interface.
<tree>.keys(<list of keys>) will return only those
keys that are in the list, or descendants of keys in
the list.
<tree>.toList()
Returns tree as list of lists suitable for instantiating
a new tree.
<tree>.retrieve(<key>):
Utility that returns the vector of a node.
Implemented as:
return self.dex[nodeID].vector
<tree>.get(<key>)
Same as retrieve.
<tree>.swap(key1, key2)
Allows rearranging items in the tree. Only allows the swapping
of items that are siblings.
<tree>.clone()
Copies the tree.
<tree>.clone(<key>)
If a key is given, it will clone the key and any descendants.
<tree>.copy()
Same as clone.
<tree>+<tree>
If both trees have the same size vectors and combining
the trees would create no circular references, they are
combined. Note that in WebKeystone this is the only way
to easily add a new tree-node to an existing tree.
t = t+tree(["buggy"],["foofy"],["1"],["2"])
Adds a new node to t with the unique-ID "buggy" and the
parent "foofy".
ADVANCED FUNCTIONS:
Advanced functions accept an argument list. The
list may be a list of keys and may optionally contain
one call-back function. The call-back function accepts
a single argument which is the tree-node instance being
processed. In the following items, <key list> may optionally
contain a call-back function.
<tree>.clip(<key list>)
Clips these branches of the tree. That is,
remove them from the tree.
The clipping happens in the current tree, rather than returning
another tree. All children of any clipped node are also clipped.
If a call-back function is included in the list, the function
must be satisfied in addition to the node being present. That
is, if the function returns a "true" equivalent result, the
node may be clipped.
<tree>.deepClip(<key list>)
Works like clip except that children of clipped nodes
are not automatically clipped, but considered individually.
This function will leave a lot of trees in the forest. Its
usage is somewhat specialized. 'clip' is normally what is
intended. If deepClip is not provided with a call-back
function only the keys in the <key list> are clipped. Their
children become root nodes. If a call-back function is
provided it is applied to all the keys in the list and
all of the children in the keys. Only those keys for
which the call-back function evaluates to true are clipped.
Unclipped children of clipped nodes become root nodes.
<tree>.filter(<key list>)
Filter is similar to clipping. Named nodes are
branches on which to apply the filter. The filter is
applied to the named node. Any node and its children
are kept in the tree if the filter is satisfied.
All other nodes are clipped. This is a generated
forest consisting of the branches or sub-branches of
nodes named in the key list. If a function is provided
it is applied to all the keys and the children of
the keys. If no list is provided the list of roots
is used.
<tree>.deepFilter(<key list>)
Similar to filter, but every child is considered as well,
even if the parent node failed. This returns the complement
of the set of ids returned by deepClip.
<tree>.transform(<key list>)
This only makes sense when a call-back function is
passed.
The function passed takes a treeNode object as an argument and
returns a vector. If the returned vector id and parent match
those of the passed object, then the new vector replaces the old.
You cannot rearrange a tree using the transform function. You
can only transform the values in the tree.
Keys in the key list can be used to select branches to
transform.
If two functions are included in the argument list, the second is
used to select items to transform and the first is used to perform
the transforms. The rules for replacement are:
1. Either all vectors in the tree must be replaced, or
the replacement must result in vectors that have not
changed length.
2. If the function returns a vector where the first
two items in the vector match the unique-ID and the
parent respectively, then the whole vector is replaced.
3. If the function returns a vector with a length
of one, the last element in the treeNode vector
is replaced. If the function returns a vector
with length of two, the last two elements in the
treeNode vector are replaced, etc. If the
vector returned does not match the unique-ID
and parent, and is too long to replace the last
elements, then the treeNode vector is lengthened.
4. The transform will fail if the tree would contain
any treeNode vectors of different lengths.
<tree>.append(<call-back function>)
Transforms a tree by adding single item to vector.
This can be done using the transform method as well. In many
cases the call-back function can simply return a string.
<tree>.sort(<key list>)
The sort method accepts an argument which is a compare
function that operates on objects of the tree-node type.
It also accepts a list of nodes. The function passed is
different from any of the functions discussed above. The function
passed MUST take two arguments, each of which is a tree-node.
The function should return -1 if the first is less than the
second, 0 if they are equal and +1 if the first is greater
than the second. If no nodes are given, the whole tree is
sorted. The sort method does not make sense unless a comparison
function is not passed. The sort is done on branches. It cannot
change the linkages between nodes in the tree.
DEFINE mySort, node1, node2
EXITWITH cmp(node1.vector[0],node2.vector[0])
t.sort(mySort)
Sorts sort the complete tree t, comparing the item
ids. The
cmp function in the EXITWITH line is a built-in compare function
that uses system compares and returns -1, 0, or +1.
For further information and examples see the wkTree class document
for WebKeystone Python programmers.
+ - * ** / %
<< >> & | ^ ~
< > <= >= == != <>
The comparison operators <> and != are alternate spellings
of the same operator; != is the preferred spelling; <>
is obsolete.
These are the four arithmetic operators:
+ - * /
They also maintain their Python meanings for other
objects. + is the concatenation operator for strings and
* will operate on a string with a number to cause a repetition.
% is modulo division but will also operate on a string for
substitution and is used similarly to the C string formatting
token.
These are bitwise operators and their use is obscure in WebKeystone:
<< >> & | ^ ~
These are comparison operators:
< > <= >= == != <>
In addition, the boolean-like operators "and", "or" and "not"
are inherited from Python.
Several components are also imported from Python.
In particular all the functions and variables in the
following Python components can be used with the exception
of f.time.sleep(<seconds>) command which is not available.
"string", "math", "random", "time", "cmath", "convert", "validate".
They are used in WebKeystone by preceding their name with an
"f.". For instance:
f.string.join(['The','small','pig']) == 'The small pig'
f.string.atof('.2999') == 0.2999
Other available functions of these components can be found in
the Python Library Reference Manual. Convert and validate
are covered herein because they are WebKeyscript-specific.
Built-in Python functions that can be executed from
WebKeystone include:
hex, min, abs, int, len, round, max, range,
map, divmod, float, type, list, long, str,
ord, chr, coerce, oct, cmp, reduce, filter,
pow, complex, apply
%<variable> = <unquoted string>
The unquoted string starts after the white space following the
equals (=) sign and ends at the last non-white space character
on the line. Any string of the form %(<variable>)s in the string
on the right-hand side of the equals sign is replaced with the
value of the variable in the string. All other symbols (except
the continuation line character) are treated verbatim. The
leading and trailing white space is stripped. The continuation
line character plus (+) is honored.
<variable> = <WebKeystone expression>
<variable> => <WebKeyscript expression>
Every variable can be used like a stack using this operator. The command
DEL <variable>
will change
the value of <variable> and pop the old value of the variable off the stack unless there are
no values on the stack, in which case it will delete the
variable. The following two command sequence is equivalent to a nop in many languages, or a Python pass statement.
A => B
DEL A
A will have whatever value it did before it was assigned to B.
IF <expression>
<indented block>
The indented block is executed only if the expression is true;
otherwise the command following the indented block is
executed.
ELSE
<indented block>
The indented block is executed only if the previous expression
executed was an IF or ELIF instruction and the result
of the evaluation of the expression was false.
ELIF <expression>
<indented block>
The indented block is executed only if the previous executed
expression was an IF or ELIF expression and the result of
the evaluated previous expression following the IF or ELIF
was false and the result of evaluating the expression
following the current ELIF expression is true.
TRY
<indented block>
ELSE
<indented block>
The indented block is executed. If a WebKeystone error
occurs in the indented block, control is passed to the
indented block following the ELSE statement. The complete
exception list with formatting is contained in the
variable 'exception' if the indented exception block
following the ELSE statement is executed. The format
of this message in 'exception' is tentative and should
not be depended upon.
RETURN <nosub filename>
or
RETURN <mime type> <numbs filename>
or
RETURN
<mime type> <nosub filename>
.
.
.
A few commands, such as the CUSTOM command and the DOWNLOAD
command, use an additional indentation.
The expression following RETURN is evaluated and the result
is used as the name of a file which is returned to the user.
RETURN <filename>
is equivalent to
RETURN TEXT_HTML <filename>
RETURN is the last command executed in the Profile and terminates
the processing. The expression may optionally be preceded
by NOSUB which will prevent substitutions from taking place.
NOSUB also suppresses evaluation of all code in an Endfile. Code
in an Endfile can occur in two places. The first is at the beginning
of the file. Here it is separated from the HTML portion of the Endfile
by a comment which begins with "#-". The second is within a substitution
tag. NOSUB will suppress the evaluation of WebKeyscript code
in both places. The code at the beginning of a returned
file (which is separated from the rest of the file by the "#-"
comment line) is always stripped and never returned to
an end user over the web. When NOSUB is not present
in the RETURN statement, the WebKeyscript is executed
before substitutions are made. When NOSUB is present,
the WebKeyscript is stripped but not executed, and
the remainder of the file is returned verbatum.
RETURN uses a mime text/HTML type to return a file.
Several other mime types are available and can be combined
in a "Content-Type: multipart/x-mixed-replace" wrapper by including them
indented after the use of the RETURN MULTIPART command. Currently a RETURN with
no arguments defaults to a RETURN MULTIPART. The following may occur after a RETURN
command, or be combined in a RETURN MULTIPART command:
TEXT_HTML which is 'Content-type: text/html; charset=us-ascii'
TEXT_PLAIN which is 'Content-type: text/plain; charset=us-ascii'
DOWNLOAD which is 'Content-Type: application/octet-stream'
IMAGE experimental
CUSTOM experimental
Here is an example using mime types in a MULTIPART RETURN:
RETURN MULTIPART
TEXT_HTML
"Title.htm"
DOWNLOAD
"Dummy.txt"
TEXT_PLAIN
RAW "You are done downloading"
Not all browsers support these types.
FOR <variable> IN <list>
<indented block>
The variable is assigned to values in the list one at a time.
For each new value of the variable the indented block is
executed once.
CONTINUE
Causes the next value in the enclosing FOR loop to be
evaluated and the indented block to be reentered at the
top.
BREAK
Causes the enclosing FOR loop to be terminated and the
next expression following the indented block to be executed.
EXITWITH <WebKeystone expression>
EXITWITH is used with DEFINE to return a value from a function.
DEFINE <function name>,<dummy argument list> <WebKeystone code>
The define command declares a block of code as a function.
The indented code is executed every time the <function name> is referenced
in an expression. All variables used within the defined block are global except
those passed in the <dummy argument list>. An example of the declaration
of a function that finds the average of two numbers is:
DEFINE average2,value1,value2
EXITWITH (value1+value2)/2
An example of code that increments a variable and returns
the incremented value is:
XXX = 1
DEFINE inc_XXX
XXX = XXX+1
EXITWITH XXX
An example of a recursive function is:
DEFINE factorial,x
IF x==0
EXITWITH 1
ELSE
EXITWITH x*factorial(x-1)
After being defined, the function factorial() can be used in any
WebKeystone expression.
CALL <Profile name>
or
CALL <Profile name>[,<variable name>]*
When followed by a Profile name, this command "calls"
the Profile. The Profile name may optionally be followed
by a comma and a list of variables. These variables
are pushed and recovered after the routine returns.
All other variables are treated as globals.
Note that both the Profile name and the pushed variable
names MUST be in quotes, or be an expression that
evaluates to the name.
When a Profile is called, it is entered with the
same name space as the calling Profile. If it
finishes without performing a RETURN command, control
is returned to the calling Profile. All variables
in the variable list are popped back to their old
values.
These two command sets are equivalent:
A => A
B => B
CALL "recurse.prof"
DEL A
DEL B
and:
CALL "recurse.prof","A","B"
LOCK <lock-name>
<indented instructions>
The block of code is locked with respect to all
other processes and threads. Only one thread in one
process can execute a piece of code with the lock
named <lock-name>. The lock is maintained as long
as the indented instructions are executing and is
released when they are exited. The lock has no effect
upon the current thread or process. It is only
intended to keep others out.
'My dog is a %(dogtype)s.'
Would evaluate to:
'My dog is a mutt.'
when loaded from an Endfile or when included in an assignment
such as:
%dogline = My dog is a %(dogtype)s.
The leading % is required in this assignment and the string
is NOT included in quotes. We will call strings of the
form '%(<variable name>)s' tags. This particular tag ends
with an "s". WebKeystone provides for a number of different
formatting endings. These formatting endings are taken
from string formats in the C language. Formats allow the positioning
of a string in a fixed length
field. To justify strings, use space numbers with the tags.
This will right justify in 13 spaces::
%(<variable name>)13s
This will left justify in 13 spaces:
%(<variable name>)-13s
The first number following the ")" and preceding the
"s" will set the number of spaces in the field. If the
number is negative left justification will be used.
If the length of the variable being substituted is greater
than the field length, the field length is extended.
Use tags to force truncation. This will right justify in 13 spaces
and will truncate if the length of the variable is
over 13 spaces:
%(<variable name>)13.13s
This will left justify in 13 spaces and will truncate if
the length of the variable is over 13 spaces:
%(<variable name>)-13.13s
The truncation size can be different than the justification.
There are several other formatting options, which will not be
covered here. Refer to a Python reference manual to get these
alternative codes.
template = '<LI>%(1)s'
be an assignment in the header of our Endfile. Let:
<UL>%(aList,template)s</UL>
occur somewhere in the body of the Endfile. When the
Endfile is loaded and substituted into, each item in
the list "aList" will be preceded by the <LI> tag. Let:
aList = ['dog','cat','bird']
In the Endfile <UL>%(aList,template)s</UL> will be replaced
with:
<UL><LI>dog<LI>cat<LI>bird</UL>
Which will produce the desired list.
Multiple lists can be combined in a single substitution.
The lists are comma-separated. The general format is:
%(<list1>,<list2>,...<list>,<template>)s
If a tag contains n lists, then the n tags, %(1)s, %(2)s ...
%(n) can be used in a template to indicate an item from
<list1>, <list2> and <list> respectively. The lists
should all be the same length, although WebKeystone will
lengthen those that are shorter. The tag %(0)s can also
be used to indicate which element in the list is being
processed.
Example:
list1 = ['Bob','Mary','Bill']
list2 = ['dog','cat','bird']
template = '%(0)s %(1)s owns a %(2)s. \n'
In the file returned by a BODY command is the line:
%(list1,list2,template)s
which is replaced in the mailed item by:
1. Bob owns a dog.
2. Mary owns a cat.
3. Bill owns a bird.
%(<tree>,<node-template>)s
Like list templates,
node-templates can contain the substitution variables
%(0)s, %(1)s, %(2)s ... Within the node-template, these values
are replaced with %(0)s--The depth in the tree of the node,
%(1)s--The unique-ID of the node. %(2)s--The parent of the
node. %(3)s--The first piece of data associated with the
node (if it exists). %(4)s--The second piece of data associated
with the node, and so on.
Frequently one will want to lay out a tree in an outline
format with indentations at each branch in the tree. Directory
trees are frequently laid out this way.
In order to do this the indentation template is provided.
The substitution would have the form:
%(<tree>,<node-template>,<indentation>)s
The indentation templates is repeated 0 times for nodes with
no parents, once for nodes with parents that have no parents,
and at the nth level in the tree it is repeated n-1 times.
It is immediately followed by the node-template for the node.
The indentation-template, like the node template, can contain
substitution variables, although the usage is less common, and
frequently all the indentation-template will contain is spaces.
There are three other templates: A begin-template, and
end-template, and a beg-end-indentation template.
The begin-template and the end-template are used before and
after a new level of indentation. If not specified, the
beg-end-indentation defaults to the same as the indentation.
It is used in the same manner, as a means of indenting based
upon the depth of the nodes in the tree.
%(missingVal,oldv(missingVal),'%(1)s failed. %(2)s is wrong!')
It is safest to put evaluated strings and complex expressions
in the prologue. This statement would been better implemented as:
template = '%(1)s failed. %(2)s is wrong!'
#-----------------------------------------
...
%(missingVal,oldv(missingVal),template)
Evaluation will fail if the parentheses do not match. The
following is a legal Python expression that fails in a WebKeystone
substitution:
%(")")s
`233` == '233'
'The Number ' + `7 + 8` == 'The Number 15'
This is shorthand for the function str() which works the same:
str(233) == '233'
'The Number ' + str(7 + 8) == 'The Number 15'
MAIL
<indented block>
This command has no argument and starts collecting information
for a mailing. Only the commands described in this section
can be used in the indented block following a MAIL command.
All other WebKeystone commands are illegal here.
SUBJECT <expression>
The expression is evaluated as a string and used as the
subject line of the email. Only one of these should appear
in the indented block.
FROM <expression>
The expression is evaluated as a string and used as the
from address of the mail. Only one of these should appear
in the indented block.
TO <expression>
The expression is evaluated as a string and used as the
to address of the mail. If the string evaluates to a
list of addresses separated by commas, the mail is sent
to the whole list. If the expression evaluates to a
list, the list is treated as a list of addresses and
the mail is sent to each address. Only one of these should
appear in the indented block.
BODY <nosub filename>
The expression following BODY (or NOSUB if it is present)
is evaluated as an expression and used as the filename
of a file to be returned as the body of the email. If
NOSUB is not present, then substitutions are done in the
file. Only one of these should appear in the indented block.
If RAW is present the string is used directly and not as
a filename.
ATTACH <nosub filename>
The expression following ATTACH (or NOSUB if it is present)
is evaluated as an expression and used as the filename
of a file to be returned as an attachment to the email. If
NOSUB is not present, then substitutions are done in the
file. Any number of these may appear in the indented block.
If RAW is present the string is used directly and not as
a filename.
ATTACH
<attachment name1>, <filename1>
<attachment name2>, <filename2>
...
The attached file is assigned the new name. The filename
may be preceded with NOSUB and RAW.
DB_READ <filename>
<variable>
...
The file <filename> contains the variable <variable>.
The file may contain multiple variables. They can each
be read by naming them in the indented list. In order
to read a variable its name must be known. The new variable
will overwrite any old variable of the same name.
DB_WRITE <filename>
<variable>
...
Multiple variables may be written to a database file
by including their names in the indented list. Each
new value of a variable already in the database overwrites
the old value in the database.
DB_DELETE <filename>
(depreciated in favor of DELETE)
Deletes the file named <filename> in reference to the
WebKeystone database area.
FF_READ <filename>
<variable>
FF_READ<filename>
<variable1>
<variable2>
A command for opening and reading a flat
file with respect to the flat file area of the system.
The contents of the file <filename> are assigned to
the variable <variable>. The old value of the
variable is destroyed.
In the second form of FF_READ, <filename>
and <variable1> have the same meaning as <filename>
and <variable>. The additional variable, <variable2>,
is assigned a timestamp value which can be used with the
FF_WRITE command (see below). If we read a file and obtain the timestamp,
then modify the file and write it back out, declaring
the timestamp, the file will fail to write if it
has been changed since that last read. The timestamp
is checked against the file; if the timestamp of
the file being written and the file being overwritten
do not match, the write is aborted.
FF_READLINES <filename>
<variable>
A function for opening and reading a list into a flat
file with respect to the flat file area of the system.
The items in the list <variable> are lines from
the file <filename>. The newline character is not stripped from the end
of each line in the list. The old value of the variable is destroyed.
FF_WRITE <filename>
<variable>
...
FF_WRITE TIMESTAMP <variable1><filename>
<variable2>
A function for writing a string to a flat file with respect
to the flat file area of the system. The string <variable>
is written on the file <filename>. Multiple indented strings
may be included. They will be concatenated together in order.
In the second form of the command, <filename>
and <variable1> have the same meaning as <filename> and <variable>
have in the first form. The variable <variable2>, declared by the TIMESTAMP command,
contains a value which is
compared against the timestamp for the file. If they do not match, an exception is thrown.
This is to ensure that when a file is rewritten after being read, it has not been changed in
the interim.
There is a side effect. If <variable1> is an actual
variable instead of an expression, and if the write succeeds, the variable
is reset with the new timestamp of the file that is written.
This allows it to be rewritten multiple times without reads
between to access the timestamp. Read FF_READ above for more
information on this variation of FF_WRITE and FF_READ.
FF_APPEND <filename> <variable>
...
This is almost identical to the FF_WRITE command. The
behavior differs only in that the string <variable> is appended to the
existing file instead of overwriting the file.
FF_WRITELINES <filename>
<variable>
...
The items of the list <variable> are concatenated together
and written to the file <filename>. No other separators are
added. Multiple indented lists may be included. They will be
concatenated together.
FF_APPENDLINES <filename> <variable>
...
This is almost identical to the FF_WRITELINES command. The
behavior differs only in that the items of the list <variable> are appended
to the existing file instead of overwriting the file.
FF_DELETE <filename>
(depreciated in favor of DELETE)
Deletes the file named <filename> in reference to the
WebKeystone flatfile area.
TF_READ <filename)>
<variable>
A function for opening and reading from a temporary
file into the name space of the Profile. The variables
read from the temporary file are the same as the variables
written to the temporary file using the TF_WRITE command.
The file name <filename> should be created using the
newid() function. Since the temporary file area is
shared among users, it is important that the file name
is both unique and secure. The newid() function implements
this. <variable> is used to indicate success or failure.
The variable will be set equal to "OK" if the file read
was successful. A temporary file that exists
for fifteen minutes without being read is automatically
destroyed. The fifteen minute limit is configurable when
installing the WebKeystone system.
TF_WRITE <filename>
<variable>
...
A command for preserving a portion of a Profile's
name space. The <variable> and all other variables
in the indented list are written to the temporary file
named <filename>. When this file is read, the variables
are restored. There are two ways to read this file.
First is the use of TF_READ and the second is the use of
the hidden field named "TPersist_".
Both TF_READ and TF_WRITE can be made more secure
by adding parameters to the file name. Any file
written with TF_WRITE <filename>,<parameter1>, ...
Must be read with the same parameter list or the read
will fail.
Currently all temporary files self-expire. The amount of
time before self-expiration is a system constant. In the default
installation a temporary file must be touched (read or written) every 15 minutes
to not self-expire. Under no circumstances can the programmer assume that
the file will be allowed to exist for more than four hours. These are WebKeystone
system-wide constants and can only be changed in any given WebKeystone installation
by changing constants in the wkConfig.py file (the file which sets all
system constants).
It is polite and saves system resources to delete
temporary files when they are no longer needed. However, this is NOT necessary, and in
many cases the programmer cannot know that the file needs deletion. In these cases the
temporary file self-expiration takes care of the situation. For instance,
in the shopper application, this is how abandoned shopping carts are
deleted.
TF_DELETE <filename>
Deletes the temporary file named <filename>.
NEWID <variable>
Creates a name of a file that is unique. It may be
used as the name of a temporary file that preserves
state. This instruction is depreciated in favor of
the newid function:
<variable> = newid()
The usage of the newid function is described in the
Chapter on WebKeystone Functions.
MERGE <variable>
<expression 1>
<expression 2>
<expression 3>
...
"variable" must be a dictionary in the name space.
"expression 1", "expression 2", "expression 3", and so on represent an
indented list of expressions, one per line, indented to the
same level with no punctuation, each of which evaluates to a dictionary. Each dictionary
in the list is merged into the dictionary in the variable. If a duplicate key occurs,
the new value replaces the old value of the key.
REMOVE <variable>
<expression 1>
<expression 2>
<expression 3>
...
"variable" must evaluate to a dictionary or a list.
"expression 1", "expression 2", "expression 3", and so on represent an
indented list of expressions, one per |