Reading a R&R libraries #1
I've made these function myself, so that's why I put it first in the list.
There are actually 2 functions:
■ RR_Names()
a function to give you the names of all stored reports
■ RR_Details()
a function to give you all required details of a specific report
Both functions are stored in the same .prg, listed next.
*┌───────────────────────────────────────────────────────────────────────────┐
*│RR_Names() Retrieve names of R&R reports stored in a library │
*│ │
*│Syntax: │
*│ RR_Names( <cLibfile> ) │
*│ │
*│Parameters: │
*│ <cLibfile> Name of the R&R report library file to be processed │
*│ │
*│Returns: │
*│ <aReportNames> Array with the names of the reports stored in the library │
*│ If there are no reports, an empty array is returned │
*│ │
*│ │
*│ Developed and donated to the public domain in march 1993 by │
*│ Ernst Peter Tamminga CIS 100042,1760 │
*│ XBASE expertise center │
*│ Aalsmeerderdijk 640, 1435 BW Rijsenhout, The Netherlands │
*└───────────────────────────────────────────────────────────────────────────┛
function RR_Names( cLibFile ) // Calling functions map here
return RRLibInfo( cLibFile )
*┌───────────────────────────────────────────────────────────────────────────┐
*│RR_Details() Retrieve details of a R&R report stored in a library │
*│ │
*│Syntax: │
*│ RR_Details( <cLibfile>, <cReport> ) │
*│ │
*│Parameters: │
*│ <cLibfile> Name of the R&R report library file to be processed │
*│ │
*│ <cReport> The exact name of the report from which the details │
*│ are requested from. Retrieve this name with RR_names(). │
*│ │
*│Returns: │
*│ <aDetails> Array with report detail info: │
*│ [1,*] Master database info │
*│ [1, 1] Master database name │
*│ [1, 2] Master index name │
*│ [1, 3] Alias name of the master database │
*│ [1, 4] Tag name (if applicable, else NIL) │
*│ │
*│ [2,*] Related file settings │
*│ [2, *, 1] Related database name │
*│ [2, *, 2] Related index name │
*│ [2, *, 3] Alias name of the related file │
*│ [2, *, 4] Tag name (if applicable, else NIL) │
*│ len( aDetails[2] ) gives you the number of │
*│ related files │
*│ │
*│ [3] Font file attached (if different from default │
*│ font file). If NIL, no seperate font attached. │
*│ │
*│ [4,*] Ascii file attached │
*│ [4, 1] Full name of the ASCII file │
*│ [4, 2] Alias name of the ASCII file │
*│ Returns NIL If no ascii file attached │
*│ │
*│ [5] Name of the UDF library attached if different │
*│ from default udf library │
*│ │
*│ [6] Date at which the report was saved │
*│ N.B. Only valid from R&R V4.0 and up │
*│ │
*│ [7] Time at which the report was saved as HH:MM:SS │
*│ N.B. Only valid from R&R V4.0 and up │
*│ │
*│ [8] Number of copies │
*│ │
*│ [9,*] Calculated field │
*│ [9, 1] Name of the calculated field │
*│ [9, 2] Expression of the calculated field │
*│ [9, 3] File number the calculated field relates to │
*│ Special file numbers are: │
*│ 0 = master database │
*│ 1-n = related database │
*│ 254 = calculated during run, e.g. pageno() │
*│ 255 = system setting, e.g. date() │
*│ │
*│ │
*│ Developed and donated to the public domain in march 1993 by │
*│ Ernst Peter Tamminga CIS 100042,1760 │
*│ XBASE expertise center │
*│ Aalsmeerderdijk 640, 1435 BW Rijsenhout, The Netherlands │
*└───────────────────────────────────────────────────────────────────────────┛
function RR_Details( cLibFile, cReport )
return RRLibInfo( cLibFile, cReport )
*┌───────────────────────────────────────────────────────────────────────────┐
*└───────────────────────────────────────────────────────────────────────────┛
#include "fileio.ch" // We need this for low level IO
#define RECOFFSET 3 // Offset of complete file due to header
#define HEADERLEN 19 // Header size of the library
#define RECIDLEN 4 // Record ID length
*┌───────────────────────────────────────────────────────────────────────────┐
*│ Next function handles both requests │
*└───────────────────────────────────────────────────────────────────────────┛
static function RRLibInfo( cLibFile, cReport )
local nHandle // For reading the .rp1 file
local nOffset // Offset in data chain
local nBlkSize // Fixed block length of library
local nBlock := 0 // Block pointer
local nRepNo // Requested report number in array
local aReportDir // The .rp1 directory structure
local aResult := {} // The resulting array with contents
// Open .cnf file in shared/read mode
if (( nHandle := fopen(cLibfile, FO_READ + FO_SHARED)) >= 0 )
nBlkSize := RRLibHdr( nHandle, @nBlock )
aReportDir := RRNames ( nHandle, nBlkSize, nBlock )
if cReport == NIL // Names of reports are required
aeval( aReportDir, { | aRep | aadd( aResult, aRep[1] ) } )
else // Report details needed
// Find requested report first
for nRepNo := 1 to len(aReportDir)
if cReport == aReportDir[ nRepNo, 1]
aResult := ReportDetail( nHandle, nBlkSize, ;
aReportDir[nRepNo,2] )
exit // Once found, w're ready
endif
next
endif
fclose( nHandle ) // You're done
endif
return ( aResult ) // You want some? You'll get it
*┌───────────────────────────────────────────────────────────────────────────┐
*│ Read the header info of a library │
*└───────────────────────────────────────────────────────────────────────────┛
static function RRLibHdr( nHandle, nBlkStr )
// Header size of library
local cHeader := space(HEADERLEN + RECOFFSET)
local nBlkSiz // Report library block size
// Read the header
fread( nHandle, @cHeader, HEADERLEN + RECOFFSET )
// Calculate directory starting block
nBlkStr := RRNumber( substr( cHeader, 9, 3 ) )
// Here is the block size
nBlkSiz := bin2I(substr(cHeader, 20, 2))
// Block size zero means
// .. a block size of 32
return ( iif( nBlkSiz == 0, 32,nBlkSiz ) )
*┌───────────────────────────────────────────────────────────────────────────┐
*│ R&R uses 3 byte integers │
*└───────────────────────────────────────────────────────────────────────────┛
static function RRNumber( cString )
return 256*256*asc(substr(cString,1,1)) + ;
256*asc(substr(cString,3,1)) + ;
asc(substr(cstring,2,1))
*┌───────────────────────────────────────────────────────────────────────────┐
*│ Parse the names from the directory structure │
*└───────────────────────────────────────────────────────────────────────────┛
static function RRNames ( nHandle, nBlkSize, nBlock )
local cNames // Read name buffer
local aNames := {} // This will hold the names + start
local nReports // Number of reports
local lnOffSet := 3 // Offset in character chain of names
local nRepNamLen // Report name length
cNames := ReadChain( nHandle, nBlkSize, nBlock )
nReports := asc( cNames) // First byte = # of reports
do while nReports-- > 0 // Handle all reports
nRepNamLen := asc( substr(cNames, lnOffSet, 1) )
// 1st entry is name of report
// 2nd entry is starting block
aadd( aNames, { substr( cNames, lnOffSet+5, nRepNamLen ), ;
bin2L( substr(cNames, lnOffSet+1, 4) ) } )
lnOffSet += nRepNamLen + 5 // For the next report
enddo
return (aNames) // Return report base info
*┌───────────────────────────────────────────────────────────────────────────┐
*│ A chain is either the complete directory or a complete report │
*│ The complete chain is read as character string. There is a │
*│ possibility that this overflows maximum string length (64K), but │
*│ than: who developes such lengthy reports? │
*└───────────────────────────────────────────────────────────────────────────┛
static function ReadChain( nHandle, nBlkSize, nBlock )
local cChain := "" // Complete data of one chain
local cChunk // One buffer of the chain
do while nBlock > 0 // Handle all blocks in the chain
cChunk := space( nBlkSize ) // Prepare the buffer
fseek( nHandle, nBlock*nBlkSize ) // Position in library file
fread( nHandle, @cChunk, nBlkSize) // Read one record
// Next block number
nBlock := RRnumber( substr(cChunk, 1, 3 ) )
cChain += substr(cChunk, 4) // Rest is data
enddo
return (cChain) // Return all data
*┌───────────────────────────────────────────────────────────────────────────┐
*│ Next functions retrieves all interesting data from a report │
*└───────────────────────────────────────────────────────────────────────────┛
#define WITHTAG .T. // If related file contains tag
static function ReportDetail( nHandle, nBlkSize, nBlock )
local cReport // Complete report definition
local lIsEof := .F. // Don' try to read after the EOF
local nOffSet := 1 // W'll parse the complete report
local cRecId // The record identification + length
local nRecType // Recordtype indication
local nReclen // Data length of the record
local cRecCont // Contents of the data part
local aDetail // Resulting array
aDetail := { array(4), {}, NIL, array(2), NIL, ctod(""), "", 1, {} }
cReport := ReadChain( nHandle, nBlkSize, nBlock )
do while nOffSet < len(cReport) .and. !lIsEof
// Get the record header
cRecId := substr(cReport, nOffSet, RECIDLEN )
// 2 bytes is for ID#
// 2 bytes for data length
nRecType := bin2I( substr( cRecId, 1, 2 ) )
nRecLen := bin2I( substr( cRecId, 3, 2 ) )
// Read the record contents
cRecCont := substr(cReport, nOffSet + RECIDLEN, nRecLen )
nOffSet += RECIDLEN + nRecLen
do case // Dispatch interesting record types
case nRecType == 1 // This record type if BOR
aDetail[6] := ParseDate( substr(cRecCont, 3, 4) )
aDetail[7] := ParseTime( substr(cRecCont, 7, 3) )
case nRecType == 2 // This record type if EOF
lIsEof := .T.
case nRecType == 30 .or. ; // Master database in same dir as lib
nRecType == 52 // or in other directory than lib
aDetail[1,1] := cRecCont
case nRecType == 3 // Master index name
aDetail[1,2] := substr(cRecCont, 4)
case nRecType == 4 // Master alias name
aDetail[1,3] := cRecCont
case nRecType == 47 // Master index tag
aDetail[1,4] := cRecCont
case nRecType == 8 // Related file without TAG
aadd( aDetail[2], ParseRelation(cRecCont, !WITHTAG ))
case nRecType == 46 // Related file with TAG
aadd( aDetail[2], ParseRelation(cRecCont, WITHTAG ))
case nRecType == 64 // Font file
aDetail[3] := cRecCont
case nRecType == 59 // Ascii file attach
aDetail[4] := ParseAscii(cRecCont)
case nRecType == 80 // UDF library
aDetail[5] := cRecCont
case nRecType == 49 // Number of copies
aDetail[8] := bin2I( cRecCont )
case nRecType == 6 // Calculated field
aadd( aDetail[9], ParseCalcFld( cRecCont ) )
endcase
enddo
return (aDetail)
*┌───────────────────────────────────────────────────────────────────────────┐
*│ This wil solve a related file setting │
*└───────────────────────────────────────────────────────────────────────────┛
static function ParseRelation( cRelation, lWithTag )
local nFldNamLen // Field name length
local nFilNamLen // File name length
local nIdxNamLen // Index name length
local nTagNamLen // Index tag name length
local aRelation := array(4) // Relation definition
// [1] = Related file name
// [2] = Related file index name
// [3] = Alias name of this relation
// [4] = Tag name (if available)
nFldNamLen := asc(substr(cRelation, 6, 1))
nFilNamLen := asc(substr(cRelation, 7+nFldNamLen, 1) )
aRelation[1] := substr(cRelation, 8+nFldNamLen, nFilNamLen )
nIdxNamLen := asc(substr(cRelation, 8+nFldNamLen+nFilNamLen, 1))
aRelation[2] := substr(cRelation, 9+nFldNamLen+nFilNamLen, nIdxNamLen)
if !lWithTag
aRelation[3] := substr(cRelation, 9+nFldNamLen+nFilNamLen+nIdxNamLen )
else
nTagNamLen := asc(substr(cRelation,10+nFldNamLen+nFilNamLen+nIdxNamLen))
aRelation[4] := substr(cRelation, 11+nFldNamLen+nFilNamLen+nIdxNamLen, nTagNamLen)
aRelation[3] := substr(cRelation, 11+nFldNamLen+nFilNamLen+nIdxNamLen+nTagNamLen )
endif
return (aRelation)
*┌───────────────────────────────────────────────────────────────────────────┐
*│ ASCII file attachements are stored with alias name │
*└───────────────────────────────────────────────────────────────────────────┛
static function ParseAscii( cAscii )
local nPatNamLen // Path name length
local aAsciiSet := array(2) // [1] = ascii file with pathname
// [2] = alias name
nPatNamLen := asc(cAscii) // Calculated from first byte
aAsciiSet[1] := substr(cAscii, 2, nPatNamLen )
aAsciiSet[2] := substr(cAscii, 2+nPatNamLen )
return (aAsciiset)
*┌───────────────────────────────────────────────────────────────────────────┐
*│ Date last stored is a number of integeres │
*└───────────────────────────────────────────────────────────────────────────┛
static function ParseDate( cDateSave )
local nYear := bin2I( left(cDateSave, 2) )
local nMonth := asc( substr(cDateSave, 3, 1) )
local nDay := asc( substr(cDateSave, 4, 1) )
local cOldDateFrm // Use a predefined date format
local dSaved
cOldDateFrm := set( _SET_DATEFORMAT, "yyyymmdd" )
dSaved := ctod( str(nYear,4) + str(nMonth) + str(nDay) )
set( _SET_DATEFORMAT, cOldDateFrm )
return (dSaved)
*┌───────────────────────────────────────────────────────────────────────────┐
*│ Time last stored is a number of integeres for hours, minutes & seconds │
*└───────────────────────────────────────────────────────────────────────────┛
static function ParseTime( cTime )
// Take care of left zero fill when number is below 10
#define LFTZERO( c ) substr( str( asc( c ) + 100, 3), 2 )
local cHour := LFTZERO( substr(cTime, 1, 1) )
local cMin := LFTZERO( substr(cTime, 2, 1) )
local cSec := LFTZERO( substr(cTime, 3, 1) )
return ( cHour + ":" + cMin + ":" + cSec )
*┌───────────────────────────────────────────────────────────────────────────┐
*│ Parse calculated field definition │
*└───────────────────────────────────────────────────────────────────────────┛
static function ParseCalcFld( cCalcFld )
local nFileNo := asc( substr( cCalcFld, 14) )
local nFieldLen := asc( substr( cCalcFld, 15) )
local cField := substr( cCalcFld, 16, nFieldLen )
local cExpression := substr( cCalcFld, 16+nFieldLen )
return { cField, cExpression, nFileNo }
*─────────────────────────────────────────────────────────────────────────────