home *** CD-ROM | disk | FTP | other *** search
- /***
- * Frmback.prg
- * Create a report array from a (.frm) file
- * Copyright (c) 1990 Nantucket Corp. All rights reserved.
- *
- * Compile: /n/w/m
- */
-
- #include "frmdef.ch"
- #include "error.ch"
-
- // 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
-
- // Declare file-wide statics
- STATIC cExprBuff
- STATIC cOffsetsBuff
- STATIC cLengthsBuff
-
- /***
- *
- * __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.
- */
-
- 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
-
- LOCAL s, paths
- LOCAL i
-
- // 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 )
- nFileError = FERROR()
-
- IF !( "\" $ cFrmFile .or. ":" $ cFrmFile )
- // if not found and no path in name, go looking
-
- IF nFileError != F_OK
-
- s := SET( _SET_DEFAULT )
-
- IF !Empty( s )
- nFrmHandle := FOPEN( s + "\" + cFrmFile )
- nFileError := FERROR()
- END
- END
-
- IF nFileError != F_OK
-
- s := SET( _SET_PATH )
- s := StrTran(s, ",", ";") // convert any commas in path spec
-
- paths := ListAsArray( s )
-
- FOR i := 1 to Len(paths)
- nFrmHandle := FOPEN( paths[i] + "\" + cFrmFile )
- nFileError := FERROR()
-
- IF nFileError == F_OK
- EXIT
- END
- NEXT
- END
- END
-
- // File error
- IF nFileError != F_OK
- err := ErrorNew()
- err:severity := 2
- err:genCode := EG_OPEN
- err:subSystem := "FRMLBL"
- 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) $ "Yy", 2, 1)
-
- // Summary report flag
- aReport[ RP_SUMMARY ] := IF(SUBSTR(cParamsBuff, SUMMARY_RPT_OFFSET, 1) $ "Yy", .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))
-
- aReport[ RP_HEADER ] := ;
- ListAsArray(GetExpr( nPointer ), ;
- ";", ;
- aReport[ RP_WIDTH ] - aReport[ RP_RMARGIN ] )
-
-
- // 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) $ "Yy", .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
-
- 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)
- *
- */
- 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) $ "Yy", .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>, [<nWidth>] ) --> aList
- * Convert a delimited string to an array
- *
- */
- STATIC FUNCTION ListAsArray( cList, cDelimiter, nWidth )
-
- LOCAL nPos
- LOCAL aList := {} // Define an empty array
- LOCAL lDelimLast := .f.
-
- IF cDelimiter = NIL
- cDelimiter := ","
- ENDIF
-
- if nWidth == NIL
- nWidth := Len(cList)
- end
-
- DO WHILE ( Len(cList) <> 0 )
-
- nPos := AT(cDelimiter, cList)
-
- if ( nPos == 0 )
- nPos := Len(cList)
- end
-
- if ( nPos - 1 > nWidth )
- nPos := nWidth
-
- while ( nPos > 0 .and. substr(cList, nPos, 1) <> " " )
- nPos --
- end
-
- if ( nPos == 0 )
- nPos := nWidth
- end
- end
-
- 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
- end
-
- cList := SUBSTR(cList, nPos + 1)
-
- ENDDO
-
- if ( lDelimLast )
- AADD(aList, "")
- end
-
- RETURN aList // Return the array
-