MEMO TO CHARACTER CONVERSION

By Rick Fillman

An article in the May 1987 issue of TechNotes, "Memo Fields
Revisited," described the behavior of memo fields, including the
limitations on manipulating memo field data under program
control. Since it is not possible to use string operations with
memo field data, you cannot search memo fields in a dBASE III
PLUS program. Neither can you tell anything about the size of the
memo data or even whether a memo field is empty. 

As a result, if you want to manipulate memo field data, you must
somehow convert it to character-type data. The program supplied
here, Memo2dbf.PRG, does exactly that. Memo field data is
APPENDed into a character field in a new .DBF file.

Although you can run Memo2dbf standalone, three example programs,
each of which calls Memo2dbf as a subroutine, are included here.
Each program demonstrates a way to use Memo2dbf with memo files.
Memo2dbf must be available on the disk for any of these sample
applications to run. 

What Memo2dbf Can Do

Memo2dbf places memo field data, one line at a time, into a
character field in a separate database file for later use. This
is what makes it possible to search, sort, and perform other
string-type functions on the information that was contained in
the memo fields. The example programs show some practical ways
that you can put this new capability to use:
1. Memotype.PRG types out only nonempty memo fields to the
printer or screen.
2. Memoline.PRG calculates the length of all memo fields, given
the width of the page.
3. Memochar.PRG copies the first line of memo field data to a
character field.

How Memo2dbf Operates

Memo2dbf is called with four parameters:  
1. The name of the memo field (in quotation marks or  in a memory
variable)
2. The width of the memo field (a number between 10 and 254)
3. The name of the file to be created (in quotation marks, with
no extension)
4. A numeric memory variable (to hold an exit status code), which
must be initialized before you run the program

SET ALTERNATE TO <filename>, followed by  SET ALTERNATE ON, sets
up redirection of screen output to a file. The memo field data is
written to the screen using LIST. Word wrapping occurs
automatically within the bounds of the width setting passed in
the second parameter.

The program then creates the target database file, with the name
specified in the third parameter. The new file contains two
character fields. The first is called "Mrecord" and contains the
original record number of the memo field. The second field is
given the name of the original memo field. The length of this
field is determined by the second parameter. When the text file
is APPENDed SDF, the memo field text data from the original
database file is placed into this character field, using as many
records as necessary for the entire contents.

The last parameter is a numeric memory variable into which
Memo2dbf places an exit status code. This permits a program to
call Memo2dbf as a subprocedure and handle errors that might
occur. The error code is set to one of four values:

0 - No error occurred.
1 - No database in USE in workarea 1.
2 - The specified field does not exist or is not a memo field.
3 - The text line width is not between 10 and 254.

The database file with the memo field to be converted must be in
USE in workarea 1. If no database file is in USE, the program
aborts. In addition, two potentially large files are created by
this process; so, plenty of free disk space will be required. The
program does not attempt to check for disk space. The amount of
free space required will be approximately twice the size of the
present .DBT file.

Results 

Once the (formerly) memo data is in a character field, string
searches and string operations of all kinds are possible. The new
database has one record for each line of memo data. Even empty
memo fields generate one record. The record number from the
original file appears in the Mrecord field opposite the first
line of each memo text block of records. Also, if any memo fields
contained extra carriage returns at the bottom, these are
represented as blank lines in the text file and thus blank
records in the .DBF file. 

Limitations

The method used by this program, namely writing out to a
temporary text file and APPENDing back into a .DBF file, is
somewhat slow. Note that lots of free disk space is required.
This method relies only on commands and procedures available
within dBASE III PLUS.

Memochar.PRG

At some point, you might decide to do away with memo fields
entirely and convert as much of the existing memo field data as
possible to character-type data. Memochar.PRG accomplishes this.
The fixed-length (254 characters maximum) and no-word-wrap
limitations of character-type data must be considered. Memo field
data that is beyond the bounds of the specified line width will
be lost during the conversion.

