!==================================================================================
! Класс для работы с CSV-файлами.
! Вячеслав Черников, г.Рязань, 2017г.
!==================================================================================

                     MEMBER

   INCLUDE('fsCSV.INC')
   INCLUDE('WINEQU.INC')

   MAP
     INCLUDE('BUILTINS.CLW'),ONCE   !функции RTL
     MODULE('Windows API')
        CreateFile(*CSTRING,ULONG=GENERIC_READ+GENERIC_WRITE,ULONG=0,ULONG=0,ULONG=CREATE_ALWAYS,ULONG=FILE_FLAG_RANDOM_ACCESS,UNSIGNED=0),UNSIGNED,PASCAL,RAW,NAME('CreateFileA')
        CloseFile(UNSIGNED),SIGNED,PROC,PASCAL,NAME('CloseHandle')
        WriteFile(UNSIGNED,<*?>,ULONG,*ULONG,<*?>),BOOL,PASCAL,RAW,PROC
        ReadFile(UNSIGNED,LONG,ULONG,*ULONG,LONG),BOOL,PASCAL,RAW,NAME('ReadFile')
        CloseHandle(UNSIGNED),BOOL,PASCAL,PROC,NAME('CloseHandle')
        GetFileSize(UNSIGNED,*ULONG),ULONG,PASCAL,NAME('GetFileSize')
     END
   END

FILE_ATTRIBUTE_NORMAL     equate(00000080H)

!===================================================================================
! Инициализация класса
!
FsCSV.Init PROCEDURE 
      CODE

   if SELF.Active=1
      return
   end

   SELF.Active=1

   SELF.BufPoint    =0
   SELF.BufPointMax = 1024
   SELF.StructFieldsMax = 100

   SELF.QueueBuf       &=  new(QueueCSVType)       !создание буфера
   SELF.QueueStructCSV &= new(QueueStructCSVType)  !создание списка структур


!===================================================================================
! Деактивация класса
!
FsCSV.Kill PROCEDURE
      CODE
   if SELF.Active=0
      return
   end

   free(SELF.QueueBuf)
   free(SELF.QueueStructCSV)

   dispose(SELF.QueueBuf)
   dispose(SELF.QueueStructCSV)

   SELF.Active=0

!===================================================================================
!
! Деструктор класса
!
FsCSV.Destruct PROCEDURE
   CODE
      SELF.Kill

!===================================================================================
!
! Установить структуру группы
!
FsCSV.SetStruct    PROCEDURE (STRING pName, *QUEUE pQueue)
loc:i    long
loc:str  string(40)

   CODE

     if SELF.Active = 0
        return
     end    

     clear(SELF.QueueStructCSV)
     SELF.QueueStructCSV.StructName = lower(pName)
     get(SELF.QueueStructCSV,SELF.QueueStructCSV.StructName)
     if ~error()
        return
     end      

     SELF.AddStrInt2('#' & lower(pName))

     loc:i=0
     loop
        loc:i+=1
        if loc:i>SELF.StructFieldsMax
           break
        end
        loc:str=lower(who(pQueue,loc:i))
        if loc:str=''
           break 
        end
        SELF.QueueStructCSV.StructField[loc:i] &= what(pQueue,loc:i)
        SELF.AddStrInt2(',' & loc:str)
     end

     SELF.QueueStructCSV.StructFields = loc:i
     SELF.QueueStructCSV.StructRef &= pQueue
     add(SELF.QueueStructCSV,SELF.QueueStructCSV.StructName)

     SELF.AddStrInt2(CHR(13) & CHR(10))

