Printable manual: brl.pdf (340Kb) or brl.dvi (204Kb) |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
The example below, `bruce.brl', illustrates the basics of BRL.
[(define myname "Bruce") (define my "Bruce's")] <pre> This is [my] web page. [myname myname myname myname] [my] favorite person is [[your name here]. [my] favorite number is [(brl-random 2)]. </pre> |
When you visit this page, the web server uses BRL to transform this page into regular HTML to be sent back to your browser. Your browser does not need to know anything about BRL to view this page. Here is the output sent to your browser:
<pre> This is Bruce's web page. BruceBruceBruceBruce Bruce's favorite person is [your name here]. Bruce's favorite number is 0. </pre> |
As you can see, the define
expressions do not result in any
output. They merely define variables (myname
and my
) for
later output. Variable substitution transforms "This is [my] web
page" into "This is Bruce's web page."
The "BruceBruceBruceBruce" output shows that spaces and line breaks do nothing inside square brackets. Values of multiple expressions are output with no space in between. Outside of square brackets, spaces and line breaks are sent verbatim.
An important note on the "[my] favorite person is [[your name here]" line is that outside square brackets there is only one special character, the open square bracket. If you want an open square bracket to appear in the output, it needs to be "escaped" by putting two. The close square bracket is not special and does not need to be escaped.
The (brl-random 2)
expression outputs 0 or 1, so different
visitors will see different results.
Advocacy: Syntax for variable substitution is extremely intuitive. Square brackets are used just as in English, i.e. text outside the brackets is a literal quotation; text inside the brackets is not.
The first thing that speeds BRL programming is that square brackets
are easier to type than the combination "angle-bracket plus something
else" syntax used in ASP, JSP and PHP. Such syntax is billed as more
standards-compliant, but generally isn't. It's often used in entity
attributes, e.g. <img src="<%=myurl%>">
, where angle brackets are
illegal. Other illegal angle brackets, e.g. the less-than operator,
show up in embedded code. Even if that syntax is used in such a way as
to produce a standards-compliant source file, there is no guarantee that
standards-compliant output will be produced.
Static HTML content tends to have a lot of angle brackets. Delimiting dynamic content with angle brackets makes it difficult to visually pick out. BRL's square-bracket syntax makes it easy, without the need for a syntax-highlighting editor.
Non-literal expressions other than variable substitution are quickly spotted by the opening paren, e.g. [(brl-random 2)] is quickly recognized as more than variable substitution. A more common syntax for programming languages would be [brl-random(2)], which would take slightly longer to recognize as more than variable substitution. Small improvements in readability add up to a more beautiful language.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
This next example will go one step farther with the (define myname
"Bruce")
expression in Web Page for Bruce.
[(define myface ] <img src="/img/myface.jpg" alt="Bruce's face"> [)] |
Prior to now, it looked as if BRL was just Scheme expressions enclosed
in square brackets. This example breaks that mental model. It looks
like a define
expression that starts within one set of square
brackets ends within another set. However, there's another way of
looking at it.
BRL's use of square brackets is the opposite of what you might think.
It isn't that [ marks the start of Scheme code and ] marks the end.
Instead, ] marks the start of a string and [ marks the end
of a string. For example, (define myname "Bruce")
could also be
written as (define myname ]Bruce[)
. The example above differs
only in the amount of text between ] and [.
Additionally, strings can be delimited by the beginning and end of a BRL file.
The best way to think of BRL is as a Scheme interpreter(2) with an alternative syntax for strings. Similar to when you type "Hello World" to a Scheme interpreter and it echoes back the string you typed, BRL reads from the beginning of a file to the first non-escaped [ and outputs the resulting string.
In Web Page for Bruce, BRL reads a string starting with the beginning of
the file and ending with [. The next thing BRL reads is a compound
expression consisting of the three sub-expressions define
,
myname
, and "Bruce"
. This doesn't produce any output.
Then it reads another define expression. Go back and read that example
again, this time looking from an inside-out perspective. Play the role
of a BRL interpreter. Read a Scheme expression. Output its value.
Repeat. BRL is extremely simple when you know how to look at it.
Advocacy: Other languages can make only very limited use of the text "outside" code delimiters, because it must be converted to print statements. These can be enclosed in loops or other flow-control constructs, but can't be used as function arguments.
As a result, programmers frequently use quote-and-backslash syntax for
strings. Omitted backslashes are a common reason for code not working
right the first time. Important spaces are often omitted because it's
counterintuitive to put them between a quote mark and the quote-enclosed
text, e.g. " favorite...
. Using BRL speeds development by
eliminating these annoying sources of error. With other languages, the
best you can hope for is that yet another string-quoting syntax is
available and better than either the template syntax or
quote-and-backslash.
Other languages are designed by piling feature upon feature. BRL is designed by removing the restrictions on existing features that make additional features appear necessary. There is tremendous power in being able to use BRL's template syntax in any context, not just "top-level" output to the web browser, as subsequent examples will illustrate.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
Bruce's Web Page is already playing games with visitors. His "favorite number" is either 0 or 1, chosen randomly. Let's take this one step further, adding more sophisticated random content.
[(define myname "Bruce") (define my "Bruce's")] <pre> This is [my] web page. [(if (zero? (brl-random 2)) (brl myname] refers to [myname] in 3rd person.[) (brl ]Should [myname] run for office? Yes![))] </pre> |
In this example, (brl-random 2)
returns either 0 or 1. If it
returns 0, visitors will see "Bruce refers to Bruce in 3rd person."
Otherwise they will see "Should Bruce run for office? Yes!"
Let's use the inside-out perspective to clarify what's happening. We
have an if
expression, which has 3 sub-expressions. The first
sub-expression is a predicate, in this case, (zero? (brl-random
2))
. If the predicate evaluates to true, the second sub-expression is
evaluated. If false, the third is evaluated.
The second sub-expression is a brl
expression. It takes all its
sub-expressions and outputs them as part of the web page. Using the
inside-out perspective, we see that its sub-expressions are the variable
myname
, the string " refers to "
, the variable
myname
again, and the string " in 3rd person."
.
As a performance optimization, brl
expressions print directly to
the web page rather than returning a string to be output. When you need
such a string, use brl-string
instead of brl
. For
example:
[(define myface (brl-string ]<img src="/img/myface.jpg" alt="[my] face">[)] |
You can also use the standard Scheme string-append
in cases like
the above, when no non-strings are involved.
Advocacy: HTML coders who know nothing about programming can
quickly learn to deal with BRL. Everything outside square brackets is
their domain; everything inside is off limits. In the define
myface
example above, width/height attributes could be added.
Attributes with quotes could be inserted without backslashes. The HTML
coder needn't know five different syntactic conventions for strings,
just one. An adventurous HTML coder might even replace alt="[my]
face"
with alt="[myname]"
and find that it works! BRL truly
provides gentle-slope learning for the non-programmer.
Traditional tools require extreme caution by HTML coders working "within" programming code. New tools continue to be written in an attempt to make walls that keep non-programmers away from programming code. However, there will always be a point where programming and non-programming meet. BRL is the best tool for that meeting point.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
Any definition from "Web Page for Bruce", e.g. (define myname
"Bruce")
, can be cut from that page and put into your
`sitedefs.scm' file. Then any instance of [myname]
on
any page will output Bruce
. If you don't know where
`sitedefs.scm' is, put the following code in a BRL page:
Look in [brl-scmdir] for sitedefs.scm |
That page will tell you where to look.
Function definitions go in `sitedefs.scm' as well. A convenient way to do BRL development is to create functions in the file you're working on as needed, then later cut/paste useful functions into `sitedefs.scm' for use in other pages.
Common headers/footers can be implemented by defining strings in sitedefs.scm, but there is a better way. To output a common header in a BRL page, use the following code:
[(paste "/header.brl")] |
This will insert output from `header.brl' (found in the document root, or under brldir, depending how BRL is configured at your site). This will not introduce variables from `header.brl' into your page. Use `sitedefs.scm' for that.
In the above example, "header.brl"
and "../header.brl"
would work just as they would in an HTML anchor. Use
brl-url-contents
instead of paste
if you want to paste in
output from another server.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
1 [ 2 (define-input word) 3 ] 4 <html> 5 <head> 6 <title>Your word: [word]</title> 7 </head> 8 9 <body> 10 <p>Your word is [word].</p> 11 <form> 11 Type a word: <input name="word"> 13 <input type="Submit"> 14 </form> 15 </body> 16 </html> |
In this example, the define-input
syntax on line 2 is used to name a
variable that will hold the value of an HTML input of the same name. If
there had been more than one such input, they would all be included in
the same line, e.g. (define-input word1 word2)
.
If you try out this example, you'll get a form into which you can enter
a word. When you submit the form, you'll go to the same page, with the
word you typed substituted for [word]
.
Interestingly, when you first try this example, before you submit any
form, you'll see ()
substituted for [word]
. This is
because no form input named word
was submitted. Form inputs can
be submitted with any number of values, e.g. same-name checkboxes.
Usually you expect one value, in which case you'll get a string.
However, if there are zero, two or more values, you'll get a list.
Here's something important to remember about HTML forms: You never know what value you're going to get, or if you're going to get any input at all. Validate your inputs if you plan on doing anything interesting with them.
Below is an example of validating inputs. It's a rather involved example. The source for it is in `learnbrl.war'. It will pay to try out this example live as you learn the concepts in it.
1 [ 2 (define (valid w) 3 (if (string? w) 4 w 5 "something")) 6 (define-input (valid word1) (brl-nonblank? word2) word3) 7 (define-cgi SERVER_NAME) 8 ] 9 <html> 10 <head> 11 <title>Backwards</title> 12 </head> 13 14 <body> 15 <form> 16 Type three words: <input name="word1"> 17 <input name="word2"> <input name="word3"> 18 <input type="Submit"> 19 </form> 20 <p>[word1] spelled backwards is 21 [(list->string (reverse (string->list word1)))] 22 </p> 23 [(brl-when word2 ] 24 <p>Second word: [word2]</p> 25 [)] 26 <p>Third word: [word3]</p> 27 <p>This message brought to you by [SERVER_NAME] as a public 28 service.</p> 29 [(brl-guess-input brl-context)] 30 </body> 31 </html> |
In this example, valid
is a procedure defined in lines 2-5 to
validate inputs. For those who skipped the introduction to Scheme,
here's what it does: It takes an argument, w
. If w
is a
string, w
is returned. If w
is not a string, return the
string "something"
.
The define-input
syntax on line 6 passes the word1
input through
the procedure we named valid
before binding it to the variable
word1
. Later, line 21 can simply assume that word1
is a
string. Note that if you want to use valid
in other pages, you
can cut it from lines 2-5 and paste it into `sitedefs.scm'.
Also on line 6, brl-nonblank?
is used on word2
to return
false (#f
) if word2
is an empty list or a blank string.
Otherwise it returns word2
(3) This makes word2
useful in all kinds of
conditionals, including the (brl-when ...)
code on lines 23-25.
On line 6, we chose to leave word3
unvalidated, just to
show that validating some inputs does not mandate that you validate
all. Try changing it to validate with brl-list
so that it's
always a list, even if there's exactly one value. This is useful for
same-name checkboxes.
Line 7 illustrates the define-cgi
syntax. Though we chose not to
validate the variable in this example, you can do validation just as
with the define-input
example. Validation is sometimes necessary
because of user-supplied data. Even though BRL is not implemented using
CGI, it uses this syntax to mimic
CGI environment
variables
On line 29, brl-guess-input
returns a list that prints as a
valid define-input
line for all the form inputs received. This is
handy if you have a form with lots of inputs. When you first write a
BRL page as the action for that form, you don't have to pick out all the
input names. Just put [(brl-guess-input brl-context)]
on the
BRL page, submit the form, and use the resulting define-input
line as
your starting point.
Advocacy: Competing systems have two common approaches to handling form inputs. Some, e.g. PHP3, automatically assign variables named according to form inputs. This is makes for convenient coding, but not convenient debugging. There is no way to distinguish a typo from a valid form input name that simply wasn't supplied. In large pages, this can cause difficult-to-track bugs, where an empty string is being used somewhere. Also, having form inputs automatically shadow global variables opens some serious security holes. In PHP4, the variables are no longer assigned automatically. However, you still have the typo issue.
Other systems, e.g. Java servlets, have a special Request object with
methods for getting form input values. You assign a variable,
e.g. word=request.getParameter("word");
. This is good because it
gives you only one place to make a difficult-to-catch typo. If you
later reference werd
instead of word
, you'll get an error
rather than simply having werd
used as an empty string.
BRL takes the optimal middle ground. You have one place where inputs are declared. Typos later will result in errors. However, this one place uses a concise, non-redundant syntax, making it almost as convenient to code as PHP3 minus the security holes, and just as convenient to debug as a servlet.
What's more, the validation syntax encourages developers to write code that's easy to audit for security holes related to user-supplied input.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
Suppose you want, for example, http://example.com/map/5/6
to
represent points on a map with coordinates (5, 6). You'll first need to
tell the BRL servlet what file to look at for such URIs. Here's what
you'd put in `sitedefs.scm', assuming that
`/var/www/html/map.brl' is the full path to the BRL file:
(define (brl-uri-map uri default-file) (cond ((brl-starts-with? "/map/" uri) "/var/www/html/map.brl") (else default-file))) |
You may also need to configure your web server to recognize that such
URIs should be passed to the BRL servlet. However, for testing purposes
it might be easier to just use
http://example.com/servlet/brlsv/map/5/6
as the URL.
Here is the code you would put in `map.brl':
[ (define-path (string->number x) (string->number y)) ] <html> <head><title>Position ([x], [y])</title></head> <body> You are at position ([x], [y]) on the map. You may go <a href="[(+ y 1)]">north</a>, <a href="[(- y 1)]">south</a>, <a href="../[(+ x 1)]/[y]">east</a>, <a href="../[(- x 1)]/[y]">west</a>. </body> </html> |
On line 2, define-path
takes the last two path segments of
/map/5/6
, passes them to the string->number
procedure, and
binds the results to variables x
and y
. Had we been
content to leave them as strings, we could have simply used
(define-path x y)
.
We could also have used (brl-path-segments brl-context)
to return
a list of the three strings, ("map" "5" "6")
. This is more
useful than the define-path
syntax if the number of segments
varies.
We can omit line numbers in this example, because only line 2 teaches something new about BRL. The only other thing I'd point out is the HTML title in line 4. It is good to make the title as specific as possible. Most browsers have a menu associated with the Back button to enable users to go back several pages at once. A more specific title makes this menu useful.
Advocacy: Isn't [x]
a lot more readable than
<%=x%>
would have been in this example? Particularly look at the
<a href="...">
parts.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
A web server may pass a name/value pair called a "cookie" back to a web client, with the intent that the client pass that same cookie back to the server from then on. Do not use cookies to keep track of the state of what a particular client is doing. That client might have multiple windows open doing different things, or might have used the back button.
Appropriate uses of cookies would be to remember a user-supplied time
zone or postal code, or keeping track of items in a shopping cart.
Cookies in BRL are always strings, or the Scheme value #f
if no
cookie by that name is set.
The following example sets a cookie named c
:
1 [(define-cookie c)] 2 <p>The cookie named c has value "[c]".</p> 3 [(set! c (brl-random-typeable 2))] 4 <p>The <a href="">next</a> value will be "[c]".</p> |
If we were doing anything more interesting with the cookie value than
displaying it, we would have used the same validation syntax as with
define-input
.
If the value were set!
multiple times in that page, only the last
value would be sent back to the client as a cookie.
See section 5.2 Web Functions, for more sophisticated ways to get/set cookies.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
Sometimes a cookie is almost the right thing, except the value is too large to be a practical cookie value, or the nature of the data makes it hard to validate. In such cases, data can be kept on the server side in what's called a session variable. A cookie is used to associate the client with this data, so all the caveats of the previous section apply. Additionally, session variables are transient, and time out after a certain period, configurable in your servlet engine. Most of the time, information should either be kept client-side or be put in a database on the server side. However, session variables are convenient and sometimes appropriate.
1 [(define-session a b c)] 2 3 <p>The session variables are [(list a b c)].</p> 4 5 [ 6 (set! a b) 7 (set! b c) 8 (set! c (brl-random-typeable 2)) 9 ] 10 11 <p>The session variables <a href="">will be</a> [(list a b c)].</p> |
As with cookies, these values carry over page to page. If the value is
set!
multiple times, only the last becomes the new session
variable value.
Unlike with cookies, session variables are not necessarily strings.
They can be numbers, lists, vectors, serializable Java objects, etc.
There is no validation syntax in define-session
. It is not
user-supplied data.
With the syntax (define-session (ses) a b c)
we could have
bound the variable ses
to the session object, allowing more
sophisticated operations, such as retrieving session variables whose
names conflict with input names or other local variables. See section 5.2 Web Functions.
The most common misuse of session variables is in multi-step forms. To
accumulate values from previous forms as you go along, use hidden form
inputs instead. For example, [(brl-html-hidden a b c)]
would
expand to the following html:
<input type="hidden" name="a" value="a's value"> <input type="hidden" name="b" value="b's value"> <input type="hidden" name="c" value="c's value"> |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
The definition below goes in `sitedefs.scm' to set things up to send mail via an SMTP server. If you are using BRL Cabaret or some other site that has already been set up, this definition is already there for you. You do not have to include it in an individual page; it is a site-wide definition (ergo, `sitedefs.scm').
(define mail (brl-smtp-sender "mail.example.com" ; the SMTP server "www.example.com" ; the web server "webmaster@example.com" ; bounced mail goes here )) |
This creates a procedure called mail
that takes at least two
arguments. The first argument is a list of e-mail addresses that are
recipients of the message. The second and subsequent argument become a
string that forms an e-mail message, i.e. Internet mail headers followed
by a blank line, then the body of the message.
Obviously, you can define several such procedures with any names you like, but always include a default procedure named mail to ease portability of BRL code from site to site.
The following page shows how to use the mail procedure. It would be the ACTION page for an HTML form:
1 <html> 2 <head><title>Mail sent</title></head> 3 4 <body> 5 [(define-input email yourname quest colour) 6 7 (mail (list "strangeman@example.com") 8 ]From: [email] 9 To: strangeman@example.com 10 Subject: questions three 11 12 What is your name? [yourname] 13 What is your quest? [quest] 14 What is your favourite colour? [colour] 15 [)] 16 17 <p>Your e-mail has been sent. Thank you.</p> 18 19 </body> 20 </html> |
Line 5 defines the inputs that come from the HTML form. Line 7 invokes the mail procedure with a list of one recipient. Lines 8-14 define the body of the e-mail message. It is important that there be no spaces between ] and the first header (line 8), and that there is a blank line after the headers (line 11). Line 15 has the close paren for the mail expression.
Let's look at this example from the perspective discussed earlier
(see section 4.1.1 Understanding BRL Inside-Out). Don't look at square brackets
as enclosing [code]
. Look at them in the reverse sense,
enclosing ]strings[
to see what's happening. The first argument
to mail
on line 7 is a list. The second argument, on line 8, is
the string "From: "
, followed by the variable email
as the
third argument. The fourth argument is a string that starts on line 8
with a newline character, spans several lines, and ends just before the
variable yourname
(the fifth argument, on line 12).
The mail
procedure, as explained earlier, concatenates its second
and subsequent arguments to create an Internet message, which it then
sends via SMTP. Taking the inside-out perspective, the closing paren on
line 15 makes perfect sense. The square bracket before it ends a string
that is part of the e-mail message, since it's an argument to
mail
. The square bracket after the close paren begins
a string that will be sent back to the browser because it's at the top
level, i.e. not being used as an argument to anything.
Advocacy: In 1994, cgiemail was developed at the Massachusetts Institute of Technology to allow non-programmers to easily specify what e-mail messages sent from their HTML forms should look like using a simple template syntax. Lines 8-14 look like that syntax. A non-programmer could easily customize the example above. BRL is almost as simple as cgiemail, but much more powerful.
Only BRL uses the same friendly template syntax for constructing e-mail messages as is used for constructing web pages.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
If you are using BRL Cabaret, the table and JDBC driver are already set up. You may skip ahead to 4.8.2 Queries in an Individual Page.
The following examples will assume you have a JDBC driver for a database in which you've created the following table:
create table favcolor( name varchar(20), color varchar(20)) |
In your `sitedefs.scm' file, register your JDBC driver with the
sql-driver
procedure. Then define a procedure for connecting to
your database. By defining this procedure in your `sitedefs.scm'
file, you avoid putting database usernames/passwords in BRL web pages.
For example, if you were using the PostgreSQL driver:
(sql-driver "org.postgresql.Driver") (define (db1 brl-context) (brl-sql-connection brl-context "jdbc:postgresql://localhost/db-here" "user-here" "password-here")) (define ss brl-mysql-string) |
The brl-sql-connection
procedure returns a connection to your
database that can then be used to create SQL statements, and assures
that this connection will be closed once the BRL page finishes, whether
normally or by error. The brl-context
variable is a structure
that contains various information in the context of the current BRL
page.
The purpose of (define ss brl-mysql-string)
in this context is in
case you switch back and forth between a driver that expects standard
SQL string syntax (only single quotes need escaping) and a driver that
expects non-standard string syntax, e.g. MySQL or PostgreSQL
(backslashes also need escaping). Use ss
wherever you would
otherwise use brl-sql-string
or brl-mysql-string
.
If you need further help getting your driver working, consult your JDBC driver supplier or the following Usenet group: `comp.lang.java.databases'
Advocacy: Simple examples for competing systems generally put database info (driver, server, user, etc.) directly in the page. Only with BRL is it natural and easy to centralize, while still maintaining the flexibility of having many different DBs to connect to.
Unclosed database objects (connections, statements, etc) are a major headache for server-side web programming. Sometimes this happens because of an uncaught error. Sometimes a programmer simply forgets to close the object when done. BRL eliminates the problem in both cases.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
1 [ 2 (define conn (db1 brl-context)) 3 (define st (brl-sql-statement brl-context conn)) 4 ]<html> 5 <head><title>Favorite Colors</title></head> 6 <body> 7 8 <ul> 9 [(define rowcount 10 (sql-repeat st (name color) ("select * from favcolor") 11 (brl ]<li> [name] likes [color] 12 [)))] 13 </ul> 14 15 <p>Count: [rowcount]</p> 16 17 </body> 18 </html> |
In line 2, the db1
procedure previously defined in
`sitedefs.scm' is used to get an SQL connection that will be
automatically closed later. In line 3, the brl-sql-statement
procedure is used to get an object(4) that can be used repeatedly
for SQL queries on that connection.
In line 10, we see BRL's sql-repeat
syntax. The first argument
is the SQL statement object to be used in the query. The next argument
specifies the variable names to be used for the columns returned by the
query, in this case name
and color
. The third argument is
the text of the query. In this example it is just one string, but it
could have been several pieces, some strings, some not, that would be
concatenated.
Any remaining arguments to sql-repeat
are Scheme expressions to
be evaluated once for each row returned in the query. In this example,
there is just one expression. In standard Scheme, the expression would
be written as follows:
(brl "<li> " name " likes " color " ") |
BRL's square-bracket syntax helps clarify what text is literal and what
is evaluated. It just takes a little getting used to code like
(brl ]
, which signifies the beginning of literal text contained
within a BRL expression. This same syntax can be used for SQL
queries, as will be seen in later examples.
The brl
syntax is necessary because of the fundamental difference
between BRL and Scheme. In Scheme, sequences of expressions generally
return the value of the last expression. In BRL, all expressions in a
sequence are output. The brl
syntax is most commonly used in
sql-repeat
and if
expressions.
The sql-repeat
syntax returns the number of rows from the query.
Since BRL pages normally show the return value of expressions, this
example uses define rowcount
to capture the value so that it can
be output in a more appropriate place on line 15.
The three parentheses on line 12 close the brl
expression, the
sql-repeat
expression, and the define
expression,
respectively.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
For people to enter their own favorite colors into the database, they'll need a form. This is simply HTML; there is no BRL programming involved.
1 <html> 2 <head><title>Choose Favorite Color</title></head> 3 4 <body> 5 6 <form action="chosen.brl"> 7 Name: <input name="name"><br> 8 Color: <input name="color"><br> 9 <input type="submit"> 10 </form> 11 12 </body> 13 </html> |
The action
in line 6 will work if your web server knows how to
handle the `.brl' extension. If not, you'll need to use a longer
URL. Here is `chosen.brl':
1 <html> 2 <head><title>Favorite Color Chosen</title></head> 3 [(brl-referer-check brl-context) 4 (define-input name color) 5 (define conn (db1 brl-context)) 6 (define st (brl-sql-statement brl-context conn)) 7 ] 8 <body> 9 10 [(if (or (brl-blank? name) 11 (brl-blank? color)) 12 "<p>Please go back and fill in your name/color.</p>" 13 (if (positive? (sql-execute-update st 14 "update favcolor set color=" 15 (brl-sql-string color) 16 " where name=" 17 (brl-sql-string name))) 18 "<p>Your favorite color has been updated.</p>" 19 (begin 20 (sql-execute-update st 21 ]insert favcolor(name, color) 22 values([(brl-sql-string name)], [(brl-sql-string color)])[) 23 "<p>Welcome to the favorite color database!</p>" 24 ) 25 ) 26 )] 27 28 </body> 29 </html> |
You've seen lines like 1-8 before, except for line 3. If it weren't for
the brl-referer-check
expression ("referrer" misspelled for
historical reasons), some joker could create a form on his own web site
that looks like it's doing one thing, but then when a user hits the
Submit button it would change the user's favorite color in your
database. The brl-referer-check
expression restricts referers to
your own web site. If this is not restrictive enough, you can write
your own code to check the value of (cgi HTTP_REFERER)
. A simple
brl-referer-check
will eliminate most mischief, but if your site
includes URLs submitted from outside sources, or if some of your
privileged users have setups that do not send referer info, you should
include additional checks in your code.
Here is a translations of lines 10-23 into English: If either input has
not been filled in (10-11), return a message asking the user to do so
(12). Otherwise, try executing an update
for the user (13-17).
If that update affects a positive number of rows (13), return an update
message (19). Otherwise insert the appropriate data (19-22) and return
a welcome message (23).
The sql-execute-update
procedure takes two arguments or more.
The first is the statement object to use. The second and subsequent
arguments become a string representing an SQL insert, update or delete.
The return value is the number of rows affected.
Starting on line 14, the standard Scheme syntax is used for strings. The alternate BRL syntax is used starting on line 21, using square brackets to mark the dividing lines between Scheme code and literal strings. Use the standard Scheme syntax for short, simple strings. Use the BRL syntax for longer strings or strings containing lots of double quotes or parentheses, or anywhere that you think someone reading your code would have trouble telling whether a double quote is starting or ending a literal string.
The brl-sql-string
procedure on lines 15, 17 and 22 takes one
argument. If this argument is null, "NULL"
is returned.
Otherwise a string is returned enclosed in single quotes, with any
internal single quotes doubled, as required by SQL. This is important.
A knowledgeable intruder might otherwise supply form input that executes
arbitrary SQL statements in your database. If you are using or
might start using MySQL, which has has non-standard string-escaping
rules, see 4.8.1 One-Time Preparation for All Pages.
The final bit of syntax that deserves explanation is in lines 19-24.
Use begin
to group expressions so that only the value of the last
expression is returned. If you want all the values to be output, group
the expressions with brl
instead. In this example, we don't want
to see the number of rows affected by the insert; that number should
always be 1.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
SQL results do not always map neatly into the result you want to see on an HTML page. Sometimes you want subtotals for numeric data or sectional divisions for other data. Like other report systems, BRL provides a mechanism for grouping SQL results. For example, suppose you had the following results:
color name ------ ----- blue Jane blue Joe blue Lancelot red Bill red Yuri |
Suppose you wanted output that looked like this:
Here is the BRL code that lets you do it:
1 [ 2 (define conn (db1 brl-context)) 3 (define st (brl-sql-statement brl-context conn)) 4 ]<html> 5 <head><title>Favorite Colors</title></head> 6 <body> 7 8 <ul> 9 [(define rowcount 10 (sql-repeat st (name color) 11 ("select * from favcolor 12 order by color, name") 13 (if (group-beginning? color) 14 (brl ]<li><strong>[color]</strong>: [name) 15 (brl ", " name)) 16 (brl-when (group-ending? color) 17 #\newline)))] 18 </ul> 19 20 <p>Count: [rowcount]</p> 21 22 </body> 23 </html> |
Lines 1-12 aren't significantly different from earlier examples in
this chapter. Lines 13-17 are where it gets interesting. The
group-beginning?
syntax determines if a group is beginning. In
this example, color
determines the beginning of the group. So as
each color group begins (13), a bullet is output, along with the color
and a colon, plus the first name associated with that color (14). If a group
is not beginning (i.e. 2nd and subsequent names), a comma is output
followed by the name (15). Then, if we are at the end of a group based
on color (16), a #\newline
character is output. This just makes
the HTML output cleaner in case you do "View Source" from your browser.
If you want a subgroup based on a column, e.g. col2
within a
group based on col1
, use (group-beginning? col1)
to test
for the outer group and (group-beginning? (list col1 col2))
to
test for the inner group. To nest groups deeper, simply add the column
names to the list.
The argument to group-beginning?
or group-ending?
does not
have to be just a column name. For example, (group-beginning?
(string-ref color 0))
would group colors together by their first
letter.
If you use a constant, group-beginning?
will be true only at the
beginning of the result set, and group-ending?
will be true only
at the end of the result set. I suggest using the symbol 'all
for this purpose, as it makes your intent clear.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
Continuing with the previous examples, we've set up a web site that lets
people put in arbitrary strings as colors. This introduces a security
issue: What if someone introduces a "color" that is actually JavaScript
code or some such? The code might cause other users' browsers to do
something undesired. The easy way to fix this problem is to translate
the characters <
, >
, &
and "
into their
respective HTML escape sequences. This is done in the following example
with the brl-html-escape
procedure.
1 [ 2 (define conn (db1 brl-context)) 3 (define st (brl-sql-statement brl-context conn)) 4 ]<html> 5 <head><title>Choose a Color</title></head> 6 <body> 7 8 <ul> 9 [(define rowcount 10 (sql-repeat st (color) 11 ("select distinct color from favcolor 12 order by color") 13 (brl ]<li><strong> 14 <a href="p2.brl?[ 15 (brl-url-args brl-blank? color) 16 ]">[(brl-html-escape color)]</a></strong> 17 [)))]</ul> 18 19 <p>Count: [rowcount]</p> 20 21 </body> 22 </html> |
We see in line 16 that the color name is properly escaped before being used as the text of an anchor.
It would be a waste to use a 22-line example just to illustrate one
procedure, so the brl-url-args
syntax is also illustrated in line
15. The first argument to brl-url-args
should generally be
brl-blank?
. If you want to draw a distinction between inputs
that have been sent as blank strings and inputs that have not been sent
at all, you might use null?
instead, but this is generally a bad
idea. The remaining arguments in the brl-url-args
syntax should
be variable names. The variable names and their values are encoded in
such a way as to be included in a URL. In the example above, a URL is
generated that, when followed, goes to a page `p2.brl' and
color
is given as an input.
The brl-url-args
syntax is convenient in that it omits blank
inputs, resulting in nice, compact URLs. But sometimes you may want to
simply URL-escape a value. In that case, use the brl-url-escape
procedure exactly as brl-html-escape
is used.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
The next example, `colors.brl', is intended to be the action of an HTML form to search the database by name, color or both. The results appear in columns which can be sorted by clicking(5) on a column header. Successive clicks on a header reverse direction. If you're looking at the list in say, reverse order by name and then click on the color header, the list will then be sorted by color, but within each color they will be sorted in reverse order by name.
1 <html> 2 <head> 3 <!-- [ 4 (define-input color name order1 order2) 5 (define where-clause 6 (string-append "where 1=1" 7 (if (brl-nonblank? name) 8 (string-append " and name=" (ss name)) 9 "") 10 (if (brl-nonblank? color) 11 (string-append " and color=" (ss color)) 12 ""))) 13 (define (valid-order str) 14 (if (or (brl-blank? str) 15 (sql-order-member str (list "name" "color"))) 16 str 17 "")) 18 (set! order1 (valid-order order1)) 19 (set! order2 (valid-order order2)) 20 (define nonblank-orders (brl-nonblanks (list order1 order2))) 21 (if (null? nonblank-orders) (set! nonblank-orders (list "name"))) 22 (define order-columns (brl-string-join ", " nonblank-orders)) 23 (define (sort colname) 24 (string-append 25 "colors.brl?" 26 (brl-string-join brl-url-arg-separator 27 (brl-nonblanks 28 (list 29 (brl-url-args brl-blank? name color) 30 (brl-url-arg-seq "order" 31 (sql-order-prepend colname nonblank-orders) 1)))))) 32 (define conn (db1 brl-context)) 33 (define st (brl-sql-statement brl-context conn)) 34 ] --> 35 <title>Search Results</title> 36 </head> 37 38 <body> 39 40 <table> 41 <tr> 42 <th><a title="sort" href="[(sort "name")]">Name</a></th> 43 <th><a title="sort" href="[(sort "color")]">Color</a></th> 44 </tr> 45 [(define rowcount 46 (sql-repeat st (name color) 47 (]select name, color 48 from favcolor 49 [where-clause] 50 order by [order-columns) 51 (brl ] <tr> 52 <td>[(brl-html-escape name)]</td> 53 <td>[(brl-html-escape color)]</td> 54 </tr> 55 [)))] 56 </table> 57 58 <p>Found: [rowcount] [where-clause]</p> 59 </body> 60 </html> |
Normally a search query has conditions that are always part of the where
clause. In this example there are none except the search conditions,
but we've put 1=1
in as a placeholder (line 6). Lines 7-9
specify a name if one was typed in the appropriate input. Lines 10-12
do the same for color. If there were other search criteria, we could
add them in much the same way.
The order1 and order2 inputs are URL arguments that `colors.brl' sends to itself in order to do column sorting.
Putting arbitrary strings from form input into your SQL query is a risky
proposition: there's no guarantee that the order1
and
order2
inputs will be what you intended. Lines 13-17 define a
function that returns a valid value for either of these variables
regardless of its input. This function is used in lines 18 and 19.
Line 20 gets a list of all the non-blank order columns. This is the
last line that would have to change if you were to add another column.
Line 21 makes the default sort be by name. Line 22 gets a
comma-separated list of columns to sort by. Lines 23-31 define a
function that returns a relative URL that points back to
`colors.brl' with arguments for the search parameters and the sort
columns. The new BRL functions introduced here are
brl-url-arg-seq
, which returns a string of URL parameters with
sequentially-ordered names, and sql-order-prepend
, which takes a
column and a list, and returns a new list with that column at the
front. If the column was previously at the front, it is toggled between
ascending/descending order.
Lines 32-41 are old material. Lines 42 and 43 show how the sort function is used (defined in lines 23-31). Lines 47-50 show a dynamically-generated SQL statement that incorporates the search criteria and sort columns.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
Scheme has basic string functions which are described in R5RS and work in all Scheme implementations. The Kawa implementation of Scheme has additional string functions described in the Kawa manual. BRL provides yet more string functions, the most important of which are described here.
Any object can be converted to a string with the brl-string
procedure. But there are more efficient and more powerful means of
converting objects to strings if you know something about the object.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
Scheme (and thus BRL) includes a procedure called number->string
whose only formatting capability is an optional argument specifying a
number base. E.g. (number->string 255)
yields "255"
, and
(number->string 255 16)
yields "ff"
.
For other formatting capabilities, the brl-format
procedure
provides a simple interface:
(brl-format 65535 "#,###")
yields "65,535"
(brl-format 1/3 "$0.00")
yields "$0.33"
(brl-format 1 "#%")
yields "100%"
For more details, see your Java documentation for
java.text.DecimalFormat
. If BRL is later ported to a non-Java
platform, brl-format
will still work the same way.
If you will be converting a lot of numbers to strings of the same
format, brl-decimal-formatter
will be efficient and convenient.
> (define money (brl-decimal-formatter "$#,##0.00" "NULL")) > (money (* 100 100)) $10,000.00 > (money 1/3) $0.33 > (money (list)) NULL |
As can be seen in the above example, brl-decimal-formatter
takes
two arguments, the first being a string suitable for use with
brl-format
, and the optional second argument being a string to
use for null values. The return value of brl-decimal-formatter
is a procedure that takes one argument and returns a string. If the
concept of a procedure that returns a procedure seems confusing, study
the example above for a minute. You'll see it's actually quite simple.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
In addition to numbers, brl-format
can also do simple date
formatting, e.g. (brl-format (brl-now) "yyyy-MM-dd")
might yield
"2000-05-19"
.
For more details, see your Java documentation for
java.text.SimpleDateFormat
. If BRL is later ported to a non-Java
platform, brl-format
will still work the same way.
Your site should have a standard format for dates that is used
everywhere. It is best to define such a format in your
`sitedefs.scm' file using the brl-simple-date-formatter
procedure.
(define date10 (brl-simple-date-formatter "yyyy-MM-dd" "NULL")) |
You would then use the date10
procedure anywhere a date should be
formatted into a string of at most 10 characters.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
To trim the whitespace off of both sides of a string, use the return
value of (brl-trim str)
, which returns a trimmed version. It
does not modify the string passed to it. If you want to modify a
string, do this: (set! str (brl-trim str))
Use brl-split
to split a single string into a list of strings
based on a separator string, e.g. (brl-split " and " "lions and
tigers and bears")
yields ("lions" "tigers" "bears")
.
To join the string representations of a number of objects into a single
string with a separator, use brl-string-join
. For example,
(brl-string-join ", " (list "one" 'two (+ 2 1)))
yields
"one, two, 3"
.
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [Contents] | [Index] | [ ? ] |
The Kawa manual describes make
, invoke
, and
invoke-static
syntax for manipulating Java objects. This section
will illustrate their use within `sitedefs.scm'. You should define
procedures that manipulate Java objects only in this file, then use
those procedures in individual pages. This methodology will keep the
individual pages clean and portable.
The following example uses DbConnectionBroker, but could easily be adapted to use another connection-pool object.
1 (define testdb-pool 2 (make <com.javaexchange.dbConnectionBroker.DbConnectionBroker> 3 "com.internetcds.jdbc.tds.Driver" 4 "jdbc:url-here" 5 "user-here" "password-here" 1 5 "/tmp/testdb.log" 1)) 6 7 (define (db1 brl-context) 8 (let ((c (invoke testdb-pool 'getConnection))) 9 (brl-prepend-endproc! 10 brl-context 11 (lambda () (invoke testdb-pool 'freeConnection c))) 12 c)) |
When the BRL servlet starts or re-reads `sitedefs.scm', a variable
testdb-pool
(line 1) is bound to a new object (2). In this
example, an 8-argument constructor is used. Then, the db1
procedure is defined to get a connection from this pool (line 8), then
prepend to the list of things to do when a BRL-page request is complete
(line 9), a procedure that takes no arguments and frees the connection
(line 11). The return value is the connection (line 12).
Note that no changes are required for individual pages to take advantage
of the connection pool. Previously, db1
was a procedure that
opened a connection and assured that it was closed later. Now it is a
procedure that gets a connection from the pool and assures that it is
freed later. The individual pages don't know the difference.
[ << ] | [ >> ] | [Top] | [Contents] | [Index] | [ ? ] |