Memochar uses a file created by Memo2dbf to create a new database
file with the memo field replaced by a character field. Memochar
is called with five parameters:
1. The name of the memo field to be converted (in quotation
marks)
2. The width of the character field to be created (between 10 and
254)
3. The name of the file created by Memo2dbf (in quotation marks,
with no extension)
4. A numeric memory variable (to hold an exit status code), which
should be initialized before you run the program
5. The name of the new database to be created (in quotation
marks, with no extension)

For example, to convert the memo fields of a file called
Sales.DBF, use these commands:

error = 0
DO Memochar WITH "Sales", 65, "Temp", error, "Newsales"

Memochar tests for the existence of the file named by the third
parameter. If the file exists, it will be used to create the new
database. If it does not exist, Memochar calls Memo2dbf to create
the file. 

Memochar returns one of five possible exit status codes:

0 - No errors occurred.
1 - No database open in workarea 1.
2 - Field specified does not exist or is not a memo field.
3 - Width of field to create is outside of 10 to 254 range.
5 - Concatenated field names exceed 254 characters.

SET RELATION TO, SET FIELDS TO, and COPY TO are used to create
the new database. Again, plenty of free disk space is needed. The
COPY TO command must list all the fields in the original
database. Since a command must not exceed 254 characters, the
combined width of the field names from the original database,
including delimiting commas, must not exceed approximately 220
characters. An exit status code has been established to indicate
that this limitation has been exceeded.

The new file will have the same structure as your original
database, minus the memo field, but with a new character-type
data field. It will have the same name as your original memo
field and will contain the former memo field data.

The length of this new character field is specified by Memochar's
second parameter. Since only one line of text from each memo
field can be placed in the character field, text beyond the first
line will be discarded.

Memotype.PRG

The contents of memo fields can be "dumped" by the command LIST
<memofield>. If there are many empty memo fields, however, this
results in many blank lines. At least one line will be output for
each record, regardless of whether the memo field is empty. If
the destination is a printer, this can result in much wasted
paper. The Memotype.PRG utility will print only those memo fields
that have something in them.

Memotype deletes all records in a file created by Memo2dbf that
do not contain a record number. This leaves only the first line
of each memo field. A SET RELATION statement links the SELECT
workarea 2 file to the SELECT workarea 1 file. The relation
controls which records from the SELECT workarea 1 file are
printed.

The program checks for the existence of text in the first line of
the memo field only. Consequently, any memo fields with blank
first lines are treated as if they were empty.

Memotype.PRG is called with five parameters:

1. The name of the memo field to list (in quotation marks)
2. The width of the character field created by Memo2dbf.PRG
(between 10 and 254)
3. The name of the database file created by Memo2dbf.PRG (in
quotation marks, with no extension)
4. A numeric memory variable (to hold an exit status code), which
should be initialized before you run the program
5. A logical value: .T. to print to printer, .F. to print to
screen

For example, to print the memo fields of a file called Sales.DBF
to your printer use these commands:

error = 0
DO Memotype WITH "Sales", 65, "Temp", error, "Newsales", .T.

The program checks for the existence of the file specified by the
third parameter. If it does not exist, Memotype calls Memo2dbf to
create the file. Memotype returns one of four exit codes in the
fourth parameter:

0 - No errors occurred.
1 - No database open in workarea 1.
2 - Field specified does not exist or is not a memo field.
3 - Width of field to create is outside of 10 to 254 range.

Memoline.PRG

A frustrating problem encountered when developing custom reports
based on memo fields is that you can't tell in advance how many
lines of text are contained in a memo field. Memoline.PRG will
REPLACE into a field in the original database a number
representing how many lines of text data are found in the memo
field for each record. 

The program requires that the user MODIFY STRUCTURE to add a
numeric field (width 5, no decimals) called Memoline to the
original database. The program REPLACEs the memo line count into
this field for each record. If the memo field is empty, a zero is
entered.

Memoline is called with four parameters:

1. The name of the memo field (in quotation marks)
2. The width of the character field in the database created by
Memo2dbf (between 10 and 254)
3. The name of the file created by Memo2dbf (in quotation marks,
with no extension)
4. A numeric memory variable to hold the exit status code, which
should be initialized before you run the program

