Summary:
See also: Built-in classes
The Channel class provides basic read/write functionality for access to files or communication with sub-processes.
base.Channel
Class Methods | |
Name | Description |
create() RETURNING base.Channel |
Creates a new Channel object. |
Object Methods | |
Name | Description |
isEof() RETURNING INTEGER |
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 ) RETURNING
INTEGER |
Reads data from the input. |
write( buffer-list
) |
Writes data to the output. |
readLine() RETURNING STRING |
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.
Warning: As with other BDL instructions, when you are reading or writing strings the escape character is the backslash "\".
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 typical 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.
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.
Warning: The network 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 the Channel is open, you can read and/or write data from/to the
input/output. You must provide a variable list by using the the square brace
notation ([param1,param2,...]
). The read function returns TRUE
if data could be read.
01
DEFINE a,b INTEGER02
DEFINE c,d CHAR(20)03
WHILE ch1.read([a,b,c,d])04
CALL ch2.write([a,b,c,d])05
END WHILE
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
Warning: The End Of File can only be detected after the last read (first read, then check EOF and process if not EOF) .
If the stream does not contain
lines with fields (and field separators), you should use the readLine()
and writeLine()
methods. These methods 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.
Warning: 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 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.
Warning: 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