home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
mail.altrad.com
/
2015.02.mail.altrad.com.tar
/
mail.altrad.com
/
TEST
/
COMMERC_72_53
/
PROGS
/
MYREPORT.PRG
< prev
next >
Wrap
Text File
|
2014-04-10
|
58KB
|
1,885 lines
//Programme: REPORT.PRG
//Auteur...: R M ALCOCK
//Date.....: 16:24:20 5/19/1992
//Copyright: (c) 2002, R M ALCOCK, Tous droits réservés
//Notes....: REPORT SYSTEM FOR ALASKA
//
//
#include "MYREPORT.CH"
*PROCEDURE DUMMY
STATIC aReportData, nPageNumber, nLinesLeft, aReportTotals
STATIC aGroupTotals, lFirstPass, lFormFeeds, nMaxLinesAvail
// Declare file-wide statics for FRMBACK.PRG
STATIC cExprBuff
STATIC cOffsetsBuff
STATIC cLengthsBuff
// File-wide static declarations for LABEL FORM
// Label definition array
STATIC aLabelData := {}
STATIC aBandToPrint := {}
STATIC cBlank := ""
STATIC lOneMoreBand := .T.
STATIC nCurrentCol := 1 // The current column in the band
*RETURN
/***
*
* __ReportForm( <cFRMName>, [<lPrinter>], <cAltFile>,
* [<lNoConsole>], <bFor>, <bWhile>, <nNext>, <nRecord>,
* <lRest>, <lPlain>, [<cHeading>], [<lBEject>],
* [<lSummary>] )
*
*/
PROCEDURE __ReportForm( cFRMName, lPrinter, cAltFile, lNoConsole, bFor, ;
bWhile, nNext, nRecord, lRest, lPlain, cHeading, ;
lBEject, lSummary )
LOCAL lPrintOn, lConsoleOn // Status of PRINTER and CONSOLE
LOCAL cExtraFile, lExtraState // Status of EXTRA
LOCAL nCol, nGroup
LOCAL xBreakVal, lBroke := .F.
LOCAL err
LOCAL lAnyTotals
LOCAL lAnySubTotals
// Resolve parameters
IF cFRMName == NIL
err := ErrorNew()
err:severity := ES_ERROR
err:genCode := EG_ARG
err:subSystem := "FRMLBL"
Eval(ErrorBlock(), err)
ELSE
IF AT( ".", cFRMName ) == 0
cFRMName := TRIM( cFRMName ) + ".FRM"
ENDIF
ENDIF
#ifdef OLDCODE
IF lPrinter == NIL
lPrinter := .F.
ENDIF
#endif
IF cHeading == NIL
cHeading := ""
ENDIF
// Set output devices
#ifdef OLDCODE
lPrintOn := SET( _SET_PRINTER, lPrinter )
lConsoleOn := SET( _SET_CONSOLE, .F. )
SET( _SET_CONSOLE, ! ( lNoConsole .OR. !lConsoleOn ) )
#endif
lPrintOn := IF( lPrinter, SET( _SET_PRINTER, lPrinter ), ;
SET( _SET_PRINTER ) )
lConsoleOn := IF( lNoConsole, SET( _SET_CONSOLE, .F.), ;
SET( _SET_CONSOLE) )
IF lPrinter // To the printer
lFormFeeds := .T.
ELSE
lFormFeeds := .F.
ENDIF
IF (!Empty(cAltFile)) // To file
lExtraState := SET( _SET_EXTRA, .T. )
cExtraFile := SET( _SET_EXTRAFILE, cAltFile )
ENDIF
BEGIN SEQUENCE
aReportData := __FrmLoad( cFRMName ) // Load the frm into an array
nMaxLinesAvail := aReportData[RP_LINES]
// Modify aReportData based on the report parameters
#ifdef OLDCODE
IF lSummary != NIL // Set the summary only flag
#else
IF lSummary == .T. // Set the summary only flag
#endif
aReportData[ RP_SUMMARY ] := lSummary
ENDIF
IF lBEject != NIL .AND. lBEject
aReportData[ RP_BEJECT ] := .F.
ENDIF
IF lPlain // Set plain report flag
aReportData[ RP_PLAIN ] := .T.
cHeading := ""
lFormFeeds := .F.
ENDIF
aReportData[ RP_HEADING ] := cHeading
// Add to the left margin if a SET MARGIN has been defined
// NOTE: uncommenting this line will cause REPORT FORM to respect
// SET MARGIN to screen/to file, but double the margin TO PRINT
// aReportData[ RP_LMARGIN ] += SET( _SET_MARGIN )
nPageNumber := 1 // Set the initial page number
lFirstPass := .T. // Set the first pass flag
nLinesLeft := aReportData[ RP_LINES ]
#ifdef S87_COMPAT
QOUT() // output additional line on first page
nLinesLeft--
#endif
// Check to see if a "before report" eject, or TO FILE has been specified
IF aReportData[ RP_BEJECT ]
EjectPage()
ENDIF
// Generate the initial report header manually (in case there are no
// records that match the report scope)
ReportHeader()
// Initialize aReportTotals to track both group and report totals, then
// set the column total elements to 0 if they are to be totaled, otherwise
// leave them NIL
aReportTotals := ARRAY( LEN(aReportData[RP_GROUPS]) + 1, ;
LEN(aReportData[RP_COLUMNS]) )
// Column total elements
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
FOR nGroup := 1 TO LEN(aReportTotals)
aReportTotals[nGroup,nCol] := 0
NEXT
ENDIF
NEXT
// Initialize aGroupTotals as an array
aGroupTotals := ARRAY( LEN(aReportData[RP_GROUPS]) )
// Execute the actual report based on matching records
DBEval( { || ExecuteReport() }, bFor, bWhile, nNext, nRecord, lRest )
// Generate any totals that may have been identified
// Make a pass through all the groups
FOR nGroup := LEN(aReportData[RP_GROUPS]) TO 1 STEP -1
// make sure group has subtotals
lAnySubTotals := .F.
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
lAnySubTotals := .T.
EXIT // NOTE
ENDIF
NEXT
IF !lAnySubTotals
LOOP // NOTE
ENDIF
// Check to see if we need to eject the page
IF nLinesLeft < 2
EjectPage()
IF aReportData[ RP_PLAIN ]
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
ENDIF
// Print the first line
PrintIt( SPACE(aReportData[RP_LMARGIN]) + ;
IF(nGroup==1,NationMsg(_RF_SUBTOTAL),;
NationMsg(_RF_SUBSUBTOTAL) ) )
// Print the second line
QQOUT( SPACE(aReportData[RP_LMARGIN]) )
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
IF nCol > 1
QQOUT( " " )
ENDIF
IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
QQOUT( TRANSFORM(aReportTotals[nGroup+1,nCol], ;
aReportData[RP_COLUMNS,nCol,RC_PICT]) )
ELSE
QQOUT( SPACE(aReportData[RP_COLUMNS,nCol,RC_WIDTH]) )
ENDIF
NEXT
// Send a cr/lf for the last line
QOUT()
NEXT
#ifdef OLDCODE
// Generate the "Grand totals"
// Check to see if we need to eject the page
IF nLinesLeft < 2
EjectPage()
IF aReportData[ RP_PLAIN ]
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
ENDIF
#endif
// Any report totals?
lAnyTotals := .F.
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
lAnyTotals := .T.
EXIT
ENDIF
NEXT nCol
IF lAnyTotals
#ifndef OLDCODE
// Check to see if we need to eject the page
IF nLinesLeft < 2
EjectPage()
IF aReportData[ RP_PLAIN ]
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
ENDIF
#endif
// Print the first line
PrintIt( SPACE(aReportData[RP_LMARGIN]) + NationMsg(_RF_TOTAL ) )
// Print the second line
QQOUT( SPACE(aReportData[RP_LMARGIN]) )
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
IF nCol > 1
QQOUT( " " )
ENDIF
IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
QQOUT( TRANSFORM(aReportTotals[1,nCol], ;
aReportData[RP_COLUMNS,nCol,RC_PICT]) )
ELSE
QQOUT( SPACE(aReportData[RP_COLUMNS,nCol,RC_WIDTH]) )
ENDIF
NEXT nCol
// Send a cr/lf for the last line
QOUT()
ENDIF
// Check to see if an "after report" eject, or TO FILE has been specified
IF aReportData[ RP_AEJECT ]
EjectPage()
ENDIF
RECOVER USING xBreakVal
lBroke := .T.
END SEQUENCE
// Clean up and leave
aReportData := NIL // Recover the space
aReportTotals := NIL
aGroupTotals := NIL
nPageNumber := NIL
lFirstPass := NIL
nLinesLeft := NIL
lFormFeeds := NIL
nMaxLinesAvail := NIL
// clean up
SET( _SET_PRINTER, lPrintOn ) // Set the printer back to prior state
SET( _SET_CONSOLE, lConsoleOn ) // Set the console back to prior state
IF (!Empty(cAltFile)) // Set extrafile back
SET( _SET_EXTRAFILE, cExtraFile )
SET( _SET_EXTRA, lExtraState )
ENDIF
IF lBroke
// keep the break value going
BREAK xBreakVal
END
RETURN
/***
* ExecuteReport()
* Executed by DBEVAL() for each record that matches record scope
*/
STATIC PROCEDURE ExecuteReport
LOCAL aRecordHeader := {} // Header for the current record
LOCAL aRecordToPrint := {} // Current record to print
LOCAL nCol // Counter for the column work
LOCAL nGroup // Counter for the group work
LOCAL lGroupChanged := .F. // Has any group changed?
LOCAL lEjectGrp := .F. // Group eject indicator
LOCAL nMaxLines // Number of lines needed by record
LOCAL nLine // Counter for each record line
LOCAL cLine // Current line of text for parsing
LOCAL nLastElement // Last element pointer if record is
LOCAL lAnySubTotals
// Add to the main column totals
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
// If this column should be totaled, do it
aReportTotals[ 1 ,nCol] += ;
EVAL( aReportData[RP_COLUMNS,nCol,RC_EXP] )
ENDIF
NEXT
// Determine if any of the groups have changed. If so, add the appropriate
// line to aRecordHeader for totaling out the previous records
IF !lFirstPass // Don't bother first time through
// Make a pass through all the groups
FOR nGroup := LEN(aReportData[RP_GROUPS]) TO 1 STEP -1
// make sure group has subtotals
lAnySubTotals := .F.
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
lAnySubTotals := .T.
EXIT // NOTE
ENDIF
NEXT
#ifndef OLDCODE
// retrieve group eject state from report form
IF ( nGroup == 1 )
lEjectGrp := aReportData[ RP_GROUPS, nGroup, RG_AEJECT ]
ENDIF
#endif
IF !lAnySubTotals
LOOP // NOTE
ENDIF
// For subgroup processing: check if group has been changed
IF MakeAStr(EVAL(aReportData[RP_GROUPS, 1, RG_EXP]),;
aReportData[RP_GROUPS, 1, RG_TYPE]) != aGroupTotals[1]
lGroupChanged := .T.
ENDIF
// If this (sub)group has changed since the last record
IF lGroupChanged .OR. MakeAStr(EVAL(aReportData[RP_GROUPS,nGroup,RG_EXP]),;
aReportData[RP_GROUPS,nGroup,RG_TYPE]) != aGroupTotals[nGroup]
AADD( aRecordHeader, IF(nGroup==1,NationMsg(_RF_SUBTOTAL),;
NationMsg(_RF_SUBSUBTOTAL)) )
AADD( aRecordHeader, "" )
#ifdef OLDCODE
// retrieve group eject state from report form
IF ( nGroup == 1 )
lEjectGrp := aReportData[ RP_GROUPS, nGroup, RG_AEJECT ]
ENDIF
#endif
// Cycle through the columns, adding either the group
// amount from aReportTotals or spaces wide enough for
// the non-totaled columns
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
aRecordHeader[ LEN(aRecordHeader) ] += ;
TRANSFORM(aReportTotals[nGroup+1,nCol], ;
aReportData[RP_COLUMNS,nCol,RC_PICT])
// Zero out the group totals column from aReportTotals
aReportTotals[nGroup+1,nCol] := 0
ELSE
aRecordHeader[ LEN(aRecordHeader) ] += ;
SPACE(aReportData[RP_COLUMNS,nCol,RC_WIDTH])
ENDIF
aRecordHeader[ LEN(aRecordHeader) ] += " "
NEXT
// Get rid of the extra space from the last column
aRecordHeader[LEN(aRecordHeader)] := ;
LEFT( aRecordHeader[LEN(aRecordHeader)], ;
LEN(aRecordHeader[LEN(aRecordHeader)]) - 1 )
ENDIF
NEXT
ENDIF
#ifdef OLDCODE
lFirstPass = .F.
#endif
IF ( LEN( aRecordHeader ) > 0 ) .AND. lEjectGrp .AND. lGroupChanged
IF LEN( aRecordHeader ) > nLinesLeft
EjectPage()
IF ( aReportData[ RP_PLAIN ] )
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
ENDIF
AEVAL( aRecordHeader, { | HeaderLine | ;
PrintIt( SPACE( aReportData[ RP_LMARGIN ] ) + HeaderLine ) } )
aRecordHeader := {}
EjectPage()
IF ( aReportData[ RP_PLAIN ] )
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
ENDIF
// Add to aRecordHeader in the event that the group has changed and
// new group headers need to be generated
// Cycle through the groups
FOR nGroup := 1 TO LEN(aReportData[RP_GROUPS])
// If the group has changed
IF MakeAStr(EVAL(aReportData[RP_GROUPS,nGroup,RG_EXP]),;
aReportData[RP_GROUPS,nGroup,RG_TYPE]) == aGroupTotals[nGroup]
ELSE
AADD( aRecordHeader, "" ) // The blank line
// page eject after group
#ifndef OLDCODE
// put CRFF after group
IF nGroup == 1 .AND. !lFirstPass .AND. !lAnySubTotals
IF lEjectGrp := aReportData[ RP_GROUPS, nGroup, RG_AEJECT ]
nLinesLeft := 0
ENDIF
ENDIF
#endif
AADD( aRecordHeader, IF(nGroup==1,"** ","* ") +;
aReportData[RP_GROUPS,nGroup,RG_HEADER] + " " +;
MakeAStr(EVAL(aReportData[RP_GROUPS,nGroup,RG_EXP]), ;
aReportData[RP_GROUPS,nGroup,RG_TYPE]) )
ENDIF
NEXT
#ifndef OLDCODE
lFirstPass := .F.
#endif
// Is there anything in the record header?
IF LEN( aRecordHeader ) > 0
// Determine if aRecordHeader will fit on the current page. If not,
// start a new header
IF LEN( aRecordHeader ) > nLinesLeft
EjectPage()
IF aReportData[ RP_PLAIN ]
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
ENDIF
// Send aRecordHeader to the output device, resetting nLinesLeft
AEVAL( aRecordHeader, { | HeaderLine | ;
PrintIt( SPACE(aReportData[RP_LMARGIN])+ HeaderLine ) } )
nLinesLeft -= LEN( aRecordHeader )
// Make sure it didn't hit the bottom margin
IF nLinesLeft == 0
EjectPage()
IF aReportData[ RP_PLAIN ]
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
ENDIF
ENDIF
// Add to the group totals
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
// If this column should be totaled, do it
IF aReportData[RP_COLUMNS,nCol,RC_TOTAL]
// Cycle through the groups
FOR nGroup := 1 TO LEN( aReportTotals ) - 1
aReportTotals[nGroup+1,nCol] += ;
EVAL( aReportData[RP_COLUMNS,nCol,RC_EXP] )
NEXT
ENDIF
NEXT
// Reset the group expressions in aGroupTotals
FOR nGroup := 1 TO LEN(aReportData[RP_GROUPS])
aGroupTotals[nGroup] := MakeAStr(EVAL(aReportData[RP_GROUPS,nGroup,RG_EXP]),;
aReportData[RP_GROUPS,nGroup,RG_TYPE])
NEXT
// Only run through the record detail if this is NOT a summary report
IF !aReportData[ RP_SUMMARY ]
// Determine the max number of lines needed by each expression
nMaxLines := 1
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
IF aReportData[RP_COLUMNS,nCol,RC_TYPE] $ "M"
nMaxLines := MAX(XMLCOUNT(EVAL(aReportData[RP_COLUMNS,nCol,RC_EXP]),;
aReportData[RP_COLUMNS,nCol,RC_WIDTH]), nMaxLines)
ELSEIF aReportData[RP_COLUMNS,nCol,RC_TYPE] $ "C"
nMaxLines := MAX( XMLCOUNT( STRTRAN( EVAL( aReportData[RP_COLUMNS,nCol,RC_EXP]),;
";", CHR(13)+CHR(10)),;
aReportData[RP_COLUMNS,nCol,RC_WIDTH]), nMaxLines)
ENDIF
NEXT
// Size aRecordToPrint to the maximum number of lines it will need, then
// fill it with nulls
ASIZE( aRecordToPrint, nMaxLines )
AFILL( aRecordToPrint, "" )
// Load the current record into aRecordToPrint
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS])
FOR nLine := 1 TO nMaxLines
// Check to see if it's a memo or character
IF aReportData[RP_COLUMNS,nCol,RC_TYPE] $ "CM"
// Load the current line of the current column into cLine
// with multi-lines per record ";"- method
IF aReportData[RP_COLUMNS,nCol,RC_TYPE] $ "C"
cLine := XMEMOLINE( TRIM( STRTRAN( EVAL(aReportData[RP_COLUMNS,nCol,RC_EXP]),;
";", CHR(13)+CHR(10)) ),;
aReportData[RP_COLUMNS,nCol,RC_WIDTH], nLine )
ELSE
cLine := XMEMOLINE(TRIM(EVAL(aReportData[RP_COLUMNS,nCol,RC_EXP])),;
aReportData[RP_COLUMNS,nCol,RC_WIDTH], nLine )
ENDIF
cLine := PADR( cLine, aReportData[RP_COLUMNS,nCol,RC_WIDTH] )
ELSE
IF nLine == 1
cLine := TRANSFORM(EVAL(aReportData[RP_COLUMNS,nCol,RC_EXP]),;
aReportData[RP_COLUMNS,nCol,RC_PICT])
cLine := PADR( cLine, aReportData[RP_COLUMNS,nCol,RC_WIDTH] )
ELSE
cLine := SPACE( aReportData[RP_COLUMNS,nCol,RC_WIDTH])
ENDIF
ENDIF
// Add it to the existing report line
IF nCol > 1
aRecordToPrint[ nLine ] += " "
ENDIF
aRecordToPrint[ nLine ] += cLine
NEXT
NEXT
// Determine if aRecordToPrint will fit on the current page
IF LEN( aRecordToPrint ) > nLinesLeft
// The record will not fit on the current page - will it fit on
// a full page? If not, break it up and print it.
IF LEN( aRecordToPrint ) > nMaxLinesAvail
// This record is HUGE! Break it up...
nLine := 1
DO WHILE nLine < LEN( aRecordToPrint )
PrintIt( SPACE(aReportData[RP_LMARGIN]) + aRecordToPrint[nLine] )
nLine++
nLinesLeft--
IF nLinesLeft == 0
EjectPage()
IF aReportData[ RP_PLAIN ]
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
ENDIF
ENDDO
ELSE
EjectPage()
IF aReportData[ RP_PLAIN ]
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
AEVAL( aRecordToPrint, ;
{ | RecordLine | ;
PrintIt( SPACE(aReportData[RP_LMARGIN])+ RecordLine ) ;
} ;
)
nLinesLeft -= LEN( aRecordToPrint )
ENDIF
ELSE
// Send aRecordToPrint to the output device, resetting nLinesLeft
AEVAL( aRecordToPrint, ;
{ | RecordLine | ;
PrintIt( SPACE(aReportData[RP_LMARGIN])+ RecordLine ) ;
} ;
)
nLinesLeft -= LEN( aRecordToPrint )
ENDIF
#ifdef OLDCODE
// Make sure it didn't hit the bottom margin
IF nLinesLeft == 0
EjectPage()
IF aReportData[ RP_PLAIN ]
nLinesLeft := 1000
ELSE
ReportHeader()
ENDIF
ENDIF
#endif
// Tack on the spacing for double/triple/etc.
IF aReportData[ RP_SPACING ] > 1
/* Double space problem in REPORT FORM at the bottom of the page */
#ifdef OLDCODE
IF nLinesLeft > aReportData[ RP_SPACING ] - 1
#else
IF nLinesLeft >= aReportData[ RP_SPACING ] - 1
#endif
FOR nLine := 2 TO aReportData[ RP_SPACING ]
PrintIt()
nLinesLeft--
NEXT
ENDIF
ENDIF
ENDIF // Was this a summary report?
RETURN
/***
*
* ReportHeader()
*
*/
STATIC PROCEDURE ReportHeader
LOCAL nLinesInHeader := 0
LOCAL aPageHeader := {}
LOCAL nHeadingLength := aReportData[RP_WIDTH] - aReportData[RP_LMARGIN] - 30
LOCAL nCol, nLine, nMaxColLength, nGroup, cHeader
LOCAL nHeadLine // lines in a single heading
LOCAL nRPageSize // width of report after subtracting right margin
LOCAL aTempPgHeader // temporary page header array
LOCAL nHeadSize
nRPageSize := aReportData[RP_WIDTH] - aReportData[RP_RMARGIN]
// Header width should be less then 255 characters.
nHeadSize := MIN (nRPageSize, 254)
// Create the header and drop it into aPageHeader
// Start with the heading
IF !aReportData[ RP_PLAIN ] // If not a plain paper report, build
IF aReportData[RP_HEADING] == "" // the heading
AADD( aPageHeader, NationMsg(_RF_PAGENO) + STR(nPageNumber,6) )
ELSE
aTempPgHeader := ParseHeader( aReportData[ RP_HEADING ], ;
Occurs( ";", aReportData[ RP_HEADING ] ) + 1 )
FOR nLine := 1 TO LEN( aTempPgHeader )
// determine number of lines in header given current report dimensions
nLinesInHeader := MAX( XMLCOUNT( LTRIM( aTempPgHeader[ nLine ] ), ;
nHeadingLength ), 1 )
// extract lines and add to array
FOR nHeadLine := 1 TO nLinesInHeader
AADD( aPageHeader, SPACE( 15 ) + ;
PADC( TRIM( XMEMOLINE( LTRIM( aTempPgHeader[ nLine ] ),;
nHeadingLength, nHeadLine ) ), nHeadingLength ) )
NEXT nHeadLine
NEXT nLine
aPageHeader[ 1 ] := STUFF( aPageHeader[ 1 ], 1, 14, ;
NationMsg(_RF_PAGENO) + STR(nPageNumber,6) )
ENDIF
AADD( aPageHeader, DTOC(DATE()) )
ENDIF
// Tack on the actual header from the FRM
FOR nLine := 1 TO LEN( aReportData[RP_HEADER] )
// determine number of lines in header given current report dimensions
nLinesInHeader := MAX( XMLCOUNT( LTRIM( aReportData[RP_HEADER, ;
nLine ] ), nHeadSize ), 1 )
// extract lines and add to array
FOR nHeadLine := 1 TO nLinesInHeader
cHeader := TRIM( XMEMOLINE( LTRIM( aReportData[ RP_HEADER, nLine ] ),;
nHeadSize, nHeadLine) )
AADD( aPageHeader, SPACE( ( nRPageSize - aReportData[ RP_LMARGIN ] - ;
LEN( cHeader ) ) / 2 ) + cHeader )
NEXT nHeadLine
NEXT nLine
#ifdef S87_COMPAT
// S87 compat.
AADD( aPageHeader, "" )
#endif
// Now add the column headings
nLinesInHeader := LEN( aPageHeader )
// Determine the longest column header
nMaxColLength := 0
FOR nCol := 1 TO LEN( aReportData[ RP_COLUMNS ] )
nMaxColLength := MAX( LEN(aReportData[RP_COLUMNS,nCol,RC_HEADER]), ;
nMaxColLength )
NEXT
FOR nCol := 1 TO LEN( aReportData[ RP_COLUMNS ] )
ASIZE( aReportData[RP_COLUMNS,nCol,RC_HEADER], nMaxColLength )
NEXT
FOR nLine := 1 TO nMaxColLength
AADD( aPageHeader, "" )
NEXT
FOR nCol := 1 TO LEN(aReportData[RP_COLUMNS]) // Cycle through the columns
FOR nLine := 1 TO nMaxColLength
IF nCol > 1
aPageHeader[ nLinesInHeader + nLine ] += " "
ENDIF
IF aReportData[RP_COLUMNS,nCol,RC_HEADER,nLine] == NIL
aPageHeader[ nLinesInHeader + nLine ] += ;
SPACE( aReportData[RP_COLUMNS,nCol,RC_WIDTH] )
ELSE
IF aReportData[RP_COLUMNS,nCol,RC_TYPE] == "N"
aPageHeader[ nLinesInHeader + nLine ] += ;
PADL(aReportData[RP_COLUMNS,nCol,RC_HEADER,nLine],;
aReportData[RP_COLUMNS,nCol,RC_WIDTH])
ELSE
aPageHeader[ nLinesInHeader + nLine ] += ;
PADR(aReportData[RP_COLUMNS,nCol,RC_HEADER,nLine],;
aReportData[RP_COLUMNS,nCol,RC_WIDTH])
ENDIF
ENDIF
NEXT
NEXT
// Insert the two blank lines between the heading and the actual data
AADD( aPageHeader, "" )
AADD( aPageHeader, "" )
AEVAL( aPageHeader, { | HeaderLine | ;
PrintIt( SPACE(aReportData[RP_LMARGIN])+ HeaderLine ) } )
// Set the page number and number of available lines
nPageNumber++
// adjust the line count to account for Summer '87 behavior
nLinesLeft := aReportData[RP_LINES] - LEN( aPageHeader )
nMaxLinesAvail := aReportData[RP_LINES] - LEN( aPageHeader )
RETURN
/***
* Occurs( <cSearch>, <cTarget> ) --> nCount
* Determine the number of times <cSearch> is found in <cTarget>
*
*/
STATIC FUNCTION Occurs( cSearch, cTarget )
LOCAL nPos, nCount := 0
DO WHILE !EMPTY( cTarget )
IF (nPos := AT( cSearch, cTarget )) != 0
nCount++
cTarget := SUBSTR( cTarget, nPos + 1 )
ELSE
// End of string
cTarget := ""
ENDIF
ENDDO
RETURN nCount
/***
* MakeStr( <exp>, <cType> ) --> value
* Convert a value of any data type into string to add to the group header
*/
STATIC FUNCTION MakeAStr( uVar, cType )
LOCAL cString
DO CASE
CASE UPPER(cType) == "D"
cString := DTOC( uVar )
CASE UPPER(cType) == "L"
cString := IF( uVar, "T", "F" )
CASE UPPER(cType) == "N"
cString := STR( uVar )
CASE UPPER(cType) == "C"
cString := uVar
OTHERWISE
cString := "INVALID EXPRESSION"
ENDCASE
RETURN( cString )
/***
* PrintIt( <cString> )
* Print a string, THEN send a CRLF
*/
STATIC PROCEDURE PrintIt( cString )
IF cString == NIL
cString := ""
ELSE
#ifdef S87_COMPAT
// prevents output of trailing space, also prevents wrapping of some
// lines when sent to screen or 80-column printer. Comment out this
// line for complete Summer 87 compatibility.
//cString := Trim( cString )
#endif
ENDIF
QQOUT( cString )
QOUT()
RETURN
/***
*
* EjectPage()
* Eject a page if the form feed option is set
*
*/
STATIC PROCEDURE EjectPage
IF lFormFeeds
EJECT
ENDIF
RETURN
/***
*
* XMLCOUNT( <cString>, [<nLineLength>], [<nTabSize>],
* [<lWrap>] ) --> nLineCount
*
*/
STATIC FUNCTION XMLCOUNT( cString, nLineLength, nTabSize, lWrap )
// Set defaults if none specified
nLineLength := IF( nLineLength == NIL, 79, nLineLength )
nTabSize := IF( nTabSize == NIL, 4, nTabSize )
lWrap := IF( lWrap == NIL, .T., .F. )
IF nTabSize >= nLineLength
nTabSize := nLineLength - 1
ENDIF
RETURN( MLCOUNT( TRIM(cString), nLineLength, nTabSize, lWrap ) )
/***
*
* XMEMOLINE( <cString>, [<nLineLength>], [<nLineNumber>],
* [<nTabSize>], [<lWrap>] ) --> cLine
*
*/
STATIC FUNCTION XMEMOLINE( cString, nLineLength, nLineNumber, nTabSize, lWrap )
// Set defaults if none specified
nLineLength := IF( nLineLength == NIL, 79, nLineLength )
nLineNumber := IF( nLineNumber == NIL, 1, nLineNumber )
nTabSize := IF( nTabSize == NIL, 4, nTabSize )
lWrap := IF( lWrap == NIL, .T., lWrap )
IF nTabSize >= nLineLength
nTabSize := nLineLength - 1
ENDIF
RETURN( MEMOLINE( cString, nLineLength, nLineNumber, nTabSize, lWrap ) )
/***
*
* Frmback.prg
*
* Create a report array from a (.frm) file
*
* Copyright (c) 1990-1993, Computer Associates International, Inc.
* All rights reserved.
*
* Compile: /m /n /w
*
*/
// Definitions for buffer sizes
#define SIZE_FILE_BUFF 1990 // Size of report file
#define SIZE_LENGTHS_BUFF 110
#define SIZE_OFFSETS_BUFF 110
#define SIZE_EXPR_BUFF 1440
#define SIZE_FIELDS_BUFF 300
#define SIZE_PARAMS_BUFF 24
// Definitions for offsets into the FILE_BUFF string
#define LENGTHS_OFFSET 5 // Start of expression length array
#define OFFSETS_OFFSET 115 // Start of expression position array
#define EXPR_OFFSET 225 // Start of expression data area
#define FIELDS_OFFSET 1665 // Start of report columns (fields)
#define PARAMS_OFFSET 1965 // Start of report parameters block
// These are offsets into the FIELDS_BUFF string to actual values
// Values are added to a block offset FLD_OFFSET that is moved in
// increments of 12
#define FIELD_WIDTH_OFFSET 1
#define FIELD_TOTALS_OFFSET 6
#define FIELD_DECIMALS_OFFSET 7
// These are offsets into FIELDS_BUFF which are used to 'point' into
// the EXPR_BUFF string which contains the textual data
#define FIELD_CONTENT_EXPR_OFFSET 9
#define FIELD_HEADER_EXPR_OFFSET 11
// These are actual offsets into the PARAMS_BUFF string which
// are used to 'point' into the EXPR_BUFF string
#define PAGE_HDR_OFFSET 1
#define GRP_EXPR_OFFSET 3
#define SUB_EXPR_OFFSET 5
#define GRP_HDR_OFFSET 7
#define SUB_HDR_OFFSET 9
// These are actual offsets into the PARAMS_BUFF string to actual values
#define PAGE_WIDTH_OFFSET 11
#define LNS_PER_PAGE_OFFSET 13
#define LEFT_MRGN_OFFSET 15
#define RIGHT_MGRN_OFFSET 17
#define COL_COUNT_OFFSET 19
#define DBL_SPACE_OFFSET 21
#define SUMMARY_RPT_OFFSET 22
#define PE_OFFSET 23
#define OPTION_OFFSET 24
// File error definitions
#define F_OK 0 // No error
#define F_EMPTY -3 // File is empty
#define F_ERROR -1 // Some kind of error
#define F_NOEXIST 2 // File does not exist
/***
*
* __FrmLoad( cFrmFile ) --> aReport
* Reads a report (.frm) file and creates a report array
*
* Notes:
*
* 1. Report file name has extension.
* 2. File error number placed in nFileError
* 3. Offsets start at 1. Offsets are into a Clipper string, 1 to 1990
* 4. The offsets mentioned in these notes are actual DOS FILE offsets,
* not like the offsets declared in the body of FrmLoad()
* which are Clipper STRING offsets.
* 5. Report file length is 7C6h (1990d) bytes.
* 6. Expression length array starts at 04h (4d) and can
* contain upto 55 short (2 byte) numbers.
* 7. Expression offset index array starts at 72h (114d) and
* can contain upto 55 short (2 byte) numbers.
* 8. Expression area starts at offset E0h (224d).
* 9. Expression area length is 5A0h (1440d).
* 10. Expressions in expression area are null terminated.
* 11. Field expression area starts at offset 680h (1664d).
* 12. Field expressions (column definition) are null terminated.
* 13. Field expression area can contain upto 25 12-byte blocks.
*/
/***
*
* __FrmLoad( <cFrmFile> ) --> aReport
*
*/
FUNCTION __FrmLoad( cFrmFile )
LOCAL cFieldsBuff
LOCAL cParamsBuff
LOCAL nFieldOffset := 0
LOCAL cFileBuff := SPACE(SIZE_FILE_BUFF)
LOCAL cGroupExp := SPACE(200)
LOCAL cSubGroupExp := SPACE(200)
LOCAL nColCount := 0 // Number of columns in report
LOCAL nCount
LOCAL nFrmHandle // (.frm) file handle
LOCAL nBytesRead // Read/write and content record counter
LOCAL nPointer := 0 // Points to an offset into EXPR_BUFF string
LOCAL nFileError // Contains current file error
LOCAL cOptionByte // Contains option byte
LOCAL aReport[ RP_COUNT ] // Create report array
LOCAL err // error object
LOCAL cDefPath // contents of SET DEFAULT string
LOCAL aPaths // array of paths
LOCAL nPathIndex := 0 // iteration counter
LOCAL s, paths
LOCAL i
LOCAL aHeader // temporary storage for report form headings
LOCAL nHeaderIndex // index into temporary header array
// Initialize STATIC buffer values
cLengthsBuff := ""
cOffsetsBuff := ""
cExprBuff := ""
// Default report values
aReport[ RP_HEADER ] := {}
aReport[ RP_WIDTH ] := 80
aReport[ RP_LMARGIN ] := 8
aReport[ RP_RMARGIN ] := 0
aReport[ RP_LINES ] := 58
aReport[ RP_SPACING ] := 1
aReport[ RP_BEJECT ] := .T.
aReport[ RP_AEJECT ] := .F.
aReport[ RP_PLAIN ] := .F.
aReport[ RP_SUMMARY ] := .F.
aReport[ RP_COLUMNS ] := {}
aReport[ RP_GROUPS ] := {}
aReport[ RP_HEADING ] := ""
// Open the report file
nFrmHandle := FOPEN( cFrmFile )
IF ( !EMPTY( nFileError := FERROR() ) ) .AND. !( "\" $ cFrmFile .OR. ":" $ cFrmFile )
// Search through default path; attempt to open report file
cDefPath := SET( _SET_DEFAULT ) + ";" + SET( _SET_PATH )
cDefPath := STRTRAN( cDefPath, ",", ";" )
aPaths := ListAsArray( cDefPath, ";" )
FOR nPathIndex := 1 TO LEN( aPaths )
nFrmHandle := FOPEN( aPaths[ nPathIndex ] + "\" + cFrmFile )
// if no error is reported, we have our report file
IF EMPTY( nFileError := FERROR() )
EXIT
ENDIF
NEXT nPathIndex
ENDIF
// File error
IF nFileError != F_OK
err := ErrorNew()
err:severity := ES_ERROR
err:genCode := EG_OPEN
err:subSystem := "FRMLBL"
err:osCode := nFileError
err:filename := cFrmFile
Eval(ErrorBlock(), err)
ENDIF
// OPEN ok?
IF nFileError = F_OK
// Go to START of report file
FSEEK(nFrmHandle, 0)
// SEEK ok?
nFileError = FERROR()
IF nFileError = F_OK
// Read entire file into process buffer
nBytesRead = FREAD(nFrmHandle, @cFileBuff, SIZE_FILE_BUFF)
// READ ok?
IF nBytesRead = 0
nFileError = F_EMPTY // file is empty
ELSE
nFileError = FERROR() // check for DOS errors
ENDIF
IF nFileError = F_OK
// Is this a .FRM type file (2 at start and end of file)
IF BIN2W(SUBSTR(cFileBuff, 1, 2)) = 2 .AND.;
BIN2W(SUBSTR(cFileBuff, SIZE_FILE_BUFF - 1, 2)) = 2
nFileError = F_OK
ELSE
nFileError = F_ERROR
ENDIF
ENDIF
ENDIF
// Close file
IF !FCLOSE(nFrmHandle)
nFileError = FERROR()
ENDIF
ENDIF
// File existed, was opened and read ok and is a .FRM file
IF nFileError = F_OK
// Fill processing buffers
cLengthsBuff = SUBSTR(cFileBuff, LENGTHS_OFFSET, SIZE_LENGTHS_BUFF)
cOffsetsBuff = SUBSTR(cFileBuff, OFFSETS_OFFSET, SIZE_OFFSETS_BUFF)
cExprBuff = SUBSTR(cFileBuff, EXPR_OFFSET, SIZE_EXPR_BUFF)
cFieldsBuff = SUBSTR(cFileBuff, FIELDS_OFFSET, SIZE_FIELDS_BUFF)
cParamsBuff = SUBSTR(cFileBuff, PARAMS_OFFSET, SIZE_PARAMS_BUFF)
// Process report attributes
// Report width
aReport[ RP_WIDTH ] := BIN2W(SUBSTR(cParamsBuff, PAGE_WIDTH_OFFSET, 2))
// Lines per page
aReport[ RP_LINES ] := BIN2W(SUBSTR(cParamsBuff, LNS_PER_PAGE_OFFSET, 2))
// Page offset (left margin)
aReport[ RP_LMARGIN ] := BIN2W(SUBSTR(cParamsBuff, LEFT_MRGN_OFFSET, 2))
// Page right margin (not used)
aReport[ RP_RMARGIN ] := BIN2W(SUBSTR(cParamsBuff, RIGHT_MGRN_OFFSET, 2))
nColCount = BIN2W(SUBSTR(cParamsBuff, COL_COUNT_OFFSET, 2))
// Line spacing
// Spacing is 1, 2, or 3
aReport[ RP_SPACING ] := IF(SUBSTR(cParamsBuff, ;
DBL_SPACE_OFFSET, 1) $ "YyTt", 2, 1)
// Summary report flag
aReport[ RP_SUMMARY ] := IF(SUBSTR(cParamsBuff, ;
SUMMARY_RPT_OFFSET, 1) $ "YyTt", .T., .F.)
// Process report eject and plain attributes option byte
cOptionByte = ASC(SUBSTR(cParamsBuff, OPTION_OFFSET, 1))
IF INT(cOptionByte / 4) = 1
aReport[ RP_PLAIN ] := .T. // Plain page
cOptionByte -= 4
ENDIF
IF INT(cOptionByte / 2) = 1
aReport[ RP_AEJECT ] := .T. // Page eject after report
cOptionByte -= 2
ENDIF
IF INT(cOptionByte / 1) = 1
aReport[ RP_BEJECT ] := .F. // Page eject before report
cOptionByte -= 1
ENDIF
// Page heading, report title
nPointer = BIN2W(SUBSTR(cParamsBuff, PAGE_HDR_OFFSET, 2))
// Retrieve the header stored in the .FRM file
nHeaderIndex := 4
aHeader := ParseHeader( GetExpr( nPointer ), nHeaderIndex )
// certain that we have retrieved all heading entries from the .FRM file, we
// now retract the empty headings
DO WHILE ( nHeaderIndex > 0 )
IF ! EMPTY( aHeader[ nHeaderIndex ] )
EXIT
ENDIF
nHeaderIndex--
ENDDO
aReport[ RP_HEADER ] := IIF( EMPTY( nHeaderIndex ) , {}, ;
ASIZE( aHeader, nHeaderIndex ) )
// Process Groups
// Group
nPointer = BIN2W(SUBSTR(cParamsBuff, GRP_EXPR_OFFSET, 2))
IF !EMPTY(cGroupExp := GetExpr( nPointer ))
// Add a new group array
AADD( aReport[ RP_GROUPS ], ARRAY( RG_COUNT ))
// Group expression
aReport[ RP_GROUPS ][1][ RG_TEXT ] := cGroupExp
aReport[ RP_GROUPS ][1][ RG_EXP ] := &( "{ || " + cGroupExp + "}" )
IF USED()
aReport[ RP_GROUPS ][1][ RG_TYPE ] := ;
VALTYPE( EVAL( aReport[ RP_GROUPS ][1][ RG_EXP ] ) )
ENDIF
// Group header
nPointer = BIN2W(SUBSTR(cParamsBuff, GRP_HDR_OFFSET, 2))
aReport[ RP_GROUPS ][1][ RG_HEADER ] := GetExpr( nPointer )
// Page eject after group
aReport[ RP_GROUPS ][1][ RG_AEJECT ] := IF(SUBSTR(cParamsBuff, ;
PE_OFFSET, 1) $ "YyTt", .T., .F.)
ENDIF
// Subgroup
nPointer = BIN2W(SUBSTR(cParamsBuff, SUB_EXPR_OFFSET, 2))
IF !EMPTY(cSubGroupExp := GetExpr( nPointer ))
// Add new group array
AADD( aReport[ RP_GROUPS ], ARRAY( RG_COUNT ))
// Subgroup expression
aReport[ RP_GROUPS ][2][ RG_TEXT ] := cSubGroupExp
aReport[ RP_GROUPS ][2][ RG_EXP ] := &( "{ || " + cSubGroupExp + "}" )
IF USED()
aReport[ RP_GROUPS ][2][ RG_TYPE ] := ;
VALTYPE( EVAL( aReport[ RP_GROUPS ][2][ RG_EXP ] ) )
ENDIF
// Subgroup header
nPointer = BIN2W(SUBSTR(cParamsBuff, SUB_HDR_OFFSET, 2))
aReport[ RP_GROUPS ][2][ RG_HEADER ] := GetExpr( nPointer )
// Page eject after subgroup
aReport[ RP_GROUPS ][2][ RG_AEJECT ] := .F.
ENDIF
// Process columns
nFieldOffset := 12 // dBASE skips first 12 byte fields block.
FOR nCount := 1 to nColCount
AADD( aReport[ RP_COLUMNS ], GetColumn( cFieldsBuff, @nFieldOffset ) )
NEXT nCount
ENDIF
RETURN aReport
/***
*
* ParseHeader( <cHeaderString>, <nFields> ) --> aPageHeader
*
* Parse report header (title) field from .FRM and populate page header
* array. Processing is complicated somewhat by varying .FRM storage
* formats of dBASE III+ and CA-Clipper. Although similar to ListAsArray(),
* this function also accounts for fixed-length strings.
*
*/
FUNCTION ParseHeader( cHeaderString, nFields )
LOCAL cItem
LOCAL nItemCount := 0
LOCAL aPageHeader := {}
LOCAL nHeaderLen := 254
LOCAL nPos
DO WHILE ( ++nItemCount <= nFields )
cItem := SUBSTR( cHeaderString, 1, nHeaderLen )
// check for explicit delimiter
nPos := AT( ";", cItem )
IF ! EMPTY( nPos )
// delimiter present
AADD( aPageHeader, SUBSTR( cItem, 1, nPos - 1 ) )
ELSE
IF EMPTY( cItem )
// empty string for S87 and 5.0 compatibility
AADD( aPageHeader, "" )
ELSE
// exception
AADD( aPageHeader, cItem )
ENDIF
// empty or not, we jump past the field
nPos := nHeaderLen
ENDIF
cHeaderString := SUBSTR( cHeaderString, nPos + 1 )
ENDDO
RETURN( aPageHeader )
/***
* GetExpr( nPointer ) --> cString
*
* Reads an expression from EXPR_BUFF via the OFFSETS_BUFF and returns
* a pointer to offset contained in OFFSETS_BUFF that in turn points
* to an expression located in the EXPR_BUFF string.
*
* Notes:
*
* 1. The expression is empty if:
* a. Passed pointer is equal to 65535
* b. Character following character pointed to by pointer is CHR(0)
*
*/
STATIC FUNCTION GetExpr( nPointer )
LOCAL nExprOffset := 0
LOCAL nExprLength := 0
LOCAL nOffsetOffset := 0
LOCAL cString := ""
// Stuff for dBASE compatability.
IF nPointer != 65535
// Convert DOS FILE offset to CLIPPER string offset
nPointer++
// Calculate offset into OFFSETS_BUFF
IF nPointer > 1
nOffsetOffset = (nPointer * 2) - 1
ENDIF
nExprOffset = BIN2W(SUBSTR(cOffsetsBuff, nOffsetOffset, 2))
nExprLength = BIN2W(SUBSTR(cLengthsBuff, nOffsetOffset, 2))
// EXPR_OFFSET points to a NULL, so add one (+1) to get the string
// and subtract one (-1) from EXPR_LENGTH for correct length
nExprOffset++
nExprLength--
// Extract string
cString = SUBSTR(cExprBuff, nExprOffset, nExprLength)
// dBASE does this so we must do it too
// Character following character pointed to by pointer is NULL
IF CHR(0) == SUBSTR(cString, 1, 1) .AND. LEN(SUBSTR(cString,1,1)) = 1
cString = ""
ENDIF
ENDIF
RETURN (cString)
/***
* GetColumn( <cFieldBuffer>, @<nOffset> ) --> aColumn
*
* Get a COLUMN element from FIELDS_BUFF string using nOffset to point to
* the current FIELDS_OFFSET block.
*
* Notes:
* 1. The Header or Contents expressions are empty if:
* a. Passed pointer is equal to 65535
* b. Character following character pointed to by pointer is CHR(0)
*
*/
STATIC FUNCTION GetColumn( cFieldsBuffer, nOffset )
LOCAL nPointer := 0, nNumber := 0, aColumn[ RC_COUNT ], cType
// Column width
aColumn[ RC_WIDTH ] := BIN2W(SUBSTR(cFieldsBuffer, nOffset + ;
FIELD_WIDTH_OFFSET, 2))
// Total column?
aColumn[ RC_TOTAL ] := IF(SUBSTR(cFieldsBuffer, nOffset + ;
FIELD_TOTALS_OFFSET, 1) $ "YyTt", .T., .F.)
// Decimals width
aColumn[ RC_DECIMALS ] := BIN2W(SUBSTR(cFieldsBuffer, nOffset + ;
FIELD_DECIMALS_OFFSET, 2))
// Offset (relative to FIELDS_OFFSET), 'point' to
// expression area via array OFFSETS[]
// Content expression
nPointer = BIN2W(SUBSTR(cFieldsBuffer, nOffset +;
FIELD_CONTENT_EXPR_OFFSET, 2))
aColumn[ RC_TEXT ] := GetExpr( nPointer )
aColumn[ RC_EXP ] := &( "{ || " + GetExpr( nPointer ) + "}" )
// Header expression
nPointer = BIN2W(SUBSTR(cFieldsBuffer, nOffset +;
FIELD_HEADER_EXPR_OFFSET, 2))
aColumn[ RC_HEADER ] := ListAsArray(GetExpr( nPointer ), ";")
// Column picture
// Setup picture only if a database file is open
IF USED()
cType := VALTYPE( EVAL(aColumn[ RC_EXP ]) )
aColumn[ RC_TYPE ] := cType
DO CASE
CASE cType = "C" .OR. cType = "M"
aColumn[ RC_PICT ] := REPLICATE("X", aColumn[ RC_WIDTH ])
CASE cType = "D"
aColumn[ RC_PICT ] := "@D"
CASE cType = "N"
IF aColumn[ RC_DECIMALS ] != 0
aColumn[ RC_PICT ] := REPLICATE("9", aColumn[ RC_WIDTH ] - aColumn[ RC_DECIMALS ] -1) + "." + ;
REPLICATE("9", aColumn[ RC_DECIMALS ])
ELSE
aColumn[ RC_PICT ] := REPLICATE("9", aColumn[ RC_WIDTH ])
ENDIF
CASE cType = "L"
aColumn[ RC_PICT ] := "@L" + REPLICATE("X",aColumn[ RC_WIDTH ]-1)
ENDCASE
ENDIF
// Update offset into ?_buffer
nOffset += 12
RETURN ( aColumn )
/***
*
* ListAsArray( <cList>, <cDelimiter> ) --> aList
* Convert a delimited string to an array
*
*/
STATIC FUNCTION ListAsArray( cList, cDelimiter )
LOCAL nPos
LOCAL aList := {} // Define an empty array
LOCAL lDelimLast := .F.
IF cDelimiter == NIL
cDelimiter := ","
ENDIF
DO WHILE ( LEN(cList) <> 0 )
nPos := AT(cDelimiter, cList)
IF ( nPos == 0 )
nPos := LEN(cList)
ENDIF
IF ( SUBSTR( cList, nPos, 1 ) == cDelimiter )
lDelimLast := .T.
AADD(aList, SUBSTR(cList, 1, nPos - 1)) // Add a new element
ELSE
lDelimLast := .F.
AADD(aList, SUBSTR(cList, 1, nPos)) // Add a new element
ENDIF
cList := SUBSTR(cList, nPos + 1)
ENDDO
IF ( lDelimLast )
AADD(aList, "")
ENDIF
RETURN aList // Return the array
/***
*
* Lblrun.prg
*
* Clipper LABEL FORM runtime system
*
* Copyright (c) 1990-1993, Computer Associates International, Inc.
* All rights reserved.
*
* Compile: /m /n /w
*
*/
/***
* Nation Message Constants
* These constants are used with the NationMsg(<msg>) function.
* The <msg> parameter can range from 1-12 and returns the national
* version of the system message.
*/
#define _LF_SAMPLES 2 // "Do you want more samples?"
#define _LF_YN 12 // "Y/N"
/***
*
* __LabelForm( <cLBLName>, [<lPrinter>], <cAltFile>, [<lNoConsole>],
* <bFor>, <bWhile>, <nNext>, <nRecord>, <lRest>, [<lSample>] )
*
* Print the specified (.lbl) definition for specified records
* meeting specified scope and condition
*
*/
PROCEDURE __LabelForm( cLBLName, lPrinter, cAltFile, lNoConsole, bFor, ;
bWhile, nNext, nRecord, lRest, lSample )
LOCAL lPrintOn := .F. // PRINTER status
LOCAL lConsoleOn // CONSOLE status
LOCAL cExtraFile, lExtraState // EXTRA file status
LOCAL xBreakVal, lBroke := .F.
LOCAL err
Local OldMargin
// Resolve parameters
IF cLBLName == NIL
err := ErrorNew()
err:severity := ES_ERROR
err:genCode := EG_ARG
err:subSystem := "FRMLBL"
Eval(ErrorBlock(), err)
ELSE
IF AT( ".", cLBLName ) == 0
cLBLName := TRIM( cLBLName ) + ".LBL"
ENDIF
ENDIF
IF lPrinter == NIL
lPrinter := .F.
ENDIF
IF lSample == NIL
lSample := .F.
ENDIF
// Set output devices
IF lPrinter // To the printer
lPrintOn := SET( _SET_PRINTER, lPrinter )
ENDIF
lConsoleOn := SET( _SET_CONSOLE )
SET( _SET_CONSOLE, ! ( lNoConsole .OR. !lConsoleOn ) )
IF (!Empty(cAltFile)) // To file
lExtraState := SET( _SET_EXTRA, .T. )
cExtraFile := SET( _SET_EXTRAFILE, cAltFile )
ENDIF
OldMargin := SET( _SET_MARGIN, 0)
BEGIN SEQUENCE
aLabelData := __LblLoad( cLBLName ) // Load the (.lbl) into an array
// Add to the left margin if a SET MARGIN has been defined
aLabelData[ LB_LMARGIN ] += OldMargin
// Size the aBandToPrint array to the number of fields
ASIZE( aBandToPrint, LEN( aLabelData[ LB_FIELDS ] ) )
AFILL( aBandToPrint, SPACE( aLabelData[ LB_LMARGIN ] ) )
// Create enough space for a blank record
cBlank := SPACE( aLabelData[ LB_WIDTH ] + aLabelData[ LB_SPACES ] )
// Handle sample labels
IF lSample
SampleLabels()
ENDIF
// Execute the actual label run based on matching records
DBEval( { || ExecuteLabel() }, bFor, bWhile, nNext, nRecord, lRest )
// Print the last band if there is one
IF lOneMoreBand
// Print the band
AEVAL( aBandToPrint, { | BandLine | PrintIt( BandLine ) } )
ENDIF
RECOVER USING xBreakVal
lBroke := .T.
END SEQUENCE
// Clean up and leave
aLabelData := {} // Recover the space
aBandToPrint := {}
nCurrentCol := 1
cBlank := ""
lOneMoreBand :=.T.
// clean up
SET( _SET_PRINTER, lPrintOn ) // Set the printer back to prior state
SET( _SET_CONSOLE, lConsoleOn ) // Set the console back to prior state
IF (!Empty(cAltFile)) // Set extrafile back
SET( _SET_EXTRAFILE, cExtraFile )
SET( _SET_EXTRA, lExtraState )
ENDIF
IF lBroke
BREAK xBreakVal // continue breaking
ENDIF
SET( _SET_MARGIN, OldMargin)
RETURN
/***
*
* ExecuteLabel()
* Process the label array using the current record
*
*/
STATIC PROCEDURE ExecuteLabel
LOCAL nField, nMoreLines, aBuffer := {}, cBuffer
LOCAL v
// Load the current record into aBuffer
FOR nField := 1 TO LEN( aLabelData[ LB_FIELDS ] )
if ( aLabelData[ LB_FIELDS, nField ] <> NIL )
v := Eval( aLabelData[ LB_FIELDS, nField, LF_EXP ] )
cBuffer := PadR( v, aLabelData[ LB_WIDTH ] )
cBuffer += Space( aLabelData[ LB_SPACES ] )
if ( aLabelData[ LB_FIELDS, nField, LF_BLANK ] )
if ( !Empty( cBuffer ) )
AADD( aBuffer, cBuffer )
end
else
AADD( aBuffer, cBuffer )
endif
else
AADD( aBuffer, NIL )
end
NEXT
ASIZE( aBuffer, LEN( aLabelData[ LB_FIELDS ] ) )
// Add aBuffer to aBandToPrint
FOR nField := 1 TO LEN( aLabelData[ LB_FIELDS ] )
IF aBuffer[ nField ] == NIL
aBandToPrint[ nField ] += cBlank
ELSE
aBandToPrint[ nField ] += aBuffer[ nField ]
ENDIF
NEXT
IF nCurrentCol == aLabelData[ LB_ACROSS ]
// trim
FOR nField := 1 TO LEN( aBandToPrint )
aBandToPrint[ nField ] := Trim( aBandToPrint[ nField ] )
NEXT
lOneMoreBand := .F.
nCurrentCol := 1
// Print the band
AEVAL( aBandToPrint, { | BandLine | PrintIt( BandLine ) } )
nMoreLines := aLabelData[ LB_HEIGHT ] - LEN( aBandToPrint )
IF nMoreLines > 0
FOR nField := 1 TO nMoreLines
PrintIt()
NEXT
ENDIF
IF aLabelData[ LB_LINES ] > 0
// Add the spaces between the label lines
FOR nField := 1 TO aLabelData[ LB_LINES ]
PrintIt()
NEXT
ENDIF
// Clear out the band
AFILL( aBandToPrint, SPACE( aLabelData[ LB_LMARGIN ] ) )
ELSE
lOneMoreBand := .T.
nCurrentCol++
ENDIF
RETURN
/***
*
* SampleLabels()
* Print sample labels
*
*/
STATIC PROCEDURE SampleLabels
LOCAL nGetKey, lMoreSamples := .T., nField
LOCAL aBand := {}
// Create the sample label row
ASIZE( aBand, aLabelData[ LB_HEIGHT ] )
AFILL( aBand, SPACE( aLabelData[ LB_LMARGIN ] ) +;
REPLICATE( REPLICATE( "*", ;
aLabelData[ LB_WIDTH ] ) + ;
SPACE( aLabelData[ LB_SPACES ] ), ;
aLabelData[ LB_ACROSS ] ) )
// Prints sample labels
DO WHILE lMoreSamples
// Print the samples
AEVAL( aBand, { | BandLine | PrintIt( BandLine ) } )
IF aLabelData[ LB_LINES ] > 0
// Add the spaces between the label lines
FOR nField := 1 TO aLabelData[ LB_LINES ]
PrintIt()
NEXT nField
ENDIF
// Prompt for more
@ ROW(), 0 SAY NationMsg(_LF_SAMPLES)+" ("+Nationmsg(_LF_YN)+")"
nGetKey := INKEY(0)
@ ROW(), COL() SAY CHR(nGetKey)
IF ROW() == MAXROW()
SCROLL( 0, 0, MAXROW(), MAXCOL(), 1 )
@ MAXROW(), 0 SAY ""
ELSE
@ ROW()+1, 0 SAY ""
ENDIF
IF IsNegative(CHR(nGetKey)) // Don't give sample labels
lMoreSamples := .F.
ENDIF
ENDDO
RETURN
/***
*
* PrintIt( <cString> )
* Print a string, then send a CRLF
*
*/
*STATIC PROCEDURE PrintIt( cString )
* IF cString == NIL
* cString := ""
* ENDIF
* QQOUT( cString )
* QOUT()
* RETURN
FUNCTION __LblLoad( cLblFile )
LOCAL i, j := 0 // Counters
LOCAL cBuff := SPACE(BUFFSIZE) // File buffer
LOCAL nHandle := 0 // File handle
LOCAL nReadCount := 0 // Bytes read from file
LOCAL lStatus := .F. // Status
LOCAL nOffset := FILEOFFSET // Offset into file
LOCAL nFileError := F_OK // File error
LOCAL cFieldText := "" // Text expression container
LOCAL err // error object
LOCAL cDefPath // contents of SET DEFAULT string
LOCAL aPaths // array of paths
LOCAL nPathIndex := 0 // iteration counter
// Create and initialize default label array
LOCAL aLabel[ LB_COUNT ]
aLabel[ LB_REMARK ] := SPACE(60) // Label remark
aLabel[ LB_HEIGHT ] := 5 // Label height
aLabel[ LB_WIDTH ] := 35 // Label width
aLabel[ LB_LMARGIN ] := 0 // Left margin
aLabel[ LB_LINES ] := 1 // Lines between labels
aLabel[ LB_SPACES ] := 0 // Spaces between labels
aLabel[ LB_ACROSS ] := 1 // Number of labels across
aLabel[ LB_FIELDS ] := {} // Array of label fields
// Open the label file
nHandle := FOPEN( cLblFile )
IF ( ! EMPTY( nFileError := FERROR() ) ) .AND. !( "\" $ cLblFile .OR. ":" $ cLblFile )
// Search through default path; attempt to open label file
cDefPath := SET( _SET_DEFAULT )
cDefPath := STRTRAN( cDefPath, ",", ";" )
aPaths := ListAsArray( cDefPath, ";" )
FOR nPathIndex := 1 TO LEN( aPaths )
nHandle := FOPEN( aPaths[ nPathIndex ] + "\" + cLblFile )
// if no error is reported, we have our label file
IF EMPTY( nFileError := FERROR() )
EXIT
ENDIF
NEXT nPathIndex
ENDIF
// File error
IF nFileError != F_OK
err := ErrorNew()
err:severity := ES_ERROR
err:genCode := EG_OPEN
err:subSystem := "FRMLBL"
err:osCode := nFileError
err:filename := cLblFile
Eval(ErrorBlock(), err)
ENDIF
// If we got this far, assume the label file is open and ready to go
// and so go ahead and read it
nReadCount := FREAD( nHandle, @cBuff, BUFFSIZE )
// READ ok?
IF nReadCount == 0
nFileError := F_EMPTY // File is empty
ELSE
nFileError := FERROR() // Check for DOS errors
ENDIF
IF nFileError == 0
// Load label dimension into aLabel
aLabel[ LB_REMARK ] := SUBSTR(cBuff, REMARKOFFSET, REMARKSIZE)
aLabel[ LB_HEIGHT ] := BIN2W(SUBSTR(cBuff, HEIGHTOFFSET, HEIGHTSIZE))
aLabel[ LB_WIDTH ] := BIN2W(SUBSTR(cBuff, WIDTHOFFSET, WIDTHSIZE))
aLabel[ LB_LMARGIN] := BIN2W(SUBSTR(cBuff, LMARGINOFFSET, LMARGINSIZE))
aLabel[ LB_LINES ] := BIN2W(SUBSTR(cBuff, LINESOFFSET, LINESSIZE))
aLabel[ LB_SPACES ] := BIN2W(SUBSTR(cBuff, SPACESOFFSET, SPACESSIZE))
aLabel[ LB_ACROSS ] := BIN2W(SUBSTR(cBuff, ACROSSOFFSET, ACROSSSIZE))
FOR i := 1 TO aLabel[ LB_HEIGHT ]
// Get the text of the expression
cFieldText := TRIM( SUBSTR( cBuff, nOffset, FIELDSIZE ) )
nOffset += 60
IF !EMPTY( cFieldText )
AADD( aLabel[ LB_FIELDS ], {} )
// Field expression
AADD( aLabel[ LB_FIELDS, i ], &( "{ || " + cFieldText + "}" ) )
// Text of field
AADD( aLabel[ LB_FIELDS, i ], cFieldText )
// Compression option
AADD( aLabel[ LB_FIELDS, i ], .T. )
ELSE
AADD( aLabel[ LB_FIELDS ], NIL )
ENDIF
NEXT
// Close file
FCLOSE( nHandle )
nFileError = FERROR()
ENDIF
RETURN( aLabel )
FUNCTION IsNegative(AVal)
RETURN AVal < 0