Import Non-.DBF Data
Explore one of the many uses of low-level file
functions: Convert Cardfile data to FoxPro tables.
By Ted Roche
[Originally published in FoxPro Advisor, January 1995]
What are LLFFs?
Low-level file functions (LLFFs) provide the ability to read and write files in foreign file formats within FoxPro. These files can be in any format—text files, binary data, such as bitmaps, or even .EXE files. (Caution: You’ll probably want to experiment on copies of important files, as there is no UnDo() function.) In this article, I discuss and demonstrate reading a Windows Cardfile into a FoxPro database. While LLFFs also provide limited ability to access other DOS devices (such as the serial port) directly, the focus of this article is on file manipulation.
Why use LLFFs?
Low-level file functions are useful in so many situations, your imagination may be all that limits them. LLFFs are used to read system files, such as CONFIG.SYS and AUTOEXEC.BAT, to ensure that required settings or commands are present. They are also used to read and correct corrupted FoxPro databases or indexes. As a security measure, they can be used to make your application’s .DBF files unreadable by others. Or, as in the example given here, they can be used to read in data files whose format is not supported by FoxPro’s IMPORT command.
How to use LLFFs
Using LLFFs in your program doesn’t require a different programming structure or approach than you have used for FoxPro’s native high-level functions. The main difference is that you need to be aware of any structure built into the files, whereas FoxPro knows how to work with .DBF, .CDX, and .FPT files. In fact, you could look at low-level file functions as providing access to files that consist of a sequence of one-byte records. Rather than USEing a database, you FOPEN() or FCREATE() a file. Instead of an alias or select area, you specify the file you’re using by referring to the file handle returned by the functions.
Rather than SCATTERing the contents of a .DBF’s fields, you FREAD() one or more bytes or FGETS() characters up to the next carriage return in the file. Conversely, you would FWRITE() one or more bytes or FPUTS() a character string, rather than REPLACEing or GATHERing memory variables to disk.
Because the file consists of unindexed one-byte records, there’s no equivalent to SEEK or LOCATE. Instead, you just FREAD() until you find the character you’re looking for. However, if you know the location in the file where you want to work, the FSEEK() function works similarly to the GO and SKIP
commands to reposition you within the file.
Finally, when you’re done with the file, you FCLOSE() it, just as you would USE a .DBF when done.
Error handling
LLFFs have their own error-handling capabilities that are separate from the usual ON ERROR trapping used by FoxPro. There are two levels of error handling. First, check for proper response when issuing a function. Then, investigate details of the error using the FERROR() function. For example, when FOPEN() is used, a non-negative number is returned if the file is opened successfully. If a negative number is returned, you know an error has occurred and you can proceed from there. Similarly, the reading, writing, and pointer repositioning functions return the number of bytes processed, so that an incomplete process can be detected and handled appropriately.
Putting LLFFs to work
The sample code in this article, demonstrates importing data from Windows’ Cardfile format. Cardfile is a Windows applet, a simple demonstration application that ships with Windows. It has basic indexing, search, and autodial capabilities and uses few resources, limiting itself to a 64K memory segment. For a simple database application or for machines severely limited in resources, Cardfile is a handy way of letting users view names, addresses, or other company information without incurring the heavy overhead of keeping a FoxPro session running.
The application, included on this issue’s COMPANION RESOURCE DISK and reproduced here, consists of three files. READCARD.PRG is the project’s main file and the driving program, ASC2Num.PRG is a UDF to convert binary-stored numbers to numerics, and LLError.PRG is a generic low-level error handler.
Bear in mind these are simplified routines designed to illustrate basic functionality, and additional safeguards may be needed for a commercial-grade application. The COMPANION RESOURCE DISK also contains a sample Cardfile containing the text of Lincoln’s Gettysburg address. (See the sidebar “Windows Cardfile Format” later in this article for a description of a Cardfile format.)
Main Program – READCARD.PRG
* READCARD.PRG - read in cardfile database
* Purpose: Read a Window CardFile,
*
Write a DBF
PRIVATE ALL LIKE L*
* Select Input CardFile and Output DBF *
****************************************
lcCardFile = GETFILE("CRD", ;
"Read which cardfile?","Read",0)
IF EMPTY(lcCardFile) OR NOT FILE(lcCardFile)
* Cancel or no file
RETURN
ENDIF
lcCardDBF = PUTFILE("Save As:", ;
STRTRAN(lcCardFile, "CRD", "DBF"), ;
"DBF")
IF EMPTY(lcCardDBF)
* an empty return -> cancel selected
RETURN
ENDIF
* Open the input Cardfile *
***************************
lnCardHandle = FOPEN(lcCardFile,0)
IF lnCardHandle = -1
DO llError
ENDIF
* Create the output table *
***************************
CREATE TABLE (lcCardDBF) (Topic C(50),Contents M)
* Read CardFile Header *
************************
IF FSEEK(lnCardHandle,0,0) # 0
DO llError
ENDIF
&& go to BOF
lcFileType = FREAD(lnCardHandle,3)
IF lcFileType # "MGC" AND lcFileType # "RRG"
WAIT WINDOW lcCardFile + ;
"is not a valid CardFile."
=FCLOSE(lnCardHandle)
RETURN
ENDIF
* Calculate the number of cards *
*********************************
* # cards, binary
lbCardCnt = FREAD(lnCardHandle,2)
DO llError
* # cards, decimal
lnCardCnt = ASC2Num(lbCardCnt)
* Loop through the cards *
**************************
FOR lnCardNum = 1 TO lnCardCnt
* read in cards
* Display a status window *
***************************
WAIT WINDOW NOWAIT "Reading card #" ;
+TRANSFORM(lnCardNum,"999")
* Position at the record *
**************************
lnPosition = 11 + 52 * (lnCardNum - 1)
IF lnPosition # FSEEK(lnCardHandle,lnPosition,0)
DO llError
ENDIF* The first five characters point to
* the location of the card's contents *
***************************************
* offset,binary
lbContPtr = FREAD(lnCardHandle,5)
DO llError
* offset, decimal
lnContPtr = Asc2Num(lbContPtr)
* Read the Topic *
******************
lcTopic = FREAD(lnCardHandle,47)
DO llError
ln0End = AT(CHR(00),lcTopic)
lcTopic = iif(ln0End=0, ;
lcTopic,left(lcTopic,ln0End-1))
* Reposition to the Contents & Read *
*************************************
IF lnContPtr+2 # ;
FSEEK(lnCardHandle,lnContPtr+2,0)
DO llError
ENDIF
lbContSize = FREAD(lnCardHandle,2)
DO llError
lnContSize = Asc2Num(lbContSize)
IF lnContSize # 0
lcContents = FREAD(lnCardHandle,lnContSize)
DO llError
ELSE
lcContents = ""
ENDIF
* Create the output record *
****************************
INSERT INTO (lcCardDBF) ;
(Topic
, ;
Contents) ;
VALUES
;
(lcTopic , ;
lcContents)
NEXT
=FCLOSE(lnCardHandle)
RETURN
ASCII-to-numeric conversion UDF
Asc2Num.PRG
* Asc2Num
* Convert multi-character hi-byte, lo-byte
* to decimal
PARAMETER tcString
PRIVATE lnNum
* Trim off ending CHR(00)'s
DO WHILE RIGHT(tcString,LEN(tcString)) = CHR(00)
tcString = LEFT(tcString,LEN(tcString)-1)
ENDDO
lnNum = 0
FOR i = LEN(tcString) TO 1 STEP -1
lnNum = lnNum + ;
ASC(SUBSTR(tcString,i,1))*256^(i-1)
NEXT
RETURN lnNum
Low-level file function error handler
* LLError.PRG
* Low Level Error Handler
PRIVATE ALL LIKE l*
IF FERROR() # 0
DO CASE && Determine which error
CASE FERROR() = 2
lcReason = 'File not found'
CASE FERROR() = 4
lcReason = ;
'Too many files open (out of handles)'
CASE FERROR() = 5
lcReason = 'Access denied'
CASE FERROR() = 6
lcReason = 'Invalid file handle given'
CASE FERROR() = 8
lcReason = 'Out of memory'
CASE FERROR() = 25
lcReason = 'Seek error'
CASE FERROR() = 29
lcReason = 'Disk full'
CASE FERROR() = 31
lcReason = 'General Failure'
OTHERWISE
lcReason = 'Unknown LL error '+ ;
LTRIM(STR(FERROR()))
ENDCASE
*** Display the error ***
WAIT WINDOW "Low level file error: "+ ;
lcReason NOWAIT
CLOSE ALL
CANCEL && you may want to return to master
ENDIF
RETURN
Windows Cardfile Format
The complete file format for Windows Cardfiles is in the Microsoft Knowledge Base as article #Q99340. The file formats for many types of data files are either documented by the manufacturer, or by third parties in books and magazines. Once the format is known, FoxPro’s low-level file functions let you go in and get the data.
Cardfile 3.0 Header
Bytes
|
Contents
|
0,1,2
|
Signature / version bytes, always "MGC" for 3.0 and for 3.1 files containing only text, "RRG" for 3.1 containing OLE
objects
|
3,4
|
No. of cards, high byte, low byte format Cardfile 3.0 Index line
|
0,5
|
Reserved for future use, always CHR(00)'s
|
6-9
|
Pointer to card's body text
|
10
|
Always CHR(00), 'flag byte'
|
11-50
|
Index text (appearing atop card)
|
51
|
Always CHR(00)
|
Cardfile 3.0 Body
Bytes
|
Contents
|
0,1
|
length of body text (440 characters max)
|
2-xxx
|
body text
|
Ted Roche enhances Xbase systems and trains programmers in more efficient development techniques. He is a 1994 recipient of the Microsoft Support Most Valuable Professional Award. Ted has published several articles, contributed to two books on FoxPro, and was a technical editor for four FoxPro 2.5 books. He is co-editor of the Boston Computer Network News, and is a frequent speaker at user group and professional conferences. CompuServe 76400,2503.
Copyright 1995, 2017 by Ted Roche under a Creative Commons Attribution, Share-alike, Non-commercial license.