For example, to count the number of lines of text in memo fields
of a file called Sales.DBF use these commands:

error = 0
DO Memoline WITH "Sales", 65, "Temp", error

Memoline checks for the existence of the file specified by the
third parameter. If it does not exist, Memo2dbf is called to
create the file. 

Record number values from the resulting database are used to 
determine the number of lines of text. It is important to
remember that the number of lines will be directly affected by
the line-length setting in the second parameter.

Memoline returns one of five exit status codes in its third
parameter:

0 - No errors occurred.
1 - No database open in workarea 1.
2 - Field specified does not exist or is not a memo field.
3 - Width of field to create is outside of 10 to 254 range.
4 - The Memoline field does not exist in the database.

Memo2dbf
* Program ....: Memo2dbf.PRG
* Author .....: Rick Fillman
* Date .......: November 1, 1987
* Version ....: dBASE III PLUS
* Notes(s) ...: Utility to bring MEMO field (.DBT file) data into a
*               dBASE database file (.DBF file) for analysis.
*               Make sure you have plenty of free disk space since
*                 1. Memotext.TXT temporary text file is created to
*                    hold contents of all Memo field data, and
*                 2. Memo2dbf.DBF will APPEND (import) the entire
*                    contents of Memotext.TXT
*               Thus, two possibly large temporary files will be created.
*               Floppy disk system users need to take special care.
*               
*               Errors are returned in the parameter "error."
*
*                       0    no error
*                       1    no DBF in use
*                       2    wrong field type
*                       3    field width out of range
*
PARAMETERS memofield, memowidth, memofile, error
SET TALK OFF
SET SAFETY OFF
SET TITLE OFF
error = 0
* ---In case of setup error, ABORT program.
DO WHILE .T.
     *
     * ---Make sure a .DBF file is in USE in SELECT 1.
     SELECT 1
     IF LEN(DBF()) = 0
          error = 1
          EXIT
     ENDIF
     *
     * ---Make sure that field name represents a valid memo field.
     IF TYPE("&memofield") <> "M"
          error = 2
          EXIT
     ENDIF
   IF memowidth < 10 .OR. memowidth > 254
          error = 3 
          EXIT
     ENDIF
   SET MEMOWIDTH TO memowidth
     *
     * ---Create output file from current database file.
   COPY TO Tempstru STRUCTURE EXTENDED
     SELECT 2
     *
     * ---Create .DBF structure with just two fields.
     USE Tempstru    
   ZAP
   APPEND BLANK
   REPLACE Field_name WITH "mrecord",;
                  Field_type WITH "C",;
                  Field_len  WITH 8
   APPEND BLANK
   REPLACE Field_name WITH memofield,;
                    Field_type WITH "C",;
                    Field_len  WITH memowidth
   USE
   CREATE &memofile FROM Tempstru
     *
     * ---Clean up used temporary file.
     ERASE Tempstru.DBF
     * 
     * ---List memo fields to a text file (and to the screen, too!)
     SET HEADING OFF
     SELECT 1
     SET ALTERNATE TO Memotext
     SET ALTERNATE ON
     LIST FIELDS &memofield
     CLOSE ALTERNATE
     *
     * ---Append from text file into memofile.
     SELECT 2
     USE &memofile
     ZAP
     APPEND FROM Memotext SDF
     *
     * ---Erase useless files.
     ERASE Memotext.TXT
     USE
     EXIT            
ENDDO             
SET TITLE ON
SET HEADING ON
SET MEMOWIDTH TO 65
RETURN
* EOP Memo2dbf.PRG