!===================================================================================
!
! Экспортировать группу
!
FsCSV.ExportGroup    PROCEDURE (STRING pName, *GROUP pGroup)
loc:i    long
loc:zn   any
loc:str  string(40)
   CODE

     if SELF.Active = 0
        return
     end    

     clear(SELF.QueueStructCSV)
     SELF.QueueStructCSV.StructName = lower(pName)
     get(SELF.QueueStructCSV,SELF.QueueStructCSV.StructName)
     if error()
        return
     end   

     SELF.AddStrInt2(lower(pName))   

     loc:i=0
     loop
        loc:i+=1
        if loc:i>SELF.StructFieldsMax
           break
        end
        loc:str=lower(who(pGroup,loc:i))
        if loc:str=''
           break 
        end
        loc:zn &= what(pGroup,loc:i)
        SELF.QueueStructCSV.StructField[loc:i] = loc:zn
        SELF.AddStrInt2(',"' & clip(SELF.StrReplaceSpec(SELF.QueueStructCSV.StructField[loc:i])) & '"')
     end

     SELF.AddStrInt2(CHR(13) & CHR(10))

!===================================================================================
!
! Экспортировать очередь
!
FsCSV.ExportQueue    PROCEDURE (STRING pName, *QUEUE pQueue)
loc:i    long
   CODE

     if SELF.Active = 0
        return
     end    

     clear(SELF.QueueStructCSV)
     SELF.QueueStructCSV.StructName = lower(pName)
     get(SELF.QueueStructCSV,SELF.QueueStructCSV.StructName)
     if error()
        return
     end   

     if records(pQueue)=0
        return
     end

     loop loc:i=1 to records(pQueue)
        get(pQueue,loc:i)
        SELF.ExportGroup(pName,pQueue) 
     end

!===================================================================================
!
! Импортировать файл
!
FsCSV.ImportFile  PROCEDURE (STRING pName, BYTE pReg)

ASCIIFile  FILE,DRIVER('ASCII'),PRE(ARF),CREATE
Record     RECORD,PRE()
rec    STRING(4096)
     END
   END

loc:i    long
loc:j    long
loc:p1   long
loc:p2   long
loc:num  long
loc:ok   byte
loc:zn   string(40)
loc:str  string(40)

loc:queue &queue

   CODE

     if SELF.Active = 0
        return
     end    

     if ~exists(pName)
        SELF.ErrorCode=1
        SELF.ErrorMessage='Файл для загрузки не найден! - ' & clip(pName)
        return
     end

     SELF.FileNameCSV = pName

     ASCIIFile{PROP:NAME}=SELF.FileNameCSV
     open(ASCIIFile)
     if error()
        SELF.ErrorCode=error()
        SELF.ErrorMessage='Не могу открыть файл для загрузки - ' & clip(pName)
        return
     end         

     set(ASCIIFile)
     loop
        next(ASCIIFile)
        if error()
           break
        end
        if sub(arf:rec,1,1)='#'  !структура
           do loadStruct_r
        else
           if pReg=1    !только структуры
              break
           end
           do loadRec_r
        end
     end

     close(ASCIIFile)

loadStruct_r  routine   !загрузить структуру
   loc:p1=instring(',',arf:rec,1,1)
   if loc:p1=0
      exit
   end 

   clear(SELF.QueueStructCSV)
   SELF.QueueStructCSV.StructName=lower(sub(arf:rec,2,loc:p1-2))
   get(SELF.QueueStructCSV,SELF.QueueStructCSV.StructName)
   if error()
      exit
   end

   loc:queue &= SELF.QueueStructCSV.StructRef

   loc:j=0
   loop
      loc:p2=instring(',',arf:rec,1,loc:p1+1)
      if loc:p2=0
         loc:zn=sub(arf:rec,loc:p1+1,len(clip(arf:rec))-loc:p1)  
      else
         loc:zn=sub(arf:rec,loc:p1+1,loc:p2-loc:p1-1)
      end
      loc:j+=1
      loc:i=0
      loop
         loc:i+=1
         if loc:i>SELF.StructFieldsMax
            break
         end
         loc:str=lower(who(loc:queue,loc:i))
         if loc:str=''
            break 
         end
         if loc:str=loc:zn
            SELF.QueueStructCSV.StructNums[loc:j] = loc:i
            break
         end 
      end
      if loc:p2=0
         break
      end
      loc:p1=loc:p2
   end

   put(SELF.QueueStructCSV)

