Summary:
See also: Classes and Objects
The Channel class provides basic read/write functionality for access to files or communication with sub-processes.
base.Channel
Class Methods | |
Name | Description |
create() |
Creates a new Channel object. |
Object Methods | |
Name | Description |
isEof() |
Returns TRUE if end of file is reached. |
openFile( path STRING, flags STRING ) |
Opens a Channel to a file identified by path, with options. |
openPipe( scmd STRING, flags STRING ) |
Opens a Channel to a process by executing the command scmd, with options. |
openClientSocket( host STRING,
port INTEGER, flags STRING, timeout INTEGER ) |
Opens a Channel to a socket server identified by host and port, with options. |
setDelimiter( d STRING ) |
Sets the field delimiter of the Channel. |
read( buffer-list ) |
Reads data from the input. |
write( buffer-list ) |
Writes data to the output. |
readLine() |
Reads a complete line of data from the Channel and returns the string. |
writeLine( buffer STRING ) |
Writes a complete line of data to the Channel. |
close() |
Closes the Channel. |
The Channel class is a built-in class that provides basic read/write functionality for accessing files or communicating with sub-processes.
Note that as with other BDL instructions, when you are reading or writing strings the escape character is the backslash "\".
Pay attention to the fact that no character set conversion is done when reading or writing data with channel objects: The code-set used in the data file must correspond to the locale of the runtime system, for both input and output.
First you must declare a base.Channel
variable; then, create a Channel object and optionally set the field delimiter:
01
DEFINE ch base.Channel02
LET ch = base.Channel.create()
After creating the Channel object, you typically set the field value delimiter with:
01
CALL ch.setDelimiter("^")
The default is DBDELIMITER, or "|"
if DBDELIMITER is not defined. If you pass NULL, no delimiter is used.
If the delimiter is set to NULL, the read() and write() methods do not use the backslash \ escape character. See read()/write() methods for more details.
You can specify "CSV" as the delimiter to read/write in Comma Separated Value format. This format is similar to the standard Channel format described in this page, with the following differences:
Example:
01
CALL ch.setDelimiter("CSV")
You can open a file for reading, writing, or both, by using the openFile()
method:
01
CALL ch.openFile( "file.txt", "w" )
The parameters for this method are:
The opening flags can be one of:
Any of the above options can be followed by:
When you use the "w"
or "a"
modes, the file is created if it does not exist.
With the openPipe()
method, you can read from the standard output of a sub-process, write to the standard input, or both.
01
CALL ch.openPipe( "ls", "r" )
The parameters for this method are:
The opening flags can be one of:
Use the openClientSocket()
method to establish a TCP connection to a server.
Pay attention to character set used by the network protocol you want to use by opening a channel with this method: The protocol must be based on ASCII, or must use the same character set as the application.
Example:
01
CALL ch.openClientSocket( "localhost", 80, "ub", 5 )
The parameters for this method are:
The opening flags can be one of:
Any of the above options can be followed by:
When you have finished with the Channel, close it with the close()
method:
01
CALL ch.close()
A Channel is automatically closed when the last reference to the Channel object is deleted.
You can trap exceptions with the standard WHENEVER ERROR exception handler:
01
WHENEVER ERROR CONTINUE02
CALL ch.write([num,label])03
IF STATUS THEN04
ERROR "An error occurred while reading from Channel"05
CALL ch.close()06
RETURN -107
END IF08
WHENEVER ERROR STOP
To detect the end of a file while reading from a Channel, you can use the isEof()
method:
01
DEFINE s STRING02
WHILE TRUE03
LET s = ch.readLine()04
IF ch.isEof() THEN EXIT WHILE END IF05
DISPLAY s06
END WHILE
The End Of File can only be detected after the last read (first read, then check EOF and process if not EOF).
When the Channel is open, you can use the readLine()
and writeLine()
method to read/write a complete line from/to the Channel, by ignoring the delimiter defined by setDelimiter()
:
01
DEFINE buff STRING02
CALL ch.writeLine("this is a complete line")03
LET buff = ch.readLine()
The readLine()
method must be used if the source stream does not contain lines with field separators.
The readLine()
method returns an empty string if the line is empty.
The readLine() function returns NULL if end of file is reached. To distinguish empty lines from NULL, you must use the STRING data type. If you use a CHAR or VARCHAR, you will get NULL for empty lines. To properly detect end of file, you can use the isEof() method.
When the Channel is open, you can use the the read()/write()
methods to read/write data records where field values are separated by a delimiter. If you want to read simple lines, use the readLine()/writeLine() methods instead.
You must provide a variable list by using the square brace notation ([param1,param2,...]
):
01
DEFINE a,b INTEGER02
DEFINE c,d CHAR(20)03
...04
WHILE ch1.read([a,b,c,d])05
CALL ch2.write([a,b,c,d])06
END WHILE
You can also pass all members of a record variable with the dot-star notation:
01
DEFINE rec RECORD02
a,b INTEGER,03
c,d CHAR(20)04
END RECORD05
...06
WHILE ch1.read([rec.*])07
CALL ch2.write([rec.*])08
END WHILE
The read()
function returns TRUE if data could be read. Otherwise, it returns FALSE, which indicates the end of the file or stream.
The input or output stream is text data where each line contains the string representation of a record. Field values are separated by the delimiter character defined with the setDelimiter() method.
For example, a formatted text file corresponding to the above code would look like this, when using a default pipe |
delimiter:
1|22|aaaaa|bbbbbbbb| 1234|456|xxxaa|bbbzzz| 12|4111|xxxaa|bbbzzz| 333||xxxaa||
Note that empty fields have a length of zero (||
) and will be read as NULL.
If and only if a delimiter is used, the backslash \
becomes the implicit escape character: When writing data with write()
, special characters like the backslash, line-feed or the delimiter character will be escaped. When reading data with read()
, any escaped \char
character will be converted to char
.
For example, the next code writes a single field value where the character string contains a backslash, the pipe delimiter and a line-feed character. Note that the backslash is also the escape character for string literals in Genero BDL, therefore we need to double the backslash to get a backslash in the string, while the line-feed character (<lf>)
is represented by backslash-n (\n
) in BDL string literals:
01
CALL ch.setDelimiter("|")02
CALL ch.write("aaa\\bbb|ccc\nddd") -- [aaa<bs>bbb|ccc<lf>ddd]
The above code will produce the following text file:
aaa\\bbb\|ccc\ ddd|
When reading such a line back into memory with the read()
method, all escaped characters will be converted back to the single character. In this example, \\
becomes \
, \|
becomes |
and \<lf>
becomes <lf>
.
Note that if the delimiter is set to NULL, the read() and write() methods do not use the backslash \ escape character. As a result, data with special characters like backslash, delimiter or line-feed will be written as is, and reading data will ignore escaped characters in the source stream. If you need to read or write non-formatted data, you should use the readLine()/writeLine() methods instead: These methods do not use a delimiter, nor do they use the backslash escape character.
Note that the LOAD/UNLOAD SQL instructions follow the same formatting rules as the read()/write()
channel methods, except that you can not define an NULL delimiter, because an empty DBDELIMITER environment variable defaults to the |
pipe.
When using the read()
/write()
functions, the escaped line-feed (LF, \n) characters are written as BS + LF to the output. When reading data, BS + LF are detected and interpreted, to be restituted as if the value was assigned by a LET instruction, with the same string used in the write()
function.
If you want to write a LF as part of a value, the string must contain the backslash and line-feed as two independent characters. You need to escape the backslash when you write the string constant in the BDL source file.
In the following code example, an empty delimiter is used to simplify explanation:
01
CALL ch.setDelimiter("")02
CALL ch.write("aaa\\\nbbb") -- [aaa<bs><lf>bbb]03
CALL ch.write("ccc\nddd") -- [aaa<lf>bbb]
... would generate the following output:
01
aaa\02
bbb03
ccc04
ddd
where line 01
and 02
contain data for the same line, in the meaning of a Channel record.
When you read these lines back with a read()
call, you get the following strings in memory:
Read 1
aaa<bs><lf>bbbRead 2
cccRead 3
ddd
These reads would correspond to the following assignments when using string constants:
01
LET s = "aaa\\\nbbb"02
LET s = "ccc"03
LET s = "ddd"
When using the readLine()
and writeLine()
functions, a LF character represents the end of a line.
Note that LF characters escaped by a backslash are not interpreted as part of the line during a readLine() call.
When a line is written, any LF characters in the string will be written as is to the output. When a line is read, the LF escaped by a backslash is not interpreted as part of the line.
For example, the following code:
01
CALL ch.writeLine("aaa\\\nbbb") -- [aaa<bs><lf>bbb]02
CALL ch.writeLine("ccc\nddd") -- [aaa<lf>bbb]
... would generate the following output:
01
aaa\02
bbb03
ccc04
ddd
... and the subsequent readLine()
will read four different lines, where the first line would be ended by a backslash:
Read 1
aaa<bs>Read 2
bbbRead 3
cccRead 4
ddd
On Windows platforms, DOS formatted text files use CR/LF as line terminators. You can manage this type of files with the Channel class.
By default, on both Windows and Unix platforms, when records are read from a DOS file with the Channel class, the CR/LF line terminator is removed. When a record is written to a file on Windows, the lines are terminated with CR/LF in the file; on UNIX, the lines are terminated with LF only.
If you want to avoid the automatic translation of CR/LF on Windows, you can use the b option of the openFil
e()
and openPipe()
methods. You typically combine the b option with r or w, based on the read or write operations that you want to do:
01
CALL ch.openFile( "mytext.txt", "rb" )
On Windows, when lines are read with the b option, only LF is removed from CR/LF line terminators; CR will be copied as a character part of the last field. In contrast, when lines are written with the b option, LF characters will not be converted to CR/LF.
On UNIX, writing lines with or without the binary mode option does not matter.
This program reads data from the "file.txt" file that contains two columns separated by a | (pipe) character, and re-writes this data at the end of the "fileout.txt" file, separated by "%"
01
MAIN02
DEFINE buff1, buff2 STRING03
DEFINE ch_in, ch_out base.Channel04
LET ch_in = base.Channel.create()05
CALL ch_in.setDelimiter("|")06
LET ch_out = base.Channel.create()07
CALL ch_out.setDelimiter("%")08
CALL ch_in.openFile("file.txt","r")09
CALL ch_out.openFile("fileout.txt","w")10
WHILE ch_in.read([buff1,buff2])11
CALL ch_out.write([buff1,buff2])12
END WHILE13
CALL ch_in.close()14
CALL ch_out.close()15
END MAIN
This program executes the "ls" command and displays the filenames and extensions separately:
01
MAIN02
DEFINE fn CHAR(40)03
DEFINE ex CHAR(10)04
DEFINE ch base.Channel05
LET ch = base.Channel.create()06
CALL ch.setDelimiter(".")07
CALL ch.openPipe("ls -l","r")08
WHILE ch.read([fn,ex])09
DISPLAY fn, " ", ex10
END WHILE11
CALL ch.close()12
END MAIN
01
MAIN02
DEFINE i INTEGER03
DEFINE s STRING04
DEFINE ch base.Channel05
LET ch = base.Channel.create()06
CALL ch.openFile("file.txt","r")07
LET i = 108
WHILE TRUE09
LET s = ch.readLine()10
IF ch.isEof() THEN EXIT WHILE END IF11
DISPLAY i, " ", s12
LET i = i + 113
END WHILE14
CALL ch.close()15
END MAIN
01
MAIN02
DEFINE ch base.Channel, eof INTEGER03
LET ch = base.Channel.create()04
-- We open the Channel in binary mode to control CR+LF05
CALL ch.openClientSocket("localhost",80,"ub", 30)06
-- HTTP expects CR+LF: Note that LF is added by writeLine()!07
CALL ch.writeLine("GET / HTTP/1.0\r")08
-- No HTTP headers...09
-- Empty line = end of headers10
CALL ch.writeLine("\r")11
WHILE NOT eof12
DISPLAY ch.readLine()13
LET eof = ch.isEof()14
END WHILE15
CALL ch.close()16
END MAIN