Memochar
* Program ....: Memochar.PRG
* Author .....: Rick Fillman
* Date .......: November 1, 1987
* Version ....: dBASE III PLUS
* Notes(s) ...: Utility to move memo field data to a character-type field.
*               Source file is assumed to be in USE in SELECT 1 workarea.
*               A new dBASE file is created with the added field. (You
*               want to examine the results and then delete the original
*               file and rename the new file.) Dbfstruc.DBF is used internally 
*               and then erased.
*
*               Errors are returned in the parameter "error."
*
*                       0    no error
*                       1    no DBF in use
*                       2    wrong field type
*                       3    field width out of range
*                       5    concatenated field names exceed 254 characters
*
PARAMETERS memofield, memowidth, memofile, error, newfile
SET SAFETY OFF
DO WHILE .T.
     * ---Make sure there's a datafile in USE.
     SELECT 1
     IF LEN(DBF()) = 0
          error = 1
          EXIT
     ENDIF
     IF TYPE("&memofield") <> "M"
          error = 2
          EXIT
     ENDIF
   IF memowidth < 10 .OR. memowidth > 254
          error = 3 
          EXIT
     ENDIF
     SELECT 2
     IF .NOT. FILE("&memofile..DBF")
          DO Memo2DBF WITH memofield, memowidth, memofile, error
          IF error <> 0
               *
               * ---Pass back Memo2dbf's error number.
               EXIT
          ELSE
            SELECT 2
          ENDIF
     ENDIF
     USE &memofile
     * ---Construct a memory variable containing names of fields
     SELECT 1
     COPY TO Dbfstruc STRUCTURE EXTENDED
     SELECT 3
     USE Dbfstruc
     mfields = ""
     DO WHILE .NOT. EOF()
          *
          * ---Make sure this memory variable length doesn't exceed maximum
        IF LEN(mfields) >= 220
               error = 5
               EXIT
          ENDIF
          *
          * ---Build variable by repeated concatenation, not including Memo field.
        mfields = IIF(UPPER("&memofield") <> TRIM(field_name), ;
                              mfields  + "," + TRIM(field_name), mfields)
        SKIP
     ENDDO
     USE
     ERASE Dbfstruc.DBF
     *
     * ---Create index file to be used with SET RELATION.
     SELECT 2
     INDEX ON VAL(mrecord) TO &memofile
     SELECT 1
     *
     * ---Make fields from both files available.
     SET FIELDS TO B->&memofield &mfields
     SET RELATION TO RECNO() INTO B
     *
     * ---Create Newfile.DBF from fields in both database files
     COPY TO &newfile FIELDS &memofield &mfields
     SET RELATION TO
     SELECT 2
     USE
     EXIT
ENDDO
SELECT 1
SET INDEX TO
ERASE &memofile..NDX
SET SAFETY ON
RETURN
* EOP Memochar.PRG

Memotype
* Program ....: Memotype.PRG
* Author .....: Rick Fillman
* Date .......: November 1, 1987
* Version ....: dBASE III PLUS
* Notes(s) ...: Utility to eliminate empty Memo fields for printing.
*               Source file is assumed to be in USE in SELECT 1 workarea.
*               Only those that have content will print.
*
*               USAGE:
*                 DO Memotype WITH "<memofield>",;   && Name of memo field
*                                  memowidth,;       && Width of memo field
*                                  memofile,;        && Name of file created by
*                                                    &&    Memo2dbf.PRG
*                                  error,;           && Variable for errors.
*                                  logical_var       && Print to printer?
*
*                 Errors are returned in the parameter "error."
*
*                       0    no error
*                       1    no DBF in use
*                       2    wrong field type
*                       3    field width out of range
*
*               Example:
*                 DO Memotype WITH "memo", 78, "Mymemos", error, .F.
*
PARAMETERS memofield, memowidth, memofile, error, printer
SET SAFETY OFF
SET TALK OFF
DO WHILE .T.
     *
     * ---Make sure a .DBF file is in USE in SELECT 1.
     SELECT 1
     IF LEN(DBF()) = 0
          error = 1
          EXIT
     ENDIF
     *
     * ---Make sure that field name represents a valid memo field.
     IF TYPE("&memofield") <> "M"
          error = 2
          EXIT
     ENDIF
   IF memowidth < 10 .OR. memowidth > 254
          error = 3 
          EXIT
     ENDIF
     SELECT 2
     IF .NOT. FILE("&memofile..DBF")
          DO Memo2DBF WITH memofield, memowidth, memofile, error
          IF error <> 0
               *
               * ---Pass back Memo2dbf's error number.
               EXIT
          ELSE
            SELECT 2
          ENDIF
     ENDIF
     SET DELETED ON
     USE &memofile
     *
     * ---Mark for deletion all but the first record of any memo group
     DELETE FOR VAL(Mrecord) = 0
     SET RELATION TO VAL(mrecord) INTO A
     IF printer
        EJECT
        SET PRINT ON
     ENDIF
     LIST FIELDS mrecord, A->&memofield OFF FOR LEN(TRIM(&memofield)) <> 0
     SET PRINT OFF
     SET RELATION TO
     SET DELETED OFF
     RECALL ALL
     EXIT