loadRec_r     routine   !загрузить запись
   arf:rec=left(arf:rec)
   loc:p1=instring(',',arf:rec,1,1)
   if loc:p1=0
      exit
   end 

   loc:ok=0
   clear(SELF.QueueStructCSV)
   SELF.QueueStructCSV.StructName=lower(sub(arf:rec,1,loc:p1-1))
   get(SELF.QueueStructCSV,SELF.QueueStructCSV.StructName)
   if error()
      exit
   end

   loc:j=0
   loop
      loc:p2=instring(',',arf:rec,1,loc:p1+1)
      loc:j+=1
      if loc:j>SELF.StructFieldsMax
         break
      end
      loc:num=SELF.QueueStructCSV.StructNums[loc:j]
      if loc:num=0
         if loc:p2=0
            break
         else 
            cycle
         end
      end 
      loc:ok=1
      if loc:p2=0
         SELF.QueueStructCSV.StructField[loc:num] = SELF.StrReplaceSpecImp(sub(arf:rec,loc:p1+1,len(clip(arf:rec))-loc:p1))
         break
      else
         SELF.QueueStructCSV.StructField[loc:num] = SELF.StrReplaceSpecImp(sub(arf:rec,loc:p1+1,loc:p2-loc:p1-1))
      end
      loc:p1=loc:p2 
   end

   if loc:ok=1
      loc:queue &= SELF.QueueStructCSV.StructRef
      add(loc:queue) 
   end

!===================================================================================
!
! Импортировать запись
!
FsCSV.ImportRec  FUNCTION (STRING Str)

loc:j    long
loc:p1   long
loc:p2   long
loc:num  long

   CODE

     if SELF.Active = 0
        return('')
     end    

     loc:p1=instring(',',Str,1,1)
     if loc:p1=0
        return('')
     end 

    clear(SELF.QueueStructCSV)
    SELF.QueueStructCSV.StructName=lower(sub(Str,1,loc:p1-1))
    get(SELF.QueueStructCSV,SELF.QueueStructCSV.StructName)
    if error()
       return('')
    end

    loc:j=0
    loop
       loc:p2=instring(',',Str,1,loc:p1+1)
       loc:j+=1
       if loc:j>SELF.StructFieldsMax
          break
       end
       loc:num=SELF.QueueStructCSV.StructNums[loc:j]
       if loc:num=0
          if loc:p2=0
             break
          else 
             cycle
          end
       end 
       if loc:p2=0
          SELF.QueueStructCSV.StructField[loc:num] = SELF.StrReplaceSpecImp(sub(Str,loc:p1+1,len(clip(Str))-loc:p1))
          break
       else
          SELF.QueueStructCSV.StructField[loc:num] = SELF.StrReplaceSpecImp(sub(Str,loc:p1+1,loc:p2-loc:p1-1))
       end
       loc:p1=loc:p2 
    end

    return(SELF.QueueStructCSV.StructName)

