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 >
Text File  |  2003-07-09  |  23KB  |  657 lines

  1. ///////////////////////////////////////////////////////////////////////////////
  2. //
  3. //                             A  C  S  N
  4. //
  5. // +---------------  Alaska Certified Solutions Network  -------------------+
  6. // |                                                                        |
  7. // |        This file is proved and certified by Alaska Software            |
  8. // |                                                                        |
  9. // |                   No: <Certification number>                           |
  10. // |                          109xxx-01-0010                                |
  11. // |                                                                        |
  12. // |   For more information about ACSN read the appropriate announcement    |
  13. // |      or scan for ACSN in the Alaska Support-LIBs on CompuServe or      |
  14. // |                   at WWW.ALASKA-SOFTWARE.COM                           |
  15. // |                                                                        |
  16. // +------------------------------------------------------------------------+
  17. //
  18. // FILE NAME
  19. //
  20. //    SDF.PRG
  21. //
  22. // AUTHOR
  23. //
  24. //    (c) Copyright 1998-2003, Frank Grossheinrich
  25. //
  26. //    ALL RIGHTS RESERVED
  27. //
  28. //    This file is the property of AUTHOR. It participates in the
  29. //    Alaska Certified Solutions Network program. Permission to use,
  30. //    copy, modify, and distribute this software for any purpose and
  31. //    without fee is hereby granted, provided that the above copyright
  32. //    notice appear in all copies and that the name of the author or
  33. //    Alaska Software not be used in advertising or publicity pertaining
  34. //    to distribution of the software without specific, written prior
  35. //    permission.
  36. //
  37. // WARRANTY
  38. //
  39. //    THE MATERIAL EMBODIED ON THIS SOFTWARE IS PROVIDED TO YOU "AS-IS"
  40. //    AND WITHOUT WARRANTY OF ANY KIND, EXPRESS, IMPLIED OR OTHERWISE,
  41. //    INCLUDING WITHOUT LIMITATION, ANY WARRANTY OF MERCHANTABILITY OR
  42. //    FITNESS FOR A PARTICULAR PURPOSE.  IN NO EVENT SHALL THE AUTHOR
  43. //    OR ALASKA SOFTWARE BE LIABLE TO YOU OR ANYONE ELSE FOR ANY DIRECT,
  44. //    SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
  45. //    INCLUDING WITHOUT LIMITATION, LOSS OF PROFIT AND LOSS OF USE.
  46. //
  47. // DESCRIPTION
  48. //
  49. //    This file contains the functions which would be needed
  50. //    when using SDF.CH in a PRG of your choice.
  51. //
  52. //         So it just needs a
  53. //             #include "SDF.CH"
  54. //         in your source code to get the same results as Clipper when
  55. //         copying or appending to/from a SDF/DELIMITED file.
  56. //
  57. //         This version also supports a non constant length of records
  58. //                of an SDF file.
  59. //
  60. // HISTORY
  61. //
  62. //    09.07.03 FG/HZ      Speeeeeeeeeeed; please see #DEFINE MAKE_IT_FAST
  63. //    09.07.03 FG         now it looks perfect for me <g>; 
  64. //                        corrected an error which has been reported by 
  65. //                            Clifford Wiernik through the newsgroups 
  66. //                            (THANKS to him!!!)
  67. //                        and while I were sitting at it I did correct a few
  68. //                            other minor issues
  69. //                        made it compile without any warning
  70. //    04.07.01 DH         error when appending from SDF; had to move
  71. //                              a line (see $FG$ 07/04/2001 comment)
  72. //    28.11.00 DH         error on field splitting when tokens used in
  73. //                                     delimited character fields
  74. //    26.05.98 FG         error when there have been more tokens then fields
  75. //    01.04.98 FG         creation file
  76. //
  77. ///////////////////////////////////////////////////////////////////////////////
  78.  
  79.  
  80. #INCLUDE "common.CH"
  81. #include "Dmlb.ch"
  82. #include "Error.ch"
  83. #include "Deldbe.ch"
  84. #include "Sdfdbe.ch"
  85. #INCLUDE "fileio.CH"
  86.  
  87. #DEFINE  MAKE_IT_FAST
  88.  
  89. #DEFINE UNUSED( foo )    (( foo ) := (  foo ))
  90.  
  91. #DEFINE CRLF   CHR( 13) + CHR( 10)     // carriage return and line feed
  92.  
  93. #DEFINE BUF_SIZE        20000          // max buffer size which will be read
  94. #DEFINE DEL_TOKEN       1
  95. #DEFINE DEL_DELIMITER   2
  96.  
  97. STATIC saDEL                           // the info about the format
  98.  
  99. /*
  100.  * this function will be preprocessed by the SDF.CH file
  101.  * and is responsible for the export of SDF or DELIMITED
  102.  * files     COPY TO ... SDF/DELIMITED
  103. */
  104. FUNCTION _XDbExport( cFile,       ;    // file name
  105.                      aFieldNames, ;    // field names
  106.                      bFor,        ;    // FOR condition
  107.                      bWhile,      ;    // WHILE condition
  108.                      nNext,       ;    // NEXT option
  109.                      nRecord,     ;    // how many records of NEXT option
  110.                      lRest,       ;    // REST option
  111.                      cDbe,        ;    // SDF or DELIMITED format
  112.                      cDelimiter )      // delimiters
  113.  
  114.    saDEL := ARRAY( 2)                  // initialize the format info
  115.  
  116.    IF cDbe == "DELDBE"                 // prepare info of format
  117.       saDEL[ DEL_TOKEN] := ","
  118.       saDEL[ DEL_DELIMITER] := cDelimiter
  119.       IF Valtype( cDelimiter ) <> "C"
  120.          saDEL[ DEL_DELIMITER] := '"'
  121.       ELSEIF "BLANK" $ Upper( cDelimiter )
  122.          saDEL[ DEL_TOKEN] := " "
  123.          saDEL[ DEL_DELIMITER] := ""
  124.       ELSEIF Empty( cDelimiter )
  125.          saDEL[ DEL_DELIMITER] := '"'
  126.       ENDIF
  127.    ELSE
  128.       saDEL[ DEL_TOKEN] := ""
  129.       saDEL[ DEL_DELIMITER] := ""
  130.    ENDIF
  131.  
  132.  
  133.    XDbExport( cFile,       ;           // pass paramters to export function
  134.               aFieldNames, ;
  135.               bFor,        ;
  136.               bWhile,      ;
  137.               nNext,       ;
  138.               nRecord,     ;
  139.               lRest,       ;
  140.               cDbe)
  141.  
  142. RETURN NIL
  143.  
  144.  
  145. /*
  146.  * we're getting closer and closer to our TXT file
  147. */
  148. FUNCTION XDbExport( cFile,       ;     // get closer to the export function
  149.                     aFieldNames, ;
  150.                     bFor,        ;
  151.                     bWhile,      ;
  152.                     nNext,       ;
  153.                     nRecord,     ;
  154.                     lRest,       ;
  155.                     cDbe)
  156.  
  157.    LOCAL nSourceArea, aSource, aTarget, nCount, aFieldPos
  158.    LOCAL bError, i:=0 , cFieldTypes := "CNLD", hFile
  159.  
  160.    IF Valtype( aFieldNames ) <> "A"    // did we get any filed infos
  161.       aFieldNames := {}
  162.    ENDIF
  163.  
  164.    aSource     := DbStruct()           // what is the source file like
  165.    nCount      := Len( aFieldNames )
  166.    nSourceArea := Select()
  167.  
  168.    IF nCount == 0                      // did we get field names
  169.       nCount    := Len( aSource )      // then our source table is default
  170.       aTarget   := aSource
  171.       aFieldPos := Array( nCount )     // prepare array of field positions
  172.       DO WHILE ++i <= nCount
  173.          aFieldPos[i] := i
  174.       ENDDO
  175.    ELSE
  176.       aTarget   := Array( nCount )     // yes, we got filed names
  177.       aFieldPos := Array( nCount )     // then it is easy playing
  178.       DO WHILE ++i <= nCount
  179.          aFieldPos[i] := FieldPos( aFieldNames[i] )
  180.          aTarget[i]   := aSource[ aFieldPos[i] ]
  181.       ENDDO
  182.    ENDIF
  183.  
  184.    i := 0
  185.    DO WHILE ++i <= nCount              // which filed types do we support
  186.       IF ! aTarget[i,2] $ cFieldTypes  // get rid of the "not supported" ones"
  187.          ADel( aTarget, i )
  188.          ADel( aFieldPos, i )
  189.          i--
  190.          nCount--
  191.       ENDIF
  192.    ENDDO
  193.  
  194.    IF ATail( aTarget ) == NIL          // did we delete a field
  195.       ASize( aTarget  , nCount )
  196.       ASize( aFieldPos, nCount )
  197.    ENDIF
  198.  
  199.    cFile += IIF( AT( ".", cFile) == 0, ".TXT", "")
  200.    hFile := FCreate( cFile)            // generate a new TXT file
  201.  
  202.    IF hFile != -1                      // did we succed to generate the TXT file
  203.       SELECT (nSourceArea)             // select the source table
  204.       bError := ErrorBlock( {|e| BREAK(e)} )
  205.  
  206.                                        // let's start working
  207.       DbEval( {|| XDbExportRecord( aFieldPos  , ;
  208.                                    aTarget    , ;
  209.                                    nCount     , ;
  210.                                    nSourceArea, ;
  211.                                    hFile      , ;
  212.                                    cDbe ) },    ;
  213.          bFor, bWhile, nNext, nRecord, lRest )
  214.  
  215.       FClose( hFile)                   // close the generated file
  216.       ErrorBlock( bError )
  217.       SELECT (nSourceArea)
  218.    ENDIF
  219.  
  220. RETURN NIL
  221.  
  222.  
  223. /*
  224.  * this is the copying stuff
  225. */
  226. STATIC PROCEDURE XDbExportRecord( aFieldPos, aTarget, nCount,  ;
  227.                                   nSource, hFile, cDbe)
  228.    LOCAL i := 0, lDeleted := Deleted()
  229.    LOCAL cRecord := "", cTemp
  230.  
  231.    DO WHILE ++i <= nCount              // read all fields of source table
  232.       aTarget[i] := FieldGet( aFieldPos[i] )
  233.    ENDDO
  234.  
  235.    i := 0
  236.    DO WHILE ++i <= nCount              // prepare the ASCII record
  237.       DO CASE
  238.                                        // if it is already a string field
  239.       CASE ValType( aTarget[ i]) == "C"
  240.          IF cDbe == "DELDBE"
  241.             aTarget[ i] := ALLTRIM( aTarget[ i] )
  242.          ENDIF
  243.                                        // just put the delimiters around it
  244.          cRecord += ( saDEL[ DEL_DELIMITER] + aTarget[ i] + ;
  245.                       saDEL[ DEL_DELIMITER])
  246.  
  247.                                        // if it is numeric
  248.       CASE ValType( aTarget[ i]) == "N"
  249.                                        // we need more info about length
  250.                                        // and decimals
  251.          cTemp := STR( aTarget[ i],                ;
  252.                        FieldInfo( aFieldPos[ i], FLD_LEN), ;
  253.                        FieldInfo( aFieldPos[ i], FLD_DEC))
  254.          IF cDbe == "DELDBE"
  255.             cTemp := ALLTRIM( cTemp )
  256.          ENDIF
  257.          cRecord += cTemp
  258.  
  259.                                        // if it is a date
  260.       CASE ValType( aTarget[ i]) == "D"
  261.          cRecord += DTOS( aTarget[ i]) // that is an easy one
  262.  
  263.                                        // if it is logical
  264.       CASE ValType( aTarget[ i]) == "L"
  265.                                        // this is also quite easy
  266.          cRecord += IIF( aTarget[ i], "T", "F")
  267.       ENDCASE
  268.  
  269.       IF i < nCount                    // add the field seperator
  270.          cRecord += saDEL[ DEL_TOKEN]
  271.       ENDIF
  272.    ENDDO
  273.  
  274.    FWrite( hFile, cRecord + CRLF)      // write record to TXT file
  275.                                        // plus the CRLF
  276.  
  277.    BEGIN SEQUENCE
  278.       IF lDeleted
  279.          DbDelete()
  280.       ENDIF
  281.    ENDSEQUENCE
  282.  
  283.    SELECT (nSource)
  284. RETURN
  285.  
  286.  
  287. /*
  288.  * here is the entry point for the import functionality. This
  289.  * function will also be preprocessed by SDF.CH
  290.  *                         APPEND FROM ... SDF/DELIMITED
  291.  * This implementation can also manage a "non static length"
  292.  *    of a record/line
  293. */
  294. FUNCTION _XDbImport( cFile,       ;    // file name
  295.                      aFieldNames, ;    // filed names
  296.                      bFor,        ;    // FOR condition
  297.                      bWhile,      ;    // WHILE condition
  298.                      nNext,       ;    // NEXT option
  299.                      nRecord,     ;    // how many records of NEXT option
  300.                      lRest,       ;    // REST option
  301.                      cDbe,        ;    // SDF or DELIMITED format
  302.                      cDelimiter )      // delimiters
  303.  
  304.         LOCAL oError
  305.  
  306.    saDEL := ARRAY( 2)                  // intialize the format info
  307.  
  308.    IF cDbe == "DELDBE"                 // if we are talking of a DELIMITED file
  309.       saDEL[ DEL_TOKEN] := ","
  310.       saDEL[ DEL_DELIMITER] := cDelimiter
  311.       IF Valtype( cDelimiter ) <> "C"
  312.          saDEL[ DEL_DELIMITER] := '"'
  313.                                        // but we do not support BLANK option
  314.       ELSEIF "BLANK" $ Upper( cDelimiter )
  315.          oError               := Error():new()
  316.          oError:canDefault    := .F.
  317.          oError:canRetry      := .F.
  318.          oError:canSubstitute := .F.
  319.          oError:description   := "option BLANK is not supported"
  320.          oError:filename      := cFile
  321.          oError:genCode       := XPP_ERR_DBE_UNSUPPORTED
  322.          oError:operation     := "_XDbImport()"
  323.          oError:severity      := XPP_ES_ERROR
  324.          oError:subSystem     := "DELDBE"
  325.          oError:args          := { cFile,       ;
  326.                                    aFieldNames, ;
  327.                                    bFor,        ;
  328.                                    bWhile,      ;
  329.                                    nNext,       ;
  330.                                    nRecord,     ;
  331.                                    lRest,       ;
  332.                                    cDbe,        ;
  333.                                    cDelimiter }
  334.          Eval( ErrorBlock(), oError )
  335.       ELSEIF Empty( cDelimiter )
  336.          saDEL[ DEL_DELIMITER] := '"'
  337.       ENDIF
  338.    ELSE
  339.       saDEL[ DEL_TOKEN] := " "
  340.       saDEL[ DEL_DELIMITER] := ""
  341.    ENDIF
  342.  
  343.    XDbImport( cFile, ;                 // let's start working
  344.               aFieldNames, ;
  345.               bFor, ;
  346.               bWhile, ;
  347.               nNext, ;
  348.               nRecord, ;
  349.               lRest, ;
  350.               cDbe)
  351.  
  352. RETURN NIL
  353.  
  354.  
  355. /*
  356.  * here we are to do some final intialization
  357. */
  358. FUNCTION XDbImport( cFile,       ;
  359.                     aFieldNames, ;
  360.                     bFor,        ;
  361.                     bWhile,      ;
  362.                     nNext,       ;
  363.                     nRecord,     ;
  364.                     lRest,       ;
  365.                     cDbe)
  366.  
  367. #ifdef MAKE_IT_FAST
  368.    LOCAL nTargetArea, aTargetStruct, cTargetTypes := "CLND"
  369.    LOCAL i, j, nCount
  370.    LOCAL aPosField
  371.    LOCAL aRecords, nRecCount
  372. #else
  373.    LOCAL nTargetArea, aTargetStruct, cTargetTypes := "CLND"
  374.    LOCAL i, j, nCount, nEOF, cRecord
  375.    LOCAL aPosField, hFile
  376. #endif
  377.  
  378.    UNUSED( bFor )
  379.    UNUSED( bWhile )
  380.    UNUSED( lRest )
  381.    UNUSED( nNext )
  382.    UNUSED( nRecord )
  383.  
  384.    IF Valtype( aFieldNames) <> "A"     // did we get some field names
  385.       aFieldNames := {}
  386.    ENDIF
  387.  
  388.    aTargetStruct := DbStruct()         // what is the table like where
  389.                                        // the ASCII records should go
  390.    nCount        := LEN( aTargetStruct)
  391.    nTargetArea   := Select()
  392.  
  393.    i := 0
  394.    DO WHILE ++i <= nCount              // let's get rid of the "not supported"
  395.                                        // field types
  396.       IF ! aTargetStruct[ i, 2] $ cTargetTypes
  397.          ADel( aTargetStruct, i )
  398.          i--
  399.          nCount--
  400.       ENDIF
  401.    ENDDO
  402.  
  403.    IF ATail( aTargetStruct ) == NIL    // did we drop some fields
  404.       ASize( aTargetStruct, nCount )
  405.    ENDIF
  406.  
  407.    IF EMPTY( aFieldNames)              // but no field is too less
  408.       j := LEN( aTargetStruct)
  409.       FOR i := 1 TO j
  410.          AADD( aFieldNames, aTargetStruct[ i, 1])
  411.       NEXT
  412.    ENDIF
  413.    aPosField := ( nTargetArea)->( xFieldPosArray( aFieldNames ) )
  414.  
  415.    cFile += IIF( AT( ".", cFile) == 0, ".TXT", "")
  416.  
  417. #ifdef MAKE_IT_FAST
  418.    aRecords  := Split( Memoread( cFile ), CRLF )
  419.    nRecCount := Len( aRecords )
  420.  
  421.    FOR i:=1 TO nRecCount
  422.      xDbImportRecord( aTargetStruct, aPosField, aRecords[i], ;
  423.                       nTargetArea, cDbe)
  424.    NEXT   
  425.  
  426. #else
  427.    hFile := FOpen( cFile, FO_SHARED)   // open the ASCII file
  428.    IF hFile != -1                      // did we succeed
  429.       nEOF := FSeek( hFile, 0, FS_END) // what is the length of the file
  430.       FSeek( hFile, 0, FS_SET)         // go to the top of the ASCII file
  431.  
  432.       DO WHILE ! FEof( hFile, nEOF)    // are we at the end-of-the-file
  433.          cRecord := FGetRecord( hFile) // get that record
  434.                                        // and nail it to the disk
  435.          xDbImportRecord( aTargetStruct, aPosField, cRecord,   ;
  436.                           nTargetArea, cDbe)
  437.          FSkipRecord( hFile)           // where is the next one
  438.       ENDDO
  439.  
  440.       FClose( hFile)                   // close the file
  441.    ENDIF
  442. #endif
  443.    SELECT (nTargetArea)
  444.  
  445. RETURN NIL
  446.  
  447.  
  448. /*
  449.  * this is again the copy stuff
  450. */
  451. STATIC PROCEDURE XDbImportRecord( aTargetStruct, aPosField,    ;
  452.                                   cRecord, nTargetArea, cDbe)
  453.    LOCAL i := 0, lDel := ( cDbe == "DELDBE")
  454.    LOCAL cFieldContent, y := LEN( aTargetStruct)
  455.    LOCAL nPosToken, cType
  456.  
  457.    SELECT (nTargetArea)
  458.    DbAppend()                          // append a new record to the table
  459.  
  460.    DO WHILE TRUE
  461.       i++                              // field counter
  462.       IF i > y                         // if there are more tokens then fields
  463.          EXIT
  464.       ENDIF
  465.  
  466.       cType := aTargetStruct[ aPosField[ i], 2]        // $FG$ 07/04/2001
  467.       IF lDel                          // if it is a DELIMITED file
  468.  
  469.                                               // Where is the token
  470.          IF cType == "C" .AND. Left( cRecord, 1 ) == saDel[ DEL_DELIMITER ]
  471.             nPosToken := AT( saDel[ DEL_DELIMITER ] + saDel[ DEL_TOKEN ]      ;
  472.                              , cRecord, 2 )
  473.  
  474.             IF nPosToken != 0          // '","' .OR. '",' found ?
  475.                                        // if yes, just add length of
  476.                                        // delimiter itself
  477.                nPosToken += LEN( saDel[ DEL_DELIMITER ])
  478.                                        // to get the token
  479.             ENDIF
  480.          ELSE
  481.                                        // there is no delimiter
  482.             nPosToken := AT( saDel[ DEL_TOKEN], cRecord )
  483.          ENDIF
  484.                                        // when no token found, read up to the
  485.                                        // end of the record
  486.          nPosToken := IIF( nPosToken == 0, LEN( cRecord ) + 1, nPosToken )
  487.  
  488.                                        // get the token
  489.          cFieldContent := SUBSTR( cRecord, 1, ;
  490.                                   nPosToken - LEN( saDEL[ DEL_TOKEN]))
  491.  
  492.                                        // that will become our record
  493.          cRecord := SUBSTR( cRecord, nPosToken + LEN( saDEL[ DEL_TOKEN]))
  494.  
  495.       ELSE
  496.                                        // and the same procedure for SDF format
  497.          cFieldContent := SUBSTR( cRecord, 1, ;
  498.                                   aTargetStruct[ aPosField[ i], 3])
  499.          cRecord := SUBSTR( cRecord, LEN( cFieldContent) + 1)
  500.          nPosToken := LEN( cRecord)
  501.  
  502.       ENDIF
  503.  
  504.       DO CASE
  505.          CASE cType == "C"             // if it is a string
  506.             FieldPut( aPosField[ i], StrTran( cFieldContent, ;
  507.                   saDel[ DEL_DELIMITER]))
  508.  
  509.          CASE cType == "N"             // or numeric
  510.             FieldPut( aPosField[ i], VAL( cFieldContent))
  511.  
  512.          CASE cType == "L"             // or logical
  513.             FieldPut( aPosField[ i], ( cFieldContent == "T"))
  514.  
  515.          CASE cType == "D"             // or even a date
  516.             FieldPut( aPosField[ i], STOD( cFieldContent) )
  517.  
  518.       ENDCASE
  519.  
  520.       IF EMPTY( cRecord )              // if we do not find another token
  521.          EXIT
  522.       ENDIF
  523.  
  524.    ENDDO
  525.  
  526. RETURN
  527.  
  528.  
  529. /*
  530.  * EOF for low level file functions
  531. */
  532. STATIC FUNCTION FEof( hFile, nEOF)
  533. RETURN( FSeek( hFile, 0, FS_RELATIVE) == nEOF)
  534.  
  535.  
  536. /*
  537.  * Get the "ASCII record"
  538. */
  539. STATIC FUNCTION FGetRecord( hFile)
  540.    LOCAL cRet := "", nBuffer, nPosCR
  541.    LOCAL cBuffer := SPACE( BUF_SIZE)
  542.                                        // were are we now
  543.    LOCAL nPos := FSeek( hFile, 0, FS_RELATIVE)
  544.  
  545.    UNUSED( nBuffer )
  546.  
  547.                                        // we just read the length of the
  548.                                        // buffer like defined above
  549.    nBuffer := FRead( hFile, @cBuffer, BUF_SIZE)
  550.    nPosCR := AT( CRLF, cBuffer)        // there should be a CRLF
  551.    IF nPosCR > 1                       // if found ...
  552.       cRet := SUBSTR( cBuffer, 1, nPosCR - 1)
  553.    ELSEIF nPosCR == 0
  554.       cRet := cBuffer
  555.    ENDIF
  556.    FSeek( hFile, nPos, FS_SET)         // get back where we have been
  557. RETURN cRet
  558.  
  559.  
  560. /*
  561.  * go to the next record/next CRLF
  562. */
  563. STATIC FUNCTION FSkipRecord( hFile)
  564.    LOCAL lRet := FALSE , nBuffer, nPosCR
  565.    LOCAL cBuffer := SPACE( BUF_SIZE)
  566.                                        // were are we now
  567.    LOCAL nPos := FSeek( hFile, 0, FS_RELATIVE)
  568.  
  569.    UNUSED( nBuffer )
  570.                                        // we just read the length of the
  571.                                        // buffer like defined above
  572.    nBuffer := FRead( hFile, @cBuffer, BUF_SIZE)
  573.    nPosCR := AT( CRLF, cBuffer)        // there should be a CRLF
  574.    IF nPosCR > 0                       // if yes ... we could skip
  575.       lRet := TRUE
  576.       FSeek( hFile, nPos, FS_SET)      // go back where we have been
  577.       FSeek( hFile, nPosCR - 1 + LEN( CRLF), FS_RELATIVE)
  578.    ELSEIF nPosCR == 0
  579.       lRet := TRUE
  580.       FSeek( hFile, 0, FS_END)         // go back where we have been
  581.    ENDIF
  582.                                        // and go further to the next CRLF
  583. RETURN lRet
  584.  
  585.  
  586. /*
  587.  * small helper function to find position in table
  588. */
  589. STATIC FUNCTION xFieldPosArray( aFieldNames )
  590.    LOCAL i :=0, nCount := Len( aFieldNames), nFCount := 0
  591.    LOCAL aFieldPos[nCount], nPos
  592.    DO WHILE ++i <= nCount
  593.       IF ( nPos := FieldPos( aFieldNames[i] ) ) > 0
  594.          aFieldPos[++nFCount] := nPos
  595.       ENDIF
  596.    ENDDO
  597. RETURN ASize( aFieldPos, nFCount )
  598.  
  599.  
  600. #ifdef MAKE_IT_FAST
  601.  
  602. ******************************************************************************
  603. * Create an array from a delimited string
  604. * and remove the delimiters (really fast!)
  605. *
  606. * USAGE:
  607. *   LOCAL cStr := "A;BC;DEF;GHIJ"
  608. *
  609. *   ? Split( cStr, ";" ) // -> { A, BC, DEF, GHIJ }
  610. *
  611. ******************************************************************************
  612. STATIC FUNCTION Split( cString, cDelimiter )
  613.    LOCAL aArray, nStep, nSize
  614.    LOCAL nStart, nEnd, nCount, nLen
  615.  
  616.    nStart := 1
  617.    nEnd   := At( cDelimiter, cString ) 
  618.  
  619.    IF nEnd == 0
  620.       RETURN IIF( Len(cString)==0, {}, { cString } )
  621.    ENDIF
  622.  
  623.    nCount := 1
  624.    nLen   := Len( cDelimiter )
  625.    nSize  := Int( Rat( cDelimiter, cString ) / nEnd )
  626.    nStep  := Max( 100, nSize )
  627.    aArray := Array( nSize )
  628.  
  629.    DO WHILE .T.
  630.       aArray[ nCount ] := SubStr( cString, nStart, nEnd-nStart )
  631.  
  632.       // next position to continue search from
  633.       nStart := nEnd + nLen
  634.  
  635.       IF (nEnd := At( cDelimiter, cString, nStart ) ) == 0 
  636.          // no more delimiters found in string
  637.          IF nStart > Len( cString )
  638.             ASize( aArray, nCount )
  639.             RETURN aArray
  640.  
  641.          ELSEIF ++nCount > nSize
  642.             // Exactly one more array element is required
  643.             AAdd( aArray, SubStr( cString, nStart ) )
  644.             RETURN aArray
  645.          ENDIF
  646.  
  647.          EXIT
  648.       ELSEIF ++nCount > nSize
  649.          // Array has no more NIL elements
  650.          ASize( aArray, nSize += nStep )
  651.       ENDIF
  652.    ENDDO
  653.  
  654.    aArray[ nCount ] := SubStr( cString, nStart )
  655. RETURN ASize( aArray, nCount )
  656.  
  657. #endif