Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.
SET TEXTMERGE
, Set(“TextMerge”), \, \, _PreText, _Text, SET TEXTMERGE
Delimiters, TEXTMERGE()
Textmerge is a combination of several of FoxPro’s best features—it creates low-level file output, performs runtime evaluation and eases formatting of different types of data. \ and \ are the textmerge equivalents of ? and ??, outputting to the destination specified by SET TEXTMERGE
TO, rather than SET DEVICE
TO. Starting in VFP 7, you have several new options for creating textmerge output, including the ability to send the results to a variable.
SET TEXTMERGE [ ON | OFF ] |
[ TO FileName | TO MEMVAR VarName [ ADDITIVE ] ]
[ WINDOW WindowName ]
[ SHOW | NOSHOW ]
cOnOrOff = SET( "TEXTMERGE" )
cMergeFile = SET( "TEXTMERGE", 2)
cShowOrNot = SET( "TEXTMERGE", 3)
nRecursionLevel = SET( "TEXTMERGE", 4)
Here’s the deal. When you issue the command SET TEXTMERGE
TO FileName, a low-level file channel is opened and the file FileName is opened or created. The low-level file handle is stored in the system memory variable _TEXT. When you use the TO MEMVAR clause (added in VFP 7), there’s no file involved, but the named variable is created, if necessary. Output created with the single or double backslash (\ or \) is echoed to the file or stored in the variable, with \ issuing a carriage return before outputting the remainder of the line, and \ issuing its characters immediately following the text that had been output before. “Big deal,” you say. “I can do the same thing with SET ALTERNATE
or SET PRINTER
.” Yes, BUT! Textmerge really shines when TEXTMERGE is set ON.
SET TEXTMERGE
ON tells FoxPro to examine each line of output for expressions encased in textmerge delimiters. If these are found, the expression within delimiters is evaluated, and the result of that expression is output. Still not impressed? Here’s the key: The result of the expressions is converted to text automatically—dates, numerics, datetimes, whatever. To output a line containing a number, date, datetime and page number, you would have to convert each one, as in:
? LTRIM(STR(nNumber,4)) + space(6) + ;
DTOC(Date()) + SPACE(7) + ;
TTOC(DateTime()) + SPACE(4) + ;
"Page #" + ltrim(_PAGENO)
In a textmerge document, you would just say:
\<<nNumber>> <<Date()>> <<Datetime()>> Page # <<_PAGENO>>
Now which would you prefer to have to decode and maintain six months after you wrote it?
SET TEXTMERGE is different from SET ALTERNATE or SET PRINT in that you can both SET TEXTMERGE TO and ON in a single line. For ease of maintenance, we advise you to splurge on the two lines of code to SET TEXTMERGE TO and ON. |
Unlike the command to SET TEXTMERGE ON and SET TEXTMERGE TO, you must write two separate lines of code to SET TEXTMERGE OFF and SET TEXTMERGE TO, so that's an even a better reason to have two matching pairs of commands. |
And textmerge is fast! Ted wrote some processing code a while back to read through 800 HTML documents, parse the contents, and generate an HTML Help index in SiteMap format. Processing all of the files took eight seconds! One key factor was to set NOSHOW. With the text scrolling past on the screen, it took eight minutes for the same process.
The original release of VFP 7 sometimes crashes when you mix TEXT TO processing with SET TEXTMERGE processing. It's fixed in SP1. |
The remaining optional clauses are pretty straightforward. ADDITIVE specifies that output is appended to the end of an existing file or variable. WINDOW WindowName allows you to specify an output window where the echoed textmerge text should appear. We recommend you always specify a window if you want the output echoed, for two reasons. First, in Visual FoxPro 5 and later, it’s possible to release the main FoxPro window and run your application as a top-level form. In that case, your output might be lost. Secondly, it’s been our experience that Windows seems to slow down when outputting scrolling text to the screen or a window not on top. If you create a specific window for your textmerge, you can force it to be WONTOP()
for the operation.
SHOW | NOSHOW specifies whether text output to the merge file should also be echoed to the screen or optional window.
The help file claims that SHOW is the default—that's true the first time SET TEXTMERGE is issued. After that, the default appears to be the last setting used—that is, if NOSHOW was issued with the previous command, text will not be echoed unless SHOW is explicitly specified. So this isn't really a "default" behavior, it's more like a global SET behavior. Always specify SHOW or NOSHOW and you won't have a problem. |
If you choose to send output to a file, rather than using SET TEXTMERGE
TO a particular file, we suggest you use FCREATE()
or FOPEN()
to access the file. Why? If SAFETY is ON, SET TEXTMERGE
generates a confirmation dialog to overwrite the file. Any number of errors can prevent the file from being created—improper file names, bad drive designator, lack of rights—but FCREATE()
and FOPEN()
allow you to handle the errors using the simpler low-level error handler FERROR()
, rather than the massive CASE statement needed to process all of FoxPro’s possible file errors, or dropping though to your global error handler. If the low-level function was successful, you can set _TEXT to the file handle returned from these Low-level File Functions
.
Overall, though, we recommend sending textmerge output to a string, then if a file is needed, using StrToFile()
.
There are other neat commands to use with text merging. TEXT…ENDTEXT allows you to include a block of text right in your program; it obeys the settings of _PRETEXT and evaluates any expressions within delimiters. It’s also been enhanced in VFP 7 to include a lot of its settings right in the command.
The various forms of SET(“TEXTMERGE”) let you see how you have things set up. Some of them work better than others and some of them make more sense than others.
SET(“TEXTMERGE”), by itself, returns either “ON” or “OFF”. SET(“TEXTMERGE”,2) gives you the name of the file to which you’ve SET TEXTMERGE
. SET(“TEXTMERGE”,3) tells you whether textmerge is currently SHOW or NOSHOW. Since textmerge can be recursive (when the string merged in contains the textmerge delimiters), SET(“TEXTMERGE”, 4) tells you how many levels of recursion are pending at the moment.
Unfortunately, SET("TEXTMERGE",4) uses a ridiculously narrow definition of recursion. It adds a level to the count only if the expanded string contains the same variable from which it was expanded. That is, if you expand the variable ExpandMe and its text contains the string "<<ExpandMe>>", the count goes up (which it should). However, when talking about textmerge, the term "recusion" generally applies to any expanded string that contains the textmerge delimiters, not just expanding the same string over and over. SET("TEXTMERGE",4) doesn't go up if you expand the variable ExpandMe and it contains the string "<<MeToo>>". |
There's no way to find out what variable you've SET TEXTMERGE to. |
* The *best* examples of textmerge are available in your main
* Visual FoxPro directory - check out GENMENU.PRG and others.
* But here's something for you to try:
LOCAL lcOldPreText
* SET TEXTMERGE ON TO textmerg.txt NOSHOW && Don't do this!
SET TEXTMERGE TO textmerg.txt
SET TEXTMERGE ON NOSHOW
\Generated at <<DATE()>> <<TIME()>>
\
TEXT
This is a block of text with
no comments or anything before
it
ENDTEXT
\
\Now we add a pretext
\
lcOldPreText = _PRETEXT
_PRETEXT = "*"+CHR(09)
TEXT
Four score and seven years ago,
our forefathers (and mothers) brought
forth upon this continent a new nation,
ENDTEXT
_PRETEXT = lcOldPreText
\
\ And delimiters are evaluated within TEXT...ENDTEXT
\
TEXT
Today is <<DATE()>> and the time is <<TIME()>>
ENDTEXT
SET TEXTMERGE OFF
SET TEXTMERGE TO
MODI FILE textmerg.txt
* Textmerge to a variable
SET TEXTMERGE TO MEMVAR cHTML
SET TEXTMERGE ON NOSHOW
\<HTML>
\<HEAD>
\<TITLE><<Customer.Company>></TITLE>
\</HEAD>
\<BODY>
\Address: <<Customer.Address>>
\</BODY>
\</HTML>
* and so forth to build an HTML document
SET TEXTMERGE OFF
SET TEXTMERGE TO
StrToFile( cHTML, "MyHTMLDoc.HTM" )
_PRETEXT = cTextToPrecede
cTextToPrecede = _PRETEXT
_TEXT = nHandle
nHandle = _TEXT
_PRETEXT is not a system-generated excuse like “I just happened to be in the neighborhood,” but a system memory variable that holds a character expression. This expression is inserted at the beginning of each line of text generated by the textmerge commands. Set _PRETEXT to “*” to comment a block of code, or to a set of tabs to indent a block.
_TEXT is a built-in FoxPro system memory variable. It contains the low-level file handle of the destination of textmerged output. Set _TEXT to –1 (negative one) to temporarily shut off output to the designated file. Since _TEXT stores the number of a low-level file handle opened for output, switching _TEXT programmatically allows you to switch output back and forth between several destinations, a trick the FoxPro 2.x screen generator used to separate startup and cleanup code.
_TEXT is set to –1 when you SET TEXTMERGE
TO a variable. More importantly, the current textmerge output file is closed at that time, so you can’t save the value of _TEXT and restore it after sending textmerge output to a variable.
_PRETEXT = "*" + CHR(9) && Comment and indent merged text.
SET TEXTMERGE TO D:\TEXTMERG.TXT
? _TEXT && Displays the opened file handle.
SET TEXTMERGE DELIMITERS TO [ cLeftExp [, cRightExp ] ]
cAllDelimiters = SET( "TEXTMERGE", 1 )
Delimiters specified with this command tell FoxPro what to look for when outputting a line with the textmerge commands \, \, TEXT or TEXTMERGE()
. Expressions contained within the delimiters are evaluated when TEXTMERGE is SET ON. If no delimiters are specified, they default to << and >>, respectively. If a single character expression is specified, it’s used for both the left and right delimiters. Avoid using those single characters already used by other FoxPro functions—%, &, $, *, (, or ). Colons, used singly or doubly, are probably a safe bet, as long as you don’t use the scope resolution operator in the code. It’s best to leave the delimiters as is, unless you need to output the << or >> characters themselves. Each delimiter expression can be either one or two characters in length.
This means that you can't really parse the return value of the SET() function and be sure where the first delimiter ends and the second begins—see the second example below. So a "black box" routine cannot mess with these values. A return length for SET("TEXT",1) of 3 means you're doomed, since you don't know which one of the delimiters has two characters. It would be better if this SET() function, like most of them, returned the explicit string that we could use to SET TEXTMERGE DELIMITERS TO &OurString, and leave the parsing to us. Our advice: Don't do this. Leave the delimiters as they are, or if you must change them, change them to something simple like a matched pair of single or double curly braces or something. |
SET TEXTMERGE DELIMITERS TO "::", "::"
SET TEXTMERGE DELIMITERS TO "@~","@"
? SET("TEXTMERGE",1) && returns "@~@" - but which is which?
cMergedOutput = TEXTMERGE( cString [, lRecursive
[, cLeftDelim [, cRightDelim ] ] ] )
Parameter |
Value |
Meaning |
cString |
Character |
The string to be evaluated. |
lRecursive |
.T. |
If a merged expression contains the textmerge delimiters, keep evaluating the contents of the delimiters again until no more delimiters are found. |
.F. or omitted |
Evaluate strings inside the textmerge delimiters only once. |
|
cLeftDelim |
One or two characters |
The left delimiter for textmerge within the function. |
Omitted |
Use the current left delimiter from the SET TEXTMERGE DELIMITERS setting. |
|
cRightDelim |
One or two characters |
The right delimiter for textmerge within the function. |
Omitted |
Use the current right delimiter from the SET TEXTMERGE DELIMITERS setting. |
This function, added in VFP 7, makes textmerge easier than ever. No need to issue SET commands; no need to put text inside a TEXT … ENDTEXT block. Just build the string and call the function.
If you don’t specify delimiters in the function call, the current textmerge delimiters are used. However, specifying delimiters in the call doesn’t change the current delimiters. Unlike SET TEXTMERGE
DELIMITERS, with TEXTMERGE()
, you can specify a left delimiter without specifying a right delimiter. In that case, the left delimiter in the function call is used, along with the current right delimiter.
#DEFINE CRLF CHR(13) + CHR(10)
cString = "<HTML>" + CRLF
cString = cString + "<HEAD>" + CRLF
cString = cString + "<TITLE><<Customer.Company>>" + ;
"</TITLE>" + CRLF
cString = cString + "<BODY>" + CRLF
cString = cString + "Address: <<Customer.Address>>" + CRLF
cString = cString + "</BODY>" + CRLF
cString = cString + "</HEAD>" + CRLF
cString = cString + "</HTML>"
cHTML = TextMerge( cString )
FError(), Low-Level File Functions, Set Alternate, Set Printer, Text … EndText