!===================================================================================
!
! Добавить строку в буфер 
!
FsCSV.AddStrInt    PROCEDURE (STRING Str)
loc:dl  long
loc:poz long
   CODE

      if SELF.Active = 0
         return
      end    

      loc:dl = len(clip(str))+2
      loc:poz = 0

      if (SELF.BufPoint+loc:dl)<=SELF.BufPointMax   !вся строка помещается в строку буфера
         if SELF.BufPoint=0
            SELF.QueueBuf = clip(str) & CHR(13) & CHR(10)
         else
            SELF.QueueBuf = sub(SELF.QueueBuf,1,SELF.BufPoint) & clip(str) & CHR(13) & CHR(10)
         end
         SELF.BufPoint += loc:dl
         return
      end

      if SELF.BufPoint>0 and (SELF.BufPoint+loc:dl)>=SELF.BufPointMax  !дописываем последнюю строку в кьюшке скрипта
         SELF.QueueBuf = sub(SELF.QueueBuf,1,SELF.BufPoint) & sub(clip(str) & CHR(13) & CHR(10),1,SELF.BufPointMax-SELF.BufPoint)
         add(SELF.QueueBuf)
         loc:poz=SELF.BufPointMax-SELF.BufPoint
         SELF.BufPoint=0
      end

      loop  !вписываем целые части
         if loc:dl-loc:poz<SELF.BufPointMax
            break
         end 
         SELF.QueueBuf = sub(clip(str) & CHR(13) & CHR(10),loc:poz+1,SELF.BufPointMax)
         add(SELF.QueueBuf)
         loc:poz+=SELF.BufPointMax
      end

      if loc:dl-loc:poz>0
         SELF.QueueBuf = sub(clip(str) & CHR(13) & CHR(10),loc:poz+1,loc:dl-loc:poz)
         SELF.BufPoint=loc:dl-loc:poz
      end

!===================================================================================
!
! Добавить фрагмент строки в буфер
!
FsCSV.AddStrInt2    PROCEDURE (STRING Str)
loc:dl  long
loc:poz long
   CODE

      if SELF.Active = 0
         return
      end    

      loc:dl = len(clip(str))
      loc:poz = 0

      if (SELF.BufPoint+loc:dl)<=SELF.BufPointMax   !вся строка помещается в строку буфера
         if SELF.BufPoint=0
            SELF.QueueBuf = clip(str)
         else
            SELF.QueueBuf = sub(SELF.QueueBuf,1,SELF.BufPoint) & clip(str)
         end
         SELF.BufPoint += loc:dl
         return
      end

      if SELF.BufPoint>0 and (SELF.BufPoint+loc:dl)>=SELF.BufPointMax  !дописываем последнюю строку в кьюшке скрипта
         SELF.QueueBuf = sub(SELF.QueueBuf,1,SELF.BufPoint) & sub(clip(str),1,SELF.BufPointMax-SELF.BufPoint)
         add(SELF.QueueBuf)
         loc:poz=SELF.BufPointMax-SELF.BufPoint
         SELF.BufPoint=0
      end

      loop  !вписываем целые части
         if loc:dl-loc:poz<SELF.BufPointMax
            break
         end 
         SELF.QueueBuf = sub(clip(str),loc:poz+1,SELF.BufPointMax)
         add(SELF.QueueBuf)
         loc:poz+=SELF.BufPointMax
      end

      if loc:dl-loc:poz>0
         SELF.QueueBuf = sub(clip(str),loc:poz+1,loc:dl-loc:poz)
         SELF.BufPoint=loc:dl-loc:poz
      end

!===================================================================================
! Сформировать файл
!
FsCSV.MakeFile PROCEDURE (STRING pName)

loc:LocalFile     CSTRING(255)   !имя локального файла
loc:hLocal        UNSIGNED       !ссылка на локальный файл
loc:BufferLength  ULONG
loc:Buffer        &CSTRING

