home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
ftp.alaska-software.com
/
2014.06.ftp.alaska-software.com.tar
/
ftp.alaska-software.com
/
acsn
/
SDFDEL.ZIP
/
SDF.PRG
< prev
next >
Wrap
Text File
|
2003-07-09
|
23KB
|
657 lines
///////////////////////////////////////////////////////////////////////////////
//
// A C S N
//
// +--------------- Alaska Certified Solutions Network -------------------+
// | |
// | This file is proved and certified by Alaska Software |
// | |
// | No: <Certification number> |
// | 109xxx-01-0010 |
// | |
// | For more information about ACSN read the appropriate announcement |
// | or scan for ACSN in the Alaska Support-LIBs on CompuServe or |
// | at WWW.ALASKA-SOFTWARE.COM |
// | |
// +------------------------------------------------------------------------+
//
// FILE NAME
//
// SDF.PRG
//
// AUTHOR
//
// (c) Copyright 1998-2003, Frank Grossheinrich
//
// ALL RIGHTS RESERVED
//
// This file is the property of AUTHOR. It participates in the
// Alaska Certified Solutions Network program. Permission to use,
// copy, modify, and distribute this software for any purpose and
// without fee is hereby granted, provided that the above copyright
// notice appear in all copies and that the name of the author or
// Alaska Software not be used in advertising or publicity pertaining
// to distribution of the software without specific, written prior
// permission.
//
// WARRANTY
//
// THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
// AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
// INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
// FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL THE AUTHOR
// OR ALASKA SOFTWARE BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
// SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
// INCLUDING WITHOUT LIMITATION, LOSS OF PROFIT AND LOSS OF USE.
//
// DESCRIPTION
//
// This file contains the functions which would be needed
// when using SDF.CH in a PRG of your choice.
//
// So it just needs a
// #include "SDF.CH"
// in your source code to get the same results as Clipper when
// copying or appending to/from a SDF/DELIMITED file.
//
// This version also supports a non constant length of records
// of an SDF file.
//
// HISTORY
//
// 09.07.03 FG/HZ Speeeeeeeeeeed; please see #DEFINE MAKE_IT_FAST
// 09.07.03 FG now it looks perfect for me <g>;
// corrected an error which has been reported by
// Clifford Wiernik through the newsgroups
// (THANKS to him!!!)
// and while I were sitting at it I did correct a few
// other minor issues
// made it compile without any warning
// 04.07.01 DH error when appending from SDF; had to move
// a line (see $FG$ 07/04/2001 comment)
// 28.11.00 DH error on field splitting when tokens used in
// delimited character fields
// 26.05.98 FG error when there have been more tokens then fields
// 01.04.98 FG creation file
//
///////////////////////////////////////////////////////////////////////////////
#INCLUDE "common.CH"
#include "Dmlb.ch"
#include "Error.ch"
#include "Deldbe.ch"
#include "Sdfdbe.ch"
#INCLUDE "fileio.CH"
#DEFINE MAKE_IT_FAST
#DEFINE UNUSED( foo ) (( foo ) := ( foo ))
#DEFINE CRLF CHR( 13) + CHR( 10) // carriage return and line feed
#DEFINE BUF_SIZE 20000 // max buffer size which will be read
#DEFINE DEL_TOKEN 1
#DEFINE DEL_DELIMITER 2
STATIC saDEL // the info about the format
/*
* this function will be preprocessed by the SDF.CH file
* and is responsible for the export of SDF or DELIMITED
* files COPY TO ... SDF/DELIMITED
*/
FUNCTION _XDbExport( cFile, ; // file name
aFieldNames, ; // field names
bFor, ; // FOR condition
bWhile, ; // WHILE condition
nNext, ; // NEXT option
nRecord, ; // how many records of NEXT option
lRest, ; // REST option
cDbe, ; // SDF or DELIMITED format
cDelimiter ) // delimiters
saDEL := ARRAY( 2) // initialize the format info
IF cDbe == "DELDBE" // prepare info of format
saDEL[ DEL_TOKEN] := ","
saDEL[ DEL_DELIMITER] := cDelimiter
IF Valtype( cDelimiter ) <> "C"
saDEL[ DEL_DELIMITER] := '"'
ELSEIF "BLANK" $ Upper( cDelimiter )
saDEL[ DEL_TOKEN] := " "
saDEL[ DEL_DELIMITER] := ""
ELSEIF Empty( cDelimiter )
saDEL[ DEL_DELIMITER] := '"'
ENDIF
ELSE
saDEL[ DEL_TOKEN] := ""
saDEL[ DEL_DELIMITER] := ""
ENDIF
XDbExport( cFile, ; // pass paramters to export function
aFieldNames, ;
bFor, ;
bWhile, ;
nNext, ;
nRecord, ;
lRest, ;
cDbe)
RETURN NIL
/*
* we're getting closer and closer to our TXT file
*/
FUNCTION XDbExport( cFile, ; // get closer to the export function
aFieldNames, ;
bFor, ;
bWhile, ;
nNext, ;
nRecord, ;
lRest, ;
cDbe)
LOCAL nSourceArea, aSource, aTarget, nCount, aFieldPos
LOCAL bError, i:=0 , cFieldTypes := "CNLD", hFile
IF Valtype( aFieldNames ) <> "A" // did we get any filed infos
aFieldNames := {}
ENDIF
aSource := DbStruct() // what is the source file like
nCount := Len( aFieldNames )
nSourceArea := Select()
IF nCount == 0 // did we get field names
nCount := Len( aSource ) // then our source table is default
aTarget := aSource
aFieldPos := Array( nCount ) // prepare array of field positions
DO WHILE ++i <= nCount
aFieldPos[i] := i
ENDDO
ELSE
aTarget := Array( nCount ) // yes, we got filed names
aFieldPos := Array( nCount ) // then it is easy playing
DO WHILE ++i <= nCount
aFieldPos[i] := FieldPos( aFieldNames[i] )
aTarget[i] := aSource[ aFieldPos[i] ]
ENDDO
ENDIF
i := 0
DO WHILE ++i <= nCount // which filed types do we support
IF ! aTarget[i,2] $ cFieldTypes // get rid of the "not supported" ones"
ADel( aTarget, i )
ADel( aFieldPos, i )
i--
nCount--
ENDIF
ENDDO
IF ATail( aTarget ) == NIL // did we delete a field
ASize( aTarget , nCount )
ASize( aFieldPos, nCount )
ENDIF
cFile += IIF( AT( ".", cFile) == 0, ".TXT", "")
hFile := FCreate( cFile) // generate a new TXT file
IF hFile != -1 // did we succed to generate the TXT file
SELECT (nSourceArea) // select the source table
bError := ErrorBlock( {|e| BREAK(e)} )
// let's start working
DbEval( {|| XDbExportRecord( aFieldPos , ;
aTarget , ;
nCount , ;
nSourceArea, ;
hFile , ;
cDbe ) }, ;
bFor, bWhile, nNext, nRecord, lRest )
FClose( hFile) // close the generated file
ErrorBlock( bError )
SELECT (nSourceArea)
ENDIF
RETURN NIL
/*
* this is the copying stuff
*/
STATIC PROCEDURE XDbExportRecord( aFieldPos, aTarget, nCount, ;
nSource, hFile, cDbe)
LOCAL i := 0, lDeleted := Deleted()
LOCAL cRecord := "", cTemp
DO WHILE ++i <= nCount // read all fields of source table
aTarget[i] := FieldGet( aFieldPos[i] )
ENDDO
i := 0
DO WHILE ++i <= nCount // prepare the ASCII record
DO CASE
// if it is already a string field
CASE ValType( aTarget[ i]) == "C"
IF cDbe == "DELDBE"
aTarget[ i] := ALLTRIM( aTarget[ i] )
ENDIF
// just put the delimiters around it
cRecord += ( saDEL[ DEL_DELIMITER] + aTarget[ i] + ;
saDEL[ DEL_DELIMITER])
// if it is numeric
CASE ValType( aTarget[ i]) == "N"
// we need more info about length
// and decimals
cTemp := STR( aTarget[ i], ;
FieldInfo( aFieldPos[ i], FLD_LEN), ;
FieldInfo( aFieldPos[ i], FLD_DEC))
IF cDbe == "DELDBE"
cTemp := ALLTRIM( cTemp )
ENDIF
cRecord += cTemp
// if it is a date
CASE ValType( aTarget[ i]) == "D"
cRecord += DTOS( aTarget[ i]) // that is an easy one
// if it is logical
CASE ValType( aTarget[ i]) == "L"
// this is also quite easy
cRecord += IIF( aTarget[ i], "T", "F")
ENDCASE
IF i < nCount // add the field seperator
cRecord += saDEL[ DEL_TOKEN]
ENDIF
ENDDO
FWrite( hFile, cRecord + CRLF) // write record to TXT file
// plus the CRLF
BEGIN SEQUENCE
IF lDeleted
DbDelete()
ENDIF
ENDSEQUENCE
SELECT (nSource)
RETURN
/*
* here is the entry point for the import functionality. This
* function will also be preprocessed by SDF.CH
* APPEND FROM ... SDF/DELIMITED
* This implementation can also manage a "non static length"
* of a record/line
*/
FUNCTION _XDbImport( cFile, ; // file name
aFieldNames, ; // filed names
bFor, ; // FOR condition
bWhile, ; // WHILE condition
nNext, ; // NEXT option
nRecord, ; // how many records of NEXT option
lRest, ; // REST option
cDbe, ; // SDF or DELIMITED format
cDelimiter ) // delimiters
LOCAL oError
saDEL := ARRAY( 2) // intialize the format info
IF cDbe == "DELDBE" // if we are talking of a DELIMITED file
saDEL[ DEL_TOKEN] := ","
saDEL[ DEL_DELIMITER] := cDelimiter
IF Valtype( cDelimiter ) <> "C"
saDEL[ DEL_DELIMITER] := '"'
// but we do not support BLANK option
ELSEIF "BLANK" $ Upper( cDelimiter )
oError := Error():new()
oError:canDefault := .F.
oError:canRetry := .F.
oError:canSubstitute := .F.
oError:description := "option BLANK is not supported"
oError:filename := cFile
oError:genCode := XPP_ERR_DBE_UNSUPPORTED
oError:operation := "_XDbImport()"
oError:severity := XPP_ES_ERROR
oError:subSystem := "DELDBE"
oError:args := { cFile, ;
aFieldNames, ;
bFor, ;
bWhile, ;
nNext, ;
nRecord, ;
lRest, ;
cDbe, ;
cDelimiter }
Eval( ErrorBlock(), oError )
ELSEIF Empty( cDelimiter )
saDEL[ DEL_DELIMITER] := '"'
ENDIF
ELSE
saDEL[ DEL_TOKEN] := " "
saDEL[ DEL_DELIMITER] := ""
ENDIF
XDbImport( cFile, ; // let's start working
aFieldNames, ;
bFor, ;
bWhile, ;
nNext, ;
nRecord, ;
lRest, ;
cDbe)
RETURN NIL
/*
* here we are to do some final intialization
*/
FUNCTION XDbImport( cFile, ;
aFieldNames, ;
bFor, ;
bWhile, ;
nNext, ;
nRecord, ;
lRest, ;
cDbe)
#ifdef MAKE_IT_FAST
LOCAL nTargetArea, aTargetStruct, cTargetTypes := "CLND"
LOCAL i, j, nCount
LOCAL aPosField
LOCAL aRecords, nRecCount
#else
LOCAL nTargetArea, aTargetStruct, cTargetTypes := "CLND"
LOCAL i, j, nCount, nEOF, cRecord
LOCAL aPosField, hFile
#endif
UNUSED( bFor )
UNUSED( bWhile )
UNUSED( lRest )
UNUSED( nNext )
UNUSED( nRecord )
IF Valtype( aFieldNames) <> "A" // did we get some field names
aFieldNames := {}
ENDIF
aTargetStruct := DbStruct() // what is the table like where
// the ASCII records should go
nCount := LEN( aTargetStruct)
nTargetArea := Select()
i := 0
DO WHILE ++i <= nCount // let's get rid of the "not supported"
// field types
IF ! aTargetStruct[ i, 2] $ cTargetTypes
ADel( aTargetStruct, i )
i--
nCount--
ENDIF
ENDDO
IF ATail( aTargetStruct ) == NIL // did we drop some fields
ASize( aTargetStruct, nCount )
ENDIF
IF EMPTY( aFieldNames) // but no field is too less
j := LEN( aTargetStruct)
FOR i := 1 TO j
AADD( aFieldNames, aTargetStruct[ i, 1])
NEXT
ENDIF
aPosField := ( nTargetArea)->( xFieldPosArray( aFieldNames ) )
cFile += IIF( AT( ".", cFile) == 0, ".TXT", "")
#ifdef MAKE_IT_FAST
aRecords := Split( Memoread( cFile ), CRLF )
nRecCount := Len( aRecords )
FOR i:=1 TO nRecCount
xDbImportRecord( aTargetStruct, aPosField, aRecords[i], ;
nTargetArea, cDbe)
NEXT
#else
hFile := FOpen( cFile, FO_SHARED) // open the ASCII file
IF hFile != -1 // did we succeed
nEOF := FSeek( hFile, 0, FS_END) // what is the length of the file
FSeek( hFile, 0, FS_SET) // go to the top of the ASCII file
DO WHILE ! FEof( hFile, nEOF) // are we at the end-of-the-file
cRecord := FGetRecord( hFile) // get that record
// and nail it to the disk
xDbImportRecord( aTargetStruct, aPosField, cRecord, ;
nTargetArea, cDbe)
FSkipRecord( hFile) // where is the next one
ENDDO
FClose( hFile) // close the file
ENDIF
#endif
SELECT (nTargetArea)
RETURN NIL
/*
* this is again the copy stuff
*/
STATIC PROCEDURE XDbImportRecord( aTargetStruct, aPosField, ;
cRecord, nTargetArea, cDbe)
LOCAL i := 0, lDel := ( cDbe == "DELDBE")
LOCAL cFieldContent, y := LEN( aTargetStruct)
LOCAL nPosToken, cType
SELECT (nTargetArea)
DbAppend() // append a new record to the table
DO WHILE TRUE
i++ // field counter
IF i > y // if there are more tokens then fields
EXIT
ENDIF
cType := aTargetStruct[ aPosField[ i], 2] // $FG$ 07/04/2001
IF lDel // if it is a DELIMITED file
// Where is the token
IF cType == "C" .AND. Left( cRecord, 1 ) == saDel[ DEL_DELIMITER ]
nPosToken := AT( saDel[ DEL_DELIMITER ] + saDel[ DEL_TOKEN ] ;
, cRecord, 2 )
IF nPosToken != 0 // '","' .OR. '",' found ?
// if yes, just add length of
// delimiter itself
nPosToken += LEN( saDel[ DEL_DELIMITER ])
// to get the token
ENDIF
ELSE
// there is no delimiter
nPosToken := AT( saDel[ DEL_TOKEN], cRecord )
ENDIF
// when no token found, read up to the
// end of the record
nPosToken := IIF( nPosToken == 0, LEN( cRecord ) + 1, nPosToken )
// get the token
cFieldContent := SUBSTR( cRecord, 1, ;
nPosToken - LEN( saDEL[ DEL_TOKEN]))
// that will become our record
cRecord := SUBSTR( cRecord, nPosToken + LEN( saDEL[ DEL_TOKEN]))
ELSE
// and the same procedure for SDF format
cFieldContent := SUBSTR( cRecord, 1, ;
aTargetStruct[ aPosField[ i], 3])
cRecord := SUBSTR( cRecord, LEN( cFieldContent) + 1)
nPosToken := LEN( cRecord)
ENDIF
DO CASE
CASE cType == "C" // if it is a string
FieldPut( aPosField[ i], StrTran( cFieldContent, ;
saDel[ DEL_DELIMITER]))
CASE cType == "N" // or numeric
FieldPut( aPosField[ i], VAL( cFieldContent))
CASE cType == "L" // or logical
FieldPut( aPosField[ i], ( cFieldContent == "T"))
CASE cType == "D" // or even a date
FieldPut( aPosField[ i], STOD( cFieldContent) )
ENDCASE
IF EMPTY( cRecord ) // if we do not find another token
EXIT
ENDIF
ENDDO
RETURN
/*
* EOF for low level file functions
*/
STATIC FUNCTION FEof( hFile, nEOF)
RETURN( FSeek( hFile, 0, FS_RELATIVE) == nEOF)
/*
* Get the "ASCII record"
*/
STATIC FUNCTION FGetRecord( hFile)
LOCAL cRet := "", nBuffer, nPosCR
LOCAL cBuffer := SPACE( BUF_SIZE)
// were are we now
LOCAL nPos := FSeek( hFile, 0, FS_RELATIVE)
UNUSED( nBuffer )
// we just read the length of the
// buffer like defined above
nBuffer := FRead( hFile, @cBuffer, BUF_SIZE)
nPosCR := AT( CRLF, cBuffer) // there should be a CRLF
IF nPosCR > 1 // if found ...
cRet := SUBSTR( cBuffer, 1, nPosCR - 1)
ELSEIF nPosCR == 0
cRet := cBuffer
ENDIF
FSeek( hFile, nPos, FS_SET) // get back where we have been
RETURN cRet
/*
* go to the next record/next CRLF
*/
STATIC FUNCTION FSkipRecord( hFile)
LOCAL lRet := FALSE , nBuffer, nPosCR
LOCAL cBuffer := SPACE( BUF_SIZE)
// were are we now
LOCAL nPos := FSeek( hFile, 0, FS_RELATIVE)
UNUSED( nBuffer )
// we just read the length of the
// buffer like defined above
nBuffer := FRead( hFile, @cBuffer, BUF_SIZE)
nPosCR := AT( CRLF, cBuffer) // there should be a CRLF
IF nPosCR > 0 // if yes ... we could skip
lRet := TRUE
FSeek( hFile, nPos, FS_SET) // go back where we have been
FSeek( hFile, nPosCR - 1 + LEN( CRLF), FS_RELATIVE)
ELSEIF nPosCR == 0
lRet := TRUE
FSeek( hFile, 0, FS_END) // go back where we have been
ENDIF
// and go further to the next CRLF
RETURN lRet
/*
* small helper function to find position in table
*/
STATIC FUNCTION xFieldPosArray( aFieldNames )
LOCAL i :=0, nCount := Len( aFieldNames), nFCount := 0
LOCAL aFieldPos[nCount], nPos
DO WHILE ++i <= nCount
IF ( nPos := FieldPos( aFieldNames[i] ) ) > 0
aFieldPos[++nFCount] := nPos
ENDIF
ENDDO
RETURN ASize( aFieldPos, nFCount )
#ifdef MAKE_IT_FAST
******************************************************************************
* Create an array from a delimited string
* and remove the delimiters (really fast!)
*
* USAGE:
* LOCAL cStr := "A;BC;DEF;GHIJ"
*
* ? Split( cStr, ";" ) // -> { A, BC, DEF, GHIJ }
*
******************************************************************************
STATIC FUNCTION Split( cString, cDelimiter )
LOCAL aArray, nStep, nSize
LOCAL nStart, nEnd, nCount, nLen
nStart := 1
nEnd := At( cDelimiter, cString )
IF nEnd == 0
RETURN IIF( Len(cString)==0, {}, { cString } )
ENDIF
nCount := 1
nLen := Len( cDelimiter )
nSize := Int( Rat( cDelimiter, cString ) / nEnd )
nStep := Max( 100, nSize )
aArray := Array( nSize )
DO WHILE .T.
aArray[ nCount ] := SubStr( cString, nStart, nEnd-nStart )
// next position to continue search from
nStart := nEnd + nLen
IF (nEnd := At( cDelimiter, cString, nStart ) ) == 0
// no more delimiters found in string
IF nStart > Len( cString )
ASize( aArray, nCount )
RETURN aArray
ELSEIF ++nCount > nSize
// Exactly one more array element is required
AAdd( aArray, SubStr( cString, nStart ) )
RETURN aArray
ENDIF
EXIT
ELSEIF ++nCount > nSize
// Array has no more NIL elements
ASize( aArray, nSize += nStep )
ENDIF
ENDDO
aArray[ nCount ] := SubStr( cString, nStart )
RETURN ASize( aArray, nCount )
#endif