![]() |
|
Published 1997-12-01 Printer-friendly version
Templates can come in all shapes and sizes. You might make the assumption that I like to write only sophisticated templates to solve complex and intriguing problems. Quite the contrary, I use templates to enhance my productivity for most of the problems that I encounter. Thats the benefit of Clarion: If the system isnt productive with the things that you need to do, then you can write a template to improve it.
One of the requirements for most of my programs is the ability to export data for use in other programs (like spreadsheets and word processors). I have created a commercial template that allows the user to pick and choose fields for export, but this can be overkill. Much of the time the user simply wants everything exported to a file, without intervention. For that matter, they may want to export multiple files all at once.
In my case, I like to use CSV files for exports. (This is the BASIC file driver in Clarion.) Its a good choice for a universally understood file format. Lets look at how we might do this by hand.
In my opinion, thats too much work to do for each file. Ill have too many extra definitions in the dictionary and, if my source file changes, I have to remember to change the exported file too. Therefore, this is a perfect situation for a template.
Lets look at what we can expect the template to do for us. Ill number the points correspondingly with those above.
So weve eliminated four out of five steps. There is one additional step, though, and thats populating the extension template into the procedure. Beyond that, everything else is handled automatically by the template.
There is one more thing that I want the template to do, which would have been somewhat difficult to do manually: I optionally want the first record of the file to contain the field names. The reason this is difficult is that all of the field names are strings. A string cannot be outputted in a numeric field. Therefore, we would have had to create a separate file definition just to output the header records. These header records would had to have been specified manually: a tedious and error-prone operation.
To alleviate this problem, the template will not use the BASIC file driver. Instead, it will use the ASCII file driver (one string per line), and it will format the records individually. Because the template is writing the code, this complexity is not an issue. In fact, it probably takes less code internally to use an ASCII file with a single field than it does to create a complex BASIC file with multiple fields.
As I mentioned earlier, the target ASCII file definition will be defined locally. If you planned on creating many export procedures within the same program, you could change the template to define the file in global data, then just assign the filename as required. This is a good exercise for you to perform on your own.
First we have to write the header of the extension template. It contains the name in the #EXTENSION line, plus any necessary #PROMPTs to control code generation. Our header looks like this:
#EXTENSION(mhExportBasic,'Export Records to CSV File using Process'),DESCRIPTION('Export Records')
#BOXED('Export Records to CSV File')
#PROMPT('Filename:',@S80),%ExportFilename,REQ,DEFAULT('EXPORT.CSV')
#PROMPT('Maximum Record Size:',@N6),%MaxRecSize,DEFAULT(16000),REQ
#PROMPT('Include field names as first record',CHECK),%IncludeHeader,DEFAULT(%True),AT(10,,180)
#ENDBOXED
Youll notice that we prompt for a filename. The default is "EXPORT.CSV", but you can change it to any constant, or a variable prefixed with an exclamation point. If you decide to use a variable, it must be global, module or static local.
Next, you can control the maximum size of the export record. (If you decide to declare this file globally, you should probably remove this option and just set it for something big like 32,000.) Finally, adding the field names as the first record is optional.
Now we must generate the file definition. This is done in the %DeclarationSection embed:
#AT(%DeclarationSection)
#IF(SUB(%ExportFilename,1,1) = '!')
MH_Export FILE,DRIVER('ASCII'),NAME(%(SUB(%ExportFilename,2,LEN(%ExportFilename)-1))),PRE(MH_Exp),CREATE
#ELSE
MH_Export FILE,DRIVER('ASCII'),NAME('%ExportFilename'),PRE(MH_Exp),CREATE
#ENDIF
Record RECORD
STRING(%MaxRecSize)
END!RECORD
END!FILE
#ENDAT
The only interesting thing here is the name determination. If the first character is an exclamation point, then it uses the remaining letters as the name of a variable. Otherwise, it encloses the entire value in a constant string.
We are using the ASCII file driver, and there is a good chance that you don't have any other files of this type in your application. Therefore, we must have the template tell Clarion to add the ASCII file driver to the project. This is done in the %CustomGlobalDeclarations embed. This is a special embed that is always processed during generation, even when no code is actually produced. (If you used another embed point instead, you would discover that the desired library is forgotten when no code is generated.) Here's the embed:
#AT(%CustomGlobalDeclarations) #FIX(%Driver, 'ASCII') #ADD(%UsedDriverDLLs,%DriverDLL) #PROJECT(%DriverLIB) #ENDAT
The next step is to insert the CREATE and OPEN statements, and to add optionally the header record to the file. This is done in the %AfterFileOpen embed:
#AT(%AfterFileOpen)
CREATE(MH_Export)
OPEN(MH_Export)
#IF(%IncludeHeader)
MH_Exp:Record = |
#FIX(%File, %Primary)
#FOR(%Field)
#IF(INSTANCE(%Field) < ITEMS(%Field))
'"%FieldID",' & |
#ELSE
'"%FieldID"'
#ENDIF
#ENDFOR
ADD(MH_Export)
#ENDIF
#ENDAT
Now you can see the method that is used to produce the output record. We just assign a multi-part string value to the target record, with each field being represented on a separate line. The resulting code might look like this:
MH_Exp:Record = | '"Comments",' & | '"No",' & | '"Flag",' & | '"LName",' & | '"FName",' & | '"City",' & | '"State",' & | '"Zip",' & | '"Phone",' & | '"Sex",' & | '"MaritalStat",' & | '"Retired",' & | '"Birthday",' & | '"Sales"'
Notice how the #IF(INSTANCE(%Field) < ITEMS(%Field)) condition prevents the trailing concatenation characters for the last field. The resulting record value would be:
"Comments","No","Flag","LName","FName","City","State","Zip","Phone","Sex","MaritalStat","Retired","Birth
The CLOSE statement is done in the %BeforeFileClose embed:
#AT(%BeforeFileClose) CLOSE(MH_Export) #ENDAT
Finally, we have to export the data record in the %ProcessActivity embed. This technique is quite similar to that used in exporting the header record. The difference is that we have three different field types: string, number and formatted.
String fields include any fields that hold simple string values that must be clipped for export, and enclosed in quotes.
Number fields include any fields that have simple numerical values that are exported without quotes.
Formatted fields include any fields that are numbers to be interpreted as something else (like dates, phone numbers, etc.). These are fixed-length strings, enclosed in quotes. The FORMAT statement is used to produce the required string value.
The type can be determined by looking at the %FieldPicture. If it begins with @S, then it should be a clipped string. If it begins with @N, then it is a simple numeric. Anything else is a formatted field. The code looks like the following:
#AT(%ProcessActivity)
MH_Exp:Record = |
#FIX(%File, %Primary)
#FOR(%Field)
#CASE(UPPER(SUB(%FieldPicture,2,1)))
#OF('N')
#IF(INSTANCE(%Field) < ITEMS(%Field))
%Field & ',' & |
#ELSE
%Field
#ENDIF
#OF('S')
#IF(INSTANCE(%Field) < ITEMS(%Field))
'"' & CLIP(%Field) & '",' & |
#ELSE
'"' & CLIP(%Field) & '"'
#ENDIF
#ELSE
#IF(INSTANCE(%Field) < ITEMS(%Field))
'"' & FORMAT(%Field,%FieldPicture) & '",' & |
#ELSE
'"' & %Field & '"'
#ENDIF
#ENDCASE
#ENDFOR
ADD(MH_Export)
The generated source might look like this:
MH_Exp:Record = | '"' & CLIP( Cus:Comments) & '",' & | !Cus:Comments Cus:No & ',' & | !Cus:No Cus:Flag & ',' & | !Cus:Flag '"' & CLIP( Cus:LName) & '",' & | !Cus:LName '"' & CLIP( Cus:FName) & '",' & | !Cus:FName '"' & CLIP( Cus:City) & '",' & | !Cus:City '"' & CLIP( Cus:State) & '",' & | !Cus:State '"' & CLIP( Cus:Zip) & '",' & | !Cus:Zip '"' & FORMAT(Cus:Phone,@p(<<<)###-####pb) & '",' & | !Cus:Phone '"' & CLIP( Cus:Sex) & '",' & | !Cus:Sex '"' & CLIP( Cus:MaritalStat) & '",' & | !Cus:MaritalStat Cus:Retired & ',' & | !Cus:Retired '"' & FORMAT(Cus:Birthday,@D9b) & '",' & | !Cus:Birthday Cus:Sales !Cus:Sales
Well, that's all there is to it. You now have a simple extension template that can be added to any Process procedure to export your records to a CSV file. Of course, you can change the range and filter settings for the Process to restrict the exported records to a subset of your total data file. You can also call multiple Processes from a Source procedure to export a number of files. The real benefit here is that you have the ability to export data without wasting a bunch of your valuable time.
Copyright © 1999-2008 by CoveComm Inc. All Rights Reserved. Reproduction in any form without the express written consent of CoveComm Inc., except as described in the subscription agreement, is prohibited.
Clarion Magazine ISSN 1718-9942
One year: $184
(includes all back issues since '99)
Renewals from $134
Two years: $274
Renewals from $224