loc:recs          long
loc:len           long
loc:i             long
loc:err           byte

      CODE

      if SELF.Active=0
         return
      end

      if pName=''
         message('Не задано имя csv-файла!')
         return
      end

      if exists(pName)
         remove(pName)
      end       

      SELF.FileNameCSV = pName

      loc:len=len(SELF.QueueBuf)
      loc:recs=records(SELF.QueueBuf)

      if loc:recs=0 and SELF.BufPoint=0
         return
      end

      if SELF.BufPoint<>0
         add(SELF.QueueBuf)
      end

      loc:BufferLength = loc:len*loc:recs+SELF.BufPoint+1
           
      loc:Buffer &= new(CSTRING(loc:BufferLength))
      loop loc:i=1 to records(SELF.QueueBuf) 
         get(SELF.QueueBuf,loc:i)
         if loc:i=1
            loc:Buffer=SELF.QueueBuf 
         else
            loc:Buffer=sub(loc:Buffer,1,loc:len*(loc:i-1)) & SELF.QueueBuf 
         end
      end

      loc:LocalFile = SELF.FileNameCSV     

      loc:hLocal = CreateFile(loc:LocalFile, GENERIC_READ+GENERIC_WRITE, |   !создать ссылку на выходной файл
                            FILE_SHARE_READ + FILE_SHARE_WRITE, 0 ,      |
                            CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,0)

      if ~WriteFile(loc:hLocal, loc:Buffer, loc:BufferLength, loc:BufferLength)  
         loc:err=1
      else
         loc:err=0 
      end

      CloseHandle(loc:hLocal)  !закрываем выходной файл

      clear(loc:Buffer)
      dispose(loc:Buffer)

      free(SELF.QueueBuf)

      if loc:err=1
         SELF.ErrorCode = 2
         SELF.ErrorMessage = 'Ошибка при сохранении в файл ' & clip(SELF.FileNameCSV)
         message(SELF.ErrorMessage)
      end

!===================================================================================
! Замена символов в строке
!
FsCSV.StrReplace FUNCTION (STRING Str, STRING StrSource, STRING StrTarg)
loc:strRet  string(4096)
loc:k       long
loc:p       long
   CODE

    if StrSource=StrTarg  !проверка на совпадение исходной и целевой подстроки
       return(Str)
    end

    loc:StrRet=Str
    loc:k=0
    loop
       loc:p=instring(StrSource,clip(loc:strRet),1,loc:k+1)
       if loc:p=0
          break
       end
       Loc:StrRet=sub(loc:strRet,1,loc:p-1) & StrTarg & sub(loc:StrRet,loc:p+len(StrSource),len(clip(Loc:StrRet))-loc:p-len(StrSource)+1)
       loc:k=loc:p+len(StrTarg)-len(StrSource)
    end

    return(loc:strRet)

!===================================================================================
! Замена специальных символов в строке (экспорт)
!
FsCSV.StrReplaceSpec FUNCTION (STRING Str)
loc:strRet  string(4096)
loc:dl  long
loc:i   long
loc:k   long
   CODE

     loc:dl=len(clip(Str))

     loc:k=0     
     loop loc:i=1 to loc:dl
        case Str[loc:i]
           of '"'
              loc:k+=1
              loc:strRet[loc:k]='"'
              loc:k+=1
              loc:strRet[loc:k]='"'
           of CHR(13)
              loc:k+=1
              loc:strRet[loc:k]='@'
              loc:k+=1
              loc:strRet[loc:k]='`'
              loc:k+=1
              loc:strRet[loc:k]='3'
           of CHR(10)
              loc:k+=1
              loc:strRet[loc:k]='@'
              loc:k+=1
              loc:strRet[loc:k]='`'
              loc:k+=1
              loc:strRet[loc:k]='0'
         else
              loc:k+=1
              loc:strRet[loc:k]=Str[loc:i]
        end
     end
     

    return(loc:strRet)

!===================================================================================
! Замена специальных символов в строке (импорт)
!
FsCSV.StrReplaceSpecImp FUNCTION (STRING Str)
loc:strRet  string(4096)
   CODE

    if sub(Str,1,1)='"' and sub(Str,len(clip(Str)),1)='"'
       loc:strRet = sub(Str,2,len(clip(Str))-2)
    else
       loc:strRet = Str
    end

    loc:strRet=SELF.StrReplace(loc:strRet,'""','"')
    loc:strRet=SELF.StrReplace(loc:strRet,'@`0',CHR(10))
    loc:strRet=SELF.StrReplace(loc:strRet,'@`3',CHR(13))

    return(loc:strRet)
