Copyright © 2003 Tabasoft S.a.s.
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation; with no Invariant Sections, no Front-Cover Texts, and no Back-Cover Texts. A copy of the license is included in the section entitled Appendix 4, GNU Free Documentation License.
Table of Contents
List of Figures
List of Tables
Table of Contents
Table of Contents
Biferno is a new generation Web scripting language that allows developers the rapid implementation of dynamic Web applications and of Web sites that offer a high degree of user interactivity.
Biferno is a Web server add-on module that allows dynamic generation of HTML pages -the result of the processing of the Biferno code by the server determines the page content. A Biferno script is therefore a server-side script, i.e. the Web server is tasked with code processing. Biferno is currently implemented only as an interpreted language. Biferno is also a "HTML-embedded" language. Scripts can be written in pure Biferno language, but can also contain HTML code segments between Biferno code blocks. The Biferno code is always delimited by special tags that allow separation from the rest of the code (HTML or whatever).
The following tags can be used to delimit a Biferno script:
<?
// Biferno code
?>
<?biferno
// Biferno code
?>
In our examples we will always use the <? and ?> tags. The
alternative opening tag <?biferno is necessary when integrating Biferno
and XML code, or when using certain HTML development tools that are
compatible with embedded scripting languages. There should always be at
least one space, tabulator or newline character after the <? and
<?biferno tags.
When a browser requests a Web page to the server, the page is analyzed by the Biferno interpreter only if it has been stored on the server in a file with ".bfr" extension. If this is not the case, the page is simply returned to the client as a standard HTML page with no further intervention.
After processing, a page containing pure HTML code is returned by the
server. This page contains none of the Biferno instructions contained in
the original script. This implies that we do not have to worry about the
ability of the other party to read the code that produces a certain result,
which is an issue e.g. with JavaScript client side scripts. During page
processing the Biferno interpreter executes only the text (code) delimited
by the <? and ?> tags, ignoring the rest of the text, which is
returned with the output page to the client (an exception to this behavior
exists, as we will see in the following). An example is:
<html>
<body>
<b>An example of Biferno code embedded in a HTML page</b>
<hr>
The current Biferno version is
<?
print(biferno.version) //Print Biferno version
?>
<hr>
</body>
</html>
The example above produces the following HTML output:
<html>
<body>
<b> An example of Biferno code embedded in a HTML page</b>
<hr>
The current Biferno version is
1.0.0
<hr>
</body>
</html>
The Biferno code has been replaced by the result of its processing, which
is the current version of Biferno. This information has been obtained by
calling the biferno.version predefined property and sending its result in
output using the print function. The HTML code generated by this mechanism
produces the following result in the browser window:
An example of Biferno code embedded in a HTML page
The current Biferno version is 1.0.0
After showing a first simple example of Biferno script, let's talk about the main features of the language for a first taste of its potential.
All variables in Biferno are class instances. This allows developers to write more readable and modular code that can easily be reused in different projects.
The Biferno interpreter is completely written in the C language and optimization of execution speed has been a constant goal throughout the development process.
One of the main problems for Web site developers is the maze of different platforms (operating systems, Web servers, databases) and their incompatibilities. Biferno's system independence allows to migrate a whole site from a platform to a new one with minimal effort.
A complete public C interface and a downloadable SDK allow developers to write their own Biferno classes. All Biferno classes (ranging from int to string to file) are based on this interface.
Biferno runs on:
Linux (implemented as an Ap module of the Apache Web server plus Biferno daemon combination)
MacOS Classic (implemented as a WSAPI plugin of the WebSTAR Web server or as ACGI)
MacOSX (implemented as an ap module of the Apache Web server, or a WSAPI module for WebSTAR web server, plus Biferno daemon combination)
Windows (implemented as an ISAPI module for the "IIS" Web server (or an Ap module for Apache) in combination either with the "Biferno.exe" server or with the "bifernosvc.exe" Windows service).
On all these platforms Biferno can be interfaced with database applications, both via support for native drivers and via support for ODBC (Open DataBase Connectivity).
"bifernoadmin" is a web-based service that allows easy remote administration of the Biferno system and of your own Biferno application via a graphical interface. A separate document (the "Biferno: bifernoadmin Guide") describing in detail this powerful adminstration tool is available. While we will occasionaly mention the bifernoadmin tool in this document, we refer the interested reader to the bifernoadmin documentation for the details.
Table of Contents
The Biferno language online reference guide ("Biferno: Reference Guide" document) can be viewed online by accessing the Biferno home page. A copy of the Biferno language online reference guide is also installed automatically on your machine when you install the Biferno software.
Once Biferno and bifernoadmin (the latter is automatically installed with the Biferno software) are correctly installed, the online documentation can be accessed locally pointing the browser to the URL:
http://www.yourserver.com/bifernoadmin/bfdoc/index.bfr
The name or IP address of the server where Biferno has been installed
should replace the www.yourserver.com string.
The Biferno Reference Guide contains the detailed description, completed by usage examples, for all classes and predefined functions of the language, and is a key tool for the developer, both during the initial language learning phase and during the development of complex applications.
The Biferno installation procedure is described in the "Biferno: Installation and Administration Guide" document for all supported platforms.
The online documentation system installed with Biferno is described in the "Biferno: bifernoadmin Guide" document.
The goal of this document is to support the learning process for the Biferno scripting language in a simple and progressive fashion by using a large number of examples and clear simple language, even when necessarily technical.
The level of the presentation of the various topics is adequate for a reader who has basic knowledge of computer science and programming concepts. The reader who already knows how to use a structured programming language such as C or, even better, an object-oriented language such as C++ or Java, will encounter no difficulty in the materials presented in the following chapters. A good knowledge of HTML and of specific Internet/Web topics is an advantage but not a must.
This document uses the following conventions to highlight and separate different parts of the text.
All Biferno code examples are highlighted using a light gray background and a monospaced font, as in the following example:
<?
print("Welcome to the Biferno User Manual")
?>
All keywords and code fragments within the normal text are highlighted
using the same monospaced character font (e.g. the print
keyword).
The results of script processing are shaded in the same light gray color that reminds of a terminal window and are printed in the a monospaced character font, as in the example below:
Welcome to the Biferno User Manual
From time to time specific notes are inserted into the text to highlight and provide details on specific topics, introduce advance programming techniques, provide suggestions and tricks. The notes are indented to the right with respect to the main text under a "Tip" heading, as in the following example:
![]() | Tip |
|---|---|
Notice that... Etc. etc. etc. |
Finally, tables and figures are numbered with the number of the Chapter they appear in, followed by a progressive number.
Table of Contents
Prior to the discussion of the fundamental characteristics of the language we will show how to write our first Biferno script. We assume that both the Web server and Biferno have been correctly installed (see the "Biferno: Installation and Administration Guide" document). The example will help the reader familiarizing with the language basic syntax, the use of variables, functions and classes.
A Biferno script can be written using any text editor. To start, open a new text file and save it with the ".bfr" extension, e.g. "hello.bfr". It is best to insert the opening and closing tags for the Biferno code at this stage, before writing any code. Finally write a simple instruction between the tags:
<?
print("Hello world!")
?>
The file containing the newly created script must be stored in a directory where our web server can access it. The script can now be executed by requesting our Web server to serve the script file. This is done by pointing our browser to the URL that locates the script file on our Web server. If we have executed these steps correctly the browser window will return the following result:
Hello world!
For sake of simplicity we will refer to the content of the browser window as the output of the Biferno script. Notice that the actual output of a Biferno script is the HTML page produced by the script, which is returned by the Web server to the browser using the HTTP protocol.
In the example above we used the pre-defined print function that allows to
print (insert on the Biferno script output) the value of a variable, a
constant, or the result of an expression. Instead of the print keyword it is
possible to use the dollar symbol $ as in:
<?
$"Hello world!"
?>
Using the dollar symbol before and after a variable name, its value can also
be printed outside of the Biferno code delimiters. This is shown in the
following example, where the val parameter is passed to the "newpage.bfr"
page and takes the value of the myString variable, which is output on the
HTML page using the $ symbol set before and after the variable name. The
delimiter tags of the Biferno code are not used. The next row of code shows
an alternative manner of obtaining the same result using the print function.
<? myString = "Ciao" //String class variable initialization ?> <html> <body> <? print(myString) //Print the value of the myString variable $myString //Same effect as previous instruction ?> <br> <a href="newpage.bfr?val=$myString$">New Page</a> <a href="otherpage.bfr?val=<? print(myString) ?>">Other Page</a> </body> </html>
In this and the other examples that we have shown we also have introduced
comments. As in the C language, comment are introduced by the // string
(comment spanning a single rows) or enclosed between the /* and */ strings
(comment spanning multiple rows).
Whitespace and tab characters are ignored by the Biferno interpreted. They
play a role in visual formatting of the source code and make it more easily
readable. In our examples we will always use spaces to separate identifiers,
operators and values, tab characters to indent blocks of code reflecting the
code structure, and carriage return characters to indicate the end of a line
of code. The end of a line of code in Biferno can be also indicated by using
the ; character.
In the following chapters we will describe in more detail the various elements of the language (variables, operators, classes, functions, and so on).
![]() | Printing via the '$' character |
|---|---|
When two dollar characters ('
When a single ' |
Table of Contents
All Biferno identifiers, including names of variables, constants, functions, classes and language keywords, do not start with special characters, unlike other scripting languages. This design choice is possible because the Biferno code is always separated from the rest of the text on the page (HTML or other languages).
Biferno is case sensitive, i.e. uppercase characters are considered
different from lowercase characters. The identifiers myString and MyString
are different.
In Biferno every entity that can hold data has an identifier (its name), a type, a class, and a scope, which determines the code region in which a reference to the entity is legal. The scope is discussed in detail in Chapter 9, Applications and Variable Scope.
The "type" of an entity defines the entity to be a variable or a constant. A variable holds a value that can be modified at runtime by the program that is being executed. A constant has a value that can not be modified at runtime by the program that is being executed.
We have already seen an example of variable initialization. To declare a Biferno variable one can simply use its identifier followed by the assign operator (=), followed by a value or expression. The keyword var before the variable identifier can be used, but it is optional.
To declare a constant, the use of the const keyword before its name is
mandatory:
<?
var a = 1 // the var keyword is optional
const b = 1024
?>
The main characteristics of object oriented languages, including Biferno, is the use of the "object" and "class" concepts.
A class can be defined as the union of a data structure (a set of variables), called the “properties” of the class, and of the operations that can be executed on the properties, the “methods” of the class. Properties and methods of a class are referred to as the “members” of the class. An object is an entity that has the structure of the class it belongs to and contains its own values for each of its properties. An object is created in a program and then manipulated using the methods and properties defined by the class it belongs to. We can also say that an object is an instance of a class. All variables and constants in Biferno are objects and therefore instances of a class. In Biferno there is a direct correspondence between a variable and the object created when the variable is initialized. For sake of simplicity we will refer in the following indifferently to the variable or the object, as if they were the same entity.
What determines the class a variable belongs to? Usually the data type that is assigned to the variable, as in the following example:
<? a = "Hello" //String class variable b = 1 // Int class variable c = 1.5 // Double class variable d = true // Boolean class variable e = a // String class variable ?>
It is also possible to explicitly assign a class to a variable when the variable is declared. This is done by following the assign operator with the identifier of the class, followed by the value that is to be assigned to variable enclosed in parenthesis. This syntax is equivalent to calling the constructor method of the class (constructor methods are discussed in Chapter 11, User classes).
<? a = 12 // Integer class variable with value 12 b = string(a) // String class variable with value "12" c = double(b) // Double class variable with value 12 d = int(b) // Int class variable with value 12 e = boolean(c) // Boolean class variable with value true ?>
The following is also correct:
<?
f = double("1.936,27") // Double class variable
// with value 1936.27
?>
This implies that the double class constructor automatically attempts to
convert strings containing numbers to real numbers, using the default
separator characters "," and ".". We will see later that a number of
separator combinations can be used in Biferno. Notice that it is mandatory
to use the decimal point character as the comma separator in literal strings representing real numbers (as in: a = 1.5).
An alternative is the use of a similar technique, called “TypeCast”, which consists in following the assign operator with the class identifier before the value or expression to be assigned. Using typecasting, the last example can be rewritten as:
<? a = 12 b = (string)a c = (double)b d = (int)b e = (boolean)c f = (double)"1.936,27" ?>
Once initialized, a variable belongs to the same class during its entire
lifetime, except when the "curScript.Undef" method is used, as we will see in the
following.
If a variable belongs to a class, all methods and properties of that class
can be applied to the variable. Method and property identifiers must be
separated from the variable (or class) name by a colon. E.g., by writing
myString.length we invoke a property of the string class called length that
contains the value of the length (number of characters) of the string
contained in myString. Methods behave similarly to functions in procedural
programming language, such as the C language. Methods can accept one or more
parameters and are always followed by a pair of parentheses when invoked.
E.g., by writing myString.Encode() we are applying to the myString variable
the Encode method of the string class (the meaning of this method will be
defined later).
![]() | What class are you? |
|---|---|
If we are not certain of the class a variable belongs to, we can ask the
variable about the value of its |
If we need to know if a variable is defined or undefined, we can use the
curScript.IsDef predefined Biferno static method, with prototype:
boolean curScript.IsDef(string name)
This method requires a string called name as a parameter and returns the true value if a variable with identifier name exists, or false if no variable with identifier name exists:
<?
if (curScript.IsDef("myVar"))
print(myVar)
?>
The predefined static method curScript.Undef with prototype
void curScript.Undef(obj variable)
requires as a parameter an object identifier, i.e. a variable belonging to any class. The supplied object is made undefined and it will behave from now on as if it was have never defined. The Undef function returns void.
<?
curScript.Undef(myVar)
?>
Notice that the curScript.IsDef method, unlike curScript.Undef, does not take an object as
parameter, but rather a string containing the object name. This avoids an
error condition if the variable has never been defined. In the case of the
curScript.Undef method, an error condition should occur, because it is not legal to
invoke Undef on an undefined object. To validate the parameter to Undef, a
code fragment code similar to the following can be used:
<?
if (curScript.IsDef("myVar"))
curScript.Undef(myVar)
?>
A last remark: The Undef function cannot be used on "literal" variables or
on the result of an operation. The code fragments:
<?
curScript.Undef("foo") // error Err_IllegalUndef!
?>
and
<?
curScript.Undef(a + b) // error Err_IllegalUndef!
?>
both generate the Err_IllegalUndef error.
Table of Contents
This chapter illustrates several of the predefined classes that the language makes available to the programmer. Biferno offers a large number of primitive classes describing the main data types used in the language in addition to specific classes representing arrays, files, dates and times. Very specialized classes are also available to support Internet protocols and to interact with databases.
A detailed reference for all methods and properties of Biferno predefined classes can be found in the "Biferno: Reference Guide" document (see Chapter 2, Documentation).
As common in programming languages, Biferno implements classes supporting the most commonly used data types.
The boolean class describes the Boolean values (i.e. true or false) resulting from expressions of the Boolean logic.
The int class describes positive or negative integer numbers (natural numbers) with values ranging from -2,147,483,647 to 2,147,483,648 (4 byte representation).
Numeric values can also be written in hexadecimal notation as follows:
<?
a = 0xA8FF
?>
The unsigned class describes positive integer numbers with values ranging from 0 to 4,294,967,295 (4 byte representation).
The long describes positive or negative integer numbers with values ranging from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 (8 byte representation).
The double describes positive or negative real numbers with values ranging from 1.7E-308 to 1.7E+308 (8 byte representation).
All numerical classes support methods implementing the most commonly used mathematic and trigonometric functions.
The string class describes arbitrary length text strings. A string
is represented as a sequence of characters enclosed in single or double
quotes (e.g. "this is a string"). The string that contains no characters
is called the null string, and is represented by "" or ''. If a string
enclosed in double quotes contains the double quote character ", the
latter has to be escaped by using the \ character (backslash), as in the
string "<a href=\"page.bfr\">". Escaping double quotes is not
necessary if the string is enclosed in single quotes, e.g. '<a
href="page.bfr">' is correct. Similarly, if a string enclosed in
single quotes contains the single quote character ', the latter has to
be escaped by using a backslash. Escaping single quotes is not necessary
if the string is enclosed in double quotes. The special characters tab,
vertical tab, <CR> (carriage return) and <LF> (line feed)
can be represented in a string as "\t", "\v", "\r", "\n". The "\"
character itself must be escaped as "\\".
Finally, it is possible to write a string on multiple lines by using a backslash as the last character of the line before the line break (the line break inserted by our text editor will not appear in the resulting string). The same string can be written as
'string \ one'
or: 'string one'
The char class describes single characters. The read-only property
ascii of the char class contains the decimal ASCII code of the character
assigned to the object.
This class contains many generally useful methods for string manipulation derived from the ANSI standard libraries, as well as a couple of methods for the generation of random numbers. The ansi class is a "static" class, which means that only method invocation is allowed. It is not to possible to create variables of the ansi class.
The array class describes one-dimensional ordered lists of elements all belonging to the same class. We will see this class in further detail in Chapter 6, Arrays.
The file and folder classes allow creating, reading, and modifying files and directories. We will see these classes in further detail in Chapter 13, File Management.
The time class allows manipulating dates and times. We will see this class in further detail in Chapter 14, Date and Time Functionality.
The methods of these classes allow interaction with a database (often indicated with the DBMS acronym: DataBase Management System) via the SQL query language (SQL stands for Structured Query Language). The interface with the most common DBMS (Oracle, MySQL, PostgreSQL, etc.) is implemented via special-purpose native drivers or via ODBC. A number of native drivers are available and more are under development.
We will discuss these two classes in further detail in Chapter 15, Database Interaction.
When Biferno executes a script, a global variable named err belonging to
the error class is initialized. If a runtime error occurs, the err
variable contains precise information on the nature of the error itself.
We will describe the error class and error handling in Biferno in Chapter 16, Error Handling and Debugging.
Biferno implements a number of classes providing support for Internet protocols (HTTP, SMTP), for data transfer between Web pages and for client-server interaction. Here we will only touch upon the characteristics of these classes. In the following we will demonstrate their use within an application.
The multipart class describes data captured in a HTML form in the "multipart/form-data" encoding when a file is uploaded from a Web page. Chapter 18, Passing Parameters between Pages illustrates how to use properties and methods of this class.
The smtp class allows managing and sending email messages using a Biferno script. We will discuss in Chapter 19, E-mail Handling the use of methods of this class.
The header class describes the header of the HTTP protocol. The methods of this class support reading and manipulating fields in the HTTP header. Properties and methods of this class are analyzed in detail in Chapter 20, HTTP Protocol Interaction.
The httpPage class describes a Web page in the form used by the HTTP protocol during client-server communication. A HTTP page can be seen as a structured data packet composed by a header and a body. When a server sends such a packet in answer to a client request, the body of the packet contains the HTML code describing the content of the Web page, which will be visualized by the browser. In the query packet sent by the client to the server, the body can contain parameters transmitted via the HTTP protocol POST method. Properties and methods of this class will be analyzed in detail in Chapter 20, HTTP Protocol Interaction.
The request class describes a page request to the server. Its properties allow to obtain information such as the name of the requested file, its absolute path, which method was used for the request (POST or GET), the list of parameters (arguments) used in the invocation, etc. Properties and methods of this class will be analyzed in detail in Chapter 20, HTTP Protocol Interaction.
The client class allows to obtain information about the client, i.e. on the user that sent to the server a page request with extension ".bfr". Properties and methods of this class will be analyzed in detail in Chapter 20, HTTP Protocol Interaction.
The serverInfo class allows obtaining information on the Web
server, such as the application name (e.g. Apache, IIS), the
software version, the host domain name, the path of the server's root
directory etc.
A detailed reference for all properties of this class can be found in the "Biferno: Reference Guide" document (see Chapter 2, Documentation.
The cacheItem class describes a Biferno cache element. We will discuss this class in more detail in Chapter 22, Cache Management.
Table of Contents
An array is a one or multi dimensional list of elements (data, values, objects). All elements in an array are homogeneous, i.e. all belongs to the same class. This chapter describes how to create and manipulate arrays in Biferno using the array class.
Arrays are normally used to group elements of similar nature under a
unique identifier for easier retrieval of the corresponding values. In
Biferno, as in several other programming languages, an array element is
denoted by the array name followed by the element index in the array
enclosed between square brackets. Indices start from 1. The first element
of the array myArray is denoted by myArray[1]. Values of array
elements can be read, written and printed as one would do for regular
variables.
Strings can be used as array indices in Biferno to implement a kind of array called “associative array”. Using strings as indices can be useful when array elements are associated themselves to specific categories, such as names, cities, professions, dates, etc.
Biferno array are completely dynamic. It is not necessary to specify the size of an array (the number of its elements) when the array is declared. Whenever we add a new element to an array that was previously initialized, the array is expanded automatically to accommodate the new element. This example shows how to initialize and fill a vector of strings with three elements.
<?
myArray = array()
myArray[1] = "John Doe"
myArray[2] = "San Francisco"
myArray[3] = "Accountant"
?>
The first instruction creates an object of the array class. Initially, an empty array - with zero elements - is created. The following instructions assign values to the element of the array, implicitly expanding the array, which will finally contain three elements.
The number of elements of an array is stored in the dim property of the
array class. This is a read-only property and can not be used to modify the
array dimension . Adding or deleting array elements can be achieved by
using specific methods of the array class.
The class of the first elements that is assigned to an array (the string class in our example above) implicitly specifies the class of all elements of the array. In Biferno the elements of an array must all belong to the same class. An attempt to assign to an element a value of a different class will result in automatic conversion of the value to the class of all other elements. If this is not possible the attempt will generate an error. If we write:
<?
myArray[4] = 32
?>
the fourth element of the array will be of the string class and its value
will be the numeric string "32", the result of the implicit conversion from
integer to string. An attempt to assign a non-numeric string to an element
of an array of integers will result in the Err_IllegalTypeCast error
because it is not generally possible to convert non numeric strings into
numbers.
The mechanism that allows automatic conversion of a value from the class to which it naturally belongs into another class is called implicit TypeCast. This important language feature will be described in Chapter 7, Operators and Expressions.
The GetElemClass method of the array class can be used to query the class
of the elements of an array.
Once initialized, elements of an array can be modified by simply assigning them new values. The syntax is the same we used to initialize variables.
Two things can happen when a value is assigned to an array element by referencing it via a numeric index. If the element already exists, its value will be replaced by the value of the entity on the right of the assign operator. If the element does not exists, the array is expanded by a number of elements equal to the difference between its current dimension and the specified index, and the new value is assigned to the last element added.
<?
myArray = array()
myArray[1] = 10
myArray[5] = 50
?>
The first instruction of the script initializes the first element of the array with the integer value 10. The second instruction expands the array with 4 elements and assigns the value 50 to the fifth element. The resulting array contains 5 elements.
What is the value of elements 2, 3, and 4? It is not defined. Elements of an array that are not specifically assigned, but simply added by automatic expansion, have no defined value and any operation on them (other than assigning them a value) results in an error.
Revisiting the initialization rules, the first example of this paragraph can be rewritten as:
<?
myArray = array("John Doe", "San Francisco", "Accountant")
?>
Here we are implicitly creating the array and assigning values to its elements, starting from the first and moving on in ascending order.
This is an example of initialization of an associative array:
<?
myArray = array("Name":"John Doe","Residency":"San Francisco",
"Profession":"Accountant")
?>
The syntax requires to specify, for each element to be implicitly assigned,
the name of the index followed by the ":" character and by the
corresponding value.
Two elements can not have the same name. An attempt to insert an element
with the same name of an existing element results in the
Err_DuplicatedArrayElemName error.
Array elements can be referred to by using the relative numeric index, as
in myarray[1]. Alternatively, elements can be referred to by
using the string specified at initialization time, as in
myarray["Name"]. The Index method, with prototype:
int Index(string elementName)
can be used to query the numeric index associated to a given element name.
The method accepts a string and returns the position of the corresponding
index, if it exists, or otherwise zero. Therefore the Index method can be
used to decide if a string corresponds to an array index. In the following
example the indexName string is received by the script as an input
parameter and its validity (as an array index) is verified before the
corresponding element of the myarray is printed:
<?
if (myArray.Index(indexName))
print(myArray[indexName])
else
print("Index \"" + indexName + "\" does not exist")
?>
It is not legal to expand an associative array by assigning a value to an
element which is referenced by a non existing index. Consequently, the
following code to add a new element with index "E-mail" to the myarray
array is not correct:
<?
myArray["E-mail"] = "doe@mydomain.com" // ERROR!
?>
The code above produces an error if the element with index "E-mail" does not exist. The correct code is as follows:
<?
myArray[myArray.dim + 1] = "doe@mydomain.com"
// Expand and assign
myArray.name[myArray.dim] = "E-mail"
// Rename index
?>
The name property of the array class is an array of strings containing the
names of the associative indices of the array. The name property can be
written, but in practice it is more convenient to use the Add method as
discussed in the following section.
We have described how an array is automatically expanded when an element is
initialized that had a numeric index greater than the current size of the
array. Alternatively, the size of an array can be directly increased (or
decreased) by using the SetDim method of the array class, with prototype:
void SetDim(int newDim)
The newDim parameter is the new array size. In the following example we
will expand the myArray array by 3 elements and then we will resize it to
its original size:
<?
curDim = myArray.dim
myArray.SetDim(curDim + 3)
myArray.SetDim(curDim)
?>
Remember that the dim property is read only. If the array does not contain
initialized elements, the new elements are undefined because their class
membership can not be determined. To remove all elements of an array we can
write:
<?
myArray.SetDim(0)
?>
The Add method of the array class allows to append one or more elements to
an array and, at the same time, assign them a value and optionally a name
for the new index. This method has prototype:
void Add(nonames obj elementN...)
The elementN parameter followed by three dots represents a list of objects
of any class The “obj” class is a kind of "abstract class" of Biferno, as we
will see in Chapter 11, User classes. The nonames clause, which
allows to specify a name for the new indices of the array, will be
described in Chapter 10, Functions. The following code expands the
myArray array by one element and assignes to the new element the value of
the num variable.
<?
num = 12
myArray.Add(num)
?>
Notice that the class of the object we are appending to the array must be the same as (or can be implicitly converted into) the class of the array elements that have been already assigned, if any.
In the following example, we append two elements to an associative array, specifying index names:
<?
myArray = array("Name":"John", "Cognome":"Doe")
myArray.Add("Username":"jdoe", "Password":"biferno")
?>
As shown, the syntax is <name index>:<value>. If elements are
appended with index names that already exist in the array, an error is
generated.
The Insert method of the array class allows to insert one or more elements
into an array. The prototype of this method is:
void Insert(nonames int pos, obj elementN...)
The pos parameter represents the numeric index of the first element to
insert. The elementN parameter has the same meaning as in the Add method.
In the following example, we append two new elements to an array of
strings, starting from the second element:
<?
myArray = array("John", "Frank", "Simon")
myArray.Insert(2, "Alan", "Donald")
?>
After the call to the Insert method, the myArray element contains five
elements (in this order) with values: "John", "Alan", "Donald", "Frank",
"Simon".
To remove one or more elements of an array, we can use the Delete method of
the array class, with prototype:
void Delete(int start, int end)
The start and end parameters are the index of the first and last element,
respectively, of the element block to be removed. Passing only the start
element results in removing one element. The following example shows how to
use this method:
<?
myArray = array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
myArray.Delete(5) // Delete the fifth element
myArray.Delete(5, 7) // Delete elements 5, 6 and 7
?>
The two calls to the Delete method will remove a total of four elements.
Following deletion, the myArray array consists of six elements with integer
values 1, 2, 3, 4, 9, 10. To delete all elements of an array we can write
(alternatively to the use of the SetDim method):
<?
myArray.Delete(1, myArray.dim) // Delete all elements
?>
The most efficient method for deleting all elements of an array if the use
of the Reset method, as in:
<?
myArray.Reset()
?>
Notice that it is always possible to initialize again an array by calling the constructor of the array class. In the following example we initialize an array of strings and, subsequently, call again the constructor on the same variable name, this time passing real numbers as elements:
<?
myArray = array("a", "b", "c")
myArray = array(1.6, 2.3)
?>
What is special about this operation is that not only the original array is deleted and built again on the base of the new elements passed to the constructor, but the class of the elements themselves is changed (in the example above from string to double).
Arrays can be concatenated in Biferno using the addition operator +.
Biferno operators will be illustrated in Chapter 7, Operators and Expressions. As
far as arrays are concerned it is sufficient to note that, in the example
below, the array3 array, sum of array1 and array2, has five elements with
values "a", "b", "c", "d", "e".
<?
array1 = array("a", "b", "c")
array2 = array("d", "e")
array3 = array1 + array2
?>
The arrays to be added (concatenated) must have elements of the same class or of "compatible" classes (the class of the elements of the first array in the chain prevails). It is legal to sum an array of numeric elements to an array of strings (the implicit typecast maps numbers to strings) but it is not possible to do the contrary.
When associative arrays are concatenated, elements names must be different, or an error will occur.
With the SubArray method of the array class it is possible to create a new
array consisting of a subset of consecutive elements extracted from another
array. This method has prototype:
array SubArray(int start, int end)
The start and end parameters indicate, respectively, the index of the first
and last element to be extracted, as in:
<?
array1 = array("a", "b", "c", "d", "e", "f")
array2 = array1.SubArray(2, 3) // array2 is array(b, c)
?>
When the operation is performed on associative arrays, the subarray
resulting from the application of the SubArray method has the same index
names as the original array.
Array elements can belong to any class, even to the array class. This allows to build arrays of arrays, also called multidimensional arrays.
A common use of two-dimensional arrays is in the representation of matrices or a tables. This example shows how to create and print a table containing a list of major cities in some US states:
<?
states = array(
"Ohio" :array("Columbus", "Cleveland", "Cincinnati","Dayton"),
"California" :array("San Jose", "San Francisco", "Sacramento", "Oakland"),
"Texas" :array("Houston ", "Dallas", "San Antonio", "Austin"),
"Florida" :array("Miami", "Tampa"))
?>
<html>
<body>
<?
nState = states.dim
for (state = 1; state <= nState; state++)
{
stateName = states.name[state]
print("<p>Major cities in <b>" + stateName +
"</b>:</p>\n")
nCit = states[state].dim
print("<ul>\n")
for (cit = 1; cit <= nCit; cit++)
{
print("<li>" + states[state][cit] + "</li>\n")
}
print("</ul>\n")
}
?>
</body>
</html>
This script produces the following output:
Major cities in Ohio:
* Columbus
* Cleveland
* Cincinnati
* Dayton
Major cities in California:
* San Jose
* San Francisco
* Sacramento
* Oakland
Major cities in Texas:
* Houston
* Dallas
* San Antonio
* Austin
Major cities in Florida:
* Miami
* Tampa
Notice the use of visual formatting in the declaration of the states array,
which allows to write more easily readable code, and the use of the for
flow control instruction, similar to the C language instruction with the
same name. Flow control instructions will be discussed in the following.
The array class has a method for implicit conversion to a string. The resulting string provides a textual representation of the array structure.
<?
array1 = array("Red", "Green", "Blue")
a1 = (string)array1 // a1 is "array(Red,Green,Blue)"
array2 = array("Name":"John", "Surname":"Smith")
a2 = (string)array2 // a2 is "array(Name:John, Surname:Smith)"
?>
Sometimes it can be useful to order the elements of an array, e.g. to print
a number of strings in alphabetic order. The array class makes the Sort
method available with prototype:
void Sort(int mode=asc, string compareFunc, int alg)
The optional parameter mode specifies the sorting order. The value for
this parameter can be specified as the constant asc (ascending) or the
constant desc (descending). The default is to sort element in ascending
order (from the smallest to the largest). The optional parameter
compareFunc allows to specify a comparison function. An example is given
below. If the elements of an array belong to one of the primitive classes
(strings, numbers and Booleans), the sorting criteria are those of the
class the elements belong to.
<?
array1 = array(5, 1, 4, 2, 3, 6)
array1.Sort() // array1 is array(1, 2, 3, 4, 5, 6)
array2 = array("b", "f", "a", "d", "c", "e")
array2.Sort(desc) // array2 is array(f, e, d, c, b, a)
?>
The optional alg parameter can be used to specify the sorting algorithm
that should be used. The supported values are bubble and shell. The default
is bubble if the array has less than 20 elements, shell otherwise.
When an associative array is sorted, index names are still associated to
the same elements after the sorting is completed - index names "follow" the
element they identify. Notice that the Sort method acts directly on the
input object and, in general, will modify it ("side effect").
If a comparison function is specified by passing its name to the Sort
method in the compareFunc parameter, the comparison function must have the
following prototype:
boolean CompareFileSize(array theArray, int index1, int index2, int mode)
The function must return the true value if the elements of the array with
indices index1 and index2 must be swapped in the sorting, false otherwise.
The mode parameter, if used, takes automatically the value of the parameter
with the same name in the Sort method. The first parameter is the array to
be sorted. In the following example we show how to sort an array of files
in descending order on the base of their byte size.
<?
function boolean CompareFileSize(array *ar, int ind1, int ind2, int mode)
{
if (mode == array.desc)
return (ar[ind2].length > ar[ind1].length)
else
return (ar[ind2].length < ar[ind1].length)
}
fileArray = array()
fileArray[1] = file("image1.gif")
fileArray[2] = file("image2.gif")
fileArray[3] = file("image3.gif")
fileArray[4] = file("image4.gif")
fileArray.Sort(desc, "CompareFileSize")
?>
The theArray parameter is passed by reference ("*" character in front of
the parameter name), and therefore could be modified within the
CompareFileSize function (see Chapter 10, Functions for an
explanation of passing parameters by reference). Here the parameter is
passed by reference only for performance reasons and should not be modified
within the function. To prevent a possible modification the array is locked
during the sorting operation. An attempt to modify it by the compareFunc
function would generate the Err_ObjectIsLocked error.
To invert the order of the elements of an array the Reverse method of the
array class can be used. This method has no parameters and does not return
any values:
<?
myArray = array(1, 2, 3)
myArray.Reverse() // The array is now array(3, 2, 1)
?>
The Find method allows to decide if an array contains an element with a
given value. If an element with the given value is found, the method
returns the index of the element, otherwise the method returns zero. This
method has prototype:
int Find(obj element)
An example is:
<?
myArray = array("Red", "Green", "Blue")
index = myArray.Find("Green") // index is 2
index = myArray.Find("Yellow") // index is 0 (element not found)
?>
The Min and Max methods return the index of the smallest and largest
element in an array, respectively. The criterion used to compare elements
depends on the class of the elements. An example is:
<?
array1 = array("John", "William", "Albert")
index_min = array1.Min() // index_min is 3
index_max = array1.Max() // index_max is 2
array2 = array(0.6, 12.4, 4.1)
index_min = array2.Min() // index_min is 1
index_max = array2.Max() // index_max is 2
?>
These methods are usually applied only to arrays containing string or numerical elements.
Table of Contents
We have seen in the previous sections a couple of operator examples. The "="
symbol represents the assignment operator, which can be used to assign
values to variables. The dot operator (.) is used to separate methods and
properties from variable or class identifiers. In Biferno, as in many other
programming language, a large number of operators is available. Operators
belong to three main categories: arithmetic, logical and bit-wise (acting on
individual bits).
![]() | Starting a new line within an expression |
|---|---|
Before moving on to the description of operators let's clarify how to start a new
line in the middle of an expression. The backslash character '
<?
// the following expression is correct
a = 3 + \
5
// the following expression will generate a syntax error
a = 3 +
5
?>
In the following we will often use the backslash character within an expression. Concersely, we recall that it is legal to start a new line within a Biferno string. If we insert a backslash character in front of the new line, the effect is not to include the newline character sequence in the string itself (the newline character sequence is, depending on the platform, either CR, LF or CRLF). |
Arithmetic operators implement the elementary sum, difference, multiplication, division, and modulus functions. These operations are usually performed on variables of numerical classes.
The sum operator, if applied to variables of the string class, is
equivalent to the concatenation operator. In the following example we show
how to concatenate string class variables with numerical class variables
using the + operator:
<?
n = 4 // the n variable is of the int class and has value 4
myString = "The n variable is " + n
?>
After the concatenation operation the value of the myString variable is
"The n variable is 4". This implies that a string has been automatically
derived from the integer variable n in order to perform the concatenation.
The modulus operator (%) returns the remainder of the division of two
numbers. The increment (++) and decrement (--) unary operators provide a
short way to denote the addition or the subtraction of one (the number one)
to their argument. The increment operator was introduced in the previous
chapter when we used the for flow control instruction. The i++ expression
is equivalent to i = i + 1. The increment and decrement operators can be
also used in front of a variable identifier. In this case the variable is
incremented (or decremented) before the value of the variable is returned,
which makes a difference whenever the variable is part of an expression.
Consider the following example:
<?
a = 1
b = a++ // b is 1, a is 2
c = ++a // c is 3, a is 3
?>
The second instructions assigns to b the value of a and then increments a
by 1. The third instructions increments a by 1 and then assigns the value
of a to c.
Table 7.1. Arithmetic Operators
| Operator | Operation Performed |
|---|---|
| + | Sum or concatenation |
| - | Subtraction |
| * | Multiplication |
| / | Division |
| % | Modulus |
| ++ | Increment |
| -- | Decrement |
Table 7.1, “Arithmetic Operators” lists all arithmetic operators and
their function. The first four operators in Table 7.1, “Arithmetic Operators” can be combined using the assignment
operator = to simplify code writing. E.g. we can use the shortened form a += 1 instead of a = a + 1 (the combined operators -=, *=, /= can be used
in a similar fashion).
Logical operators compare two values and return one of the two Boolean
values true or false. In Biferno, as in other programming languages, the
numerical value 0 (zero) is considered equivalent to false, while any
non-zero numerical value is considered true. Similarly, the null string
(the zero-length string represented by "") is considered false, while any
string of non-zero length is considered true.
Table 7.2 lists all logical operators and their function.
Table 7.2. Logical Operators
| Operator | Operation Performed |
|---|---|
| < | Less than |
| > | Greater than |
| <= | Less or equal than |
| >= | Greater or equal than |
| == | Is equal to |
| != | Is not equal to |
| && | And |
| || | Or |
| ! | Not |
| NOT | Not |
The XOR (exclusive or) logical operator is not predefined in Biferno.
Notice that the XOR operator can be implemented by using other logical
operators. In fact, if a and b contain Boolean values, the operation a XOR
b can be written as: !a && b || a && !b.
Readers new to computer programming languages should take note of the
difference between the assignment (=) and the equal (==) operators,
particularly in the case of the if control instruction, where it is
possible to execute an assignment operation within the conditional test. We
will discuss this in following chapters.
Bit-wise operators are different from logical operators in that they do not act on the logical value of their argument, but rather on the individual bits representing that value. A bit is a binary digit and can assume the values 1 or 0. Any numerical value can be represented in binary form as a sequence of bits.
This example uses the binary representation (also called base 2
representation) of unsigned integer numbers. To see how bit-wise operators
act, consider the expression: a = 5 & 6. The & character represents
the AND bit-wise operator. The binary representation of 5 is 0101 (or 2^2 +
2^0, i.e. 4 + 1). The binary representation of 6 is 0110. When the AND bit-
wise operator is applied to this two numbers, the AND logical operator is
applied to all corresponding bits in the binary representation of the two
numbers. The result is 0100, i.e. the number 4.
Table 7.3. Arithmetic Operators
| Operator | Operation Performed |
|---|---|
| & | Bit-Wise and |
| | | Bit-Wise or |
| >> | Shift right every bit |
| << | Shift left every bit |
Table 7.3, “Arithmetic Operators” lists all bit-wise operators and their function.
In Biferno an expression is represented by one or more identifiers or literal constants, possibly separated by operators.
Table 7.4. Arithmetic Operators
| * / % | Highest precedence |
| + - | |
| << >> | |
| < > <= >= | |
| == != | |
| & | |
| | | |
| && || | |
| = | Lowest precedence |
The value of an expression is given by the result of the operators applied
to the identifiers. The value of an expression can be null. An expression
can be situated on both sides of an assignment. When it is on the left side
(before the = operator), the result of the expression typically decides
where the result of the expression following the = operator will be
stored. Expressions are normally computed from the left to the right,
except when operator precedence rules dictate otherwise. If any other
order in the execution of operations within an expression is desired,
parentheses should be used.
When variables are used within expressions, one must take special care in the classes the variables belong to. This is important in order to correctly foresee the class of the result of the computation, to avoid errors due to the impossibility of implicitly converting values from one class to another. Notice also that some operators can be applied only to certain classes.
Table 7.4, “Arithmetic Operators” shows operator precedence rules in Biferno, sorted by precedence in descending order (from operators with higher precedence, i.e. applied first, to operators with lower precedence).
We have mentioned that in some cases the value of a variable (or constant or literal) is automatically converted from his primitive class into a new one during an assignment operation.
This automatic mechanism is called implicit type casting. Type casting is implemented by a series of internal procedure within the primitive Biferno classes, which operate a transparent on-the-fly conversion according to well-defined precedence rules (these procedures can be also applied to user-defined classes).
The primitive Biferno classes are boolean, int, unsigned, long, double, string and char.
The implicit type mechanism casting relieves the programmer, when possible, from the specification of the class a variable has to be converted into. We can write:
<?
a = 1 // a is of the int class
b = "sun" // b is of the string class
c = b + a // c is of the string class and its value is "sun1"
?>
The last line in the example above can also be written as follows using explicit type casting:
c = b + (string)a
Table 7.5. Precedence of Primitive Classes for Implicit Type Casting
| string | Highest precedence |
| double | |
| long | |
| unsigned | |
| int | |
| char | |
| boolean | Lowest precedence |
Table 7.5, “Precedence of Primitive Classes for Implicit Type Casting” lists precedence rules for Biferno implicit
classes for type casting operations. As seen from the table, the string
class has precedence on numerical classes and Booleans, and thus an
expression involving concatenation of numbers and strings always results in
a string, regardless of the ordering of terms of the expression. The
precedence rules hold for all operators except for the logical operators
&&, ||, == and !=, for which the Boolean class has highest
precedence.
Some conversions (implicit or explicit) between primitive classes are not possible. E.g. as string can be converted into a number only if it contains no characters other than numerical, separators and possibly a sign character. A char class variable, i.e. a string constituted by a single character, is converted in a number only of the corresponding character is a numerical character. This example will help understanding the mechanism:
<?
a = "20" + 1 // a is of the string class and its value is "201"
b = (int)"20" + 1 // b is of the int class and its value is 21
c = 1 + (char)"5" // c is of the int class and its value is 6
d = (int)"sun" // ERROR! (Err_IllegalTypeCast)
?>
When Booleans are implicitly converted into strings, the false value is
converted into the empty string "". Notice that the Boolean value of the
empty string is false. There are no predefined precedence rules for Biferno
non-primitive classes and user- defined classes (see Chapter 11, User classes). When using these classes, type casting must
always be performed explicitly, and care must be taken that it is possible
to convert the value of the object be type cast into the target class.
Table of Contents
Control structures in a programming language allow to execute blocks of code depending on certain conditions, typically expressed as the Boolean result of an expression. Control structures also allow repeated execution of blocks of code without the need for repeating the actual code. This chapter describes the implementation and use of such structures in Biferno.
The if instruction has the following syntax:
if (expression)
{
execute block 1
}
else
{
execute block 2
}
The if instruction executes the code block between curly brackets if the
expression between parentheses returns true. If the expression returns
false, code execution will resume from the instruction after the closing
curly bracket, or from the next code line if the block consists of one line
(brackets around a one-line code block are optional).
The code block after the if instruction can be optionally followed by the
else keyword, followed in turn by a code block. The second code block will
be executed if the conditional expression returns false. The if instruction
corresponds to the natural language conditional sentence: "If this is true,
do this, else do that".
The else keyword can also be followed by another if instruction. In this
case the code that follows will be executed only if the conditional
expression in the second if statement returns true.
The following example clarifies the concept.
<?
if (a == 0)
{
print("Warning! Null data.")
}
else if (a < 1 || a > 100)
{
print("Error! Values must be between 1 and 100.")
}
else
{
// Process value of variable a
}
?>
Notice how the condition expressed by the first if is more restrictive than
the one of the second. Also, the order of the two statements can not be
inverted, otherwise the code block for the case a == 0 will never be
executed.
Using a curly bracket to delimit a code block consisting of a single line
is optional. Biferno also features the ? ternary operator, also known from
the C language. This operator provides a concise syntax to specify simple
if-else instructions:
(expression 1) ? (expression 2) : (expression 3)
An example is:
x = (a > b) ? a : b
This is equivalent to:
if (a > b)
x = a
else
x = b
The code assigns to the x variable the largest of the values of the a and b
variables.
The if instruction allows to concatenate a sequence of logical tests by
using the if- else if-else construct. This technique can produce code that
is difficult to read and error-prone when it is necessary to execute a
different block of code for each of the many values that a variable can
assume. In this case is often preferable to use the switch instruction with
the following syntax:
switch (main expression)
{
case expression_1:
execute block 1
break
case expression_2:
execute block 2
break
...
case expression_n:
execute block n
break
default:
execute default block
}
The main expression is calculated first and its value is compared with the
values of the expressions following the case instruction, in the order in
which such expressions are written. If the value of the main expression is
equal to the value of one of these expressions, the code block between the
semicolon on the expression line and the end of the entire switch
instruction (delimited by the closing curly bracket) is executed. All
following case instructions are ignored. Limiting the execution to the
single code block corresponding to the comparison expression is possible by
ending the block with the break instruction, which causes execution to
resume with the first instruction following the switch statement.
If the comparison between the result of the main expression and the results
of the case expressions yields no match, no code block is executed, unless
the switch statement has a default clause (usually the last) followed by a
code block. This code block will be executed if the main expression does
not match any case expression. This mechanism is similar to the last else
instruction of the if-else if-else construct: if none of the logical tests
is true, then execute a default code block.
The following sample script translates and prints the names of the days of
the week from Italian into English. The script assumes that the day
variable of the string class has been initialized, e.g. by user input
(passing parameters to a Biferno script is discussed in Chapter 18, Passing Parameters between Pages).
<?
switch (day)
{
case "Lunedě":
print("Monday")
break
case "Martedě":
print("Tuesday")
break
case "Mercoledě":
print("Wednesday")
break
case "Giovedě":
print("Thursday")
break
case "Venerdě":
print("Friday")
break
case "Sabato":
print("Saturday")
break
case "Domenica":
print("Sunday")
break
default:
print("Error! The string does not \
contain the name of a day of the week")
}
?>
Expressions following the case instructions must be static constants
properties of a class, or literal expressions. They can never be object (of type var or const).
Loops allow to repeat the execution of a code block depending on a logical
condition. All iterative control loop instructions present in other
languages are implemented in Biferno: for, while and do-while. The
fundamental difference between a for and a while loop is in the way the
number of iterations is defined and controlled. Usually, for loops are used
when the number of iterations to be executed can be determined a priori.
while loops are used when the number of iterations to be executed can not
be determined in advance, but depends on the value of one or more logical
conditions.
The for instruction has the following base syntax:
for (initial value; control condition; increment)
{
code block to be repeated
}
The instructions between round brackets usually consist in the initialization of an integer index, followed by an expression that must be verified (i.e. assume the true value) to allow continuation of the loop execution, followed by an expression that increments (or decrements) the index value.
The following example shows a simple for loop that calculates the sum of
the integers between 1 and 10.
<?
sum = 0
for (i = 1; i <= 10; i++)
{
sum += i
}
?>
Outside of the loop the integer variable sum is initialized to 0. The loop
index i is initialized to 1. The control condition verifies if i is less or
equal to 10 to continue the loop. With every iteration of the loop, the
current value of the i index is added to the sum variable. At the end of
the loop execution the value of the sum variable will be 55, i.e. we have
executed the following expression:
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = 55.
Notice that after the last iteration of the loop the value of the i index
will be 11, because this is the first value that causes the control
condition i <= 10 to assume the value false, which stops the loop.
More than one element can be specified in a single for instruction for the
initial value and the increment. We can write:
<?
for (i = 1, j = 10; j > 0; i += j, j--)
{
print(i + ":" + j)
if (j > 1)
print (", ")
}
?>
This script produces the following result:
1:10, 11:9, 20:8, 28:7, 35:6, 41:5, 46:4, 50:3, 53:2, 55:1
The while instruction has the following syntax:
while (control condition)
{
code block to be repeated
}
The control condition between round brackets must be verified (i.e. assume
the true value) to allow execution of the code block between curly
brackets.
The value of the expression implementing the control condition is computed
before each repetition of the loop. This implies that the loop code block
is never executed if the expression is initially false.
For the same reason the loop code block must contain instructions that
modify the value of the expression used as the control condition (or the
break instruction, as we will see), otherwise the exit condition might
never become true, producing an infinite loop (in Biferno the execution of
a script is subject to a time limitation, after which it is forcibly
stopped).
A simple example of a while loop printing the integer numbers from 1 to 10
is:
<?
i = 1
while (i <= 10)
{
print(i + "<br>\n")
i++
}
?>
It is easy to see that, unlikely the for loop, the i variable is
initialized outside of the loop and incremented inside the loop block code.
In this way, when the i variable assumes the value 11, the condition i <= 10 is false and the loop is interrupted.
The do-while instruction has the following syntax:
do
{
code block to be repeated
}
while(control condition)
The fundamental difference between the do-while and the while instructions
is that in the do-while the control of the condition is performed at the
end of each repetition. This implies that the code block to be repeated is
always executed at least once.
The use of the break instruction to interrupt the execution of a code block
has been seen with respect to the switch instruction. In general, the break
instruction can be used to forcibly exit a loop and continue the execution
of the code starting from the first instruction after the loop block.
Sometimes it can be necessary to interrupt the execution of a script before
all its instructions have been executed. In Biferno this is realized via
the stop and exit instructions. The two instructions are different. The
exit instruction interrupts the execution of the entire script. The stop
instruction interrupts the execution of the ".bfr" file which is being
processed by the server when the stop instruction is executed. If the
current file has been included in another Biferno file using the include
instruction (see the Section 8, “The Include Instruction”), processing will resume
with the instruction following the include directive in the including file.
The continue instruction can be used to interrupt the execution of the
current iteration of a loop. This instruction immediately interrupts
execution of the current iteration and restarts execution from the first
instruction of the loop. If the current iteration is the last one,
execution resumes from the instruction following the loop code block that
has called the continue instruction. Execution of the continue instruction
outside of a loop generates the Err_IllegalUseOfKeyword error.
This instruction is actually a predefined Biferno function that takes a
file path as a parameter. When this instruction is executed in a script,
execution resumes with the first instruction of the included file. If the
included file is a Biferno script, its instructions are executed as if they
would have been contained in the including file. When execution of the
included file terminates, processing continues with the first instruction
following the include instruction in the including script. For this reason,
if a file to be included contains Biferno code, the code must always be
delimited by the <? and ?> tags as if the code would be in a stand
alone script.
To better understand the include function we show an example of its use
within a for loop. Assume we have written three files to be included as
follows. The "include1.bfr" file contains the following code:
<?
s = "include1" // initialize s variable of class string
?>
The "include2.bfr" file contains the following code:
<?
s += " include2" // concatenate a string with variable s
?>
The "include3.bfr" file contains the following code:
<?
s += " include3" // concatenate another string with variable s
?>
Our main script contains a loop that includes all three files in sequence. The name of the file to be included at each iteration is constructed using the loop index.
<?
for (i = 1; i <= 3; i++)
{
include("include" + i + ".bfr")
}
?>
When execution of this script terminates, the s variable has the value: "include1 include2 include3".
This example shows how the include function implements branching in the
execution of a script. The use of this function avoids the repetition of
code blocks that have to be executed many times in different scripts.
This instruction allows to stop execution of a script and to resume it in a
different point within the same script or code block within a single file.
The goto instruction can not be used to jump to a given position in a file
included via the include instruction. The goto instruction has the
following syntax:
goto label
where “label” identifies the code line where processing must resume. To label
a code line we write the label name followed by the ":" character
(semicolon). A simple example is the following script that prints the
integer numbers from 1 to 10 separated by a space:
<?
i = 1
start:
if (i > 10)
goto end
print(i++ + " ")
goto start
end:
?>
When the goto start instruction is executed, the execution stops and is
resumed with the instruction following the start label, i.e. the if
instruction. Similarly, the goto end instruction causes a jump to the line
following the end label, i.e. the end of the script. It should be apparent
that this script can be written in a much more elegant and natural way
using the for loop.
It has been proved that every program containing goto instructions can be
rewritten using only the fundamental control structures. Moreover, the use
of the goto instruction makes it difficult to understand program structure.
We advise to avoid using this instruction, which has been implemented in
Biferno only for reasons of completeness.
Usually Biferno scripts are executed concurrently, i.e. it is possible that two or more users request the same script to the server and that the scripts is executed in parallel by different Biferno threads. Biferno is a multi-threaded application, i.e. capable to start multiple execution instances within a single process.
The default behavior can be avoided if necessary by isolating code segments that should never be executed concurrently by multiple users.
This is accomplished in Biferno by using the lock and unlock instructions.
The script code enclosed by the lock and unlock is executed by Biferno for
a single user at a time. All other execution requests relative to that code
segment are queued and will be executed one by one in sequence.
The lock instruction is incremental in that it can be used multiple times
within the same script, as long as there is a matching unlock instruction
for every lock instruction. In any case, to avoid server blocking, Biferno
calls automatically the unlock instruction for every lock that is still
unbalanced when the script execution terminates. The lock and unlock
instructions are often used to preserve data integrity when accessing files
or database in read/write mode.
Table of Contents
This Chapter introduces the concept of Biferno "application" and discusses how to determine the scope of a variable, i.e. its region of validity.
A Biferno script, i.e. a text file with ".bfr" extension containing Biferno code and, optionally, HTML code, can implement stand-alone functionality or be part of a very complex Web site with hundreds of interlinked pages.
As far as the Web server is concerned, a Biferno script is always identified by a URL, a string that represents the Web location of an internet resource. E.g. the string "http://www.mysite.com/information/index.bfr" identifies a Biferno script called "index.bfr" in the "information" directory of the Web site found at address "www.mysite.com". In this case the path of the "index.bfr" file is relative to the site root (i.e. relative to the absolute path of the directory containing the site) as defined in the configuration of the Web server.
In Biferno a number of script contained in a directory (and possibly in its subdirectories) can be identified as belonging to the same "application".
To define a Biferno application a file called "Biferno.config.bfr" must be created in the root directory of the application. This file contains the values of some standard parameters of the application (called "preferences") and additionally may define some more environment parameters, implemented via the use of application scope variables.
The "Biferno.config.bfr" must contain at least the following line:
<?
APPLICATION_NAME = "MyApplicationName"
?>
Other configuration preferences that can be used in the "Biferno.config.bfr" file are described in the Section 2, “Application Variables”. The configuration file "Biferno.config.bfr" is interpreted and executed by Biferno once, usually when the server receives the first request for a script that belongs to the application (or to a subapplication, as we will see soon).
The "Biferno.config.bfr" file is protected. For security reasons it is not possible to execute the code contained in this file by requesting execution by directly typing the corresponding URL in a browser.
![]() | File Protection |
|---|---|
A script can be protected by assigning either the ".x.bfr" or the ".s.bfr" extension to the corresponding file. All files with these special extensions can not be executed by requesting the corresponding URL via a browser, but can always be executed by other scripts via the include instruction. |
While it is not necessary to create an application to execute a Biferno script, it is important to understand how a script is always part of an application.
When a client requests a file with extension ".bfr", Biferno looks for a "Biferno.config.bfr" file in the same directory of the requested file. If no configuration file is found, Biferno traverses the directory hierarchy upwards (until either the disk root or the Biferno home directory is reached) looking for a directory containing a file called Biferno.config.
If a configuration file is found in one of the folders, the search stops and the requested file is associated to the application of the directory in which the configuration file was found. If no other files belonging to the application had been previously requested, the Biferno.config script is executed and the application is initialized.
The Biferno home is:
the folder [startup disk]:BifernoHome on MacOS classic
the /home/BifernoHome directory on Linux
the /Users/BifernoHome directory on MacOSX
the directory specified by the user at installation time on Windows (default: [Program files]\Biferno\BifernoHome).
In a script the path of the Biferno home directory can be obtained by
calling the predefined property biferno.home.
If no configuration file is found no new application is initialized and the
requested script is considered part of an application called "BifernoMain".
The relative configuration file can always be found in the Biferno home
directory. The BifernoMain application is automatically initialized when
Biferno starts and is associated to the directory identified by
biferno.home.
This mechanism leads to the concept of subapplication and application inheritance. If we create a configuration file in a subdirectory of an application we have created a subapplication. Subapplications inherit as default the preferences of the parent application (the parent configuration file is executed before the child application configuration file). All applications running on the same Web server are subapplications of the "BifernoMain" application.
The application search mechanism explained above is performed only the first time a file is executed. In successive executions, the file path is directly associated to the application it belongs to for optimization reasons.
A list of all applications active at a certain time can be obtained with
the predefined property biferno.applications, which returns an array of
strings containing the names of all active applications.
![]() | How is a Biferno application started? |
|---|---|
To start an application when the Web server is started, without waiting for the first user click, it is possible to use a code line similar to the following in the "Biferno.config.bfr" file of the Biferno Main" application:
curApp.RegisterNewApp(application_name, root_path)
Application_name is the name of the application we want to initialize
(defined by the An example is:
curApp.RegisterNewApp("MyWebSite", "file://C/MySite/")
|
An application variable is a variable whose value is known and modifiable by all scripts belonging to a single Biferno application. The variable has the same value for all site users (clients). The scope of an application variable is the entire application. It is necessary and useful to use application variables to implement parameters whose value is associated to overall aspects of our application and influences the behavior of all its scripts. Notice that application variables have the same value for all users and therefore can not be used to pass parameters that are user-specific, such as search strings.
As a simple but significant example assume we want to define a single
background color for all pages of a Web site. This can be obtained in other
ways, e.g. by defining the background color for the HTML
body tag in an external style sheet, but also by using a
Biferno variable to store a single value for all our scripts. First, we
have to add a parameter to the "Biferno.config.bfr" configuration file of
our application (the precise meaning and use of standard parameters is
discussed in the following). This can be accomplished by adding the
following line:
application body_background = "#e3e4fa"
It is necessary to use the application identifier to specify an application
variable when the variable is defined. While this allows to define
application variables in any Biferno script, the most appropriate place for
such definitions is the application configuration file. The variable
identifier body_background is now defined in all scripts that are located
in the directory of the "Biferno.config.bfr" file or in one of its
subdirectories. To use the value of the variable within our HTML code we
can write:
<body bgcolor="$application body_background$">
Using this method the value assigned to the body_background variable in the configuration file can be changed to modify the look of all pages of our site.
![]() | When should the scope identifier be used? |
|---|---|
The explicit use of the scope identifier is mandatory:
The scope identifier is optional in the main body of a script when an already initialized variable is referenced. It is good practice to always use a scope identifier, except for local variables, as it increases the readability of the code. |
In the following example we show how the standard content of a
configuration file of a Biferno application looks like, with the most
commonly used configuration variables. The scope identifier in front of
variable names has been omitted. This is possible because the default scope
for variable defined within the "Biferno.config.bfr" is application.
<?
APPLICATION_NAME = "BifernoMain"; /* Name of the application */
SESSION = false; /* Use session variables? */
SESSION_TIMEOUT = 15; /* Timeouts for session users (minutes) */
MAX_ERRFILES = 2048; /* Max error files in folder ERRORS_FOLDER */
DEVELOPER_IP = "*,local"; /* IPs of developers */
ERRORS_FOLDER = ""; /* Folder for error files */
ERROR_PAGE = ""; /* Page error for non-developer users */
ADMIN_PASSWORD = ""; /* Password for bifernoadmin */
object.Hide(ADMIN_PASSWORD);
ADMIN_IP = ""; /* IPs allowed to access bifernoadmin */
ADMIN_PROTOCOL = "https"; /* Protocols allowed by bifernoadmin */
FILE_NOT_FOUND_PAGE = ""; /* Page to serve if file not found */
CACHE = false; /* Cache files in memory? */
TIMEOUT = 2; /* Scripts Timeout (minutes) */
HEADER = ""; /* Header file path */
FOOTER = ""; /* Footer file path */
ACCESS_CONTROL = ""; /* Security check file */
ACCESS_CONTROL_NOACCESS = ""; /* Security no access file */
ACCESS_CONTROL_REALM = ""; /* Security realm name */
DATE_FORMAT = "d-m-y h:m:s"; /* Default format for dates */
WEBMASTER = "webmaster@mysite.com"; /* WebMaster */
MAIL_HOST = "smtp@mysite.com"; /* Smtp Mail Server Host */
NOTIFY_MAIL_ERR = false; /* Send mail to webmaster if error */
DECIMAL_SEP = ","; /* Decimal separator */
THOUSAND_SEP = "."; /* Thousand separator */
SEARCH_AND = "&"; /* Default search AND operator */
SEARCH_OR = "|"; /* Default search OR operator */
SEARCH_NOT = "!"; /* Default search NOT operator */
SEARCH_WILD = "*"; /* Default search WILD char */
?>
In the following we explain some of the variables (see Appendix C for the full variable list).
The APPLICATION_NAME variable (string class) contains the name of the
Biferno application identified by this configuration file. The application
name can not contain the \ character and therefore none of the special
characters (\t, \v, \r, \n) discussed in the description of the string
class. Furthermore, the application name must be a literal (constant)
string. We can not write:
appName = "MyApp"
APPLICATION_NAME = appName
The above code generates the Err_BadSyntaxInApplicationName error. This
parameter is the only parameter needed to uniquely identify an application
(and the only one to impose the above restrictions). All other parameters
we mention in the following (see Appendix 3, Complete Preferences Listing for a
complete listing) can be omitted, in which case they will inherit the value
they have in the parent application.
The CACHE variable (Boolean class) determines if a script is cached in
volatile memory on the server. The default value for this parameter is
false (see Chapter 22, Cache Management for the details).
The DECIMAL_SEP and THOUSAND_SEP variables (string class) specify the
separation character used for decimals and thousands for our application.
Notice that when assigning a numerical value to a variable a dot has to be
used as the decimal separator. The default values for these parameters
depend on the locale settings of the machine where Biferno has been installed.
The SESSION variable (Boolean class) specifies if the use of session
variables in our application is allowed (the meaning and use of these
variables is explained in the following). The default value for this
parameter is false.
The SESSION_TIMEOUT variable (int class) specifies the lifetime of session
variables in minutes, i.e. the maximum time that such variables can exist
unused after creation before being deleted. The default value for this
parameter is 15 and the minimum value is 2.
The HEADER and FOOTER variables
(string class) can contain a file path that will be always prepended
(header) or appended (footer) to all scripts in our application.
The DEVELOPER_IP variable (string class) contains one or more IP addresses
(when the wildcard character * is used instead of an octet). The addresses
specify to Biferno which machines are allowed to receive detailed
information on variable state if a script generates an error. Typically
this variable contains the IP address of the machine where our application
is being developed. When an error occurs, all users receive a simple
message containing the error code and an invitation to email an error
report to the webmaster, whereas the developer needs much more information,
such as variable values. Notice that full error descriptions as made
available to developers are also stored in log files in a directory
specified by the ERRORS_FOLDER application variable. The protection
mechanism also avoids security issues when variable values in a script that
generates an error contain sensitive data such as passwords or credit card
numbers that should be kept confidential.
Biferno also allows developers to debug an application by connecting from
any machine (not necessarily one with IP address stored in the DEVELOPER_IP
variable). To gain full access the developer has to connect to the
bifernoadmin login page
("http://www.mysite.com/myapplicationPath/bifernoadmin/") and type in the
password (that has to match the ADMIN_PASSWORD variable). If the
administrator login succeeds, full debugging capability is available for
that connection from any IP (see the "Biferno: bifernoadmin Guide" document for details).
If suitable values have been assigned to the MAIL_HOST (set to an email
server address), NOTIFY_MAIL_ERR (set to true) and WEBMASTER (set to the
email address of the individual to be notified), the complete error screen
will be automatically sent via email to the webmaster with no need for user
intervention. The user will be displayed the error page or, if no error
page is defined, a message containing the error string "An error occurred
while processing a script. The webmaster has been notified".
The local keyword in the DEVELOPER_IP variable denotes
that local scripts (see Appendix 2, Executing and Scheduling Local Scripts) are enabled to display
the complete Biferno error screen (tipically on the system console).
The ERRORS_FOLDER variable (string class) contains the path of a directory
where error logs are stored. If this variable is not declared or does not
contain a valid path, no logs will be stored.
The MAX_ERRFILES variable (int class) specifies the maximum number of error
files that can be stored in the directory defined by the ERRORS_FOLDER
variable. The default value of this variable is 2048.
The WEBMASTER variable (string class) specifies the email address of the
staff member responsible for application support. This address is also used
in the error message that invites the user to submit an error report to the
webmaster. The default value is "webmaster@yoursite.com".
The TIMEOUT variable (int class) specifies the maximum processing time (in
minutes) that the server will wait for any Biferno script to complete. The
default value is 3. When non-zero, the timeout can not be less than 2
minutes. This mechanism allows to avoid situations in which a script is
caught in an infinite loop and never terminates. If the timeout is set to
zero, all scripts belonging to the application have no time limit. This is
dangerous because infinite loops in scripts can not be prevented.
The remaining variables will be described in the following chapters when the relevant aspects of the language will be discussed.
To modify an application variable, the Biferno.config.bfr file needs to be modified. After this modification the application has to be reloaded to force processing of the configuration file, which happens only when the first application script is executed.
![]() | Modifying script timeout |
|---|---|
It is sometimes necessary to implement scripts that may take a long time
to complete, e.g. to execute database backup or update procedures. In
these cases the timeout can be changed for a single script modifying the
value of the curScript.timeout = t where t is a parameter of class unsigned. Setting the property to a value of 0 (zero) removes the timeout limit. Removing the timeout limit is dangerous because infinite loops in a script can no longer be prevented.
Examining the value of the |
The reload operation reinitializes an application so that the
Biferno.config.bfr file is executed again at next user
click. A reload also reinitializes all application variables. Similarly, a
flush operation reloads all files in the application cache (see Chapter 22, Cache Management).
A reload or flush can be executed either using bifernoadmin or by the administrator on the command line. The reload and flush procedures for an application and its subapplications are described in "Biferno: bifernoadmin Guide".
Every time a script (other than "Biferno.config.bfr") initializes a variable without explicitly specifying its scope, a local variable is created. The scope of a local variable is limited to the area or program block in which the variable has been initially instantiated. Neither read nor write access to the variable from outside that block is possible.
Functions constitute stand-alone program blocks. If we create a variable in the main block of a script, this variable will not be visible from within a function and it will not be visible from another script. Similarly, local variables used within a function are not visible outside of the function itself (we will discuss functions further in this document). Notice that the body of a function, i.e. the code delimited by curly brackets that follows the header of the function (its prototype, i.e. class, name and parameter list), is a code block which is different from the main block of a script. Conversely, simply enclosing code between curly brackets (a legal syntax in Biferno) does not define a different code block and is used only to improve code readability.
The scope identifier for a local variable is local. In practice it is not
necessary to explicitly declare the scope of a local variable (except in
Biferno.config.bfr), as local is the default scope
when a new variable is initialized.
A global variable is visible from all blocks within a script, i.e. its scope is the entire script. This implies that the value of a global variable defined in the main body of a script can be read and modified from within a method or function.
A global variable defined in a script is not visible from other scripts within the same application and is reinitialized every time script execution is requested by a client.
Global variables are created by specifying their scope using the global
keyword in front of the variable name. E.g. if we write <? global myVar
= 1 ?>, we have created a global variable of int class and with value 1.
We can reference the myVar variable from within a function called by the
same script by using the global keyword in front of the variable name. If
we do not specify the global keyword, a local variable called myVar is
referenced. This will generate an error if the variable is not defined, or
reference the wrong variable if a local variable with the same name is
indeed defined, without any warnings or errors.
A persistent variable is visible from the entire application but, unlike an application variable, its value is stored in persistent memory on the Web server. In fact, its value is written to a file. This type of variables is useful to hold data that has to be stored indefinitely, even after Biferno has been terminated.
Assume we need to implement a counter to measure the number of accesses to the home page of our application. In the configuration file of the application we can add the following code:
if (!curScript.IsDef("persistent home_counter"))
persistent home_counter = 0
The code initializes the persistent variable to zero only if the variable had not been previously define. This avoids that the variable be reset to zero every time the server is started.
In our home page script we will write:
persistent home_counter++
The home_counter variable is incremented every time the script is executed.
The home_counter variable will store indefinitely the last assigned value,
which can then be used to count and visualize the total number of hits on
our home page.
Persistent variables can not be instantiated for some classes
(e.g. this is not possible for the db and file classes). Also, it is not
possible to create persistent variables for user defined classes (see Chapter 11, User classes).
The classInfo(className).persistentAllowed property can be examined to
query the ability of a class named classInfo to support persistent
variables.
![]() | How can a persistent variable be deleted? |
|---|---|
Persistent application variables are stored in a file called "biferno_pst.s.bfr" in the root directory of the application together with the "Biferno.config.bfr" file. To delete one or more persistent variables it is sufficient to open this file with a text editor and manually remove the lines corresponding to the variable to be deleted. After this, Biferno should be reloaded to execute the modifications (see Chapter 9, Applications and Variable Scope).
Notice that in this file the This file is generated by Biferno and should not be modified by the user other than to delete persistent variables as explained. New variable declarations should always be inserted in the Biferno.config file as in the example above. Notice that a file "biferno_pst.x.bfr" is present in the same directory as "biferno_pst.s.bfr". This is an internal Biferno file containing essential application information and should never be moved, removed or modified. If an application seems to have trouble with persistent variables, check that the two aforementioned files are owned by the Biferno user and that the owner has read/write permission on these files. Permissions are set correctly by Biferno and should be always correct unless they have been manually modified. |
Similarly to persistent variables, session variables are visible to the
entire application and are used to save information specific to a single
user that has to be accessible from all site pages.
Session variables are unique for each user that connects to the site. This allows different users to create/modify different variables or access variables with the same identifiers but different values. Instead, application and persistent variables are shared by all users of the same application.
Cookies are used to associate session variables to the users that created them. A cookie is a mechanism that is available to server side applications, such as Biferno, to store information on the client side, i.e. in the browser application that executes user connection requests. The cookie, initially created by the server and sent to the client, is resent to the server by the client upon each connection. Biferno uses the cookie to store a unique session identifier (containing, among others, the IP address of the client), which allows Biferno to distinguish among users. Notice that application pages will generate an error message for a user that has disabled cookies on his browser.
Session variables have a lifetime defined by the SESSION_TIMEOUT parameter
(the default is 15 minutes). A session variables dies (is removed from
server memory) either when the associated user is disconnected from the
site for a period of time greater than the defined lifetime or when the
user terminates his browser application.
A typical use of session variables is in the implementation of user-specific preferences for a site. We have seen in one of the examples how to use an application variable to define a common background color for all pages of a site. Assume now that we want to still make available a default color, but allow each user to choose the preferred background color.
The following script, contained in the "color.bfr" file, can be used:
<?
if (!curScript.IsDef("color"))
{
color = application body_background //Uses the application variable
}
else
{
session user_color = color //Set the session variable
}
?>
<html>
<body bgcolor="$color$">
<p>Select a background color for all pages of this site </p>
<form name="setcolor" method="post" action="color.bfr">
<select name="color">
<option value="$application body_background$">Default</option>
<option value="#ffffff">White</option>
<option value="#fffedc">Light yellow</option>
<option value="#e0ffff">Light blue</option>
</select>
<input type="submit" name="set" value="Set color">
</form>
</body>
</html>
Let's look at the first few rows of the script. The color variable is
passed to the "color.bfr" script via the "color" select of the HTML form
"setcolor". We will explain methods to pass parameters between Biferno
scripts in the following. At this time it will suffice to say that HTML
protocol parameters passed using the GET and POST methods are automatically
associated to variables with the same name and value of the corresponding
parameter. In our case the color variable is not defined when the page is
requested by the browser for the first time, because it has never been
initialized. When this is the case, we create the color variable and assign
it the value of the application variable body_background, which has been
defined in the configuration file. If the color variable exists, then the
page has been reloaded by the user by sending data via the "setcolor" form.
In this case we create a session variable (user_color) and assign it the
value of color.
From now on, this instance of the user_color variable is visible only to
the user (or better to the client) that has just loaded the "color.bfr"
page. To use the user_color variable in other site pages we use the
following self-explanatory code:
<?
if (curScript.IsDef("session user_color"))
{
color = session user_color //Use session variable
}
else
{
color = application body_background //Use application variable
}
?>
<html>
<body bgcolor="$color$">
</body>
</html>
In summary, a unique session identifier is assigned to each user that
requests a Biferno page. Users can create different instances of a session
variable that have, in general, different values. From the point of view of
the server (i.e. for Biferno), each instance of a session variable with the
same name but associated to a different user is a different variable that
has its own space in memory.
To be able to use session variables in an application the parameter SESSION
in the "Biferno.config.bfr" file must be set to true.
Biferno has a mechanism that makes it possible for an application to access
variables with application, persistent, or session scope belonging to another
application.
The predefined static method curApp.Publish makes a variable visible to all active
applications. Only variables with application, persistent or session scope can be
published. E.g. we can write:
<?
application aWebMasterName = "John Smith"
curApp.Publish(aWebMasterName)
?>
To access the value of a variable published by another application we use
the predefined static method curApp.GetPubVariable, with the following prototype:
obj curApp.GetPubVariable(string application, string scope, string name)
The name of the application that published the variable, as well as the scope and name of the published variable, must be specified. The function returns a copy object of the published variable, and therefore modifications of the returned variable have no effect on the original variable.
The following script loads in an array the names of all active applications
(using the predefined static property biferno.applications) and, for each application
found, inserts an element in another array that holds either the value of
the webMast variable, if published by the corresponding application,
or the null string if the aWebMasterName variable was not published.
<?
arr_apps = biferno.applications
nApps = arr_apps.dim
arr_wm = array()
error.Resume()
for (i = 1; i <= nApps; i++)
{
app_name = arr_apps[i]
wm = curApp.GetPubVariable(app_name, "application", "webMast")
arr_wm.Add(app_name:curScript.ValueOf("wm"))
}
?>
If the requested variable does not exist (i.e. has not been published by
the application), the curApp.GetPubVariable method generates the
Err_VariableNotDefined error and its result is undefined. The
error.Resume method allows to catch this error condition and is
discussed in Chapter 16, Error Handling and Debugging.
The complementary curApp.UnPublish method can be used to undo the effect of
the curApp.Publish method:
<?
curApp.UnPublish(aWebMasterName)
?>
Notice that, before a variable published by an application can be
requested, the publishing application must have been initialized. Remember
that an application is automatically initialized when one of its scripts is
requested or when the curApp.RegisterNewApp() method is called (see the
note How is a Biferno application started?). If the publishing application has
not been initialized, the curApp.GetPubVariable method returns the
Err_NoSuchApplication error.
Table of Contents
Functions have been introduced in previous chapters, using Biferno
predefined functions such as print and include.
A function is a code block that is separated by the main body of a script and implements a specific operation. Functions can optionally accept comma-separated input parameters, which behave as local variables within the function. The body of a function executes operations on the input parameters, on global variables, or on variables with wider scope. The result of these operations can be returned to the calling code.
Functions are a fundamental tool of programming languages. They allow to subdivide application code in blocks of elementary instructions that implement very specific tasks. This modularization of the code allows to write programs that are more efficient and more readily modifiable.
This chapter describes in detail how to declare new functions in a Biferno script, how to call them, how to pass them parameters and obtain results. All remarks concerning user defined functions hold as well for predefined functions.
The declaration of a function has the following syntax:
function [class] function_name (parameter list)
{
code block
}
The curly brackets are always mandatory for functions and they must be used even when the function body consists of a single line of code.
If the function returns a result, the class of the result must be always
specified after the function keyword. When a function does not return any
result, the class identifier can be omitted or the keyword void can be
used, which indicates the absence of a value.
A function can have no input parameters. In this case the parameter list
can be omitted or the void keyword can be used. In any case a pair of
matching parentheses must be used after the function name upon invocation.
A function can be declared anywhere in a Biferno script. A function can be used only after it has been declared.
Functions can be either of the local or of the application kind. Functions
declared in the Biferno.config.bfr file (or in
included files) are always of the application kind, other functions are of
the local kind. application functions are visible from all scripts of the
current application, local functions only from the script where the
function is declared.
A function can be explicitly declared as local or application using the
corresponding keyword in front of the function name in the declaration
statement. E.g.:
local function void MyFunc(void)
is a local function, while:
application function void MyFunc(void)
is an application function.
The following rules apply:
application functions can be declared only in the
Biferno.config.bfr file or in files included from
this file.
Functions declared in the Biferno.config.bfr
file are always application functions unless explicitly specified
otherwise.
An application function cannot be declared if a local function with the
same name exists.
application functions are in principle more efficient than
local functions because they are loaded in memory only once, while local
functions are reloaded every time a script is run. However, if the biferno
cache is active (see Chapter 22, Cache Management), the loading of functions after the first invocation
is partially optimized.
![]() | Where should application functions be declared during debugging? |
|---|---|
We have seen how application functions have to be declared in the
A good strategy is therefore to include the function scripts directly in
the various application scripts during the development phase (e.g. using
the |
The considerations of this section apply also to user classes, to be described in Chapter 11, User classes.
A function is invoked from a Biferno script using its name, which must be a
unique identifier, followed by a pair of parentheses containing the
optional parameter list. If the function returns an output parameter, its
value can be assigned to a variable of the same class. The simplest way to
return a parameter from a function is by using the return instruction.
When a function is called, the function body is executed up to and
including its last line, then execution resumes from the instruction
following the function invocation in the main script, unless the function
body contains the return instruction. If this is the case, the execution of
the return instruction is the last instruction executed in the function and
control is returned to the main script. If the return instruction is
followed by an expression, the result of the expression is calculated and
the result is returned to the calling script.
The function in the following simple script returns the sum of two integer numbers:
<?
function int sum (int a, int b)
{
return a + b
}
a = 1
b = 2
c = sum(a, b)
?>
A function can contain more than one return instruction, but beware that
multiple return instructions reduce code readability. An example is the
following script that contains a function which returns the value of the
sum of two integers or a limit maximum value if the sum is greater than the
limit value.
<?
function int sum_max (int a, int b, int max)
{
if (a + b > max)
return max
return a + b
}
?>
The same function can be also written as follows using a single return instruction:
<?
function int sum_max (int a, int b, int max)
{
s = a + b
if (s > max)
s = max
return s
}
?>
The second example uses a temporary variable, s, to store the sum of a and
b. This variable is local to the sum_max function and is not visible
outside the function. Remember that global variables or variables having as
scope the entire application (application, session and persistent) are
accessible from within a function, but their scope identifier must be
explicitly specified before the variable name.
The syntax for the parameter list of a function is the following:
class [nonames] [*]parameter_1[=expression_1],
class [*]parameter_2[=expression_2], ...,
class [*]parameter_n[...][=expression_n]
The elements between square brackets represent optional elements.
Parameters are separated by commas and must be preceded by the identifier
of the class they belong to. Optionally, a default value can be specified
for parameters by following the parameter name with the assignment operator
=, followed by an expression (normally a constant value). Notice that
actually, since a function can have no parameters, all function parameters
are optional. If no default values are specified, some predefined defaults
are used anyway. They are 0 (zero) for numerical parameters, false for
Boolean parameters and "" (empty string) for string class parameters. For
other, non-primitive classes, an uninitialized object of the required class
is passed. It is possible to determine if an object has been initialized by
using the static method:
boolean object.IsInitialized(obj variable)
Notice that variables of primitive classes are always initialized (to
either 0, or "", or false).
For other classes, next to the one case discussed here, there is only another case in which a variable can be uninitialized. We will see that in the context of the declaration of new classes in Chapter 11, User classes.
If we pass to a function upon invocation less parameters than the number
declared in the function prototype, Biferno supplies default values for
missing parameters, as explained above. If we pass too many parameters to a
function upon invocation (more than the number declared in the function
prototype), the Err_PrototypeMismatch error is generated.
It is possible to define a function that accepts a variable number of
parameters, even greater than the number implied by its prototype, by
using an ellipsis (…) after the name of the last parameter in the
parameter list. The ellipsis indicates that an arbitrary number of
parameters may follow. A function that will accept zero or more input
parameters can be defined with a prototype such as function
myFunc(…). Within the function the number and values of the
parameters actually passed can be discovered by using the Biferno
predefined methods curScript.GetTotVariables and
curScript.GetIndVariables. An example is:
<?
function array FillArray (...)
{
tot_val = curScript.GetTotVariables()
arr_obj = array()
for (i = 1; i <= tot_val; i++)
{
val = curScript.GetIndVariable(i)
arr_obj.Add(val)
}
return arr_obj
}
myArray = FillArray(12, 3, 4, 8)
// myArray is array(12, 3, 4, 8)
?>
The FillArray function fills an array with the supplied values. If we would
not have used an ellipsis in the function prototype an
Err_PrototypeMismatch would have been generated, because the number of
parameters passed would have been greater than the number of parameters
declared in the prototype.
We mentioned that, when a function is called, some or all parameters can be
omitted, and care must be taken not to alter the correspondence between
values passed and the order of parameters in the list. More flexibility can
be gained by specifying directly the correspondence between values passed
and parameters using the syntax "parameter_name:value". In this way the
order of parameters can be altered, as in the following example:
<?
function array FillArray2 (obj val1, obj val2)
{
arr_obj = array()
arr_obj.Add(val1)
arr_obj.Add(val2)
return arr_obj
}
myArray = FillArray2("val2":12, "val1":3)
// myArray is array(3, 12)
?>
The FillArray2 function fills an array with the supplied values. Notice
that in the function call the parameter order is inverted and the name of
each parameter is specified. If the nonames clause is specified at the
beginning of the parameter list, the names associated to the values passed
to the function are not taken into consideration. The aforementioned
mechanism is disabled and only the position of the value in the list
counts. Sometimes it is necessary to use the string names associated to the
passed values for other purposes, as in the Add and Insert methods of the
array class, which use the names as the names of the new element of the
associative array.
This example uses the concepts explained above:
<?
function array FillArray3 (nonames obj valN...)
{
tot_val = curScript.GetTotVariables()
arr_obj = array()
for (i = 1; i <= tot_val; i++)
{
val = curScript.GetIndVariable(i, &name)
arr_obj.Add(name:val)
}
return arr_obj
}
myArray = FillArray3("val1":12, "val2":3, "val3":4, "val4":8)
// myArray is array(val1:12, val2:3, val3:4, val4:8)
?>
The FillArray3 function accepts an arbitrary number of parameters and
creates an array with as many elements, assigning to the indices the names
associated to the parameters. The curScript.GetTotVariables method returns
the number of variables currently defined (the default is to count only
local variables). If called on the first line of code in the body of a
function, curScript.GetTotVariables returns the number of parameters
actually passed to the funtion. The curScript.GetIndVariables method
returns the value of the variable corresponding to a given position in the
list and, optionally, the name of the variable itself for a variable passed
by reference (name). The name returned for variables
corresponding to function parameters is the name assigned to the parameter
in the prototype of the function itself. In our case, having used the
nonames clause, the string in front of the parameter in the function
invocation is returned.
In the examples above, function parameters have been always passed by value. When this is the case, the function actually receives a copy of the original variables within the calling script. The value of the original variables can not be modified by the execution of function code. Passing parameters by value is the most common way to provide information to a function.
Biferno supports passing parameters to a function by reference. This is
similar to the C language, even though pointers do not exist in Biferno. A
parameter passed by reference must correspond to a variable (local or of
other scope, and possibly uninitialized) of the calling script. If the
value of a parameter passed by reference is modified, the original variable
is modified and takes the same value.
Parameters are passed by reference by using the * character (star) before
the parameter name in the function parameter list and the & character
(ampersand) before the name of the corresponding variable in the function
call. Within the function no special characters need to be used to identify
variables passed by reference. Passing parameters by reference is an
alternative way of obtaining return values from a function.
The remarks we made in the case of functions apply also when parameters are passed to the methods of a class.
![]() | Passing file and db class parameters |
|---|---|
Care must me taken when
We advise to always pass parameters by reference when using
It is possible to inquire if a class called
classInfo(className).cloneIsNeeded
|
![]() | Array type parameters |
|---|---|
In the prototype of a Biferno function it is possible to declare parameters that are arrays of elements of a given class. Since the class a parameter belongs to is always specified in a prototype, it is advisable to use a syntax which is different from the one we have seen for the declaration of array class variables. If we write:
int nome[]
we have declared an int class parameters which is a one dimensional array of integers. To declare multidimensional arrays we specify a pair of square parentheses for each dimension of the array. Array class parameters can also be declared. In this case the element class is determined by the first assignment of a value to an element of the array itself. |
A function can call other functions from within its body, and can even call itself. A function that calls itself is called a “recursive” function.
Recursion is an elegant programming technique that suits the implementation of inherently recursive algorithms. Often the same problem can be solved by recursion or by iteration.
A recursive function must be structured in such a way that infinite recursion is avoided.
A simple problem that can be solved both by iteration and by recursion is
the computation of the factorial of a number. The factorial (symbol: n!) of
a positive integer is defined by the following expression: n! = n * (n -
1) * (n - 2) * ... * 2 * 1. Notice that 0! = 1 and 1! = 1 by
definition.
An iterative function to compute factorials can be simply written as follows:
<?
function int factorial (int n)
{
if (n < 0)
return -1
else if (n == 0 || n == 1)
return 1
else
{
fact = n
for (i = 1; i < n; i++)
fact *= n - i
return fact
}
}
?>
The factorial of an integer number can also be defined as the product of
the number by the factorial of the integer immediately preceding it, i.e.
as n! = n * (n - 1)!. This definition leads to the implementation of the
recursive function:
<?
function int factorial (int n)
{
if (n < 0)
return -1
else if (n == 0 || n == 1)
return 1
else
return n * factorial (n - 1)
}
?>
This solution is more elegant and seems more efficient.
However, it should be noted that recursive functions can require a large amount of memory for local variables (stack memory). To handle such situations Biferno is forced (at least on certain platforms) to allocate sizeable memory resources, and this may slow down the execution of the script. We advise to evaluate on a case-by-case basis the use of recursive functions and to compare, whenever possible, the execution time of a recursive function versus the execution time of its iterative version.
Table of Contents
We have mentioned that Biferno is a true object oriented language, or OO language. This is one of the strongest points of the language and makes it a very versatile and suitable tool for the implementation of large projects in environments where development is done by a team of developers rather than by a single individual.
A general introduction to object oriented programming is not one of the goals of this manual. With the beginner in mind, we believe it is useful anyway to present a number of examples of creation of new classes and extension of predefined classes. These capabilities are key to understand and fully exploit the potential offered by OO languages such as Biferno.
Biferno supports the main concepts of OO programming:
Code encapsulation, the possibility to make all or some of the properties that define a class invisible from the outside world and accessible only via specific methods (the object as a logical entity is derived from this concept).
Class inheritance, the mechanism that allows to derive new classes starting from already defined classed, keeping their characteristics and functionality and adding new ones as needed.
Polymorphism, the capability of several derived classes to share the same methods that execute completely different actions. Polymorphism and inheritance are closely related.
These mechanisms foster code reusability, reduce the possibility of error, and make application update and maintenance easier.
A new class is defined as follows:
class class_name [extends class_name]
{
[static][public, private, protected][const] class property_name
[static][public, private, protected] class method_name (parameter
list)
{
code block
}
}
Notice the similarity between method definition and function definition,
while properties are declared by specifying the class they belong to.
Constants can be defined by using the keyword const in front of the
property name. Properties and constants are different entities in Biferno.
For sake of simplicity in the following we will blur the distinction and
talk generically of class properties, meaning both properties and
constants.
A class can have as many methods and properties as desired, even none.
The keywords public, private and protected define the visibility of
members (properties and methods) of a class. Members of a class defined as
public are visible, i.e. directly accessible, by all scripts of an
application, including all functions and all base and derived classes.
Members defined as private are visible only from within the class they
belong to. Members defined as protected are visible only from within the
class they belong to and from within classes derived from the latter. Class
derivation is described in the following. public, private and protected
are optional keywords (the default is public).
A static property is a property that does not belong to a single object,
but to the entire class it belongs to. static properties are similar to
global variables having the entire class as their scope. When the value of
a static property is changed, the change is reflected in all objects of
that class. If a static property is called from outside the class it
belongs to, the class identifier must be used in front of the property,
according to the class.property syntax. The class identifier can be omitted
from within the class the property belongs to. In addition to this, the
class identifier in front of the property name can be always omitted when a
static property of a class is passed as a parameter to a method of the same
class.
The public constants of a class are usually defined as static, because they
contain values that are common to all objects of the same class. We will
see examples of the use of constants in a class in Chapter 13, File Management and in Chapter 16, Error Handling and Debugging.
A method defined as static is a public method that can be called only
statically, i.e. it can not be applied to an object of that class but only
called using the class.method(parameters) syntax.
A special method, called a constructor, can be defined for each class. The constructor method creates an object of that class and initializes data (properties) describing its characteristics. The constructor has the same name as the class and a variable parameter list depending on the operations it has to execute and on the properties it has to initialize. The prototype of a class constructor is as follows:
void class_name (parameter list)
The constructor method for a given class is called automatically each time a variable of that class is created. If a class does not declare a constructor, no method is invoked at variable creation time, and a class instance is generated with all its properties set to default values (as we will see, derived classes inherit the base class constructor).
Besides a constructor method, a class can have a destructor method. A destructor method frees the memory used to store data items used by an object. When a new class is defined, it is rarely necessary to declare a destructor, because data stored in internal properties of a class is automatically eliminated together with the variable corresponding to the object.
A destructor method for a class should be defined if the object, when created via the constructor method, dynamically allocates data structures or resources external to the class, which have to be released separately from the object. The destructor method is automatically called when a variable is destroyed. This happens when, during script execution, control exits the variable scope. E.g., local variables are deleted after script or function execution terminates. The destructor method must be named "Destructor" and has no parameters:
void Destructor (void)
There are two important rules that must be followed when writing a destructor method:
Within a destructor, no reference can be made to application,
persistent or session variables.
Within a destructor, no variables of classes that have a destructor method themselves or variables of user-defined classes can be instantiated.
To inquire if a class has a destructor method the following call can be used:
boolean classInfo(className).wantDestructor
This function always returns true for user classes, because such classes
always have a destructor (an internal one if the user has defined none),
which frees memory resources allocated by Biferno to manage the class
instance. The result of classInfo(className).wantDestructor for predefined classes depends on
the implementation of the class itself.
We will discuss an example of the use of destructors when describing special methods for user classes.
![]() | Static classes |
|---|---|
As we have seen, some Biferno classes are static (e.g. the |
As an example, we define a simple class that represents a person identified by its name, surname, gender and age:
<?
class person
{
string firstName
string lastName
char gender
int age
void person (string n1, string n2, char s, int a)
{
this.firstName = n1
this.lastName = n2
if (s == "M" || s == "F")
this.gender = s
else
this.gender = "-"
if (a >= 1)
this.age = a
else
this.age = -1
}
}
?>
The person class has four public properties and one method, the constructor method. The constructor takes four parameters and uses them to initialize the class properties. Let's see how we create an object of the person class:
<?
John = person("John", "Smith", "M", 42)
?>
Initializing the John variable is done by calling the constructor of the
person class passing the required parameters. The parameter values are
assigned to the properties of the object which is created.
Properties of the person class are public and can be directly accessed for
both reading and writing. E.g. we can write <? $John.firstName ?>, or
<? John.age = 43 ?>. Notice that the properties of a class are
referred to within the constructor method by using the this identifier
followed by a dot in front of the property name. The this keyword is
meaningful only when used within a class, denotes the object of the current
class, and can be omitted when redundant. We will drop the this identifier
in the following examples whenever it is not strictly necessary.
If a constructor does not initialize some declared properties, these will
remain uninitialized. In this case, Biferno tries to assign defaults to
these properties using the mechanism explained in Chapter 10 when passing
parameters to functions. Properties of primitive classes would be assigned
the value "", 0 or false, other classes would remain uninitialized (see
Section 4, “Passing Parameters to a Function” in Chapter 10, Functions).
In the constructor of the person class, parameters are first checked before
values are assigned to the gender and age properties. Since these are
public properties, any values could be assigned to them directly, without
any kind of control by the class. This can lead to inconsistencies in
property values. In such cases it is advisable to use protected or private
properties and write specific methods to modify their value.
Let's modify the person class according to the considerations above:
<?
class person
{
string firstName
string lastName
protected char gender
protected int age
void person (string n1, string n2, char s, int a)
{
firstName = n1
lastName = n2
SetGender(s)
SetAge(a)
}
char GetGender(void)
{
return gender
}
void SetGender(char s)
{
if (s == "M" || s == "F")
gender = s
else
gender = "-"
}
int GetAge (void)
{
return age
}
void SetAge(int a)
{
if (a >= 1)
age = a
else
age = -1
}
array GetPersonData(void)
{
return array("FirstName":firstName, "LastName":lastName,
"Gender":gender, "Age":age)
}
}
?>
The example shows that it has been necessary to add specific methods to the
class to allow objects of the person class to read and write the gender and
age properties, which are now protected properties. E.g., the GetGender
method returns the value of the gender property, while the SetGender method
assigns the value of the s parameter to the gender property if this value
is "M" or "F", or otherwise the value "-" (which indicates absence of value
for the class). The GetAge and SetAge methods work in a similar fashion for
the age property. Notice that also from within the constructor the methods
SetGender and SetAge are called in order to avoid duplication of the code
that executes the checking.
The GetPersonData method returns an associative array with four elements of
the string class that contain the values of properties of the person class.
This method allows to obtain in one structure all information relative to
the person represented by an object of the person class.
![]() | Local and Application Classes |
|---|---|
Similarly to functions, classes can be of the |
![]() | Array-like properties |
|---|---|
In a Biferno class properties can be declared that are arrays of elements of a given class. Since, within a class, the class a member belongs to is always specified, we use a syntax that is different from the syntax used for the declaration of array class variables. If we write:
int nome[]
we have declared a public property of the To declare multidimensional arrays we specify a pair of square parentheses for each dimension of the array.
It is also possible to declare properties of the |
It has been mentioned that in Biferno a class can be derived from an existing class. A derived class can be considered the extension of a class, in the sense that it inherits properties and methods of another class and adds new ones. The original class is usually called the base class, while the derived class is called a subclass (or child class).
The base class can be any Biferno class, both predefined or created ex novo. Multiple classes can be derived from a single base class, and classes can be derived in turn from a derived class.
In general a derived class adds new functionality to the base class, both by adding new methods and properties, and by redefining methods of the base class. The fundamental advantage of derived classes consists in the possibility of expanding or personalizing the functionality of a base class without having to modify the base class itself.
Let's see an example of a derived class. Assume we want to store data
relative to the users of an online service that requires input of a
username and password to gain access. Since our users will be identified by
name, surname, and other personal data described by the person class, we
can create a new class called user, use the person class as a base, and
implement only the additional data and functionality required.
The keyword extends between the name of the new class and the base class
must be used to declare the extension of a class, as in the following
example:
<?
class user extends person
{
protected string username
protected string password
void user (string n1, string n2, char s, int a, string u, string p)
{
super(n1, n2, s, a)
SetUsername(u)
SetPassword(p)
}
string GetUsername(void)
{
return username
}
void SetUsername(string u)
{
username = u
}
string GetPassword(void)
{
return password
}
void SetPassword(string p)
{
password = p
}
}
?>
Notice that the user class only contains additional properties and methods
with respect to the person class. All public and protected methods of the
base class are automatically inherited by the derived class.
A derived class may have no constructor. In this case the base class constructor is automatically called when a new object of the derived class is created, with the same parameter number and ordering. For the user class we need to initialize two new properties and therefore we have declared a constructor that has the same parameters as the constructor of the base class, plus two new parameters corresponding to the additional properties of the derived class. If a derived class has its own constructor, it needs to call internally the base class constructor.
As seen in the example, the super identifier followed by the parameters
required by the base class constructor between parentheses must be used to
call the base class constructor. In the case of the user class we pass the
first four parameters to the person class constructor and then initialize
the two new ones.
The super identifier usually indicates the base class object that we intend
to extend, similarly to the use of the this identifier to denote the object
of the current class (base or derived). Instead, when followed by the '('
character, super denotes the constructor of the class we are extending
("super()").
Let's initialize a variable of the user class:
<?
user_1 = user("John", "Smith", "M", 30, "admin", "AM001232001ZA")
?>
After its creation, the user_1 object can be manipulated using both methods
and properties of the user class and of the person class, as in the
following example:
<?
$user_1.firstName
user_1.SetAge(31)
user_1.SetUsername("guest")
user_1.SetPassword("")
?>
We can also use the GetPersonData method inherited form the person class,
but this method only returns values of properties defined in the base
class. This issue can be solved by redefining the method in the derived
class in such a way that it will now return in the array also the username
and password properties. The GetPersonData method of the user class will
look as follows:
array GetPersonData(void)
{
return array("FirstName":firstName, "LastName":lastName,
"Gender":gender, "Age":age, "Username":username, "Password":password)
}
The characteristics of OO languages that allow a derived class to share methods (i.e. to have methods with the same name) of the base class is called overriding and is at the root of the concept of polymorphism, mentioned in the introduction to this chapter. Thanks to this characteristic a derived class can be specialized maintaining a strong homogeneity with the base class.
Assume that we have redefined a method of the base class in a derived
class, and that we now want to invoke the method of the base class from
within the derived class. This can be done by using the super keyword
(necessary in this case) to denote the object of the base class. E.g., if,
in a method of the user class, we write super.GetPersonData(), we are
calling the GetPersonData method of the person base class, and not the
method with the same name of the user class.
A user class can optionally add one or more special methods that are automatically invoked by Biferno when certain events happen.
There are several predefined special methods for Biferno user classes. Of course, being optional methods, these methods will be called only if effectively present (implemented) within a user class.
The SetProperty method is invoked every time an object property is
modified, except for private properties. This method must have the following
prototype:
void SetProperty (string propertyName)
The propertyName parameter contains the name of the property that has been
assigned a new value.
This method is useful to implement validity checks on data assigned to a property, or when the value of a property is strictly related to the value of another property, and every time one of the two is modified, the other has to be modified.
Considering the user class, we might want to assign empty passwords to
guest users, i.e. whenever the username property is set to "guest",
automatically the password property should be assigned the value "" (empty
string).
This simple mechanism can be implemented using the SetProperty method,
which, for the user class, is the following:
void SetProperty(string propertyName)
{
if (propertyName == "username")
{
if (username == "guest")
password = ""
}
}
It is also necessary to use the SetProperty method when we want to
implement public properties with read-only access. To demonstrate this
mechanism, consider the following example where a fictitious class with
two public properties is declared. The second property is modified only
internally to the class by the SetProperty method. If a script attempts to
assign directly a value to the property2 property, the class returns the
Err_PropertyIsOnlyRead error.
<?
class myClass
{
int property1
int property2 // Read only
void myClass (int par1)
{
property1 = par1
}
void SetProperty (string propertyName)
{
if (propertyName == "property1")
property2 = property1 + 10
else if (propertyName == "property2")
{ // An error is generated
error.ThrowException(Err_PropertyIsOnlyRead)
}
}
}
?>
The SetProperty method does not call itself, and therefore we can freely
assign values to properties within the SetProperty method.
When the error is thrown by calling the ThrowException method of the error
class, and passing it a predefined constant (in this case of the same
error class), Biferno interrupts the execution of the script and returns
the error corresponding to the specified constant. More information on the
error class and a complete discussion of error handling in Biferno is
contained in Chapter 16, Error Handling and Debugging.
The SuperIsChanged method is invoked every time the super object of the base
class is modified, e.g. when a new value is assigned to one of its (non
private) properties. This method is meaningful only for derived classes
and must have the following prototype:
void SuperIsChanged (string propertyName)
As for the SetProperty method, the propertyName parameter of the
SuperIsChanged method contains the name of the property of the super
object that has been modified, or is empty if the entire super object has
been modified (without use of any property).
This method should be declared when one or more properties of the derived
class depend on the value of one or more properties of the base class.
Since the SetProperty method of the derived class is not called if a
property of the base class changes value, the SuperIsChanged method allows
to intercept this event and to take action.
In the following example we derive a class from the myClass class defined
in the last example and we create a property that must always have the
same value as the property2 property of the myClass class, plus one.
<?
class myClassExt extends myClass
{
int propertyExt
void myClassExt(int par1)
{
super(par1)
propertyExt = super.property2 + 1
}
void SuperIsChanged(string propertyName)
{
if (propertyName == "property1")
propertyExt = super.property2 + 1
}
}
?>
The constructor of the myClassExt class calls the constructor of the base
class and assigns the value of the property2 property of the super object
incremented by one to the propertyExt property. When the value of the
property1 property is changed externally (remember that property2 is
read-only), the SuperIsChanged method is invoked and the propertyExt
property is automatically updated.
As for the SetProperty method, the SuperIsChanged method does not call
itself.
The Clone method is automatically invoked every time the object is duplicated,
either because it has been assigned to another variable, or because it has
been passed as a parameter to a function or method.
This method is rarely used because, when an object is copied, the properties of the new object are automatically initialized with the values of the corresponding properties of the original object. This is usually sufficient to guarantee the integrity and coherency of the copy object.
To illustrate the use of this method, assume we would like to know how
many objects of a class we defined are active, i.e. exist, at a certain
time. This can be done using a static counter, shared by the entire class,
that is incremented every time an object of the class is created and
decremented every time an object of the class is deleted. In the following
code we declare a fictitious class containing only the members that are
strictly necessary to implement this mechanism.
<?
class myClass
{
static int counter = 0
void myClass (void)
{
myClass.counter++
}
void Destructor (void)
{
myClass.counter--
}
void Clone (void)
{
myClass.counter++
}
}
?>
The constructor of the myClass class simply increments the static counter
and the destructor simply decrements it. This simple approach is not
sufficient to guarantee that the counter always reports the accurate count
of active objects of the myClass class, because, when an object is copied,
the constructor of the class of the copy object is not called. If the
class implements the Clone method, this method is always called on the
copy object, after all its properties have been initialized. In the case
of the myClass class, also the Clone method increments the static counter.
In the following example, we show what happens when we create and then
copy and object of the myClass class.
<?
obj1 = myClass() // The constructor increments the counter
obj2 = obj1 // The Clone method of obj2 increments the counter
?>
If the class would not implement the Clone, method, the second assignment
instruction would not cause an increment of the counter, although it
actually does create a new object of the myClass class.
Notice that the fact that the static counter is initialized to zero does not imply that the counter is zeroed at every subsequent instantiation of the class. In fact, the default value for the static properties of a class is assigned only once, when the class is created and loaded in memory for the first time. The initialization to zero of the counter might have been omitted, because Biferno implicitly assigns the default value zero to numerical static properties and the default value empty string to string class static properties.
The Operation method allows to implement arithmetic, and bit-wise operators
for a user class.
This method, if present in a class definition, is called automatically on the first (or on the second in case of forced typecast) of two objects of a user class that are passed as arguments to one of the operators listed above.
Assume we would like to implement the point class, which represents a
point in the plane via a pair of numerical coordinates, and that the sum
and difference of points should be implemented as the sum and difference
of the corresponding coordinates. The point class can be defined as
follows:
<?
class point
{
double x
double y
void point (double xval, double yval)
{
x = xval
y = yval
}
void Operation (point ptObj, string oper)
{
switch (oper)
{
case "+":
x += ptObj.x
y += ptObj.y
break
case "-":
x -= ptObj.x
y -= ptObj.y
break
default:
error.ThrowException(Err_IllegalOperation)
}
}
}
?>
The point class constructor assigns the values passed as parameters to the
x and y properties, which represent the actual coordinates of the point.
The Operation method takes as parameters a point class object and a string
that represents the operator to be applied.
The following script initializes two point class objects and assigns their
sum to a third point object.
<?
pt1 = point(1, 2)
pt2 = point(0.5, 1.5)
pt3 = pt1 + pt2 // pt3 vale (1.5, 3.5)
?>
The use of the + operator in the sum expression causes a call to the
Operation method of the pt1 object, which, after cloning itself, adds the
corresponding properties of the pt2 object to its x and y properties. The
resulting object is assigned to the pt3 variable, and the pt1 and pt2
objects are not modified.
As implemented in our example, the Operation method of the point class
handles only the + and - operators. The Err_IllegalOperation error is
generated for all other operators (we will describe the ThrowException
method of the error class in Chapter 16, Error Handling and Debugging).
The Compare method is invoked when it is necessary to perform a comparison
operation between two objects of the class. This method is similar to the
Operation method, the only difference being that the return value of the
comparison operation between two objects of the class is a Boolean
value. The prototype for the Compare method must be:
boolean Compare([classname] theobj, string oper)
This method is part of the mechanism that allows a class to generate its own errors, using error constants defined internally to the class. We will discuss this method and will see how to declare error constants in Chapter 16, Error Handling and Debugging, which is devoted to error handling.
We have seen that it is possible to convert values from a primitive class into another implicitly or explicitly, e.g. when passing parameters to a function or method, and in the concatenation of numerical variables and strings.
This mechanism is implemented in Biferno using implicit conversion methods provided by the language for predefined non static classes.
These conversion methods can be used also for user defined classes. While
it is not mandatory, it is good practice to provide for all new classes at
least a string conversion method in order to be able to represent an object
with a printable value.
If you have already played with the language and have implemented your first classes, you will probably be familiar with an error screen where, instead of the value of a variable of a class you defined, you get the message "object not printable (method tostring not found)". Why? The reason is that your class does not contain a method to implicitly convert an object of the class to a string, and the system does not know how to build a string to output the value of the variable or to provide a description of the corresponding object.
To avoid this problem we can simply add to our classes the following method:
string tostring (void)
{
return string_expression
}
where string_expression can be any expression that results in a string
class entity. Let's define a tostring method for the person class:
string tostring (void)
{
return firstName + " " + lastName
}
With this method a person class object can always be represented by a
string containing the name and surname of the person described by the
object. Using the user_1 variable, initialized in one of the previous
examples, we can now write: <? $user_1 ?>, and obtain in output the
string "John Smith" (user_1 is of the user class and inherits the tostring
method from the person class).
Similarly to the tostring method, conversion methods into all Biferno
primitive classes (toint, tolong, tounsigned, todouble, toboolean, tochar)
can be provided for user classes, and, more in general, into any predefined
language class or user class. The same syntactic rule as for the tostring
method holds (e.g., toarray, toperson, etc.).
Method overloading is a characteristic of OO languages that allows to define two or more methods for a class that have the same name but different parameters list. The compiler (or interpreter) decides which method should be chosen on the base of the parameter list supplied by the calling program.
In Biferno this mechanism can be partially implemented by using the obj abstract class. As the name suggests, this identifier denotes an object of any class, and can be used as placeholder in a parameter list for a method or function.
Using the obj abstract class we can implement a class method that accepts
parameters of various nature and can execute different operations depending
on the class of the parameters that are actually passed.
Let's consider again the point class previously defined, and implement a
Distance method to calculate the distance of the points. Before looking at
the code implementing the class, let's see a usage example:
<?
pt1 = point()
pt2 = point(0.5, 1.2)
dist1 = pt1.Distance(1.2, 2.25) // dist1 vale 2,55
dist2 = pt1.Distance(pt2) // dist2 vale 1,3
?>
The first row of the script instantiates a pt1 variable of point class
with the default values (0.0, 0.0). The second row instantiates another
variable (pt2) by passing a coordinate pair to the constructor. The third
row invokes the Distance method and computes the distance between the pt1
point and the point corresponding to the coordinates (1.2, 2.25). The
fourth row invokes again the Distance method and computes the distance
between the points pt1 and pt2.
The noteworthy characteristic of the above script is that the Distance
method is called first with two numerical values as arguments, and a then
with a point class object as argument. Calling the same method with
parameters belonging to different classes is what is we have defined as
“overloading”.
According to our discussion of parameters passing rules for functions and
methods so far, this should not be legal in Biferno. We will now see how
the Distance method of the point class can be implemented to make such
mechanism possible in Biferno.
<?
class point
{
double x
double y
void point (double xval, double yval)
{
x = xval
y = yval
}
double Distance (obj par1, double par2)
{
if (par1.class == this.class)
{
xval = par1.x
yval = par1.y
}
else
{
xval = double(par1)
yval = par2
}
return (double(xval.Sqr() + yval.Sqr()).Sqrt())
}
}
?>
The Distance method has two parameters, par1 and par2, and par1 is declared
as obj. This allows to call the method passing an object of any class as
the first parameter. In particular, we can call the method passing an
object of the point class, as we have seen in the previous example (the
second parameter is optional and can be omitted).
Internally, the method checks the class of the par1 object, and, if the
class coincides with the class of the this object (i.e. point), it
initializes the two local variables xval and yval with the values of the x
and y properties of the par1 parameter. If par1 is not of the point class,
it is typecast to double and anyway assigned to xval (the
Err_IllegalTypeCast is returned if the typecast fails), while the value of
the par2 of class double is assigned to the yval variable.
Finally, the method returns the value of the square root of the sum of the
squares of xval and yval. Notice the use of the Sqr and Sqrt methods,
available for all primitive numerical classes.
In summary, we have shown how it is possible to realize “method overloading”
for a class in Biferno, even if it would be more correct to talk about
“parameter overloading”. Notice that the obj abstract class can also be used
in passing parameters to a function.
Table of Contents
Table of Contents
Biferno provides a rich variety of string manipulation functionality. In
this chapter we analyze the most significant methods implemented by the
string and ansi classes. A detailed list of all methods and properties of
Biferno predefined classes is contained in "Biferno: Reference Guide".
Comparing two string means to compare one by one their characters by starting from the first (the leftmost), until either two different characters are found, or the end of one of the two strings is reached.
Two strings are considered equal if they consist of the same characters in the same sequence. If two different characters are found in a corresponding position, the relationship between these two characters determines the relationship between the strings.
A character is considered "less" than another character if it precedes it in the ASCII character table. A string is considered "less" than another if the first different character is "less" than the corresponding character in the other string. If the first characters of the longest string are exactly the same as the shortest string, the longest string is always considered greater. Notice that the result of character comparison for characters with ASCII code greater than 128 can depend on the operating system. These characters can assume different values on different systems.
The simplest method to compare two Biferno strings is to use logical
operators. Using logical operators the comparison is case sensitive, i.e.
the two strings "sun" and "Sun" are considered different.
An alternative is to use the Compare method of the string class, with
prototype:
int Compare(string str, boolean caseSense = false)
This method takes as parameters the string to compare to and a Boolean
value to indicate if the comparison should be case sensitive or not (the
default is false). The method returns an integer value, which can be 0
(zero), if the two strings are equal, 1, if the string to be compared is
greater, or -1 if the string to be compared is smaller. An example is:
<?
str1 = "a"
str2 = "b"
$str1.Compare(str2) // This instruction prints the value 1
str1 = "sun"
str2 = "Sun"
$str1.Compare(str2) // This instruction prints the value 0
$str1.Compare(str2, true) // This instruction prints the value -1
?>
A third way of comparing two strings is to use the strcmp method of the
ansi class:
static int strcmp(string str1, string str2)
This method is called statically and takes as parameters the two strings
to be compared. The result of the comparison is an integer value, which
can be 0 (zero), if the two strings are equal, a positive value if the
first string is greater than the second, or a negative value if the first
string is smaller than the second. If the result is non-zero, its value is
the difference between the ASCII codes of the first two characters that
differ between the strings.
In the following example the two strings str1 and str2 differ starting
from the second character. The strcmp returns the value -14, which
indicates that str1 is smaller than str2, and is the difference between
the ASCII code of the a character (97) in str1 and the ASCII code of the
corresponding character (o, code 111) in str2.
<?
str1 = "salty"
str2 = "solitary"
$ansi.strcmp(str1, str2) // The instruction prints the value -14
?>
The comparisons executed by methods of the ansi class are always case
sensitive.
It is sometimes necessary to compare a string at the same time with several
different strings. This is the same as asking if the string is contained in
a given string set. To this end the string class provides the In method
with prototype:
boolean In(string str, char sep = ",")
The string set is passed as a single string in which the individual strings of the set are separated by a special character, called a separator. The default separator is the comma. An example is:
<?
if (user.GetUsername().In("john,paul,george,ringo"))
print("<p>Welcome " + user.firstName + "</p>\n")
else
print("<p>Unknown user. Access denied.</p>\n")
?>
The wildcard character * (star) can be used in the string set. In the
following example the In method returns the true value if the keyword
string either starts by "comp", or is "software", or is "hardware".
<?
if (keyword.In("comp*,software,hardware"))
category = 1
?>
To establish if a string contains another string we use the Contains
method of the string class, with prototype:
boolean Contains(string str, boolean caseSense = false)
This method returns true if the str string is contained in the string that the method is
applied to, or false otherwise.
<?
str = client.userAgent
if (str.Contains("MSIE", true))
print("Your browser is Internet Explorer")
?>
Variants of this method are:
ContainsWordBegin, that determines if the string that the method
is applied to contains a word beginning with the string passed as
parameter. A word is defined as a group of contiguous characters
delimited by spaces or other separators (punctuation marks, tabs,
etc.).
ContainsWordEnd, that determines if the string that the method is
applied to contains a word ending with the string passed as
parameter.
ContainsWordExact, that determines if the string that the method
is applied to contains a word matching exactly the string passed as
parameter.
The ansi class provides the strstr method with prototype:
static string strstr(string str1, string str2)
This method searches for str2 in str1 and, if str2 is contained in str1,
returns a string containing the substring of str1 that starts with str2
and reaches up to the last character of str1. In the following example the
string str2 is "MSIE 5.0; Mac_PowerPC)":
<?
str1 = "Mozilla/4.0 (compatible; MSIE 5.0; Mac_PowerPC)"
str2 = ansi.strstr(str1, "MSIE")
?>
If we need to check if a string starts or ends with another string, we can
use the Begins and Ends methods of the string class, with prototypes:
boolean Begins(string str, boolean caseSense = false)
boolean Ends(string str, boolean caseSense = false)
It can be sometimes useful to know the position of the first character of
a substring. We can use the Find method of the string class with
prototype:
int Find(string str, boolean caseSense = false, int from)
The Find method returns the position of the first instance of the str
string in the string the method is applied to, starting from the character
position specified by the from parameter (the default is to start from the
first character of the string); if the str string is not found the value 0
(zero) is returned.
<?
str = "Biferno is an object oriented language"
pos = str.Find("Biferno") // pos is 1
pos = str.Find("lang") // pos is 31
pos = str.Find("x") // pos is 0
?>
The IsEMail method of the string class returns true if the string has the
format of a valid email address.
<?
userEmail = "john@domain.com"
check_email = userEmail.IsEMail() // The method returns true
?>
The IsEmail method has the following prototype:
boolean IsEMail(boolean exists, string *msg)
If the exists parameter is true and the string is is a legally formatted email
address, the IsEmail method contacts the mail exchange host for the
corresponding email domain and verifies if a user corresponding to the supplied
email address exists. The msg parameter will return an error message from
the smtp server, if there is one.
Contacting an smtp server over the Internet to verify an email address is a slow operation and may take up to several seconds. On the other hand verification can be useful to avoid sending email to non existing addresses or to check that a user did supply an existing email address to a subscription form.
The IsDate method of the string class returns true if the string has the
format of a valid date. The method has prototype:
boolean IsDate(string format)
The format parameter specifies the expected order of day, month, year (the default is the one defined by the application variable DATE_FORMAT). A
dash (-), a forward slash (/), or the separator defined in DATE_FORMAT can
be used as separators:
<?
aDate = "24-10-2001"
check_date = aDate.IsDate("d-m-y") // The method returns true
check_date = aDate.IsDate("y-m-d") // The method returns false
?>
The IsNumeric method of the string class returns true if the string has
the format of a valid number. The method has prototype:
boolean IsNumeric(void)
The methods recognizes the thousand separator (as defined by the
application variable application THOUSAND_SEP or by the curScript.SetNumFormat method)
and the decimal separator (as defined by the application variable
application DECIMAL_SEP or by the curScript.SetNumFormat method).
<?
/* Assume:
thousand separator = '.'
decimal separator = ','
*/
aNum = "30.000"
check_num = aNum.IsNumeric() // The method returns true
aNum = "300.00"
check_num = aNum.IsNumeric() // The method returns false
?>
The methods of the string class described in this paragraph transform a
string into another string, e.g. by eliminating some characters, extracting
substrings, or replacing a group of characters with another. Remember that
it is always possible to access the individual characters of a string (for
reading or writing) using the char property, which contains the string
representation as an array of characters.
The Hilite method searches for one or more strings in a text and inserts
a given string before and after each occurrence of the string(s). This
method has prototype:
string Hilite(boolean cs, boolean skipHTML, string pre, string post, obj strN...)
The cs parameter determines if the search should be case sensitive. The
skipHTML parameter can be used to exclude from the search text areas that
are HTML tags. The pre and post parameters provide the strings to be
inserted before and after each occurrence of the string. The strN
parameter provides one or more strings (or array of strings, or search,
see Chapter 15, Database Interaction) to highlight. If an array is passed, all strings in the
array are highlighted. Simple strings and strings arrays can be mixed.
<?
str = "Welcome to the Biferno user manual"
str = str.Hilite(false, true, "<b>", "</b>", "manual", "Biferno")
print(str)
?>
This example generates the following HTML code:
Welcome to the <?b?>Biferno<?/b?> user <?b?>manual<?/b?>
Which produces the following output:
Welcome to the Biferno user manual
The same example can also be written as:
<?
str = " Welcome to the Biferno user manual"
arrHilite = array("manual", "Biferno")
str = str.Hilite(false, true, "<b>", "</b>", arrHilite)
print(str)
?>
The pre and post strings can contain some special symbols that are replaced with
the string str to be highlighted:
The ** characters are replaced by str.
The $$ characters are replaced by str coded by the UrlEncode
method (see the following section on string encoding).
The ## characters are replaced by str with ISO Latin encoding (see
the following section on string encoding).
<?
str = " Welcome to the Biferno user manual "
str = str.Hilite(false, true, "<a href=\"http://www.tabasoft.it/**/\">",
"</a>", "Biferno")
print(str)
?>
This example generates the following HTML code:
Welcome to the
<a href="http://www.tabasoft.it/biferno/">Biferno</a> user manual
Which produces the following output:
Welcome to the Biferno user manual
If a variable of the search class is passed to Hilite, all substrings contained in the
search are highlighted (see Chapter 15, Database Interaction).
The LowToUpper and UpToLower methods operate on the character case,
transforming lowercase characters into uppercase characters, and vice
versa. These methods have prototypes:
string LowToUpper(int from, int len)
string UpToLower(int from, int len)
The from parameter indicates the position of the first character in the
string that should be converted (the default value is 1). The len
parameter limits the number of characters to convert. The default is to
convert all characters starting from the position indicated by the from
parameter until the end of the string.
<?
str = "biferno"
str = str.LowToUpper()
print(str + "<br>\n")
str = str.UpToLower(3, 3)
print(str + "<br>\n")
str = str.UpToLower("len":2)
print(str + "<br>\n")
str = str.UpToLower(6)
print(str + "<br>\n")
?>
The example above produces the following result:
BIFERNO BIferNO biferNO biferno
The Capitalize method transforms into uppercase the first character of all
words contained in a string, where a word is a group of characters
delimited by spaces, line breaks, tabulators, punctuation marks, quotes,
parentheses, etc.
<?
text = " The PEN is mightier than the sword"
text = text.Capitalize()
// text is "The Pen Is Mightier Than The Sword"
?>
Notice that the Capitalize method acts on all characters of a word, not just on the first
character.
The SubString method returns a substring of any length of the given
string, starting from a given character index. The method has prototype:
string SubString(int from, int len)
The int and len parameters have a meaning similar to the one described for
the LowToUpper and UpToLower methods, i.e. the method extracts len
characters starting from the position indicated by the from parameter. If
the latter is omitted, all characters until the end of the string are
extracted.
<?
str = "This is a text"
$str.SubString(1, 4) // prints "This"
$str.SubString(6) // prints "is a text"
$str.SubString(6, 2) // prints "is"
?>
The InsertSubString method allows to insert a string within another string starting from
a given character index. The method has prototype:
string InsertSubString(int pos, string subString)
E.g.:
<?
str = "What a day!"
str = str.InsertSubString(8, "nice ") // str is " What a nice
day!"
?>
The RemoveSubString method removes a certain number of characters from a
string starting from a given position and has prototype:
string RemoveSubString(int from, int len)
This method has the same parameters of the SubString method and returns
the string obtained by removing len characters from the original string
starting from the position with index from, as in:
<?
str = "What a nice day!"
str = str.RemoveSubString(8, 5) // str is "What a day!"
?>
The Substitute method replaces a substring of the given string with
another and has prototype:
string Substitute(string oldString, string newString,
boolean cs = false, boolean skipHTML = false)
The cs and skipHTML parameters have the same meaning as in the Hilite
method. The oldString and newString parameters are, respectively, the
substring to search for and the string to be substituted.
<?
str = "john,paul,george,ringo"
str = str.Substitute(",","+") // str is "john+paul+george+ringo"
?>
The ToArray method converts a string into an array of strings. The array
elements are substrings of the original string which are extracted if
delimited by the specified separator. The original string is left
unmodified. This method has prototype:
array ToArray(string separator=", ")
The default separator value is ", ", i.e. a comma followed by a space. In
the following example the ToArray method applied to the str string
generates an array of four elements containing the strings "john", "paul",
"george", "ringo" in this order.
<?
str = "john,paul,george,ringo"
myArray = str.ToArray(",")
$myArray[2] // prints the string "paul"
?>
The Pad method is used to add a certain number of repetitions of a given
character in front or at the end of a string, until a predefined length is
reached. This method has the following prototype:
string Pad(int totChars, char padChar, boolean before = false)
An example is:
<?
str = "123"
str = str.Pad(8, "0", true) // str is "00000123"
str = "123"
str = str.Pad(6, "*") // str is "123***"
?>
The Encode method encodes a string according to the ISO 8859-1 format
(Latin-1). More precisely, Biferno uses the ANSI character set
(Windows-1252), which is an extension of the ISO 8859-1 set. E.g., the
Ŕ character is encoded as à (ampersand + hash + decimal
numeric code of the character + semicolon). This encoding is necessary to
be able to output special characters on a Web page, such as vowels with
accents, and symbols that might otherwise not be visualized correctly by
the browser (this is a potential issue on the MacOS platform).
The Encode method has the following prototype:
string Encode(boolean alsoCR=false, boolean tagsVisible=false,
boolean entities=false, obj tagList)
The alsoCR parameter specifies if we want to encode new line characters
with <br> tags (default: no). The tagsVisible parameters indicates
if we want to encode the < and > that delimit HTML tags with
< and > (default: no). This is useful if one wants to
visualize a fragment of HTML code within a Web page avoiding
interpretation by the browser. The < and > characters are always
encoded when they are not part of an HTML tag. The extTags parameter
determines what constitutes a tag. When tagList is true, all words
introduced by the < character are valid tags (as in XML). When extTags
is false, only HTML tags (such as <b>, <body>, etc.) are
considered tags. If an associative array is passed as the value of the
extTags parameter, the names of the elements of the associative array
determine what is considered a tag. Notice that the actual values of the
elements of the associative array are ignored. Finally, the entities
parameters specifies if we want that encoding is performed using the
so-called HTML entities instead of numerical codes (e.g. the ŕ
character is encoded as à and not as &224;).
In the following example we demonstrate how the effect of the Encode method changes by changing the values of these parameters:
<?
str = "<p>Letters with accents: ŕ, č, ě, ň, ů\nSpecial characters:
\", &, ©, ℗</p>"
$str.Encode() + "<br>\n"
$str.Encode(true) + "<br>\n"
$str.Encode(false, true)
?>
This example generates the following HTML code:
<p>Letters with accents: à, è, ì, ò, ù
Special characters: ", &, ©, ®</p><br>
<p>Letters with accents: à, è, ì, ò, ù
<br>Special characters: ", &, ©, ®</p><br>
<p>Letters with accents: à, è, ì, ò,ù
Special characters: ", &, ©, ®</p>
which in turn generates the following output:
Letters with accents: ŕ, č, ě, ň, ů Special characters: ", &, ©, ℗
Letters with accents: ŕ, č, ě, ň, ů
Special characters: ", &, ©, ℗
<p> Letters with accents: ŕ, č, ě, ň, ů Special characters: ", &, ©, ®<p>
The Decode method allows to decode a string encoded in ISO 8859-1 format
and has the following prototype:
string Decode(boolean alsoCR = false)
The alsoCR parameter indicates if we want to convert <br> tags into
new line characters (default: no).
A URL, acronym of Uniform Resource Locator, is a convention to describe a
resource available on the Internet. The UrlEncode method applies to a
string the encoding rules used in constructing a URL. All non-alphanumeric
special characters and spaces are replaced by the percent character (%)
followed by a two-digit hexadecimal code (e.g. the ŕ character is
replaced by %E0). This encoding is necessary to avoid misinterpretation of
special characters in a URL during network transmission.
This method has the following prototype:
string UrlEncode(boolean spaceToPlus, string pre)
The spaceToPlus parameter specifies if we want to replace space characters
with %20 (standard encoding) or with the + character (default: no). The
pre parameter provides the string to use in front of hexadecimal character
codes (default: "%").
Let's see a couple of examples of use of the UrlEncode method.
<?
str = "This string contains the special characters: / $ & %"
$str.UrlEncode()
?>
This code fragment produces the following output:
This%20string%20contains%20the%20special%20characters%3A%20%2F%20%24%20%26%20%25
It is often useful to encode a string in order to pass it a parameter in a URL by using UrlEncode:
<?
city = "Mexico City"
url = "http://www.xyz-travels.com/search.bfr?dest=" + city.UrlEncode()
?>
In this example the city string is converted to : "Mexico%20City".
The UrlDecode method allows to decode a URL-encoded string and has
prototype:
string UrlDecode(boolean plusToSpace, string pre)
The plusToSpace parameter specifies if we want to replace + characters in
the string with spaces (default: no). The pre parameter specifies the
string in front of hexadecimal character codes in the text to be decoded
(default: "%").
The pre parameter can be used to execute other types of encoding. E.g.
some Javascript calls require an encoding with a "\x" prefix string, as in
"\x2E" (notice that the backslash character must be escaped using another
backslash character in Biferno to avoid its interpretation as a special
character), and sometimes in emails the text must be coded using a "="
prefix string, as in "=2E".
We have described implicit conversion methods that allow automatic typecast from numbers (integers or with a decimal part) into strings. This kind of conversion is performed using an internal default format for numerical strings.
When it is necessary to convert a number into a string using a format other
than the default, we can use the ToString method of the primitive numeric
classes (int, long, double, ecc.), with prototype:
string ToString(boolean wantThousandSep = false, int decimals = 2,
boolean cutRightZero = true)
It is possible to specify if we want a thousand separator (default: no),
the number of digits after the comma (default: 2), and if the decimal part
should be padded with zeroes to reach the required length (default: no).
The ToString method should not be confused with the tostring method (see
Chapter 11, User classes), which allows automatic string
conversion for a user class (remember that identifiers in Biferno are case
sensitive).
The curScript.SetNumFormat static method allows to specify for a single current script within an application the
decimal and thousand separators to be used during string conversion,
temporarily replacing the application defaults defined in the
"Biferno.config.bfr" file (THOUSAND_SEP and DECIMAL_SEP). This method has
prototype:
void curScript.SetNumFormat(char thousSep, char decimSep)
The following example clarifies the use of these methods.
<?
a = 1234.567 // a is of classe double
b = a.ToString() //b is "1234,57" – notice rounding
c = a.ToString(true) //c is "1.234,57"
d = a.ToString(true, 1) //d is "1.234,6"
curScript.SetNumFormat(",",".")
e = a.ToString(true, 5) //e is "1,234.567"
f = a.ToString(true, 5, false) //f is "1,234.56700"
?>
The Eval function processes a string of text containing Biferno code. The
prototype is:
string Eval(string textToEval, boolean resume)
We can write:
<?
textToEval = "a = 3"
Eval(textToEval)
$a
?>
Line 3 of the example will print the value of the variable a, which is 3.
The a variable has been defined and assigned the value 3 when the text
passed to the Eval function was processed. Notice that the textToEval
string does not start with the "<?" characters. This is because the Eval
implicitly assumes a "<?" tag before processing the text. If the
textToEval string contains plain text, the text would have to be prefixed
with the "?>" characters. An example is:
<?
textToEval = "a = 3?><b>Hello Word</b>"
result = Eval(textToEval)
$result
?>
Notice that this behavior is different from the include behavior. In an
included file, before writing Biferno code, the "<?" must be explicitly
used.
The last example also shows the meaning of the return variable of the
function, which contains the entire text sent as output during execution.
The first example generated no output, and the function returned the empty
string. In the second example the result string contains the text:
"<b>Hello Word</b>".
A possible use of the Eval function is to process a text before sending it
via email. The following code sends three emails using a text file that is
interpreted via the Eval function:
<?
email_host = "mailserver.mydomain.com"
email_from = "me@mydomain.com"
email_to = "him@hisdomain.com"
userArr = array("John", "Bob", "Carl")
for (i = 1; i <= 3; i++)
{ username = userArr[i]
email_text = "Subject: SendMail Test \r\n\r\n"
email_text += Eval(file("myMailBody.bfr").Get())
status = smtp.SendMail(email_host, email_from, email_to, email_text)
}
?>
The text file is a template containing variable parameters that are subject
to change each time the code is run (in particular the username variable).
The content of the file could look like:
Dear $username$,
Your subscription has expired!
The Eval function can also be used to invoke a function (or class member)
whose name is contained in a variable and is not known in advance, as in
the following example:
<?
// myFunc is a value passed to the script (e.g. the string "Encode")
myString = "John & Co."
textToEval = "print(myString." + myFunc + "())"
// textToEval is "print(myString.Encode())"
result = Eval(textToEval)
?>
After execution of this script, assuming the string "Encode" has been
passed to the script in the myFunc variable, the result variable will have
the value: "John & Co.". Assuming the string "UrlEncode" was passed,
the value would be "John%20%26%20Co%2E".
Notice that the textToEval must contains the print command (or $, $$) to
output the result and therefore to be able to retrieve it from the result
variable.
What happens if the text passed to the Eval function (textToEval
string) generates an error? In this case the value of the third parameter
(resume, left to its default value, false, in the previous examples) is
crucial.
If an error is generated the following applies:
We can control if Eval will interrupt our script using the
error.Resume call in the textToEval string with the error handling
rules that will be described in Chapter 16, Error Handling and Debugging. Notice
that, as we will discuss in that chapter, some errors will interrupt
code execution even if error.Resume has been called, as e.g. the
Err_BadSyntax error. In any case, the code line after the call to the
Eval function the global variable global err will contain the code of
the generated error.
The Eval function will interrupt the execution of the text
contained in textToEval according to the error handling rules, but,
instead of interrupting the execution of the calling script upon an
error, will return the name of the generated error in the return
string. In any case, on the code line after the call to the Eval
function the global variable global err will contain the code of the
generated error.
Table of Contents
Biferno provides the file and folder classes for server-side file
management operations. In this chapter we will discuss Biferno pathname
conventions and then describe the main methods and properties of these
classes.
Biferno pathname conventions are similar to those used by the HTTP protocol with a few differences.
We can define a pathname in two ways:
Using a relative path, i.e. starting from the current Web directory or from the web server root directory.
Using an absolute path, i.e. tracing the full file pathname starting from the root directory of the file system.
Relative pathnames follow the HTTP protocol and HTML
language rules. The "mypage.bfr" path indicates that the file is in the
current Web directory, which is the directory of the .bfr file that is
executing. If the path starts with a slash (/), e.g. "/mypage.bfr", the
file is in the server or site root directory.
To clarify the difference, assume we have implemented on our web server a
virtual host called mysite.com that points to the "mysite" directory, which
is a subdirectory of the server root directory. According to the HTTP
protocol a file in the mysite.com domain with path "/mypage.bfr" is located
in the "mysite" directory. If from a script contained in the "mysite"
folder we want to refer to a file called "myfile.txt" in the same "mysite"
directory, we can use either the relative path ("myfile.txt") or the
absolute path from the site root ("/myfile.txt").
To identify an absolute path in Biferno we use the "file://" string in
front of the pathname, which starts with the disk name (unit or partition)
on Mac OS and Windows (e.g. file://Mac OS HD/Internet/Web pages/mysite/myfile.txt or
file://C/Inetpub/myFile.txt), or with the name of the
first directory on the path starting form the file system root on UNIX
(e.g. file://export/home/usr/mysite/myfile.txt).
The syntax of Biferno paths allows the use of the ".." characters to denote
the parent directory.
A path can identify a file or a directory. A path to a directory always ends with the slash character.
To discover the absolute path of the root directory of the Web server (or
the current application, if associated to a virtual host) we can use the
predefined property serverInfo.root. The corresponding
absolute path can be derived from a relative path using the static method
ResolvePath of the file class. We can write:
<?
realPath = file.ResolvePath("documents/doc00001.txt")
?>
As we will see in the following when discussing alias handling, this method resolves also any aliases that may be contained in the path.
A file path can also be obtained in its native format, which depends on the
platform (operating system and file system) we are working on, using the
static method NativePath of the file class. We can write:
<?
filePath = "file://C/Internet/Web pages/mysite/myfile.txt"
nativePath = file.NativePath(filePath)
?>
On Windows the result is "C:\Internet\Web pages\mysite\myfile.txt".
In Biferno a file class variable can be initialized by simply passing to
the class constructor a string containing the path (relative or absolute)
of the file to associate to the variable. In the following example we
instantiate three variables of the file class using the three kinds of path
previously mentioned:
<?
file1 = file("documents/doc00001.txt")
file2 = file("/mysite/documents/doc00002.txt")
file3 = file("file://home/usr/user1/mysite/documents/doc00003.txt")
?>
Normally, if the path passed to the constructor actually corresponds to an existing file on disk, the file is associated to the variable that is created and an attempt is made to open the file in read/write mode. If permissions do not allow to open the file in read/write mode, an error is generated.
This standard behavior can be modified by a number of optional parameters of the file class constructor, with prototype:
file(string path, int openMode, int permission)
Table 13.1. File class: Possible values of the openMode parameter
| openMode Value | File Opening Mode |
|---|---|
| openFileAlways | If the file does not exist, is created and opened. |
| openFileExisting | If the file does not exist, an error is generated. |
| createFileNew | Create a new file. If the file does already exist, an error is generated. |
| createFileAlways | Create a new file. If the file does already exist, is deleted and replaced by the newly created file. |
| dontOpen | Do not open the file, simply check path validity. |
The openMode parameter can assume one of the constant values listed in
Table 13.1, “File class: Possible values of the openMode parameter”. The default value for this parameter is
normally openFileExisting, except if the file is an alias. In this case the
default is dontOpen.
The permission parameter can assume the constant values rw (read/write) or
r (read only). The default value is rw.
The following example shows how to use the parameters:
<?
// If the file exists, is opened read-only
file1 = file("doc00001.txt", openFileExisting, r)
// If the file does not exist, is created and opened
file2 = file("doc00002.txt", openFileAlways)
// A new file is created and opened
file3 = file("doc00003.txt", createFileAlways)
?>
All constant values we have described so far are actually static public
constants of the file class. In the previous example we have seen how in
Biferno the class name can be omitted in front of static constants when
calling a method of the same class (including the constructor). In the
general case we would have to write something like
file.openFileExisting
After creating a file class object, the complete file path on disk is
stored in the read-only property path, while the read-only property name
contains only the filename. We have mentioned that a file class variable
can be created without opening the file on disk. A file can be opened and
closed using the Open and Close methods (without parameters). All files
initialized and opened by a script are automatically closed when the script
is terminated (or, more precisely, on the exit of the corresponding file
variable scope).
To check if a file is open on disk we can use the static method IsOpen of
the file class, with prototype:
static boolean IsOpen(string filePath)
This method returns true if the file that filePath points to is currently
open (by some process active on the system), false if it is not open. To
check if a file that an object of the file class points to is open, we use
the read-only property isOpen.
<?
if (file1.isOpen)
file1.Close()
?>
To know in advance if a file or directory exists we can use the static
method Exists of the file class, with prototype:
static boolean Exists(string path)
This method returns true if the path parameter is a valid path that points
to an existing file or directory, false otherwise.
A similar method is the CheckPath method, which returns the integer value 0
(zero) if the file exists, or otherwise returns the error code generated by
the file system.
We can check in a similar fashion if a path points to an alias (i.e., as we
will see in the following, to a link to a file) or to a directory, using
the static methods IsAlias and IsFolder of the file class. These methods
return a Boolean value and take as parameter a string containing a file
path. As for the IsOpen method, the read-only property isAlias corresponds
to the IsAlias static method.
Data can be written in a file using specific methods of the file class.
The Put method allows to write a string into a file with a given offset
(distance, in characters, from the beginning of the file) and has
prototype:
void Put(int offset, string text)
The inserted text overwrites the current content of the file starting from the given offset and, if necessary, increases the length of the file.
In the following example we create a new file called "test.txt" in the current directory and insert the test "This is the first line of the file" followed by a line feed.
<?
myFile = file("test.txt", openFileAlways)
myFile.Put(0, "This is the first line of the file\n")
?>
Since the file has just been created and is empty, we specify a zero offset. In general, the offset parameter has always to be less than the logical file size (total number of bytes, or characters).
To insert a new line of text in the file it is convenient to use the
Append method, which always adds the text at the end of the file. In the
case of the previous example, we can write the following code line:
myFile.Append("This is the second line of the file \n")
After the execution of this instruction the "test.txt" file contains the following two lines:
This is the first line of the file
This is the second line of the file
The Append method can also be used if the file is empty. In practice we
will use the Put method only when we want to replace the content of an
existing file with new data. Let's take the "test.txt" file we created and
replace the second line with another string.
<?
myFile = file("test.txt")
offset = 36
str = "New text"
myFile.Put(offset, str)
myFile.length = offset + str.length
?>
The offset value was computed by manually counting the characters of the
first row, including the line feed. Notice that, since the str string is
shorter than the second line in the file, it is necessary to adjust the
file length to eliminate the remaining characters. A file size can be
decreased or increased at will by directly operating on the length
property (with the limitations imposed by the physically available space
on disk).
To read data from a file we use the Get method, with prototype:
string Get(int offset, int len = all)
In this case the offset represents the number of characters to skip from
the beginning of the file before reading data. The len parameter
represents the total number of character that we want to extract. The
default value for len is all (predefined constant of the file class),
which extracts all characters until the end of the file is reached. The
Get method returns the characters extracted from a file in a string. In
the following example we assign the entire content of the "test.txt" file
to the text variable.
<?
myFile = file("test.txt")
text = myFile.Get(0, all)
text = myFile.Get() // same as the above
?>
A file can also be read one line at a time using the GetNextLine method
with prototype:
string GetNextLine(void)
A line of a file is defined as a sequence of characters followed by a new line (CR, LF or CR+LF). In the following example we extract and print one at a time all lines of the "test.txt" file.
<?
myFile = file("test.txt")
do {
$myFile.GetNextLine()
} while (myFile.curLine != file.EOF)
?>
To check if the loop reached the end of the file, i.e. when the last line
of text has been extracted, we check the value of the curLine property,
which contains the index of the current line (the line after the last line
extracted calling the GetNextLine method). After the last line has been
extracted (read), the curLine property contains the value corresponding to
the EOF (End Of File) constant of the file class.
The Get method ignores the value of curLine and in general the position
reached by GetNextLine on the current file. It is advisable to avoid
mixing calls to GetNextLine and calls to Get on the same file instance.
To rename a file we can use the Rename method of the file class and pass it a
string that contains the new name to be assigned to the file (just the
name, not the full path).
myFile.Rename("doc001.txt")
It is necessary to verify in advance if a file with the same name already exists in the same directory, otherwise the operation will fail.
To move a file from a directory into another directory, the file class provides
the Move method, with prototype:
void Move(string filePath, boolean replace = false)
The filePath parameter specifies the new path to assign to the file, or,
alternatively, the path to the directory that the file should be moved to.
The replace parameter specifies if the file that is moved should replace a
file with the same name in the target directory, if one exists. To move a
file into a subdirectory we can write:
<?
myFile.Move("docs/")
?>
In the following example we move a file into a subdirectory and rename an existing file with the same name, if present:
<?
fileName = "doc001.txt"
newPath = "docs/" + fileName
if (file.Exists(newPath))
{
oldFile = file(newPath)
oldFile.Rename(fileName + ".old")
}
myFile = file(fileName)
myFile.Move(newPath)
?>
Notice that renaming the old file may fail if the name to be assigned is already used by another file in the same directory. In real life every time the name or location of a file is changed, the possible presence of duplicates should be checked.
The Copy method of the file class allows to create a copy of a file in a
different location. This method has the same parameters as the Move method
and the same considerations hold. The new file created by the Copy method
is not automatically opened (if opening it is necessary, use the Open
method).
In practice one will often want to move (or copy) a file and rename it at
the same time. This can be done by specifying not only a new directory,
but also a new file name in the filePath parameter. An example of renaming
by using the Move method is:
<?
myFile = file("texts/text1.txt")
myFile.Move("archived/doc001.txt")
?>
The same can be done for the Copy method, a file can be duplicated and a new name can be
assigned to the copy at the same time.
To create and manipulate a directory (also called a folder) Biferno
provides the folder class, which is in many respects similar to the file
class. This section describes the fundamental operations that can be
executed on a directory using methods of the folder class.
When a folder class variable is instantiated by passing to the class
constructor a string containing the path to a directory, we can decide if
we wish to create a new directory or to have the object point to an
existing directory.
The folder class constructor has the same parameters of the file class
constructor, except the permission parameter. The prototype is:
folder(string path, int openMode)
The openMode parameter for the folder class can assume the values listed
in Table 13.2, “Folder class: Possible values of the openMode parameter”. The default value is folderExisting.
To create the "docs" directory in our site root we will write:
<?
docsFolder = folder("/mysite/docs/", createFolderIfNeeded)
?>
Table 13.2. Folder class: Possible values of the openMode parameter
| openMode Value | Directory Creation Mode |
|---|---|
| createFolderIfNeeded | Creates a new directory if the directory does not exist. Otherwise the object points to the existing directory. |
| createFolderNew | Creates a new directory if the directory does not exist. Otherwise an error is generated. |
| folderExisting | Does not create a new directory. If the directory does not exist an error is generated. |
If the directory specified by the path parameter exists, the docsFolder
variable points to the existing directory.
The static method:
static void Create(string path)
can be used to create a directory without instantiating a variable. This method has no effect if the directory exists.
To delete a directory we use the Delete method of the folder class, as in:
<?
myFolder = folder("garbage/")
myFolder.Delete()
?>
A directory can only be deleted if it is empty. As in the case of files,
the corresponding variable is no longer valid after deletion, even if the
path property still contains the path to the deleted directory.
To rename a directory we can use the Rename method of the folder class passing
a string that contains the new name to be assigned to the directory. An
example is:
<?
myFolder = folder("/mysite/docs/")
myFolder.Rename("documents")
?>
Using the Walk method of the folder class a directory can be examined and
operations can be executed automatically on every file contained in the
directory, without previous knowledge of the number, nature and names of
the files.
The Walk method has the following prototype:
int Walk(string suffix, boolean recursive, string callBack, obj *userData)
The suffix parameters denotes the type, i.e. the extension of the files we
want to examine. Passing the ".txt" string will filter only text files,
while the ".*" string will result in no filtering, i.e. all file types
will be examined. Notice that passing an empty string in the suffix
parameter will result in filtering only files with no extension. The
Boolean recursive parameter specifies if we want to explore all
subdirectories of the target directory. The callBack parameter is the name
of a function we want to execute on each file found in the target
directory. The userData optional parameter can be an object of any class
and can be only passed by reference.
The method returns an integer value that contains an error code generated
by the callback function. If there have been no errors during execution,
the function must return the value 0 (zero).
The callBack function must have the following prototype:
function int function_name (string filePath, int variant, obj *userData)
When the Walk method is invoked, it examines one by one all files present
in the directory, possibly discarding files that should be excluded on the
base of the value of the suffix parameter (hidden files are always
excluded). For each file it calls the callback function passing as
parameters the file path, a value (variant), which tells to the function
if the path points to an alias or directory, and, if present, the userData
object.
In the following example the Walk method is used to collect general
information on a directory (number of elements and their byte size). The
GetItemInfo callback function modifies the properties of an object of the
folderInfo class (defined at the beginning of our script) which, when the
Walk method terminates, contains all desired information on the target
directory. Notice the use of the variant parameter and of the isFolderBit
and isAliasBit constants of the file class. The size (number of characters
or bytes) is contained in the length property of the file class. The resForkLength property of the file class is meaningful only on the Mac OS
platform and contains the size of the resource fork of Macintosh file
(this property has always value zero on all other platforms).
<?
class folderInfo
{
unsigned totItems = 0
unsigned subFolders = 0
unsigned aliases = 0
unsigned totSize = 0
}
function int GetItemInfo(string filePath, int variant, folderInfo
*fInfo)
{
fInfo.totItems++
if (variant & file.isFolderBit) // is a directory
fInfo.subFolders++
else
{
if (variant & file.isAliasBit) // is an alias
fInfo.aliases++
theFile = file(filePath)
fInfo.totSize += theFile.length + theFile.resForkLength
}
return 0
}
fInfo = folderInfo()
myFolder = folder("file://HD/Projects/mysite/")
theErr = myFolder.Walk(".*", true, "GetItemInfo", &fInfo)
?>
<html>
<body>
Folder: $myFolder.path$<br>
Items: $fInfo.totItems$<br>
Subfolders: $fInfo.subFolders$<br>
Aliases: $fInfo.aliases$<br>
Total Size: $fInfo.totSize$ bytes<br>
</body>
</html>
This script will produce an output similar to the following:
Folder: file://HD/Projects/mysite/
Items: 24
Subfolders: 2
Aliases: 2
Size: 89835 bytes
The behavior of the Walk method is slightly dependent on the operating
system if the content of the folder is modified while executing the method
itself. In particular, on MacOS Classic the identification of a file is
implemented via a sequence number (position) within the folder and such
numbering might become inconsistent if files are removed from the folder
using the Walk method. For this reason it is not trivial to use the Walk
method to delete the content of a directory on the MacOS Classic platform.
As mentioned previously an alias is a pointer, or symbolic link, to a physical file or directory. In general an alias can reside in a different directory than the original. In computer jargon, resolve an alias means to obtain the path to the original file or directory that the alias points to.
![]() | Alias Support |
|---|---|
Beware that no operations on aliases are supported on the Windows platform. |
When instantiating a variable of the file class passing the path to an
alias, the class constructor does not automatically resolve the alias, but
rather returns an object that points directly to the alias. Conversely, if
a file path contains directory aliases, they are all automatically resolved
by the constructor, as well as by other methods of the file class that
accept a path as a parameter (Rename, Copy, Move, etc.).
An error message is returned if we specify in the constructor an opening
mode other than dontOpen (which is the default for aliases), because an
alias can not be opened. We have mentioned the IsAlias method, which
determines if a path points to a physical file or to an alias. Once
established that a path points to an alias, the ResolvePath static method
of the file class can be used to obtain the path to the original file. This
method has prototype:
static string ResolvePath(string aliasPath)
The following example shows the use of this method in combination with the
IsAlias method.
<?
myPath = "file://HD/WebSTAR Server Suite 4.1/mysite/test.txt"
if (file.IsAlias(myPath))
{
originalPath = file.ResolvePath(myPath)
myFile = file(originalPath)
}
?>
Alternatively, we can use the ResolveAlias method that, invoked on a file
class object pointing to an alias, returns a string containing the path to
the original file. The previous example can be rewritten as follows:
<?
myFile = file("file://HD/WebSTAR Server Suite 4.1/mysite/test.txt")
if (myFile.isAlias)
{
myFile = file(myFile.ResolveAlias())
}
?>
If, after the first initialization, the myFile variable points to an alias,
the alias is resolved and the variable is reinitialized to make it point to
the original file.
The Move, Rename and Copy methods of the file class applied to an alias act
on the alias and not on the original file.
We can create an alias to a file using the MakeAlias method of the file class, with
prototype:
void MakeAlias(string aliasPath)
An example is:
<?
myFile = file("test.txt")
// Create an alias in the same directory
myFile.MakeAlias("test_alias.txt")
// Create an alias in another directory
myFile.MakeAlias("file://HD/Aliases/test_alias.txt")
?>
An alias to a directory can be created in a similar way using the MakeAlias
method of the folder class.
On Linux file and directory access permissions can be modified at the owner, group and other granularity. The possible permission values are:
Read only
Read and write
Read, write and execute.
To modify permissions on a file or directory, we use the static method
fchmod (common to the file and folder classes). This method takes two
parameters, the file or directory path, and an integer resulting from the
sum of predefined constants for the file and folder classes. The constants
are listed in Table 13.3, “File class: Predefined constants for file and directory access
permissions on Linux”.
Table 13.3. File class: Predefined constants for file and directory access permissions on Linux
| Predefined Costant | Corresponding Access Permission |
|---|---|
| S_IRGRP | Read only for the group. |
| S_IROTH | Read only for other users. |
| S_IRUSR | Read only for the owner. |
| S_IWGRP | Read and write only for the group. |
| S_IWOTH | Read and write for other users. |
| S_IWUSR | Read and write for the owner. |
| S_IXGRP | Read, write and execution for the group. |
| S_IXOTH | Read, write and execution for other users. |
| S_IXUSR | Read, write and execution for the owner. |
To assign read/write permission to the owner and read-only permission to
everybody else on the file corresponding to the myFile variable we write:
file.fchmod(myFile.path, S_IWUSR + S_IRGRP + S_IROTH)
Information on the permissions assigned to a file or directory can be
obtained using the fgetmod static method (common to the file and folder
classes). An example is:
permission = file.fgetmod(myFile.path)
On the Mac OS platform we can obtain and, if desired, modify the type and
creator of a file using the corresponding properties osType and osCreator
of the file class. In the following example, after verification that the
platform is "MacOS", we check that a file is a text file and assign it as
creator the signature of the “SimpleText” application:
<?
if (biferno.os.Begins("MacOS"))
{
if (myFile.osType == "TEXT")
myFile.osCreator = "ttxt"
}
?>
The two properties osType and osCreator are meaningful only on the Mac OS
platform and contain the empty string on all other platforms.
The biferno.os predefined property returns a string that identified the
operating system of the server running Biferno. This string always starts
with the generic system name followed by a : character (colon), e.g. with
"MacOS:" on the Macintosh Classic platform, with "Unix:" on Linux and
MacOSX, and with "Windows:" on Windows. The rest of the string is the
system version.
For all other properties of the file and folder classes (user, group,
modifTime, creatTime, etc…) see the online Reference Guide as
referenced in Chapter 2, Documentation.
Table of Contents
This chapter discusses methods and properties of the time class that support
manipulation of strings representing date and time in various formats.
Before discussing how to initialize and use a time class variable, it
is a good idea to say a few things about how date and time is internally
represented by operating systems.
In general, a certain time point is identified by a computer as the number of seconds (or ticks, one tick is 1/60th of a second) from a certain "time zero" or " epoch time", which varies from system to system. In many UNIX version this corresponds to 00:00:00 GMT (Greenwich Mean Time) of January 1, 1970. On Mac OS time zero is midnight of January 1st, 1904. Biferno adopts the convention of using 00:00:00 of January 1, 1970 as the time zero for the time class.
The above technique to represent an instant in time, i.e. a date including the time with second precision, is called timestamping.
Initializing a time class variable means to create an object associated to
a certain timestamp. The constructor of the time class has the following
prototype:
time(string timeString = current, string formatString)
To associate the current server date and time to a variable, we will write:
<?
now = time()
?>
If we print the now variable we obtain an output similar to the following:
23-07-2001 16:40:49
This is the string representation of the current timestamp when the variable was initialized. This string is returned by the implicit string conversion method of the time class. A time class variable can also be initialized passing to the constructor a string representing a valid date and time, and an optional format string. In this case the created object is associated to the timestamp corresponding to the specified date. If the string supplied to the constructor doe not correspond to a valid date, an error message is generated. An example is:
<?
date1 = time("24-3-1974")
date2 = time("24/3/1974 15:30", "d/m/y")
date3 = time("3-24-1974 15.30.00", "m-d-y h.")
?>
The format string, if present, determines the order in which day (d), month
(m) and year (y) appear in the date string and the separator character used
(typically a dash or slash, other characters can be used). The hours,
minutes, seconds separator can be specified after the h character (see
third line in the previous example). The default format depends on the
default OS settings. This value can be altered by modifying the application
variable DATE_FORMAT in the "Biferno.config.bfr" configuration file and
assigning it the format string that we want as default for our application.
An example is:
DATE_FORMAT = "y/m/d h:"
Dates that fall between Biferno's time zero and December 31st, 2036 are handled correctly by Biferno. A date outside of these bounds can be passed to the constructor of the time class, but some properties and methods, in particular those executing conversions and formatting, may report erroneous data.
Date and time information can be obtained from a time class variable in
several formats. The Date and Hour methods return a string containing the
date and time corresponding, respectively, to the timestamp of the
variable, as in:
<? now = time() ?> <html> <body> Good morning.<br>It is $now.Hour()$ o'clock of $now.Date()$. </body> </html>
This script produces and output similar to the following:
Good morning. It is 10:12:43 o'clock of 24-07-2001.
To obtain more specific information from a time class variable a number of
properties are available that contain the separate numerical values of day
(day), month (month), year (year), hour (hour), minutes (minute) and
seconds (second).
The day of the week that corresponds to a particular date is represented by
the dayOfWeek property, which contains a numerical index ranging from 1
(Sunday) to 7 (Saturday); the indices correspond to the sunday, monday,
tuesday, wednesday, thursday, friday, saturday constants of the time class.
Operations on dates and times are also supported. The individual elements of an object of the time class can be incremented or decremented, and dates and times can be added or subtracted. This example shows how to compute tomorrow's date:
<?
tomorrow = time()
tomorrow.day++ // Increment the current day by 1
?>
An alternative is:
<?
tomorrow = time()
tomorrow.hour += 24 // Add 24 hours to the current time
?>
To add two objects of the time class means to add the respective
timestamps. If the value resulting from an operation is outside of the
allowed range, the value is automatically adjusted, as in:
<?
tomorrow = time("31/1/2001")
tomorrow.day++ //the variable contains the value: 1/2/2001
?>
The ToSecs method of the time class returns the timestamp corresponding to the date
represented by the object, i.e. the number of seconds from Biferno's time zero. Notice that
the result is operating system dependent, and can be relative either to the current time zone
or to the Greenwich time zone (universal time).
The UString method returns a string containing date and time in the
Universal Time format. This method is normally used in combination with the
GMT method, which returns the timestamp value corresponding to the
Greenwich time zone, as in the following example:
<?
now = time()
$now.GMT().UString()
?>
The script will produce a result similar to the following:
Tue, 24 Jul 2001 10:48:25 GMT
Sometimes it is necessary to have the ability to format date and time in
very different ways. Towards this end the time class provides the Strftime
method, with prototype:
string Strftime(string format)
This methods returns a string containing date and time formatted on the
base of the specific format descriptor passed with the format parameter.
Table 14.1, “Format descriptors for the Strftime method” lists all descriptors that can be used with the
Strftime method together with their meaning.
Each descriptor consists in a percent symbol followed by a lowercase or
uppercase letter. Any other text in the format string is not interpreted
and is returned unaltered. A percent sign in the format string must be
escaped by using a percent sign in front of it. The following script shows
how to use the Strftime method and format descriptors.
<?
now = time()
print(now.Strftime("%a %B %d, %Y ore %X") + "<br>\n")
print(now.Strftime("%d-%m-%y %X") + "<br>\n")
print(now.Strftime("%d/%m/%Y %I:%M %p") + "<br>\n")
print(now.Strftime("%x (day %j of the year)") + "<br>\n")
print(now.Strftime("%a %b %d/%y (%W-th week)") + "<br>\n")
?>
This script, run at 15:50 on 30/7/2001, generated the following output:
Mon July 30, 2001 ore 15:50:20
30-07-01 15:50:20
30/07/2001 03:50 pm
Monday July 30 2001 (day 211 of the year)
Mon Jul 30/01 (31-th week)
Table 14.1. Format descriptors for the Strftime method
| Descriptor | Is replaced by... |
|---|---|
| %a | Abbreviated name of day of the week |
| %A | Complete name of day of the week |
| %b | Abbreviated name of month (also obtained with '%h') |
| %B | Complete name of month |
| %c | Date and time in standard format |
| %d | Day of the month as an integer number |
| %H | Time in 24-hour format (from 00 to 23) |
| %I | Time in 12-hour format (from 01 to 12) |
| %j | Day of the year as an integer number (from 001 to 365) |
| %m | Month as an integer number (from 01 to 12) |
| %M | Minutes in decimal format |
| %p | The AM or PM string, depending on the time |
| %S | Seconds in decimal format |
| %U | Week of the current year as an integer number (the first week of the year starts with the first Sunday of the year) |
| %W | Week of the current year as an integer number (the first week of the year starts with the first Monday of the year |
| %w | Day of the year as an integer number (from 0 to 6) |
| %x | Date in standard format |
| %X | Time in standard format |
| %y | Year with two digits |
| %Y | Year with four digits |
| %Z | The time zone or the standard abbreviation for the time zone |
Table of Contents
Biferno can interact with the most common commercial and open source relational DBMS. Two implementation of the database interface are available, using either native drivers (when available) or ODBC (Open DataBase Connectivity). ODBC is a standard database access method that can be used by a client application to request data from a DBMS. DBMS interaction uses SQL (Structured Query Language). SQL is a standard query language that allows to analyze, read, write, delete and modify data stored in a relational database.
This chapter illustrates the functionality provided in Biferno to communicate with a database. We assume a good understanding of SQL in the examples that will be presented in the following, since a detailed discussion of SQL as a query language is well beyond the scope of this manual.
![]() | How does ODBC work? |
|---|---|
ODBC is an API (Application Programming Interface) that client applications can use to request data from a server. Client applications using ODBC send their queries to an ODBC driver manager that runs on the same client machine. The ODBC driver manager recognizes the data source specified in the query and knows which ODBC driver is associated to that data source. The query, coded in the SQL query language, is sent via the appropriate driver to the database on the server, and the response is returned to the client application via the ODBC driver manager. |
![]() | What is a DSN? |
|---|---|
A DSN (Data Source Name) can be seen as a shortcut that allows a client application to connect directly to a database using ODBC. A DSN identifies a database with a unique name, while additional information is associated to the corresponding ODBC data source (among others, database server address and security options, such as username and password). We refer the interested reader to the in-depth discussion of ODBC in Roger E. Sanders, "ODBC 3.5 - Developer's Guide", McGraw-Hill. |
Before discussing how to interface to a database with a Biferno script it is necessary to briefly describe the elements that define an SQL database and to clarify the terminology we will use in the following.
Until now we have used the word database indifferently to refer to the DBMS as well as to the true database. A database is actually a data structure managed by a DBMS. Many individual entities, which are separate but correlated, can exist within a database: tables, indices, sequences, etc. The actual data is stored in tables, which are organized in columns (fields) and rows (records).
For exemplification purposes, assume to have access to a DBMS where, in a
database called mydb, we have created a users table that contains
information on the users of an on- line service. The information is
structured along the same lines as described in the description of the user
class in Chapter 11, User classes. To connect to the database we
will use an ODBC data source with a DSN called MYDB. We assume that the
corresponding data source has been previously defined via the ODBC driver
manager.
Table 15.1. Structure of the users table
| user_id | first_name | last_name | sex | age | username | password |
|---|---|---|---|---|---|---|
| 1 | James | Kirk | M | 38 | jtkirk | captain |
| 2 | Leonard | McCoy | M | 42 | lmccoy | bones |
| 3 | Montgomery | Scott | M | 51 | scotty | beammeup |
| 4 | Nyota | Uhura | F | 34 | nuhura | commoff |
| 5 | Pavel | Chekov | M | 28 | pcheckov | navigator |
Table 15.1, “Structure of the users table” shows the structure of the users table. The
table is organized in seven columns and contains five rows (records). The
user_id column contains a unique numerical index for each record, which is
used as a primary key during search and comparison operations. The values
of this column are automatically calculated and inserted by the DBMS when a
new record is created.
Finally we are ready to show the procedure used in Biferno to connect to a data source via ODBC (connection via native drivers uses a similar approach).
A db class variable, which represents the connection itself, i.e. a
communication channel with the database, has to be instantiated. After this
step, methods of the db class applied to the corresponding object allow
database interaction. The constructor of the db class is as follows:
db(string initString, string db)
The initString parameter identifies the connection string that contains all
necessary parameters to connect to the database. The db parameter
identifies the kind of database that we want to connect to. For an ODBC
connection the connection string has to contain the username and password
to be used to gain database access (if requested), and the db parameter
must have value "odbc". An example is:
dbconn = db("DSN=datasource;UID=username;PWD=password", "odbc")
If we use a native driver, the connection string is different. An example is the connection to a MySQL database via the Biferno native driver, which uses a syntax similar to the following:
dbconn = db("mysqlhost, mysqluser, mysqlpassword, mydb", "mysql")
The db parameter is the name of the driver, which is usually the same as
the DBMS name (the parameter is case-sensitive).
The connection string syntax (first constructor parameter) and the driver name (second constructor parameter) are listed in the following table for several native drivers available at the time of this writing:
Table 15.2. Connecting via Native Drivers
| Database | Connection String | Driver Name |
|---|---|---|
| MySQL | "[host], [user], [password], [db]" | "mysql" |
| Postgres | "hostaddr=[host] dbname=[db] user=[user]" | "postgres" |
| Oracle | "[instance], [username], [password]" | "oracle" |
With the exception of the parameters of the constructor method, all methods
of the db class do not depend on the particular DBMS or ODBC used. The
following discussion is independent of the particular database used, unless
explicitly noted.
This section discusses a practical example of database interaction and
illustrates the main methods of the db class that allow to execute SQL
queries on a database and to obtain the resulting information.
In the following script we connect to the MYDB data source (which we will
use in all our examples), then we interrogate the users table and send in
output to the browser the content of all records, formatted as an HTML
table.
<html>
<body>
<?
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
query = "SELECT first_name, last_name, sex, age FROM users"
dbconn.Exec(query)
nrec = dbconn.GetCurRecs()
if (nrec)
{
?>
<table border="0" cellpadding="4" cellspacing="0">
<?
for (i = 1; i <= nrec; i++)
{
recArray = dbconn.FetchRec()
?>
<tr>
<td>$recArray["first_name"]$</td>
<td>$recArray["last_name"]$</td>
<td>$recArray["sex"]$</td>
<td>$recArray["age"]$</td>
</tr>
<?
}
?>
</table>
<?
}
?>
</body>
</html>
After instantiation of the dbconn variable of class db, we send an SQL
query to the database using the Exec method on the dbconn object. This
method has the following prototype:
int Exec(string sql_statement, int mode=defaultMode, int rowSetSize,
boolean freeCurs)
The Exec method sends the query contained in the sql_statement parameter to
the database and returns an integer number that represents a cursor
identifier. In an SQL- capable database, a cursor indicates the current
position within a group of records resulting from the execution of an SQL
query (record set). A cursor determines which record in a record set will
be returned to the application at the next data request from that record
set. A cursor can be considered as an entity identifying both the whole
record set and the current record position within the record set.
In the previous example the cursor has not been explicitly used because
most methods of the db class operate by default on the current cursor,
which is the one returned by the last call to the Exec method.
The mode parameter of the Exec method specifies the type of cursor that
should be created (static or dynamic) and can assume the values defined by
the defaultMode, staticMode and dynamicMode constants of the db class. The
default value of this parameter is defaultMode, and in this case the db
class chooses the most efficient cursor type depending on the driver and
database type.
![]() | Static and Dynamic Cursors |
|---|---|
A static cursor is a cursor associated to a static record set resulting from an SQL query. Static cursors do not report modifications made by other application (or by other connections/users of the same application) to the record set after their creation, but they do recognize modifications made by the same application within the same connection. Static cursors can request all records in a single query to the database server. A dynamic cursor reports all modifications to its record set after its creation, including those made by other applications or users. Finally, dynamic cursors usually request records to the database server in a sequence of steps. |
The freeCurs parameter can be used to indicate that the cursor can be
automatically disposed by Biferno because it will be no longer used in the
code (see the Section "Cursor Deletion" in the following). The rowSetSize
parameter specifies the number of records that should be extracted by the
cursor when the application performs a data request to the current record
set. For performance optimization reasons it is advisable to use a value
corresponding to the number of records that the application will present
together on a page. If no value is specified for the rowSetSize parameter,
a default value is chosen that optimizes performance for the driver used.
This cursor characteristic can be modified also after its creation using
the RowSetSize method, with prototype:
void RowSetSize(int size, int cursorID)
If the cursorID parameter is omitted, the method acts on the current
cursor, which, as mentioned, is the common behavior for all db class
methods that operate on cursors.
Going back to the previous example, after execution of SQL query, the
GetCurRecs method is invoked on the dbconn variable. This method returns
the total number of records for a cursor, and has prototype:
int GetCurRecs(int cursorID)
If an SQL query modifies, inserts, or deletes records from a table, the
number of affected records can be discovered calling the GetAffectedRecs
method, with prototype:
int GetAffectedRecs(int cursorID)
To extract a record from a cursor the FetchRec method is available with prototype:
array FetchRec(int cursorID, boolean undefNULL)
When invoked, the FetchRec method advances the cursor to the record
following the current record in the record set, and returns in an
associative array the record content. On the first invocation the FetchRec
method returns the first record of the current record set. Only values
corresponding to columns specified in the SQL query are returned in the
array. If the true value is passed as the undefNULL parameter, array
elements corresponding to null values (NULL) in the record are undefined
(notice that the default for null values is to contain the empty string).
In our example, after the first call to the FetchRec method, the recArray
array will have the following structure:
array(first_name:James, last_name:Kirk, sex:M, age:38)
Element index names in the associative array that is returned correspond to
column names in the users table that are included in the SQL query "SELECT
first_name, last_name, sex, age FROM users". Values of the array elements
returned by the FetchRec methods are always of the string class,
regardless of the native type of table columns.
Calling the FetchRec method in a loop causes the cursor to iterate through
the record set extracting records one at a time. When the cursor is
positioned on the last record of the set, the next invocation of the
FetchRec method returns an empty array. Based on this behavior, the loop in
the previous example can be rewritten as follows, using a while instruction
instead of the for instruction:
<?
while (recArray = dbconn.FetchRec())
{
...
}
?>
The loop exit condition is based on the size of the recArray array (when
the size is zero, i.e. the array is empty, the loop terminates). This shows
how the implicit typecast from the array class to the boolean class is
actually done on the base of the dim array property. In other terms,
writing: if (my_array) has the same effect as writing: if (my_array.dim >
0).
The main characteristic (and limitation) of the FetchRec method is that
the cursor can be moved only forwards in a sequential fashion. To position
the cursor on an arbitrary record of the current set, we can use the Seek
method with prototype:
void Seek(int index, int cursorID)
The index parameter represents the numerical index of the record that we
want to position the cursor on. If we write:
dbconn.Seek(1)
we will position the current cursor on the first record of the set. If the
specified index is greater than the number of records in the current set,
the cursor is positioned after the last record and a successive invocation
of the FetchRec method returns an empty array. The index of the record
currently under the cursor can be obtained using the Tell method with
prototype:
int Tell(int cursorID)
An example is:
curRecIndex = dbconn.Tell()
When a script uses more than one cursor, it is sometimes necessary to
delete a cursor that is no longer necessary, because there is a limit in
Biferno on the number of cursors that can be simultaneously used on the
same db variable. Cursor deletion also optimizes database performance by
freeing resources.
To eliminate a cursor the Free method is available with prototype:
void Free(int cursorID)
There are cases when the cursor is no longer necessary after the Exec
method was called. In such cases a value of true can be passed to the
fourth argument of the Exec (freeCurs), which causes the cursor to be
automatically deleted and eliminates the need for a subsequent call to
Free. Usually the cursor is necessary when executing SELECT-like SQL
statements to be able to subsequently invoke the GetCurRecs, FetchRec etc.
methods. Sometimes the cursor is ignored for SQL statements such as
UPDATE, DELETE, INSERT (nonetheless the cursor might still be necessary,
e.g. to be able to execute the GetAffectedRecs method).
All cursors associated to a database connection are automatically
eliminated when the corresponding db class variable is deleted. This is
because cursors associated to database connections stored in variables
(local or global) having the current script as their scope are deleted
when script execution terminates. If we use cursors associated to
connections stored in variables with wider scope (e.g. application), these
cursors will have to be explicitly eliminated using the Free method when
they are no longer necessary or when the script is exited.
We have described how to execute an SQL query using the Exec method, which
immediately submits the query to the database asking immediate execution.
This method should be used when a query is used only once within a Biferno
script.
For queries that will be repeatedly used the ExecPrepared method can be a
better alternative. With this method queries are prepared using the Prepare
method, which only submits them to the database server for later execution.
It should be noted that, in Biferno, not all DBMS have native support for prepared queries.
In practice it is only worthwhile to use the two-step SQL query strategy (preparation, execution) if we make use of parametric queries, because in this case the same query template can be used multiple times with different values.
The following script shows a simple preparation and execution sequence for a non-parametric SQL query with the goal of illustrating the methods that are used. Parametric SQL queries are discussed in the following.
<?
// Database connection
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
// Query preparation
query = "SELECT * FROM users WHERE last_name = 'Kirk'"
prep_id = dbconn.Prepare(query)
// Selection and execution of the prepared query
curs_id = dbconn.GetPrepared(prep_id)
dbconn.ExecPrepared(curs_id)
// Processing of results
if (dbconn.GetCurRecs(curs_id))
{
recArray = dbconn.FetchRec(curs_id)
}
// Cursor deletion
dbconn.Free(curs_id)
?>
After the database connection has been established we prepare our SQL query
calling the Prepare method with prototype:
int Prepare(string sql_statement, int totPrepared=1, int mode=defaultMode,
int rowSetSize)
This method prepares the query corresponding to the sql_statement parameter
and returns an integer value representing a unique identifier associated to
the prepared query.
The totPrepared parameter specifies the number of queries to be prepared,
i.e. the number of cursors associated to the query that are created by the
database at preparation time. It might be convenient to prepare in advance
(when the application is started) a certain number of parametric queries,
which might be the maximum number of users that can access the application
at the same time. In this way the application performance can be improved,
especially if using ODBC, because the execution of prepared queries
requires fewer information exchanges between the client and the database.
The difference can run as high as 50%.
The mode and rowSetSize parameters have the same meaning as for the Exec
method. Before executing a previously prepared query, the corresponding
cursor must be obtained from the database. Toward this end we use the
GetPrepared method, passing as a parameter the identifier of the prepared
query we want to execute. The prototype is as follows:
int GetPrepared(int prepareID)
The invocation returns a new cursor for the prepared query (identified by
prepareID). To execute the prepared query we call the ExecPrepared method,
passing the cursor identifier obtained from the GetPrepared method as
parameter. The prototype of the ExecPrepared method is as follows:
void ExecPrepared(int cursorID)
After query execution, the results can be processed acting on the cursor
using the previously described methods (GetCurRecs, FetchRec, etc.).
Considering the previous example, assume that the string corresponding to
the surname to retrieve from the users table is obtained from user input
(e.g. from a text field within a HTML form). If we call lname the variable
associated to the value inserted by the user, we can dynamically build our
SQL string as follows:
<?
lname = db.Escape(lname)
query = "SELECT * FROM users WHERE last_name = '" + lname + "'"
?>
The static method Escape of the db class transforms a literal string into
the appropriate format for insertion in an SQL statement. The prototype is
as follows:
static string Escape(string str)
This method escapes single quotes that might be present in the string by
doubling them (see also the RealEscape method of class db in "Biferno: Reference Guide").
A parametric SQL query contains internally substitution markers (a
question mark "?"), which may be substituted by values before query
execution.
SQL markers can be used in the WHERE clause of a SELECT statement, in the SET clause of an UPDATE statement, and in the VALUES clause of an INSERT statement. Markers can not be used to make table or column names parametric.
We will now modify the script used as an example of preparation and execution of an SQL query to use a parametric query. The first step is to prepare the query, e.g. by inserting the following code rows in the "Biferno.config.bfr" configuration file of our application:
<?
// Database connection
application aDBconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
// Query preparation
local query = "SELECT * FROM users WHERE last_name = ?"
application aQuery_selname = aDBconn.Prepare(query)
?>
Since the database connection must be established before a query can be prepared, the connection must be established in the "Biferno.config.bfr" file.
This technique allows to open a single database connection at application
startup. By associating the connection to a variable with application
scope, the connection will be visible to and usable by all application
scripts, avoiding reconnection at every request.
The following script assigns the value "Kirk" to the lname variable (in
practice this variable is likely to be associated to an input value) and,
after associating the variable to the query marker using the Bind method,
executes the query and processes its results.
<?
lname = "Kirk"
// Selection of prepared query
curs_id = aDBconn.GetPrepared(aQuery_selname)
// Association of the lname variable to query marker
aDBconn.Bind(1, "lname", lname.length, inputBindMode, curs_id)
// Execution of prepared query
aDBconn.ExecPrepared(curs_id)
// Results processing
if (aDBconn.GetCurRecs(curs_id))
{
recArray = aDBconn.FetchRec(curs_id)
}
// Cursor deletion
aDBconn.Free(curs_id)
?>
The Bind method allows to associate a variable to a marker in an SQL query
and has the following prototype:
void Bind(int pos, string variableName, int bytes, int mode, int cursorID)
When the query is executed via the ExecPrepared the current value of the
variable specified by the variableName parameter is substituted to the
marker corresponding to the position identified by the pos parameter and
the resulting SQL statement is submitted to the database (more precisely,
the pos parameter specifies the index of the occurrence of the ? character
within the SQL string).
The bytes parameter denotes the length of the variable called
variableName. The mode parameter is the kind of Bind that we are
executing. In this case we are passing an input variable and therefore we
use the db.inputBindMode value. The Bind method can be also used to
obtain return values, e.g. from a stored procedure. In this case
db.outputBindMode or db.inputOutputBindMode should be used for the mode
parameter.
The cursorID parameter supplies the cursor identifier returned by the
GetPrepared method.
The Bind method can associate a variable that has not been defined. The
important point is that this variable must be defined when the
ExecPrepared method is called, or an error will be generated.
The usefulness of the Bind method is apparent when the value of the
variable associated to the SQL parametric string is modified within a
loop. In the following example we see how the value corresponding to an
array element with index varying within a for loop can be associated to a
parametric query.
<html>
<body>
<?
namesArray = array("John", "James", "Andrew", "Leonard")
names = namesArray.dim
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
query = "SELECT first_name, last_name, sex, age FROM users WHERE first_name = ?"
prep_id = dbconn.Prepare(query)
curs_id = dbconn.GetPrepared(prep_id)
dbconn.Bind(1, "namesArray[i]", namesArray[i].length,
inputBindMode, curs_id)
for (i = 1; i <= names; i++)
{
dbconn.ExecPrepared(curs_id)
nrec = dbconn.GetCurRecs(curs_id)
if (nrec)
{
?>
<table border="0" cellpadding="4" cellspacing="0">
<?
while (recArray = dbconn.FetchRec(curs_id))
{
?>
<tr>
<td>$recArray["first_name"]$</td>
<td>$recArray["last_name"]$</td>
<td>$recArray["sex"]$</td>
<td>$recArray["age"]$</td>
</tr>
<?
}
}
?>
</table>
<?
}
dbconn.Free(curs_id)
?>
</body>
</html>
Internally to the for cycle the ExecPrepared method is called. The method
executes the parametric query, where the value of the element of the
namesArray array corresponding to the current value of the loop index (i)
is substituted to the ? marker. Notice how the array name followed by the
index between square brackets is passed to the Bind method. Of course both
the array and the index must be defined when the ExecPrepared method is
called. With this technique, the search string is changed automatically at
every loop iteration.
The BindAll method is similar to the Bind method, and allows to associate
a variable to all cursors associated to a prepared query (the Bind method
associates the variable to a single cursor). The BindAll method has the
following prototype:
void BindAll(int pos, string variableName, int bytes, int mode, int poolID)
Let's see an example of the use of this method. In the "Biferno.config.bfr" configuration file of our application we write the following code lines:
<?
application aDBconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
local query = "SELECT * FROM users WHERE last_name = ?"
application aQuery_selname = aDBconn.Prepare(query, 16)
aDBconn.BindAll(1, "lname", 255, inputBindMode, aQuery_selname)
?>
Using the Prepare method 16 identical parametric queries are sent to the
database and the database generates 16 cursors associated to the
parametric query. On the next line, the lname variable (which is not yet
defined when the "Biferno.config.bfr" is executed) is associated to all 16
cursors associated to the prepared query by calling the BindAll method and
passing the query identifier returned by the Prepare method. The BindAll
method must be called right after preparation of the query and before the
GetPrepared method is invoked. Notice that the length of the lname
variable is not known when the BindAll method is called, and thus the
value 255 is passed for the bytes parameter. When executing the prepared
query (ExecPrepared call), the code should check that the length of lname
does not exceed 255 bytes.
Using this technique we avoid calling the Bind method multiple times to
associate the lname variable to the individual cursors returned by the
GetPrepared method in the application scripts where the prepared query
will be executed.
A transaction is a sequence of one or more SQL queries executed by an application that are grouped in a single logical unit in such a way that their effect on the database can be reversed. The queries typically modify the database (e.g. by record, table or index insertion, modification, deletion).
The documentation of the database we interface to should be carefully consulted, because not all DBMS support transactions. In the ODBC case transactions support is defined by the specific driver. In the MySQL case, for which Biferno implements a native MySQL driver, transactions are also supported.
The db class makes three methods available to manage transactions:
Transaction, to initiate a transaction.
Commit, to finalize the result of a transaction.
RollBack, to undo the result of a transaction.
All these methods have no parameters and do not return any value. The following script provides an example of the use of these methods:
<?
// Database connection (e.g. via ODBC)
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
// Start of a transaction
dbconn.Transaction()
// Query execution (e.g. record modification)
query = "UPDATE users SET age = 40 WHERE user_id = 1"
dbconn.Exec(query)
// End of the transaction and finalization of the modification
dbconn.Commit()
// or, to undo the database modification
// dbconn.RollBack()
?>
It should be noted that both the Commit and the RollBack methods close the
transaction and that the RollBack method can not be used to undo the effect
of a transaction closed by Commit method.
This class supports decoding of a string containing logical operators (e.g.
| or &) and parentheses to make it easier to manipulate the substrings
(keywords) relevant to a database search. In other terms, this class is
useful to execute archive searches on the base of formatted strings input
by the user in a HTML form.
The search class is only used to prepare the string and can not be directly
used to execute a database search. To do this, the search string supplied
by the user must be first translated into an SQL string of equivalent
meaning using the search class.
Table 15.3. Operators supported by the search class in search strings
| Logical Operator | Characters used |
|---|---|
| AND | Ampersand (&) |
| OR | Vertical bar (|) |
| NOT | Exclamation mark (!) |
| Wildcard character | Star (*) |
To instantiate a search class variable a suitably formatted string should
be passed to the class constructor. Table 15.3, “Operators supported by the search class in search strings” lists
the operators supported in a formatted search string for the search class
(default characters).
The following example of search strings clarify the use of these operators:
<?
mySearch = search("movies & sport")
mySearch = search("movies | sport")
mySearch = search("sport & !tennis")
mySearch = search("movi*")
?>
The first search string means: Find all elements containing both "movies"
and "sport". The second string means: Find all elements containing either
"movies" or "sport". The third string means: Find all elements containing
"movies" but not "sport". The fourth string means: Find all elements
containing words starting with "movi".
The wildcard character can be used either as the first character of the search substring, or as its last character, or both as its first and last character. Its meaning is "starts with", "ends with", or "contains" the substring, respectively.
More complex strings can be implemented using parentheses, as in:
<?
mySearch = search("movies & (action | horror)")
mySearch = search("(soccer & maradona) | (cars & senna)")
?>
Different characters (or strings) can be specified for the operators that
can be used in search strings, both at application and script level. The
default application level operators can be changed by changing the values
of the SEARCH_AND, SEARCH_OR, SEARCH_NOT and SEARCH_WILD configuration
variables in the "Biferno.config.bfr" file. Default operators can be
modified for the current script only using the SetOption static method of
the search class, with prototype:
static void SetOption(string search_and, string search_or,
string search_not, string search_wild)
The method is used as follows:
<?
search.SetOption("AND", "OR", "NOT", "%")
?>
After this call to the SetOption method the search class will recognize in search strings, within the current script, the SQL-style operators and wildcard character.
The search class has a number of properties that allow to obtain
information on the associated search string and to modify the meaning of
the different substrings. Substrings are single words (or groups of words
separated by spaces) and limited by logical operators and parentheses.
The tot read-only property contains the total number of substrings in the
search string. The mode property contains the implicit concatenation
logical operator for substrings of the search string consisting in
space-separated words. This property is used at writing time, when the
kind of SQL query that will be generated by the ToSQL method can be
defined by the concatenation of strings contained in a search class
variable and by assigning one of the logical operators and or or (search
class constants) to the mode property. We will show some examples in the
following.
The string read-only property is an array containing all substrings of the
search string. The oper read/write property is an array containing all
logical operators separating substrings of the search string. The possible
values of the array elements are the constants all, allNot, and, andNot,
or and orNot of the search class.
The group read/write property is an array containing, for each substring
of the search string, an identifier indicating the associated type of
parenthesis (open, closed, none). In correspondence to an open parenthesis
the array contains the value of the openPar constant of the search class.
In correspondence to a closed parenthesis the array contains the value of
the closePar constant of the search class. The noPar constant denotes the
absence of a parenthesis.
The findType read/write property is an array containing, for each
substring of the search string, the kind of comparison corresponding to
the position of a wildcard character in that string to use during database
search. For each substring one of the values specified by the begins, ends
and contains constants of the search class will be used.
We will now show some examples of use of the ToSQL method of the search
class to construct SQL strings for database search. The ToSQL method has
the following prototype:
string ToSQL(string fieldName, boolean isNumeric, boolean lowerSQL)
The fieldName parameter is the search column name (if no value is
supplied, the method uses the "[field]" default value). The isNumeric
Boolean parameter specifies if the column is a numeric column, i.e. if
values should be enclosed between single quotes in the SQL string. The
default value is false, based on the fact that searches are more commonly
performed on text fields. The lowerSQL Boolean parameter specifies that
the result string should use lowercase SQL keywords (the default is
uppercase keywords).
The ToSQL method returns an SQL string equivalent to the search associated
to the search class object on which the method was called.
We assume in the following script that the search string assigned to the
src_str variable was input by the user in a HTML form. Based on this
string, a search is executed on the last_name field of the users table
(see Table 15.3, “Operators supported by the search class in search strings”).
<html>
<body>
<?
src_str = "K* | M*"
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
query = "SELECT first_name, last_name FROM users WHERE "
query += search(src_str).ToSQL("last_name")
curs_id = dbconn.Exec(query)
if (dbconn.GetCurRecs(curs_id))
{
while (arr_rec = dbconn.FetchRec(curs_id))
print(arr_rec["first_name"]+" "+arr_rec["last_name"]+"<br>")
}
?>
</body>
</html>
The ToSQL method is used in the above code to generate, starting from a
search class object, an SQL string to be inserted after the WHERE clause
in the SQL SELECT statement. In the case of the "K* | M*" search string
that is contained in the src_str variable, the string is translated into
its SQL equivalent: last_name LIKE 'K%' OR last_name LIKE 'M%'. The query
resulting from this procedure selects all records in the users table where
the last_name field contains a string starting with the letters K or M.
The following example shows the combined use of the ToSQL method and of
the value of the mode property of the search class. Assume that an input
word sequence separated by spaces should result in a database search for
the same words separated by AND operators. Notice that this is not
normally the case, as spaces are usually not considered as separators in
search strings and a group of words is treated as a single substring. The
script shows the technique to use in this case:
<?
src_str = "movies sport music"
my_src = search(src_str)
my_src.mode = search.and // Search for the AND of the words
query = "SELECT * FROM tv_channels WHERE "
query += my_src.ToSQL("category")
?>
The SQL string contained in the query variable after script execution has terminated has the following value:
SELECT * FROM tv_channels WHERE category = 'movies' AND category =
'sport' AND category = 'music'.
The use of the mode property of the search class can be a valid
alternative to the use of operators, because it allows to simplify search
strings while maintaining a degree of flexibility.
An obvious limit to the above script is the fact that keywords are compared with the field content using the "equals" operator. If we wish to search for all records containing one or more keywords of the search string without forcing the user to specify wildcard characters at the beginning and at the end of all keywords, we can operate as in the following example:
<?
function SetFindType(search *inSrc, int inFind)
{
if (inFind == 0 || inFind == search.begins || inFind == search.ends || \
inFind == search.contains)
{
nFind = inSrc.findType.dim
for (i = 1; i <= nFind; i++)
inSrc.findType[i] = inFind
}
}
src_str = "movies sport music"
my_src = search(src_str)
my_src.mode = search.or // Search for the OR of the words
SetFindType(&my_src, search.contains)
query = "SELECT * FROM tv_channels WHERE "
query += my_src.ToSQL("category")
?>
The SQL query resulting from script execution is:
SELECT * FROM tv_channels WHERE category LIKE '%movies%' OR category LIKE
'%sport%' OR category LIKE '%music%'.
The SetFindType function modifies the value of the findType property for
all substrings of a search class variable passed by reference and it
assigns one of the constant values begins, ends or contains to the
property (the zero value removes the property).
Table of Contents
We have seen in the previous chapters a number of situations in which a Biferno instruction generates an error. Passing non-compatible parameters to a class method or to a function, or trying to open a non-existent file, we can cause errors that interrupt the execution of our code.
Besides errors generated by the code itself, another possible source of trouble in a Web application is input data originated by the user via a HTML form or by other external sources. When developing a Web application it is good practice to make sure from the beginning that the application must be able to handle situations where data to be manipulated might not be in the correct format, or not be available because of errors or malfunctioning in systems that an application interfaces to, such as a file system or DBMS. To avoid this kind of situations, Biferno makes available to the programmer specific tools to intercept and manage errors generated by Biferno during script execution. In this way it is possible to act in the proper fashion in case of error, e.g. by informing the user that there is a problem or by automatically correcting the situation and allowing the program to continue.
This chapter describes the Biferno diagnostic system, the handling of errors in a script, and the debugging of the code. Finally we will explain how a user class can generate its own errors defined internally to the class.
When Biferno encounters an error during the execution of a script, execution is interrupted and a message in HTML format is sent in output to the browser that contains an error description and some additional data.
We have described in Chapter 9, Applications and Variable Scope, when we discussed the use and format of the "Biferno.config.bfr" application configuration file, how to specify one or more IP addresses for developers who are authorized to received detail information on the content of variables when an error is generated during the execution of a script belonging to that particular application.
Figure 16.1, “Error description screen (developer version)”shows a sample error screen as it is sent to a developer.
The picture does not include the full identifier table with the
corresponding values for all entities (variables and constants), both local
and with wider scope, that were defined when the error occurred. As
mentioned already, this table is sent in output only to the developer.
Figure 16.2, “Error description screen (user version)” shows the message for the same error as it
is sent to the user. If we do not wish to use the standard error screen for
the user, Biferno allows to specify a personalized error page for our
application. This feature is activated by initializing the ERROR_PAGE
variable in the "Biferno.config.bfr" application configuration file and
setting its value to the path of the page to be visualized. An example is:
ERROR_PAGE = "error.bfr"
The page specified in this variable is shown only to the user, while the developer always receives the standard page.
The error information reported to the developer is:
The error type (see following section)
The numerical code identifying the error
The corresponding description string
The complete path of the script where the error was generated
The line number of the instruction causing the error
The corresponding code line
Finally, if available, an explanatory note that helps understanding the cause of the error and may suggest a remedy. An example is the name of the variable causing the error.
The full entity table is shown sorted by scope, starting with local
entities, then reporting global, application (including all standard
default parameters defined in the "Biferno.config.bfr" configuration file),
session and persistent entities. For each entity the name, type (variable
or constant), class, and value (or, more precisely, the string describing
the corresponding value) is reported.
The error screen also includes information about the current script, including the client IP address and the time in ticks used by Biferno for script processing (one tick is 1/60th of a second).
![]() | Hide and Show |
|---|---|
If we want to avoid including the value of a variable in the error screen,
e.g. because it contains a password in clear text, we can mask the value
by calling the
void object.Hide(obj varToHide)
An example is:
Hide(pass)
To undo the effect of this function the complementary method |
If an error is caused by a user (not by the developer identified by
DEVELOPER_IP), it is possible to notify the developer via email sending the
complete error screen, while a different error message is displayed to the
user. If suitable values have been assigned to the MAIL_HOST (set to an
email server address), NOTIFY_MAIL_ERR (set to true) and WEBMASTER (set to
the email address of the individual to be notified), the complete error
screen will be automatically sent via email to the webmaster with no need
for user intervention. The user will be displayed the error page or, if no
error page is defined, a message containing the error string: "An error
occurred while processing a script. The webmaster has been notified".
Biferno errors belong to two distinct categories: language errors (e.g. syntax errors, identifier errors, function or class member invocation errors), called Biferno errors in the following, and errors generated by classes (Class errors).
Biferno errors are identified by the "BIFERNO ERROR" string in the header
of the error screen, while class errors are identified by the string
"Class <class_name> Error", where <class_name> is the actual
name of the class generating the error.
An example is given in Figure 16.3, “A sample error from a predefined Biferno class” showing the
error reported by the file class when we try to open an alias.
Catching error conditions is the first step in the implementation of an
error management system. Normally, all Biferno errors are blocking, i.e.
they interrupt script processing. This behavior can be modified by using
the error.Resume() predefined language method with the following
prototype:
void error.Resume(string funcName)
When called with no parameters, this function instructs Biferno not to
block code execution if there is an error, but rather to continue from the
code line following the line where the error occurred. This behavior is
set only for the current script and only starting from the code line
following the call to the error.Resume() method. We will see in the
following the meaning of the funcName parameter.
The complementary method error.Suspend() (with no parameters) resets
Biferno to the normal error handling behavior.
Not all error conditions can be ignored. After calling the error.Resume
method, some Biferno errors can still be blocking, while errors generated by
predefined classes are always considered non-blocking. Non-blocking Biferno
errors include all operating system errors and e.g. the illicit TypeCast
error (Err_IllegalTypeCast). The resumable property of the error class can be
used to enquire if an error is blocking.
It is easy to see that in most cases continuing script execution after an
error has occurred can cause a chain of related errors and ultimately
complete malfunctioning of the script itself. When using the error.Resume
method, our script has to be able to foresee and catch all possible error
conditions. In general, it is not advisable to use the error.Resume method
during the application development phase, because it would make debugging
extremely difficult.
The err predefined global variable allows us to enquire if a Biferno
instruction has caused an error and to receive at the same time the code
of the generated error. This variable belongs to the error predefined
class and contains all information we have seen when describing Biferno
error messages.
In particular, the errNum property of the error class always contains an
integer value which, if non-zero, corresponds to the numerical code of the
last error generated by the current script. The name property contains the
name, i.e. the descriptive string, of the error (e.g.
"Err_IllegalTypeCast").
Notice that all numerical error codes are stored in static constants
within the class they belong to. Biferno error codes are constants of the
error class. For all predefined classes, the names of these constants
correspond to error names reported in the name property of the error
class. If we write:
<? errCode = error.Err_OSError ?>
the errCode variable contains the integer value 1, corresponding to the
Err_OSError Biferno error, indicating an error generated by the operating
system. To better understand the use of the error.Resume method and of
the err variable, we show how to intercept errors generated during file
access operations. Let's look at the following script:
<?
error.Resume()
myFile = file("doc001.txt", , r)
myFile.Put(0, "text")
if (err)
{
print("I/O error on the file named "" + myFile.name + "".<br>\n")
if (err.name == "Err_OSError")
print("System Error " + err.subErrDescr)
else
if (err.name == "ErrFileIsNotOpen") // File is not open
print("The file is not open<br>\n")
}
?>
After calling the error.Resume method, we open a file for reading only
and subsequently try to write the string "text" into it. This operation
will fail and, in normal conditions, we would receive an error message.
Because we called the error.Resume method, we can now catch this error
condition. The state of the err global variable is checked on the next
line and, if its value is non-zero, we warn the user that an error has
occurred. The rest of the code shows how to implement further error
management actions acting on the values of properties of the err object.
The script manages two possible errors by checking the value of the name
property. If the property contains the "Err_OSError" string (Biferno
error number 1), the error is a system error. In this case we print the
string stored in the subErrDescr property, containing numerical code and
error name (the subErr property contains only the numerical code). If the
name property contains the "ErrFileIsNotOpen" string (error generated by
the file class), an additional message is printed.
The err variable can be also directly compared to the values of class error
static constants. E.g. the code fragment of the previous script that checks
the type of error generated could be rewritten as follows:
<?
if (err == error.Err_OSError) // System error
print("System error " + err.subErrStr)
else if (err == file.ErrFileIsNotOpen)
print("The file is not open<br>\n")
?>
The err variable is global and it would be good practice to refer to it
as global err. The latter is mandatory within a function or method.
The error.Resume method can be also called by passing it the name of a
function as parameter. This function, which must be able to manage the
error and to output the appropriate messages, will be called every time a
non-blocking error occurred, and must have the following prototype:
function void function_name (error theErr)
In the following script we show an example of a function that prints error
messages on the base of the value of the properties of the error class. The
errClass property contains the name of the class that generated the error
or, for Biferno errors, the "BIFERNO ERROR" string.
<?
function void HandleError (error theErr)
{
print("Warning! An error has occurred.<br>\n")
if (theErr.errClass == "BIFERNO ERROR") // Biferno error
{
if (theErr.name == "Err_OSError")
print("System error " + theErr.subErrStr + "<br>\n")
else
print("Biferno error nr. " + theErr.errNum + " (" + \
theErr.name + ")<br>\n")
}
else // Class error
print("Error from class " + theErr.errClass + " nr. " + \
theErr.errNum + " (" + theErr.name + ")<br>\n")
}
error.Resume("HandleError")
myFile = file("doc001.txt", , r)
myFile.Put(0, "text")
?>
Two more predefined Biferno methods for error handling are defined. The
error.Function method returns a string containing the name of the function
specified in the last call to the error.Resume method. The error.State
method returns a Boolean value reflecting the present error handling
behavior in the current script. The value true indicates that the
error.Resume method has been invoked.
The err predefined global variable can be accessed in write mode. By
directly assigning an integer numerical value to the err variable, script
execution is immediately interrupted (unless error.Resume was called) and
the error corresponding to the specified numerical code is generated, i.e.
an exception is generated.
Only Biferno errors can be generated in this fashion. If an error code is
specified, which does not correspond to a Biferno error, we obtain the
"Unknown Error" error message. We generally advise to always use the error
constants of the error class instead of the corresponding numerical values,
as the latter may change in future Biferno releases. Let's see how we can
force a Biferno error (with value 1):
<?
global err = error.Err_OSError
?>
Remember that the global scope identifier can be omitted within the main
body of a script.
To generate errors of the predefined classes, or errors defined by a user
class, we can use the ThrowException static method of the error class, with
the following prototype:
static void ThrowException(int errNum, string class)
As it can be seen from the prototype, using this method allows to specify
not only the numerical code of the error we want to force, but also the
class it belongs to. To force the ErrFileIsNotOpen error of the file class,
we can write:
<?
error.ThrowException(file.ErrFileIsNotOpen, "file")
?>
It is possible, and actually advisable, to use the ThrowException method
also to force Biferno errors, instead of directly assigning a value to the
err variable, as in:
<?
error.ThrowException(Err_OSError)
?>
Notice that, because the second parameter was omitted, the error code is
implicitly attributed to the error class, and therefore a Biferno error is
generated.
A complete listing of all Biferno and predefined classes error codes with the corresponding meaning is contained in "Biferno: Reference Guide".
An alternative way to force an error in Biferno is via the debug
instruction. When this instruction is encountered, script execution is
interrupted and a specific error is generated (UserBreak).
The debug instruction allows the developer to check the state of variables
at a certain point in the code to investigate the reason of program
malfunctioning. By inserting the debug instruction in strategic points in a
Biferno script effective code debugging can be performed.
In Biferno a user defined class can generate its own errors, which are different from predefined language errors raised by Biferno or by predefined classes.
Error codes for a user class can follow an arbitrary numbering and, as for predefined classes, must be associated to static constants of the class. An error constant from a user class has to be declared using the following syntax:
static const error constant_name = integer_value
Let's use again the person class defined in Chapter 11, User classes for exemplification purposes and modify its behavior by adding to the class the ability to generate specific errors corresponding to wrong user input.
To do this we make the sex and age properties public and catch the values
assigned to them via the SetProperty method (See also Section 3.1, “The SetProperty Method”).
<?
class person
{
string firstName
string lastName
char sex
int age
static const error ErrInvalidSexId = 1,
ErrInvalidAge = 2
void person (string n1, string n2, char s, int a)
{
firstName = n1
lastName = n2
sex = s
age = a
}
void SetProperty (string propertyName)
{
if (propertyName == "sex")
{
if (!sex.In("M", "F"))
error.ThrowException(person.ErrInvalidSexId, "person")
}
else if (propertyName == "age")
{
if (age < 1)
error.ThrowException(person.ErrInvalidAge, "person")
}
}
}
?>
The SetProperty method checks that only legal values are assigned to the
sex and age properties and, if this is not the case, generates an error of
the person class using the ThrowException method.
In the example the two errors generated by the person class have numerical
codes 1 and 2, but could in general be numbered in an arbitrary fashion.
When necessary a user class can associate additional information to its own
error codes under the form of a message that will be visualized by Biferno
as a note in the error screen. To do this we use the special user class
static method GetErrMessage, with the following prototype:
static string GetErrMessage (int errCode)
Every time the ThrowException is invoked specifying the name of a user
class as the second parameter, Biferno automatically calls the
GetErrMessage method of the class (if it exists) and passes the error code
to this method. The GetErrMessage, if implemented correctly, returns a
string containing a different message for each error constant declared in
the class.
As an example consider the following implementation of the GetErrMessage
method for the person class:
static string GetErrMessage (int errCode)
{
switch (errCode)
{
case person.ErrInvalidSexId:
errMessage = "Specify M or F for the sex"
break;
case person.ErrInvalidAge:
errMessage = "Age must be an integer positive number"
break;
default:
errMessage = "Unknown error "
}
return errMessage
}
Table of Contents
Table of Contents
The examples presented in the previous chapters have shown how to use HTML
as a tool to visualized data resulting from operations performed by Biferno
code. Occasionally we have seen how Biferno can be used to output fragments
of HTML code to a page using the print (or $, $$) instruction.
This chapter shows how to use Biferno to dynamically generate HTML code, controlling the structure and flow of a Web page on the base of programmatic conditions.
We have seen in Chapter 1, Introduction how to insert Biferno code within a HTML page. This section shows how Biferno allows the developer substantial design freedom concerning the degree of integration between HTML and Biferno code.
Depending on application complexity, which can span an entire Web site or a single script, the Biferno developer can choose the strategy to use for handling HTML output.
The simplest approach is to create HTML pages in traditional fashion and insert Biferno code only where necessary. If pages are not very complex, have a low degree of interactivity, or do not need to dynamically adjust their layout on the base of the result of script code processing, this approach is the most convenient and minimizes the processing load on the server.
As the reader will have noticed, this is the approach used in most examples
in this manual. An alternative technique for programming Web sites with
Biferno is to manage the output exclusively via the scripting language. In
other words, the entire HTML code is generated via the Biferno print
instruction. Every page becomes a Biferno script enclosed in a single pair
of delimiter tags <? and ?>. This technique requires a greater effort
from the development team, but allows to implement highly modular
applications that are more easily modified and maintained. Furthermore,
processing results can be compacted and optimized, reducing the page
loading time and the HTML code processing time on the client side.
Let's reconsider one of our first examples in this manual, a program to print the current version of the language:
<html>
<body>
<b>An example of Biferno code embedded in a HTML page</b>
<hr>
The current Biferno version is
<?
print(biferno.version()) //Print Biferno version
?>
<hr>
</body>
</html>
In the script above the Biferno tags enclose only the call to the print
function, which is used to print the string returned by the biferno.version
property. The script can be rewritten as follows, using the print function
to send in output the entire HTML code:
<?
print('<html>')
print('<body>')
print('<b>An example of Biferno code embedded in a HTML page</b>')
print('<hr>')
print('The current Biferno version is ' + biferno.version)
print('<hr>')
print('</body>')
print('</html>')
?>
The HTML code generated by this script is extremely compact, without redundant spaces, tabs, and line feeds.
Another possibility is to mix the two programming techniques, with the disadvantage that the resulting code is less readable.
The choice of the most appropriate programming technique depends not only from the type of application that is being developed, but also from the degree of language knowledge and from the personal preference of the developer himself. It should be also noticed that often the HTML code and the Biferno code that will make pages dynamic are written by different teams, and this may make a clear separation between HTML and Biferno code desirable.
Biferno control structures can be used to dynamically manage the HTML page flow. In the following example a single Biferno script is used both to visualize an HTML search form and to display to the user the result of the search itself. The search is performed in the user database that was introduced in the examples of Chapter 15, Database Interaction.
<html>
<body>
<?
if (curScript.IsDef("doQuery"))
{
dbconn = db("DSN=MYDB;UID=mydbuser;PWD=enterprise", "odbc")
query = "SELECT first_name, last_name FROM users WHERE
last_name LIKE '" + key_name.Capitalize() + "%'"
curs_id = dbconn.Exec(query)
nrec = dbconn.GetCurRecs(curs_id)
if (nrec)
{
print("<p>Users found: " + nrec + "</p>")
for (i = 1; i <= nrec; i++)
{
recArray = dbconn.FetchRec(curs_id)
print(i + ") " + recArray["last_name"] + ", " + \
recArray["first_name"] + "<br>")
}
}
else
print("<p>No users found.</p>")
}
else
{
?>
<form method="post" action="form_db.bfr">
<input type="hidden" name="doQuery" value="1">
Find users with names starts with:
<input type="text" name="key_name" value="">
<input type="submit" value="Search">
</form>
<?
}
?>
</body>
</html>
The value of the doQuery variable controls the condition that determines if
the form is displayed or if the database query is executed and the result
displayed. If the variable is not defined, as it is always the case on the
first execution of the script, the code block following the else branch of
the if instruction governing the page flow is executed.
This block consists of HTML code only and describes the input form for the
doQuery parameter (the first letter of the surname). The form also contains
the additional parameter called "doQuery", hidden in an input tag of type
"hidden". When the form is sent to the server, Biferno associates the
doQuery variable to this parameter, which is now defined with value "1". At
this point the first block following the if instruction is executed, which
connects to the database, runs the SQL query, and prints the result, if
any.
The curScript.ValueOf Biferno predefined method processes the expression
contained in the string passed as the input parameter and returns a string
containing the result of the expression. If the expression can not be
computed (as in our example when the doQuery variable is not defined), the
curScript.ValueOf method returns the empty string (we could have used it
instead of curScript.IsDef).
Notice that the HTML code describing the search form is sent in output only
if the conditional expression in the if instruction has value false. This
is true also when the HTML code is outside of the Biferno tag delimiters.
In other words, the curly brackets that can follow a Biferno control
instruction (if-else, for, while, etc.) control both the execution of the
Biferno code they enclose and the output of all the non-Biferno text they
enclose.
In Biferno an HTML code fragment can be generated dynamically. We will show
as an example how to build an input HTML tag of type
select. The select HTML tag allows to
list a sequence of values as elements of a pop-up menu or of a list
supporting multiple selection.
The value list must often be built on the fly, using data extracted from a database, a file, or contained in a script data structure.
The example that follows uses three select tags to allow
the user to choose a date by separately choosing day, month and year. The
elements of the three menus are dynamically added using for loops. The
“month” menu displays the names of the twelve months, which have been stored
in an array in advance. The “year” menu displays the values from 1990 to
2010.
When the form is sent to the server, the page is reloaded and the menu items chosen by the user are automatically selected, after the date validity has been verified. Notice the technique used to select elements of the value lists on the base of values input into the form.
<html>
<body>
<?
arr_months = array("January", "February", "March", "April", "May",
"June", "July", "August", "September", "October", "November",
"December")
day = curScript.ValueOf("day")
month = curScript.ValueOf("month")
year = curScript.ValueOf("year")
if (day && month && year)
{
if (!(day + "/" + month + "/" + year).IsDate("d/m/y"))
{
print("<p>Invalid input date.</p>")
day = ""
month = ""
year = ""
}
}
?>
<h3>Select a valid date</h3>
<form method="post" action="form_data.bfr">
Day: <select name="day" <?if (!day) {print("selected")}?>>
<option value="">-</option>
<?
for (i = 1; i <= 31; i++)
{
sel_str = (i == day) ? " selected" : ""
print('<option value="' + i + '"' + sel_str + '>' + i + \
'</option>')
}
?>
</select><br>
Month: <select name="month">
<option value="" <?if (!month) {print("selected")}?>>-
</option>
<?
for (i = 1; i <= 12; i++)
{
sel_str = (i == month) ? " selected" : ""
print('<option value="' + i + '"' + sel_str + '>' + \
arr_months[i] + '</option>')
}
?>
</select><br>
Year: <select name="year" <?if (!year) {print("selected")}?>>
<option value="">-</option>
<?
for (i = 1990; i <= 2010; i++)
{
sel_str = (i == year) ? " selected" : ""
print('<option value="' + i + '"' + sel_str + '>' + i + \
'</option>')
}
?>
</select><br>
<input type="submit" value="Set Date">
</form>
</body>
</html>
We have described in Chapter 8, Control Structures the include
Biferno instruction, which allows to include the content of another file
within a main script.
The include instruction behaves similarly to a function call, in the sense
that the code contained in the included file is executed only if the
include instruction is executed. This behavior supports the implementation
of dynamic pages consisting in forms, contained in separate files, which
are included based on programmatic conditions, i.e. by inserting the
include instruction within if or switch control structures.
This kind of page modularization has many advantages, allowing to implement
very compact, optimized and easily readable scripts. Moreover, the use of
include statements supports concurrent development by a team working on the
implementation of the same site and improves code reusability, both within
the same project and across projects.
Very often Web pages have to be written using HTML pages realized by other teams as a base for the layout. In this case the job can be made easier by subdividing the pure HTML code in modules, which will then be included in Biferno scripts.
The majority of Web sites consists in a number of base elements repeated in nearly all pages. An example is the classic vertical or horizontal site navigation bar, which has to be inserted in every page to allow the user to move rapidly between site areas.
Implementing a separate module and including it whenever necessary is much more convenient than repeating the same code block in all pages. This supports fast maintenance by allowing to modify a single file, should this part of the code change. The alternative is to intervene on all pages of the site, which is time consuming and can easily introduce errors.
Debugging and code maintenance are much easier when an appropriate modularization of pages is used. We recall that Biferno supports an automated modularization mechanism that allows to specify code segments (file) to insert at the beginning (header) or at the end (footer) of every script of an application (see the discussion of the "Biferno.config.bfr" configuration file in Chapter 9, Applications and Variable Scope).
Code modularization is supported to a better extent (and beyond what can be
achieved by the use of include statements) by the use of classes and
functions that are designed to improve reusability and separation of the
code itself.
As it has been mentioned in the introduction, Biferno is a server-side scripting language, i.e. it is processed by the server, while JavaScript can be processed either by the client or by the server.
In the following we will focus on the comparison with client-side JavaScript, which is more common and popular than its server-side counterparty. JavaScript is normally used e.g. to check the validity of data input into an HTML form before it is sent to the server, or to modify form input tags on the client before forwarding them to the server. For operations that can be handled on the server it is much more efficient to use a scripting language such as Biferno. The right mix of client-side and server-side technology will provide the best and more flexible result depending on the requirements of the Web application we want to develop.
Let's see an example of integration of JavaScript and Biferno. In the HTML
page generated by the following script, the user decides if a Web page
should be displayed in the Italian or English language by selecting a
pup-up menu item (i.e. an input HTML element of the
select type). When the user chooses a menu item, the
page is reloaded and displayed in the chosen language, and the menu items
are changed accordingly.
<?
if (!curScript.ValueOf("lang").In("it,en"))
lang = "it" // Italian
arr_text = array("it":array(), "en":array())
arr_text["it"] = array("LINGUA ITALIANA", "Seleziona la lingua: ",
"Italiano", "Inglese")
arr_text["en"] = array("ENGLISH LANGUAGE", "Choose a language: ",
"Italian", "English")
?>
<html>
<head>
<title>Biferno and Javascript Example (1)</title>
<script type="text/javascript" language="JavaScript">
<!--
function ReloadPage(theSelect)
{
var selLang =
theSelect.options[theSelect.selectedIndex].value;
var url = '$request.filePath$?lang=' + selLang;
document.location = url;
}
//-->
</script>
</head>
<body>
<form name="form_lang" method="post">
<h3>$arr_text[lang][1]$</h3>
$arr_text[lang][2]$
<select name="choose_lang" onChange="javascript:
ReloadPage(this)">
<option value="">-</option>
<option value="it">$arr_text[lang][3]$</option>
<option value="en">$arr_text[lang][4]$</option>
</select>
</form>
</body>
</html>
The use of JavaScript code shows how the two languages can be embedded into each other. No confusion is possible, because the Biferno code is processed by the server and the JavaScript by the client, in two well-distinct phases.
In the ReloadPage JavaScript function we have inserted a Biferno expression
that prints the path of the page requested to the server (the request class
is described in Chapter 20, HTTP Protocol Interaction). When the client
requests to the server the page containing the script, Biferno processes
the expression $request.filePath$, substituting it with the path of the
current page, so that the line:
var url = '$request.filePath$?lang=' + selLang;
would e.g. become:
var url = '/biferno_prove/bifernojs.bfr?lang=' + selLang;
When the page processed by Biferno is sent to the client, the ReloadPage
function contains only JavaScript code. This function is automatically
called every time the user chooses a different menu item (the function is
attached to the HTML select by the onChange attribute of the
select tag) and reloads the page adding the lang
parameter to the URL with a value equal to the selected
select element.
Notice that the form does not have a submission button. The
select tag itself, via the ReloadPage JavaScript
function invoked through the onChange tag, instructs the client to repeat
the request of the script passing the lang variable with the new value.
The string in the two languages are contained in a bidimensional
associative array, making the choice of the right string based on the value
of the lang variable easy. This technique, while quite sophisticated, can
not be used for the case of very complex pages containing a large quantity
of text. In this case a better design choice might be to maintain the page
text in different languages in separate files that are included in the main
script using include instructions.
Table of Contents
One of the main problems that need to be addressed when developing Web applications is maintaining state information when switching from a page to another.
We have described how this can be done in Biferno using variables that have
the entire application as their scope (application, session or persistent
variables), with the limitation that this technique is only useful when we
implement parameters that have to be accessible from all application scripts
(and, with the exception of session variables, from all users).
When the information we want to pass to a page is meaningful only in the context of that page and within a single connection, it is convenient to use the HTTP protocol parameters. A brief description of the GET and POST methods of the HTTP protocol will help understanding the association mechanism between variables and parameters supported in the HTTP protocol.
The HTTP protocol is based on simple conversational interactions between client and server. The client connects to a server and sends a request. The server receives the request, processes it, and sends the answer to the client.
The message sent by the client to the server is structured as follows:
One line containing the specification of the method used, which can be GET or POST, the URL of the requested page, and the protocol version.
A header containing a number of additional data on the client and the request itself.
A body, which can be empty.
If the request uses the GET method, the body is empty because parameters
are associated directly to the URL separating them with the ? character. If
the request uses the POST method, the body contains the list of (optional)
parameters with their values.
The GET method is preferable to the POST method when there is limited number of parameters and their values are short strings or numbers. When there is many parameters, or their values are long, it is better, and indeed often necessary, to use the POST method.
One of the reasons is that a request sent to the server via the GET method can not exceed 12 Kb in length. Furthermore, the GET method only supports text data, while the POST method can be used to transmit any kind of data, including images, music, movies, etc. The POST method must always be used instead of the GET method when parameters should be hidden from the user and can not be sent along with the URL of the requested page.
In general, the GET method is implicitly used in links implemented with the
a HTML tag, while the POST method is the default for
forms (for which the GET method can be optionally specified).
If the page requested by the client is a Biferno script, Biferno creates a
new variable with the same name and value for each of the parameters
submitted. This is done before the script is processed and regardless of
the HTTP protocol method utilized to send data to the server. The new
variables are normally all of the string class.
Let us see through some examples how to pass parameters to a Biferno script using the GET and the POST methods.
A generic URL corresponding to a Biferno script, complete with parameters, has the following form:
http://path/script.bfr?param1=value1¶m2=value2&...¶mN=valueN
When the client requests the "script.bfr" page, Biferno associates to the
param1 parameter a variable called param1 and having the value1 string as
value. Decoding from the "UrlEncode" format is performed automatically (see
the Section "String Encoding and Decoding" in Chapter 12, String Manipulation). Similarly, the
param2 variable with value value2 is associated to the param2 parameter,
and this process is repeated for all parameters up to and including paramN.
Notice that parameter values passed with the GET method have to be encoded
with the "UrlEncode" format, when necessary, before they are inserted into
the URL string.
The default class for variables associated to HTTP parameters is string.
This behavior can be modified by executing an explicit typecast directly on
the URL. Assuming we want to pass a parameter to a script as an integer
value, we can write in HTML:
<a href="script2.bfr?int(param1)=1¶m2=2">Script 2</a>
The param1 parameter will be associated in the "script2.bfr" script to a
variable of the int class with the same name and value 1, while the
parameter param2 will be associated to a string class variable with value
"2". Notice that the typecast syntax in the URL differs from the usual
form, which is (class_name)variable_name. Conversely, in a URL the class
identifier must be in front of the parameter name enclosed in round
brackets.
If a typecast error occurs in a parameter passed to a Biferno script with
the GET or POST methods, Biferno does not stop processing and creates
anyway a string class variable with name and value corresponding to the
parameter name and value. At the same time, and before starting script
execution, Biferno assigns the Err_IllegalTypeCast value to the global err
variable. This behavior allows to catch this kind of error at the beginning
of the script. We will see in the following, when discussing passing
parameters with the POST method, an example showing how to catch typecast
errors in parameters.
It is often necessary to pass an array to a Biferno script or to associate
a sequence of homogeneous parameters passed to a script to the elements of
an array, instead of distinct variables. This can be done by using the
following syntax for the URL:
http://script.bfr?arr[1]=value1&arr[2]=value2&...&arr[N]=valueN
Parameter names correspond to the elements of a virtual array that are
assigned parameter values. In the target script an array class variable
called arr is created to store the values passed in the URL, according to
the index references as described in the URL itself.
Assume we want to pass an array of three elements with values 1,2,3 to a
Biferno script:
<a href="script2.bfr?myArr[1]=1&myArr[2]=2&myArr[3]=3">Script 2</a>
In the "script2.bfr" script the myArr variable of the array class is
created, with three elements of the string class with values "1", "2" and
"3", respectively. This syntax can be used also for multidimensional
arrays, as in the following example:
<a href="script2.bfr?myArr[2][1]=1&myArr[2][2]=2&myArr[2][3]=3">Script 2</a>
In the "script2.bfr" script the myArr array of arrays is created, where
the second array has three elements of the string class with values "1",
"2" and "3", respectively. If we want to force the element class to be the
integer class int we can write:
<a
href="script2.bfr?int(myArr)[1]=1&int(myArr)[2]=2&int(myArr)[3]=3">Script 2</a>
Biferno also supports an alternative syntax to denote a parameter as an
array element. The indices between square brackets can be omitted, and in
this case the sequence 1, 2, 3, etc. is assumed. This syntax is very
efficient and elegant, but supports only one dimensional arrays (vectors),
as in:
<a href="script2.bfr?myArr[]=1&myArr[]=2">Script 2</a>
An attempt to use this syntax for multidimensional arrays is invalid and
generates the Err_InvalidArrayIndex error.
The following example shows how to dynamically build a URL to pass an array of strings to another script:
<?
myArr = array("Primula Brandybuck", "Palo Alto", "Employee")
paramStr = "?"
arrSize = myArr.dim
for (i = 1; i <= arrSize; i++)
{
if (i > 1)
paramStr += "&"
paramStr += "myArr[" + i + "]=" + myArr[i].UrlEncode()
}
?>
<html>
<body>
<a href="script2.bfr$paramStr$">Script 2</a>
</body>
</html>
This script generates the following HTML code:
<html>
<body>
<a href="script2.bfr?myArr[1]= Primula%20Brandybuck
&myArr[2]=Palo%20Alto&myArr[3]=Employee">Script 2</a>
</body>
</html>
Notice the use of the UrlEncode method of the string class, which is
necessary to correctly encode strings within the URL.
An array can also be passed as a whole in a URL, using the syntax:
<?
myArr = array(1, 2, 3)
?>
<a href="script2.bfr?myArr_ini=$object.ValueOfInput(myArr)$">Script 2</a>
and in the following page:
<? myArr = Eval(myArr_ini) ?>
The object.ValueOfInput method provides the correct code invoking the
constructor and can be applied on an object of any class. We suggest the reader
experiments with it by invoking it on an array and printing the result.
The same function is also used to pass an array via a HTML form or to pass
variables of other non-primitive classes.
![]() | Note |
|---|---|
URL and Forms variables input syntax has some limitations.
In particular, given the URL as couples of Name=Value pairs the
following rules apply: Name expressions cannot contain constructor
other than that of primitive class. Value espressions are always evaluated as strings,
also when they contain constructors. Examples:
myUrl?int(a)=1 // a is a int of value: 1 myUrl?array(a)=1 // error (constructor in Name not of primitive class) myUrl?a=int(a) // a is a string of value: "int(a)" |
The association between HTML parameters sent with the POST method and Biferno variables is realized by associating in the destination script (specified by the "action" attribute of the form) a variable to each input tag of a HTML form. The name of the variable is the string specified in the "name" attribute and its value is the string specified in the "value" attribute. In general, a Biferno variable is associated to every form element that is characterized by the "name" and "value" attributes.
The following HTML form send the three strings input by the user to a Biferno script:
<html>
<body>
<form method="post" action="script2.bfr">
String 1: <input type="text" name="string1" value=""><br>
String 2: <input type="text" name="string2" value=""><br>
String 3: <input type="text" name="string3" value=""><br>
<input type="submit" value="Send">
</form>
</body>
</html>
If we want to transmit numeric values we can execute a typecast similarly to what was done for the GET method, as in the following code:
<html>
<body>
<form method="post" action="script2.bfr">
Name: <input type="text" name="first_name" value=""><br>
Surname: <input type="text" name="last_name" value=""><br>
Age: <input type="text" name="int(age)" value=""><br>
<input type="submit" value="Send">
</form>
</body>
</html>
In this case the third input element of the form contains in its name also
the typecast to the int class.
While the example above demonstrates the use of explicit typecast in HTML forms, particular care must be taken when the input element is an editable text field. In the case of the form produced by the example, the automatic typecast will fail if the user inputs non- numeric characters in the third field.
To avoid this, it may be necessary to limit the input to numeric characters, e.g. by using an ad hoc JavaScript function called when the user attempts to send the data in the forms. An alternative is to code the form target script in such a way that the script can handle the error. The following code, inserted at the beginning of the "script2.bfr" script (target script of the form in the last example), catches a typecast error in the "Age" field and outputs a corresponding message.
<?
if (err == error.Err_IllegalTypeCast)
{
if (err.msg == "age")
{
?>
<p>Error! Please use only integer numbers in the Age field</p>
<p>Please back up and correct </p>
<?
stop
}
}
?>
Reconsider the example concerning passing an array in a URL presented in
the previous section. The example can be rewritten using a form as
follows:
<html>
<body>
<form method="post" action="script2.bfr">
<input type="hidden" name="myArr[1]" value="1">
<input type="hidden" name="myArr[2]" value="2">
<input type="hidden" name="myArr[3]" value="3">
<input type="submit" value="Send to script 2">
</form>
</body>
</html>
Three "hidden" elements have been used with names corresponding to array
elements and values corresponding to the strings "1", "2" and "3". In this
case it is always safe to use typecast to obtain elements of the integer
class, because the values are hard-wired in the HTML code. The form of the
previous example can be rewritten as:
<html>
<body>
<form method="post" action="script2.bfr">
<input type="hidden" name="int(myArr)[1]" value="1">
<input type="hidden" name="int(myArr)[2]" value="2">
<input type="hidden" name="int(myArr)[3]" value="3">
<input type="submit" value="Send to script 2">
</form>
</body>
</html>
The alternative syntax shown when discussing passing an array in a URL can be used also in this case.
Notice that the same result can be obtained by writing:
<?
myArr = array(1, 2, 3)
?>
<html>
<body>
<form method="post" action="index.bfr">
<input type="hidden" name="myArr_ini"
value="$object.ValueOfInput(myArr)$">
<input type="submit" value="Send to script 2">
</form>
</body>
</html>
And writing in the following page the code
<? myArr = Eval(myArr_ini) ?>
Another technique to pass data to a Biferno script as an array is to use
an input element of the select type supporting multiple selection.
Normally, the variable associated to a HTML select is of the string class
and contains the selected elements separated by the ", " characters (comma
plus space). The HTML form in the following example sends a string
containing the selected elements separated by a comma and a space to the
"script2.bfr" script.
<html>
<body>
<form method="post" action="script2.bfr">
<select name="selNum" multiple>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select><br>
<input type="submit" value="Send to script 2">
</form>
</body>
</html>
If we select all three elements of the select and send the form, in the
"script2.bfr" script the selNum variable of the string class will be
defined with value "1, 2, 3".
A string in this format can simply converted into an array using the
ToArray method of the string class. However, if we want the values
selected in the select to be passed to the script in array form, we can
just append a pair of parentheses to the name of the select, in the
following fashion:
<select name="selNum[]" multiple>
Using this syntax the selNum variable will always be of the array class,
even if we select a single element from the select. The typecast to the
integer class can be realized by writing:
<select name="int(selNum)[]" multiple>
Our discussion regarding passing parameters via HTML forms assumes that
only textual (ASCII) data is sent using the default format
"application/x-www-form-urlencoded". This format was mentioned in Chapter 12, String Manipulation in the discussion of the UrlEncode method
of the string class.
The "application/x-www-form-urlencoded" encoding can not be used to transmit large quantities of binary data or text containing non-ASCII characters. To send this kind of data the "multipart/form-data" encoding should be used.
This encoding is normally used to transmit files (text or binary). The following example shows a HTML form with an input element of type file. The "enctype" attribute of a HTML form specifies the kind of content, and therefore the type of encoding, of the data set to be transmitted to the server.
<html>
<body>
<form method="post" action="handle_file.bfr"
enctype="multipart/form-data">
File to send: <input type="file" name="the_file"><br>
<input type="submit" value="Send file">
</form>
</body>
</html>
In the target Biferno script a variable of the multipart class is
associated to the form input element of type "file". The properties of this
class allow to obtain information on the received file:
The name property contains the name of the file.
The path property normally contains the complete path of the file, depending on the browser and platform used (in some cases only the file name is sent).
The contentType property contains the type identifier for the file
content (MIME type).
The data property contains the actual content of the file.
The following script provides an example of management of the information concerning the file sent by the form.
<html>
<body>
<?
file_name = curScript.ValueOf("the_file.name")
if (file_name)
{
?>
The file $file_name$ was sent.<br>
The file size is $the_file.data.length$ byte<br><br>
<?
// Store file on server
new_path = "/upload/" + file_name
the_file.ToFile(new_path)
}
else
{
?>
No file was sent.
<?
}
?>
</body>
</html>
The conditional statement verifies that a file has been effectively
uploaded in the input field. Because the the_file variable of the
multipart class associated to the form is always defined, the test checks
that the name property of the variable does not contain an empty string.
If the the_file variable contains data, the name and size (in bytes) of
the corresponding file are printed. Then, the file is stored in a
directory called "upload" located in the server root (or more precisely in
the site root). The ToFile method of the multipart class is used, with
prototype:
void ToFile(string path)
The path parameter specifies the path of the file to be stored. The use of
this method makes it easy to store on the server files submitted by users
via HTML forms. Notice that, even though in the example above the file has
been stored on the server using the same file name, any new name can be
specified for the new file.
![]() | Limiting the Size of Data Sent to the Server |
|---|---|
Biferno allows to limit the size of data sent to the server via a HTML
form using the POST method. To set a limit, we add to the name of the
Biferno page specified in the action attribute of the
<form method="post" action="page2.bfr?MAX=64">
This technique can be used to avoid that users are able to upload extremely large files that may cause problems on the server.
If the size of the file sent exceeds the maximum value set, the |
Table of Contents
This chapter discusses the tools offered by Biferno to handle email from a
script using the methods of the smtp class.
The simplest way to send an email message from a Biferno script is by using
the SendMail static method, with the following prototype:
static string SendMail(string host, string from, string to, string text)
The host parameter specifies the name or address of the SMTP server we want
to use to send our email. The from parameter specifies the email address to
be used as the address of the sender of the message. The to parameter is
the email address of the receiver, i.e. the person we want to address the
message to. The text parameter is the message text, possibly including part
of the SMTP header (in particular the subject field).
The following script shows how to use the SendMail method:
<?
email_host = "mailserver.mydomain.com"
email_from = "me@mydomain.com"
email_to = "him@hisdomain.com"
email_text = "Subject: SendMail Test\r\n\r\n"
email_text += "Test email body."
status = smtp.SendMail(email_host, email_from, email_to, email_text)
?>
Notice that in our example the message text contains a first line starting
with the word "Subject" followed by the ":" character (semicolon) and by
the subject text. This line is the header of our message. The body of the
message must be separated from the header by two new lines (each of them
being the combination of the characters CR, carriage return, and LF, line
feed).
In the message header it is also possible to insert other fields that are
specific to the SMTP header, such as "Cc:" (carbon copy) and others,
separated by a single new line character combination (CR+LF). The exact
syntax is therefore: field_name + semicolon + value + CR + LF.
The SendMail method returns a string containing the description of an error
(or warning) generated by the SMTP server. This allows us to verify if the
message has been correctly sent. If errors in the parameters are
discovered (e.g. a wrong SMTP server name), the SendMail method generates
an error of the smtp class.
The SendMail method sends the message synchronously, which implies that
script execution may block for several seconds waiting for an answer from
the SMTP server. Additionally, no resend attempts take place if sending a
message fails on the first attempt, and the email will not be sent to
destination (unless the script is run again).
The smtp class also supports asynchronous sending of email, i.e. without
waiting for an answer from the SMTP server.
To send asynchronous email messages, the smtp class makes the SendMailAsync
static method available, with prototype:
static string SendMailAsync(string host, string from, string to,
string text, string filePath, unsigned minutes)
This method has two additional optional parameters with respect to the
SendMail method. The filePath parameter specifies the path of the spool
file where Biferno temporarily stores the message while it waits to be sent to
the server. If this parameter is not passed, Biferno uses a default name
for the spool file. The minutes parameters defines the timeout, in minutes,
after which Biferno will stop trying to send the message in case of
repeated errors. The default timeout is five minutes.
The SendMailAsync works in a simple fashion. When the method is called, the
email message is stored in a special format (XML) within the file specified
by the filePath parameter. The ".xml" extension is added automaticaly to
the file name. Once this step is completed, Biferno continues to process
the current script with no further interruption, while another process
running in parallel has the task of asynchronously sending the queued
messages.
If an error occurs, Biferno will try to send the message again at regular
intervals, until the timeout specified by the minutes parameter is reached.
For this reason using the SendMailAsync method not only makes script
execution faster, but also increases the likelihood that a message can be
successfully sent in presence of temporary error conditions.
All attempts to send a message, i.e. the conversational protocol exchanges with the SMTP server, are stored in a log file in the same directory as the message file. The log file has the same name of the message file with the extension ".log" appended. The message file, together with the corresponding log file, is deleted from disk after the message has been successfully sent or after the timeout period expires.
The SendMailAsync method returns a string containing the file path of the
message, including the name with which it has been stored on disk. The
filename is returned because a default is used if no parameter was passed,
because the ".xml" extension is appended automatically, and finally
because, if the file specified by the filePath parameter already exists,
the method appends a progressive number to the file name.
The following script shows how to use the SendMailAsync method. The script
sends a message to a number of email addresses stored in an array, which
could be filled e.g. by a database query.
<?
email_host = "mailserver.mydomain.com"
email_from = "me@mydomain.com"
email_text = "Subject: SendMailAsync Test\r\n\r\n"
email_text += "Test email body."
email_timeout = 120 // Two hours
arr_email = array("user1@domain1.com", "user2@domain2.com",
"user3@domain3.com", "user4@domain4.com")
n_email = arr_email.dim
email_sent = 0
for (i = 1; i <= n_email; i++)
{
email_to = arr_email[i]
if (email_to.IsEMail())
{
email_sent++
email_file = "emails/email_" + email_sent
smtp.SendMailAsync(email_host, email_from, email_to,
email_text, email_file, email_timeout)
}
}
?>
The smtp class allows to verify, within the set timeout, individual email
messages that are still stored on disk because of a persistent error
condition that caused automatic send attempts to fail (recall that messages
are deleted from disk anyway after timeout).
The ParseMailFile static method reads a file containing an email message in
the format generated by the SendMailAsync method and returns an associative
array containing the various elements of the message. This method has the
following prototype:
static array ParseMailFile(string filePath)
The filePath parameter specifies the path of the file to be examined. The
associative array returned by this method contains six elements with the
following indices:
sm_timeout
sm_host
sm_from
sm_to
sm_body
sm_last_error
sm_creation_secs
The element with index sm_last_error contains the description of the last
error generated by an attempt to send the message.
The element with index sm_creation_secs provides the date (in seconds from
time zero, see Chapter 14, Date and Time Functionality) of the creation of the email file.
The following script shows how to verify the existence of email files
corresponding to unsent messages in the directory specified in the path
passed to the SendMailAsync method and, at the same time, how to offer to
the application user the option of manual sending.
<html>
<body>
<h2>Queued messages list</h2>
<?
function int ListMessages(string filePath, long variant)
{
if (msg = smtp.ParseMailFile(filePath))
{
global queued_msg++
print("<b>Message nr. " + global queued_msg + "</b><br>")
print("From: " + msg["sm_from"] + "<br>")
print("To: " + msg["sm_to"] + "<br>")
arr_msg = msg["sm_body"].ToArray("\r\n")
print(arr_msg[1] + "<br>") // Print subject
print("<a href=send_message.bfr?msg_path=" + filePath.UrlEncode() + \
"><b>Send now</b></a><br><br>")
}
return 0
}
global queued_msg = 0
email_dir = folder("emails/")
email_dir.Walk("", false, "ListMessages")
if (global queued_msg == 0)
print("No messages queued.")
?>
</body>
</html>
Using the Walk method of the folder class, the script examines the content
of the directory where files generated by the SendMailAsync method are
stored and, for each such file, displays the from, to and subject message
fields, associated to a link to another script ("send_message.bfr") that
sends messages in a synchronous fashion.
A very simplified version of the "send_message.bfr" script could look as follows:
<html>
<body>
<?
status = smtp.SendMailFile(msg_path)
if (status == "")
print("Message sent.")
else
print("ERRORE! " + status)
?>
</body>
</html>
This script accepts as input the path of the file corresponding to the
message to be sent and sends it using the SendMailFile static method, with
prototype:
static string SendMailFile(string filePath)
This method verifies the content of the file corresponding to the filePath
path, assembles the message, sends it synchronously, and, if no errors
occur, deletes the file. If an error occurs, a string containing the error
description is returned and the file is not removed from disk. The
SendMailFile method also generates a log file, which is deleted along with
the message file if sending is successful.
Table of Contents
This chapter describes in detail the predefined Biferno classes that support interaction with the HTTP protocol.
We have already briefly described the HTTP protocol in Chapter 18, Passing Parameters between Pages when we discussed passing parameters between Biferno pages using the GET and POST methods. To better understand the following of this chapter, it is useful to expand here on the structure of an HTTP request.
The request sent by the client to the server is structured in three distinct blocks: request, header and body.
The request contains the following information:
The method specified for the request, which can normally be GET or POST.
The address (URL) of the object (file) requested to the server.
The protocol version used.
An example is: GET /prova.bfr HTTP/1.1.
The header contains additional information on the client or format specification for the request. This information is organized in fields according to the
<field_name > : <value>
syntax.
The body is usually empty, unless the request contains the POST method. In this case the client fills this portion of the request with "name=value" pairs corresponding to the parameters originated by a HTML form (recall that parameters are included in the URL in the case of GET method).
The server answer has a structure similar to the request. In this case the portion corresponding to the request (called response) contains the version of the protocol followed by a numerical code and by a string that specify the result of the attempt by the server to interpret and satisfy the client request. E.g. the code 200 followed by the "OK" string indicates success. The server answer header contains information on the nature of the content of the page sent (type of data, dimension, encoding, etc.). The body contains the actual data, i.e. the content of the object returned to the client (HTML text, a GIF image, a Flash movie, etc.).
The header Biferno class describes the header (request or response +
header) of the HTTP protocol. Using the methods of this class the different
fields in the HTTP header can be read and manipulated.
To instantiate a variable of the header class the class constructor is
called supplying a string containing a request or response and a HTTP
header (which can be empty), separated by a new line character combination
(CR+LF). An example is:
<?
request_hdr = header("GET index.bfr HTTP/1.1")
response_hdr = header("HTTP/1.0 200 OK\r\nContent-type: text/html")
?>
Let's briefly describe the methods of the header class.
The AddField method allows to add an arbitrary field to the HTTP header and
has the following prototype:
void AddField(string name, string content)
The name parameter specifies the name of the header field that should be
added. The content is the value that should be assigned to the field. An
example is:
<?
request_hdr.AddField("Accept-Language", "it")
?>
The GetField method allows to read the content of a specific field and has
the following prototype:
string GetField(string name, long index=1)
In this case the name parameter specified the name of the header field that
we want to read. The index is the numerical index of the field for multiple
fields (cookies or others). An example is:
<?
$request_hdr.GetField("Accept-Language") // Print field value
?>
The SetField supports modification of the content of an header field. The
prototype of this method is:
void SetField(string name, string content, long index=1)
E.g.:
<?
response_hdr.SetField("Content-type", "image/gif")
?>
The RemoveField method allows to remove a field from the HTTP header. This
method has the following prototype:
void RemoveField(string name, long index=1)
All these methods accept an empty string ("") for the name parameter and act on the first line (request or response) line in that case.
Every time a client requests a file with ".bfr" extension (i.e. a Biferno
script), Biferno automatically creates two global variables of the httpPage
class called pageIn and pageOut. The pageIn variable contains the client
request in the HTTP protocol format, while the pageOut variable contains
the server response.
The httpPage page describes a Web page in the format used by the HTTP
protocol during communication between client and server. Its two properties
head (of class header) and body (of class string) contain the header
(including the request or response) and the body of the page, respectively.
The body property of the pageOut variable contains the result of script
processing (in most cases, HTML text). During processing, the entire script
output, i.e. all text outside of the Biferno code tag delimiters and all
text printed by the print function (or $, or $$), is accumulated in the
body property of the pageOut variable.
This property can be accessed for writing, e.g. to remove the output of a script we can write:
<?
pageOut.body = ""
?>
Alternatively, if an error occurs during script execution and we want to remove all previous output and send back only an error message, we can write:
<?
err_str = "<html><body>An error has occurred.</body></html>"
pageOut.body = err_str
stop
?>
The effect of the print function when printing a variable with value "hello world" can be somehow emulated by the following code:
<?
pageOut.body += "hello world"
?>
Sometimes it is necessary to insert in a Web page content that resides on
a remote server accessible only via the HTTP protocol. The Exec method of
the httpPage class allows to "execute" a HTTP page described by an object
of the httpPage class and can be used to send to a remote server a request
for an Internet resource (a HTML page, an image, a movie, or anything
else). This method has the following prototype:
httpPage Exec(string server, int port=80)
The server parameter specifies the server name (including the domain name)
or the IP address of the server where the page we want to execute resides.
The port parameter indicates the port to be used (the default value is
80). The method returns an object of the httpPage class containing the
server response, i.e. the requested page in case of success.
For exemplification purposes we will now show how to implement a simple function that executes a page request to a remote server and returns a string containing the body of the server response page.
<?
function string ExecRemote(string site_address, string resource_path)
{
http_header = header("GET " + resource_path + " HTTP/1.1")
page_request = httpPage(http_header, "")
page_response = page_request.Exec(site_address)
return page_response.body
}
?>
The ExecRemote function takes two strings as parameters, containing the
site address and the path of the page that should be requested. The latter
must always start with the / (slash) character, i.e. must be relative to
the site root. Within the function a header class object is created by
passing a string consisting of a request (the actual header is empty) to
the class constructor. The request consists in the GET method followed by
the path of the page and protocol version. On the next line an object of
the httpPage class is created by passing the newly created header and an
empty body to the class constructor. Then the Exec method is called on the
object we just created using the site address as parameter. The function
returns the body of the server response page resulting from the call to
the Exec method.
An invocation of the ExecRemote function looks like the following:
<?
result = ExecRemote("www.tabasoft.it", "/home.bfr")
?>
The use of this function can be very useful in practice when we have to
insert content (news, search forms, marketing polls) originating from
another Web site in a box on the home page of our Web site and we do not
want to use frames or inline frames (the latter are not supported by all
browsers). In this case the script that implements our home page can
execute the remote page of interest and incorporate the result (typically
HTML code) in our page. Another scenario is a site distributed across
multiple remote machines. In this case the ExecRemote function is a very
useful tool to exploit resources on remote computers.
This section provides a concise discussion of the main methods and
properties of the request and client classes. We refer to the "Biferno: Reference Guide" for a complete description of all members of these classes.
The request static class allows to obtain information on the request for a
Biferno script (a page with ".bfr" extension) submitted to the server.
Properties of this class allow to gather information about the requested
file name (filename property), its path relative to the server or site
root (filePath property), and its absolute path on disk (physicalPath
property).
The physicalPath property actually contains the page path as requested by
the client, which may contain unresolved aliases.
To obtain the physical page path the ResolvePath method of the file class
should be used, or the curFile.basePath predefined method (no parameters)
should be called (which returns the absolute path of the directory where
the current script resides) and the fileName property of the request
class should be appended to the string returned.
<?
scriptPath = file.ResolvePath(request.physicalPath)
// alternatively
scriptPath = curFile.basePath + request.fileName
?>
Notice that the second expression in the above example may not return the
correct result if the script has been inserted into another script using
the include instruction, because the latter modifies the script's
basePath, but leaves the request untouched.
Other useful properties of the request class are:
The method property, which contains the request method (POST or
GET).
The host property, which contains the server IP address or the
site address.
The url property, which contains the path of the requested file,
possibly including the list of parameters passed in the URL with the
GET method and the so-called "path arguments", i.e. the part of the URL
enclosed between the $ and the ? characters that d