Files
harbour-core/harbour/source/rtl/treport.prg

1397 lines
45 KiB
Plaintext

/*
* $Id$
*/
/*
* Harbour Project source code:
* HBreportForm class and __ReportForm()
*
* Copyright 2000 Luiz Rafael Culik <Culik@sl.conex.net>
* www - http://www.harbour-project.org
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this software; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307 USA (or visit the web site http://www.gnu.org/).
*
* As a special exception, the Harbour Project gives permission for
* additional uses of the text contained in its release of Harbour.
*
* The exception is that, if you link the Harbour libraries with other
* files to produce an executable, this does not by itself cause the
* resulting executable to be covered by the GNU General Public License.
* Your use of that executable is in no way restricted on account of
* linking the Harbour library code into it.
*
* This exception does not however invalidate any other reasons why
* the executable file might be covered by the GNU General Public License.
*
* This exception applies only to the code released by the Harbour
* Project under the name Harbour. If you copy code from other
* Harbour Project or Free Software Foundation releases into a copy of
* Harbour, as the General Public License permits, the exception does
* not apply to the code that you add in this way. To avoid misleading
* anyone as to the status of such modified files, you must delete
* this exception notice from them.
*
* If you write modifications of your own for Harbour, it is your choice
* whether to permit this exception to apply to your modifications.
* If you do not wish that, delete this exception notice.
*
*/
#include "hbclass.ch"
#include "error.ch"
#include "fileio.ch"
#include "inkey.ch"
#define F_OK 0 // No error
#define F_EMPTY -3 // File is empty
#define _RFRM_PAGENO 3 // "Page No."
#define _RFRM_SUBTOTAL 4 // "** Subtotal **"
#define _RFRM_SUBSUBTOTAL 5 // "* Subsubtotal *"
#define _RFRM_TOTAL 6 // "*** Total ***"
#define RPT_HEADER 1 // Array of header strings
#define RPT_WIDTH 2 // Numeric, report page width
#define RPT_LMARGIN 3 // Numeric, report page offset
#define RPT_RMARGIN 4 // NIL, Not used
#define RPT_LINES 5 // Numeric, number of lines per page
#define RPT_SPACING 6 // Numeric, single=1, double=2
#define RPT_BEJECT 7 // Logical, eject before 1st page, .T.=Yes .F.=No
#define RPT_AEJECT 8 // Logical, eject after last page, .T.=Yes .F.=No
#define RPT_PLAIN 9 // Logical, plain report, .T.=Yes .F.=No
#define RPT_SUMMARY 10 // Logical, no detail lines, .T.=Yes .F.=No
#define RPT_COLUMNS 11 // Array of Column arrays
#define RPT_GROUPS 12 // Array of Group arrays
#define RPT_HEADING 13 // Character, heading for the report
#define RPT_COUNT 13 // Number of elements in the Report array
// Column array definitions ( one array per column definition )
#define RCT_EXP 1 // Block, contains compiled column expression
#define RCT_TEXT 2 // Character, contains text column expression
#define RCT_TYPE 3 // Character, type of expression
#define RCT_HEADER 4 // Array of column heading strings
#define RCT_WIDTH 5 // Numeric, column width including decimals and
// decimal point
#define RCT_DECIMALS 6 // Numeric, number of decimal places
#define RCT_TOTAL 7 // Logical, total this column, .T.=Yes .F.=No
#define RCT_PICT 8 // Character, picture string
#define RCT_COUNT 8 // Number of elements in the Column array
// Group array definitions ( one array per group definition )
#define RGT_EXP 1 // Block, contains compiled group expression
#define RGT_TEXT 2 // Character, contains text group expression
#define RGT_TYPE 3 // Character, type of expression
#define RGT_HEADER 4 // Character, column heading string
#define RGT_AEJECT 5 // Logical, eject after group, .T.=Yes .F.=No
#define RGT_COUNT 5 // Number of elements in the Group array
#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
CLASS HBReportForm
DATA aReportData AS ARRAY init {}
DATA aReportTotals AS ARRAY init {}
DATA aGroupTotals AS ARRAY init {}
DATA nPageNumber AS NUMERIC
DATA nLinesLeft AS NUMERIC
DATA lFirstPass AS LOGICAL
DATA lFormFeeds AS LOGICAL
DATA nMaxLinesAvail AS NUMERIC
DATA cExprBuff AS STRING
DATA cOffsetsBuff AS STRING
DATA cLengthsBuff AS STRING
METHOD NEW( cFrmName AS STRING, lPrinter AS LOGICAL ,cAltFile AS STRING, lNoConsole AS LOGICAL ,bFor AS CODEBLOCK, ;
bWhile AS CODEBLOCK, nNext AS NUMERIC, nRecord AS NUMERIC, lRest AS LOGICAL ,lPlain AS LOGICAL, ;
cHeading AS STRING ,lBEject AS LOGICAL, lSummary AS LOGICAL )
METHOD ExecuteReport()
METHOD ReportHeader()
METHOD EjectPage()
METHOD PrintIt(cString AS STRING)
METHOD LoadReportFile(cFile AS STRING)
METHOD GetExpr( nPointer AS NUMERIC)
METHOD GetColumn( cFieldsBuffer AS STRING, nOffset AS NUMERIC)
ENDCLASS
METHOD NEW(cFrmName,lPrinter,cAltFile,lNoConsole,bFor,bWhile,nNext,nRecord,;
lRest,lPlain,cHeading,lBEject,lSummary) CLASS HBReportForm
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
lPrintOn := iif( lPrinter, SET( _SET_PRINTER, lPrinter ), ;
SET( _SET_PRINTER ) )
lConsoleOn := iif( 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 := ::LoadReportFile( cFRMName ) // Load the frm into an array
::nMaxLinesAvail := ::aReportData[RPT_LINES]
// Modify ::aReportData based on the report parameters
IF lSummary == .T. // Set the summary only flag
::aReportData[ RPT_SUMMARY ] := lSummary
ENDIF
IF lBEject != NIL .AND. lBEject
::aReportData[ RPT_BEJECT ] := .F.
ENDIF
IF lPlain // Set plain report flag
::aReportData[ RPT_PLAIN ] := .T.
cHeading := ""
::lFormFeeds := .F.
ENDIF
::aReportData[ RPT_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[ RPT_LMARGIN ] += SET( _SET_MARGIN )
::nPageNumber := 1 // Set the initial page number
::lFirstPass := .T. // Set the first pass flag
::nLinesLeft := ::aReportData[ RPT_LINES ]
// Check to see if a "before report" eject, or TO FILE has been specified
IF ::aReportData[ RPT_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[RPT_GROUPS]) + 1, ;
LEN(::aReportData[RPT_COLUMNS]) )
// Column total elements
FOR nCol := 1 TO LEN(::aReportData[RPT_COLUMNS])
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TOTAL]
FOR nGroup := 1 TO LEN(::aReportTotals)
::aReportTotals[nGroup,nCol] := 0
NEXT
ENDIF
NEXT
// Initialize ::aGroupTotals as an array
::aGroupTotals := ARRAY( LEN(::aReportData[RPT_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[RPT_GROUPS]) TO 1 STEP -1
// make sure group has subtotals
lAnySubTotals := .F.
FOR nCol := 1 TO LEN(::aReportData[RPT_COLUMNS])
IF ::aReportData[RPT_COLUMNS,nCol,RCT_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[ RPT_PLAIN ]
::nLinesLeft := 1000
ELSE
::ReportHeader()
ENDIF
ENDIF
// Print the first line
::PrintIt( SPACE(::aReportData[RPT_LMARGIN]) + ;
iif(nGroup==1,NationMsg(_RFRM_SUBTOTAL),;
NationMsg(_RFRM_SUBSUBTOTAL) ) )
// Print the second line
QQOUT( SPACE(::aReportData[RPT_LMARGIN]) )
FOR nCol := 1 TO LEN(::aReportData[RPT_COLUMNS])
IF nCol > 1
QQOUT( " " )
ENDIF
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TOTAL]
QQOUT( TRANSFORM(::aReportTotals[nGroup+1,nCol], ;
::aReportData[RPT_COLUMNS,nCol,RCT_PICT]) )
ELSE
QQOUT( SPACE(::aReportData[RPT_COLUMNS,nCol,RCT_WIDTH]) )
ENDIF
NEXT
// Send a cr/lf for the last line
QOUT()
NEXT
// Any report totals?
lAnyTotals := .F.
FOR nCol := 1 TO LEN(::aReportData[RPT_COLUMNS])
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TOTAL]
lAnyTotals := .T.
EXIT
ENDIF
NEXT nCol
IF lAnyTotals
// Check to see if we need to eject the page
IF ::nLinesLeft < 2
::EjectPage()
IF ::aReportData[ RPT_PLAIN ]
::nLinesLeft := 1000
ELSE
::ReportHeader()
ENDIF
ENDIF
// Print the first line
::PrintIt( SPACE(::aReportData[RPT_LMARGIN]) + NationMsg(_RFRM_TOTAL ) )
// Print the second line
QQOUT( SPACE(::aReportData[RPT_LMARGIN]) )
FOR nCol := 1 TO LEN(::aReportData[RPT_COLUMNS])
IF nCol > 1
QQOUT( " " )
ENDIF
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TOTAL]
QQOUT( TRANSFORM(::aReportTotals[1,nCol], ;
::aReportData[RPT_COLUMNS,nCol,RCT_PICT]) )
ELSE
QQOUT( SPACE(::aReportData[RPT_COLUMNS,nCol,RCT_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[ RPT_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 NIL
METHOD PrintIt(cString) CLASS HBReportForm
IF cString == NIL
cString := ""
ENDIF
QQOUT(cString)
QOUT()
RETURN Self
METHOD EjectPage() CLASS HBReportForm
IF ::lFormFeeds
EJECT
ENDIF
RETURN Self
METHOD ReportHeader() CLASS HBReportForm
LOCAL nLinesInHeader := 0
LOCAL aPageHeader := {}
LOCAL nHeadingLength := ::aReportData[RPT_WIDTH] - ::aReportData[RPT_LMARGIN] -30
LOCAL nCol,nLine,nMaxColLength,nGroup,cHeader
LOCAL nHeadline
LOCAL nRPageSize
LOCAL aTempPgHeader
LOCAL nHeadSize
nRPageSize := ::aReportData[RPT_WIDTH] - ::aReportData[RPT_RMARGIN]
IF !::aReportData[RPT_PLAIN]
IF ::aReportData[RPT_HEADING] == ""
AADD( aPageHeader,NationMsg(_RFRM_PAGENO) + STR(::nPageNumber,6))
ELSE
aTempPgHeader:=ParseHeader( ::aReportData[RPT_HEADING],;
Occurs(";",::aReportData[RPT_HEADING]) +1 )
FOR nLine := 1 to LEN( aTempPgHeader)
nLinesInHeader:=MAX( XMLCOUNT( LTRIM( aTempPgHeader[ nLine ] ) , ;
nHeadingLength),1)
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(_RFRM_PAGENO)+STR(::nPageNumber,6))
ENDIF
AADD( aPageHeader, DTOC(DATE()) )
ENDIF
FOR nLine := 1 TO LEN( ::aReportData[ RPT_HEADER])
nLinesInHeader := MAX( XMLCOUNT(LTRIM( ::aReportData[RPT_HEADER,;
nLine ] ), nHeadSize),1 )
FOR nHeadLine := 1 to nLinesInHeader
cHeader:=TRIM( XMEMOLINE( LTRIM( ::aReportData[RPT_HEADER, nLine ]),;
nHeadSize,nHeadLine))
AADD( aPageHeader, SPACE((nRPageSize - ::aReportData[ RPT_LMARGIN ] -;
LEN( cHeader ) ) / 2 ) + cHeader )
NEXT nHeadLine
NEXT nLine
nLinesInHeader := LEN( aPageHeader)
nMaxColLength :=0
FOR nCol := 1 TO LEN( ::aReportData[RPT_COLUMNS] )
nMaxColLength := MAX(LEN(::aReportData[RPT_COLUMNS,nCol,RCT_HEADER]), ;
nMaxColLength)
NEXT
FOR nCol := 1 to LEN( ::aReportData[RPT_COLUMNS] )
ASIZE( ::aReportData[RPT_COLUMNS,nCol,RCT_HEADER] ,nMaxColLength)
NEXT
FOR nLine:=1 TO nMaxColLength
AADD( aPageHeader, "")
NEXT
FOR nCol := 1 TO LEN(::aReportData[RPT_COLUMNS]) // Cycle through the columns
FOR nLine := 1 TO nMaxColLength
IF nCol > 1
aPageHeader[ nLinesInHeader + nLine ] += " "
ENDIF
IF ::aReportData[RPT_COLUMNS,nCol,RCT_HEADER,nLine] == NIL
aPageHeader[ nLinesInHeader + nLine ] += ;
SPACE( ::aReportData[RPT_COLUMNS,nCol,RCT_WIDTH] )
ELSE
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TYPE] == "N"
aPageHeader[ nLinesInHeader + nLine ] += ;
PADL(::aReportData[RPT_COLUMNS,nCol,RCT_HEADER,nLine],;
::aReportData[RPT_COLUMNS,nCol,RCT_WIDTH])
ELSE
aPageHeader[ nLinesInHeader + nLine ] += ;
PADR(::aReportData[RPT_COLUMNS,nCol,RCT_HEADER,nLine],;
::aReportData[RPT_COLUMNS,nCol,RCT_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[RPT_LMARGIN])+ HeaderLine ) } )
// Set the page number and number of available lines
::nPageNumber++
// adjust the line count to account for Summer '87 behavior
::nLinesLeft := ::aReportData[RPT_LINES] - LEN( aPageHeader )
::nMaxLinesAvail := ::aReportData[RPT_LINES] - LEN( aPageHeader )
RETURN SELF
METHOD ExecuteReport() CLASS HBReportForm
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[RPT_COLUMNS])
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TOTAL]
// If this column should be totaled, do it
::aReportTotals[ 1 ,nCol] += ;
EVAL( ::aReportData[RPT_COLUMNS,nCol,RCT_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[RPT_GROUPS]) TO 1 STEP -1
// make sure group has subtotals
lAnySubTotals := .F.
FOR nCol := 1 TO LEN(::aReportData[RPT_COLUMNS])
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TOTAL]
lAnySubTotals := .T.
EXIT // NOTE
ENDIF
NEXT
// retrieve group eject state from report form
IF ( nGroup == 1 )
lEjectGrp := ::aReportData[ RPT_GROUPS, nGroup, RGT_AEJECT ]
ENDIF
IF !lAnySubTotals
LOOP // NOTE
ENDIF
// For subgroup processing: check if group has been changed
IF MakeAStr(EVAL(::aReportData[RPT_GROUPS, 1, RGT_EXP]),;
::aReportData[RPT_GROUPS, 1, RGT_TYPE]) != ::aGroupTotals[1]
lGroupChanged := .T.
ENDIF
// If this (sub)group has changed since the last record
IF lGroupChanged .OR. MakeAStr(EVAL(::aReportData[RPT_GROUPS,nGroup,RGT_EXP]),;
::aReportData[RPT_GROUPS,nGroup,RGT_TYPE]) != ::aGroupTotals[nGroup]
AADD( aRecordHeader, iif(nGroup==1,NationMsg(_RFRM_SUBTOTAL),;
NationMsg(_RFRM_SUBSUBTOTAL)) )
AADD( aRecordHeader, "" )
// 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[RPT_COLUMNS])
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TOTAL]
aRecordHeader[ LEN(aRecordHeader) ] += ;
TRANSFORM(::aReportTotals[nGroup+1,nCol], ;
::aReportData[RPT_COLUMNS,nCol,RCT_PICT])
// Zero out the group totals column from aReportTotals
::aReportTotals[nGroup+1,nCol] := 0
ELSE
aRecordHeader[ LEN(aRecordHeader) ] += ;
SPACE(::aReportData[RPT_COLUMNS,nCol,RCT_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
IF ( LEN( aRecordHeader ) > 0 ) .AND. lEjectGrp .AND. lGroupChanged
IF LEN( aRecordHeader ) > ::nLinesLeft
::EjectPage()
IF ( ::aReportData[ RPT_PLAIN ] )
::nLinesLeft := 1000
ELSE
::ReportHeader()
ENDIF
ENDIF
AEVAL( aRecordHeader, { | HeaderLine | ;
::PrintIt( SPACE( ::aReportData[ RPT_LMARGIN ] ) + HeaderLine ) } )
aRecordHeader := {}
::EjectPage()
IF ( ::aReportData[ RPT_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[RPT_GROUPS])
// If the group has changed
IF MakeAStr(EVAL(::aReportData[RPT_GROUPS,nGroup,RGT_EXP]),;
::aReportData[RPT_GROUPS,nGroup,RGT_TYPE]) == ::aGroupTotals[nGroup]
ELSE
AADD( aRecordHeader, "" ) // The blank line
// page eject after group
// put CRFF after group
IF nGroup == 1 .AND. !::lFirstPass .AND. !lAnySubTotals
IF lEjectGrp := ::aReportData[ RPT_GROUPS, nGroup, RGT_AEJECT ]
::nLinesLeft := 0
ENDIF
ENDIF
AADD( aRecordHeader, iif(nGroup==1,"** ","* ") +;
::aReportData[RPT_GROUPS,nGroup,RGT_HEADER] + " " +;
MakeAStr(EVAL(::aReportData[RPT_GROUPS,nGroup,RGT_EXP]), ;
::aReportData[RPT_GROUPS,nGroup,RGT_TYPE]) )
ENDIF
NEXT
::lFirstPass := .F.
// 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[ RPT_PLAIN ]
::nLinesLeft := 1000
ELSE
::ReportHeader()
ENDIF
ENDIF
// Send aRecordHeader to the output device, resetting nLinesLeft
AEVAL( aRecordHeader, { | HeaderLine | ;
::PrintIt( SPACE(::aReportData[RPT_LMARGIN])+ HeaderLine ) } )
::nLinesLeft -= LEN( aRecordHeader )
// Make sure it didn't hit the bottom margin
IF ::nLinesLeft == 0
::EjectPage()
IF ::aReportData[ RPT_PLAIN ]
::nLinesLeft := 1000
ELSE
::ReportHeader()
ENDIF
ENDIF
ENDIF
// Add to the group totals
FOR nCol := 1 TO LEN(::aReportData[RPT_COLUMNS])
// If this column should be totaled, do it
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TOTAL]
// Cycle through the groups
FOR nGroup := 1 TO LEN( ::aReportTotals ) - 1
::aReportTotals[nGroup+1,nCol] += ;
EVAL( ::aReportData[RPT_COLUMNS,nCol,RCT_EXP] )
NEXT
ENDIF
NEXT
// Reset the group expressions in aGroupTotals
FOR nGroup := 1 TO LEN(::aReportData[RPT_GROUPS])
::aGroupTotals[nGroup] := MakeAStr(EVAL(::aReportData[RPT_GROUPS,nGroup,RGT_EXP]),;
::aReportData[RPT_GROUPS,nGroup,RGT_TYPE])
NEXT
// Only run through the record detail if this is NOT a summary report
IF !::aReportData[ RPT_SUMMARY ]
// Determine the max number of lines needed by each expression
nMaxLines := 1
FOR nCol := 1 TO LEN(::aReportData[RPT_COLUMNS])
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TYPE] $ "M"
nMaxLines := MAX(XMLCOUNT(EVAL(::aReportData[RPT_COLUMNS,nCol,RCT_EXP]),;
::aReportData[RPT_COLUMNS,nCol,RCT_WIDTH]), nMaxLines)
ELSEIF ::aReportData[RPT_COLUMNS,nCol,RCT_TYPE] $ "C"
nMaxLines := MAX( XMLCOUNT( STRTRAN( EVAL( ::aReportData[RPT_COLUMNS,nCol,RCT_EXP]),;
";", CHR(13)+CHR(10)),;
::aReportData[RPT_COLUMNS,nCol,RCT_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[RPT_COLUMNS])
FOR nLine := 1 TO nMaxLines
// Check to see if it's a memo or character
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TYPE] $ "CM"
// Load the current line of the current column into cLine
// with multi-lines per record ";"- method
IF ::aReportData[RPT_COLUMNS,nCol,RCT_TYPE] $ "C"
cLine := XMEMOLINE( TRIM( STRTRAN( EVAL(::aReportData[RPT_COLUMNS,nCol,RCT_EXP]),;
";", CHR(13)+CHR(10)) ),;
::aReportData[RPT_COLUMNS,nCol,RCT_WIDTH], nLine )
ELSE
cLine := XMEMOLINE(TRIM(EVAL(::aReportData[RPT_COLUMNS,nCol,RCT_EXP])),;
::aReportData[RPT_COLUMNS,nCol,RCT_WIDTH], nLine )
ENDIF
cLine := PADR( cLine, ::aReportData[RPT_COLUMNS,nCol,RCT_WIDTH] )
ELSE
IF nLine == 1
cLine := TRANSFORM(EVAL(::aReportData[RPT_COLUMNS,nCol,RCT_EXP]),;
::aReportData[RPT_COLUMNS,nCol,RCT_PICT])
cLine := PADR( cLine, ::aReportData[RPT_COLUMNS,nCol,RCT_WIDTH] )
ELSE
cLine := SPACE( ::aReportData[RPT_COLUMNS,nCol,RCT_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[RPT_LMARGIN]) + aRecordToPrint[nLine] )
nLine++
::nLinesLeft--
IF ::nLinesLeft == 0
::EjectPage()
IF ::aReportData[ RPT_PLAIN ]
::nLinesLeft := 1000
ELSE
::ReportHeader()
ENDIF
ENDIF
ENDDO
ELSE
::EjectPage()
IF ::aReportData[ RPT_PLAIN ]
::nLinesLeft := 1000
ELSE
::ReportHeader()
ENDIF
AEVAL( aRecordToPrint, ;
{ | RecordLine | ;
::PrintIt( SPACE(::aReportData[RPT_LMARGIN])+ RecordLine ) ;
} ;
)
::nLinesLeft -= LEN( aRecordToPrint )
ENDIF
ELSE
// Send aRecordToPrint to the output device, resetting ::nLinesLeft
AEVAL( aRecordToPrint, ;
{ | RecordLine | ;
::PrintIt( SPACE(::aReportData[RPT_LMARGIN])+ RecordLine ) ;
} ;
)
::nLinesLeft -= LEN( aRecordToPrint )
ENDIF
// Tack on the spacing for double/triple/etc.
IF ::aReportData[ RPT_SPACING ] > 1
/* Double space problem in REPORT FORM at the bottom of the page */
IF ::nLinesLeft >= ::aReportData[ RPT_SPACING ] - 1
FOR nLine := 2 TO ::aReportData[ RPT_SPACING ]
::PrintIt()
::nLinesLeft--
NEXT
ENDIF
ENDIF
ENDIF // Was this a summary report?
RETURN NIL
METHOD LoadReportFile(cFrmFile) CLASS HBReportForm
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[ RPT_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[ RPT_HEADER ] := {}
aReport[ RPT_WIDTH ] := 80
aReport[ RPT_LMARGIN ] := 8
aReport[ RPT_RMARGIN ] := 0
aReport[ RPT_LINES ] := 58
aReport[ RPT_SPACING ] := 1
aReport[ RPT_BEJECT ] := .T.
aReport[ RPT_AEJECT ] := .F.
aReport[ RPT_PLAIN ] := .F.
aReport[ RPT_SUMMARY ] := .F.
aReport[ RPT_COLUMNS ] := {}
aReport[ RPT_GROUPS ] := {}
aReport[ RPT_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[ RPT_WIDTH ] := BIN2W(SUBSTR(cParamsBuff, PAGE_WIDTH_OFFSET, 2))
// Lines per page
aReport[ RPT_LINES ] := BIN2W(SUBSTR(cParamsBuff, LNS_PER_PAGE_OFFSET, 2))
// Page offset (left margin)
aReport[ RPT_LMARGIN ] := BIN2W(SUBSTR(cParamsBuff, LEFT_MRGN_OFFSET, 2))
// Page right margin (not used)
aReport[ RPT_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[ RPT_SPACING ] := iif(SUBSTR(cParamsBuff, ;
DBL_SPACE_OFFSET, 1) $ "YyTt", 2, 1)
// Summary report flag
aReport[ RPT_SUMMARY ] := iif(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[ RPT_PLAIN ] := .T. // Plain page
cOptionByte -= 4
ENDIF
IF INT(cOptionByte / 2) == 1
aReport[ RPT_AEJECT ] := .T. // Page eject after report
cOptionByte -= 2
ENDIF
IF INT(cOptionByte / 1) == 1
aReport[ RPT_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[ RPT_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[ RPT_GROUPS ], ARRAY( RGT_COUNT ))
// Group expression
aReport[ RPT_GROUPS ][1][ RGT_TEXT ] := cGroupExp
aReport[ RPT_GROUPS ][1][ RGT_EXP ] := &( "{ || " + cGroupExp + "}" )
IF USED()
aReport[ RPT_GROUPS ][1][ RGT_TYPE ] := ;
VALTYPE( EVAL( aReport[ RPT_GROUPS ][1][ RGT_EXP ] ) )
ENDIF
// Group header
nPointer := BIN2W(SUBSTR(cParamsBuff, GRP_HDR_OFFSET, 2))
aReport[ RPT_GROUPS ][1][ RGT_HEADER ] := ::GetExpr( nPointer )
// Page eject after group
aReport[ RPT_GROUPS ][1][ RGT_AEJECT ] := iif(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[ RPT_GROUPS ], ARRAY( RGT_COUNT ))
// Subgroup expression
aReport[ RPT_GROUPS ][2][ RGT_TEXT ] := cSubGroupExp
aReport[ RPT_GROUPS ][2][ RGT_EXP ] := &( "{ || " + cSubGroupExp + "}" )
IF USED()
aReport[ RPT_GROUPS ][2][ RGT_TYPE ] := ;
VALTYPE( EVAL( aReport[ RPT_GROUPS ][2][ RGT_EXP ] ) )
ENDIF
// Subgroup header
nPointer := BIN2W(SUBSTR(cParamsBuff, SUB_HDR_OFFSET, 2))
aReport[ RPT_GROUPS ][2][ RGT_HEADER ] := ::GetExpr( nPointer )
// Page eject after subgroup
aReport[ RPT_GROUPS ][2][ RGT_AEJECT ] := .F.
ENDIF
// Process columns
nFieldOffset := 12 // dBASE skips first 12 byte fields block.
FOR nCount := 1 to nColCount
AADD( aReport[ RPT_COLUMNS ], ::GetColumn( cFieldsBuff, @nFieldOffset ) )
NEXT nCount
ENDIF
RETURN aReport
/***
* 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)
*
*/
METHOD GetExpr( nPointer ) CLASS HBReportForm
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
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
STATIC FUNCTION XMLCOUNT( cString, nLineLength, nTabSize, lWrap )
// Set defaults if none specified
nLineLength := iif( nLineLength == NIL, 79, nLineLength )
nTabSize := iif( nTabSize == NIL, 4, nTabSize )
lWrap := iif( 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 := iif( nLineLength == NIL, 79, nLineLength )
nLineNumber := iif( nLineNumber == NIL, 1, nLineNumber )
nTabSize := iif( nTabSize == NIL, 4, nTabSize )
lWrap := iif( lWrap == NIL, .T., lWrap )
IF nTabSize >= nLineLength
nTabSize := nLineLength - 1
ENDIF
RETURN( MEMOLINE( cString, nLineLength, nLineNumber, nTabSize, lWrap ) )
STATIC 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
/***
* 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)
*
*/
METHOD GetColumn( cFieldsBuffer, nOffset ) CLASS HBReportForm
LOCAL nPointer := 0, nNumber := 0, aColumn[ RCT_COUNT ], cType,cExpr
// Column width
aColumn[ RCT_WIDTH ] := BIN2W(SUBSTR(cFieldsBuffer, nOffset + ;
FIELD_WIDTH_OFFSET, 2))
// Total column?
aColumn[ RCT_TOTAL ] := iif(SUBSTR(cFieldsBuffer, nOffset + ;
FIELD_TOTALS_OFFSET, 1) $ "YyTt", .T., .F.)
// Decimals width
aColumn[ RCT_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[ RCT_TEXT ] := ::GetExpr( nPointer )
cExpr := aColumn[ RCT_TEXT ]
aColumn[ RCT_EXP ] := &( "{ || " + cExpr + "}" )
// Header expression
nPointer := BIN2W(SUBSTR(cFieldsBuffer, nOffset +;
FIELD_HEADER_EXPR_OFFSET, 2))
aColumn[ RCT_HEADER ] := ListAsArray(::GetExpr( nPointer ), ";")
// Column picture
// Setup picture only if a database file is open
IF USED()
cType := VALTYPE( EVAL(aColumn[ RCT_EXP ]) )
aColumn[ RCT_TYPE ] := cType
DO CASE
CASE cType == "C" .OR. cType == "M"
aColumn[ RCT_PICT ] := REPLICATE("X", aColumn[ RCT_WIDTH ])
CASE cType == "D"
aColumn[ RCT_PICT ] := "@D"
CASE cType == "N"
IF aColumn[ RCT_DECIMALS ] != 0
aColumn[ RCT_PICT ] := REPLICATE("9", aColumn[ RCT_WIDTH ] - aColumn[ RCT_DECIMALS ] -1) + "." + ;
REPLICATE("9", aColumn[ RCT_DECIMALS ])
ELSE
aColumn[ RCT_PICT ] := REPLICATE("9", aColumn[ RCT_WIDTH ])
ENDIF
CASE cType == "L"
aColumn[ RCT_PICT ] := "@L" + REPLICATE("X",aColumn[ RCT_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
STATIC FUNCTION MakeAStr( uVar, cType )
LOCAL cString
DO CASE
CASE UPPER(cType) == "D"
cString := DTOC( uVar )
CASE UPPER(cType) == "L"
cString := iif( uVar, "T", "F" )
CASE UPPER(cType) == "N"
cString := STR( uVar )
CASE UPPER(cType) == "C"
cString := uVar
OTHERWISE
cString := "INVALID EXPRESSION"
ENDCASE
RETURN cString
FUNCTION __ReportForm( cFRMName, lPrinter, cAltFile, lNoConsole, bFor, ;
bWhile, nNext, nRecord, lRest, lPlain, cHeading, ;
lBEject, lSummary )
RETURN HBReportForm():New(cFrmName,lPrinter,cAltFile,lNoConsole,bFor,bWhile,nNext,nRecord,;
lRest,lPlain,cHeading,lBEject,lSummary)