ENDDO
USE
SELECT 1
SET SAFETY ON
SET TALK ON
RETURN
* EOP Memotype.PRG

Memoline
* Program ....: Memoline.PRG
* Author .....: Rick Fillman
* Date .......: November 1, 1987
* Version ....: dBASE III PLUS
* Notes(s) ...: Utility to return the number of lines of text in a memo
*               field. Source database file must contain a blank numeric field
*               named Memoline. The number of memo field text lines will
*               be entered into this field.
*
*               Source file must be in USE in SELECT 1 workarea.
* 
*               USAGE:
*                 DO Memotype WITH "<memofield>",;   && Name of memo field
*                                  memowidth,;       && Width of memo field
*                                  memofile,;        && Name of file created by
*                                                    && Memo2dbf.PRG
*                                  error,;           && Variable for errors.
*                    
*                       Errors are returned in the parameter "error."
*
*                       0    no error
*                       1    no DBF in use
*                       2    wrong field type
*                       3    field width out of range
*                       4    no numeric field Memoline
*
*
PARAMETERS memofield, memowidth, memofile, error
SET SAFETY OFF
SET TALK OFF
SET HEADING OFF
SET MEMOWIDTH TO memowidth
SELECT 1
DO WHILE .T.
     *
     * ---Make sure there's a datafile in USE.
     SELECT 1
     IF LEN(DBF()) = 0
          error = 1
          EXIT
     ENDIF
     IF TYPE("&memofield") <> "M"
          error = 2
          EXIT
     ENDIF
   IF memowidth < 10 .OR. memowidth > 254
          error = 3 
          EXIT
     ENDIF
     *
     * --- Make sure Numeric field called Memoline has been created.
     IF TYPE("Memoline") <> "N"
          error = 4
          EXIT
     ENDIF
     SELECT 2
     *
     * ---Check that Memo2dbf.PRG has been previously run.
     IF .NOT. FILE("&memofile..DBF")
          DO Memo2DBF WITH memofield, memowidth, memofile, error
          IF error <> 0
               *
               * ---Pass back Memo2dbf's error number.
               EXIT
          ELSE
            SELECT 2
          ENDIF
     ENDIF
     USE &memofile
     *
     * ---Create index file to be used with SET RELATION.
     INDEX ON VAL(mrecord) TO &memofile
     SELECT 1
     SET RELATION TO RECNO() INTO B
     *
     * ---SKIP through source database
     GOTO TOP
     DO WHILE .NOT. EOF()
          ? recno()
          SELECT 2
          *
          * --- If not blank, note starting record number in Memo2dbf.DBF.
        mstart = IIF(LEN(TRIM(&memofield)) = 0, 0, RECNO())
        SELECT 1
        SKIP 1
        SELECT 2
          *
          * --- Note ending record number and calculate difference.
        mlength = IIF(mstart = 0, 0, RECNO() - mstart)
        SELECT 1
        SKIP -1
          *
          * --- Replace this value into Memoline field in source database file
        REPLACE Memoline WITH mlength
        SKIP
     ENDDO
     SELECT 2
     SET INDEX TO
     SET RELATION TO
     ERASE &memofile..NDX
     USE
     EXIT
ENDDO
SELECT 1
SET SAFETY ON
SET HEADING ON
RETURN
* EOP Memoline.PRG
