Back to Contents


The Channel class

Summary:

See also: Built-in classes


Syntax

The Channel class provides basic read/write functionality for access to files or communication with sub-processes.

Syntax:

base.Channel


Methods:

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.
readbuffer-list ) RETURNING INTEGER Reads data from the input.
writebuffer-list ) Writes data to the output.
readLine() RETURNING STRING Reads a complete line of data from the Channel and returns the string.
writeLinebuffer STRING ) Writes a complete line of data to the Channel.
close() Closes the Channel.

Usage:

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 "\".


Creating a Channel object

First you must declare a base.Channel variable; then, create a Channel object and optionally set the field delimiter:

01 DEFINE ch base.Channel
02 LET ch = base.Channel.create()

Setting the value delimiter

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.


Opening a Channel

Opening a file Channel

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:

  1. The path to the file
  2. The opening flags

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.

Opening a pipe Channel

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:

  1. The command to be executed
  2. The opening flags

The opening flags can be one of:

Opening a client socket Channel

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:

  1. The name of the host machine you want to connect to
  2. The port number of the service
  3. The opening flags
  4. The timeout in seconds. -1 indicates no timeout (wait forever)
The timeout is specified in seconds, -1 waits forever.

The opening flags can be one of:

Any of the above options can be followed by:


Reading from / Writing to a Channel

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 INTEGER
02 DEFINE c,d CHAR(20)
03 WHILE ch1.read([a,b,c,d])
04   CALL ch2.write([a,b,c,d])
05 END WHILE

Closing a Channel

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.


Exception handling

You can trap exceptions with the standard WHENEVER ERROR exception handler:

01 WHENEVER ERROR CONTINUE
02 CALL ch.write([num,label])
03 IF STATUS THEN
04    ERROR "An error occurred while reading from Channel"
05    CALL ch.close()
06    RETURN -1
07 END IF
08 WHENEVER ERROR STOP

Detecting end of file

To detect the end of a file while reading from a Channel, you can use the isEof() method:

01 DEFINE s STRING
02 WHILE TRUE
03    LET s = ch.readLine()
04    IF ch.isEof() THEN EXIT WHILE END IF
05    DISPLAY s
06 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) .


Reading and writing complete lines

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 STRING
02 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. 


Managing line terminators with read() and write()

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 bbb
03 ccc
04 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>bbb
Read 2 ccc
Read 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"

Managing line terminators with readLine() and writeLine()

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 bbb
03 ccc
04 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 bbb
Read 3 ccc
Read 4 ddd

Line terminators on Windows platforms

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 openFile() 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.


Examples

Example 1: Reading formatted data from a file

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 MAIN
02     DEFINE buff1, buff2 STRING
03     DEFINE ch_in, ch_out base.Channel
04     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 WHILE
13     CALL ch_in.close()
14     CALL ch_out.close()
15 END MAIN

Example 2: Executing the ls UNIX command

This program executes the "ls" command and displays the filenames and extensions separately:

01 MAIN
02     DEFINE fn CHAR(40)
03     DEFINE ex CHAR(10)
04     DEFINE ch base.Channel
05     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, "   ", ex
10     END WHILE
11     CALL ch.close()
12 END MAIN

Example 3: Reading lines from a file:

01 MAIN
02     DEFINE i INTEGER
03     DEFINE s STRING
04     DEFINE ch base.Channel
05     LET ch = base.Channel.create()
06     CALL ch.openFile("file.txt","r")
07     LET i = 1
08     WHILE TRUE
09         LET s = ch.readLine()
10         IF ch.isEof() THEN EXIT WHILE END IF
11         DISPLAY i, " ", s
12         LET i = i + 1
13     END WHILE
14     CALL ch.close()
15 END MAIN

Example 4: Communicating with an HTTP server:

01 MAIN
02     DEFINE ch base.Channel, eof INTEGER
03     LET ch = base.Channel.create()
04     -- We open the Channel in binary mode to control CR+LF
05     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 headers
10     CALL ch.writeLine("\r")
11     WHILE NOT eof
12         DISPLAY ch.readLine()
13         LET eof = ch.isEof()
14     END WHILE
15     CALL ch.close()
16 END MAIN