{*******************************************************}
{                                                       }
{       Tocsg.Export                                    }
{                                                       }
{       Copyright (C) 2023 kku                          }
{                                                       }
{*******************************************************}

unit Tocsg.Export;

interface

uses
  System.SysUtils, Winapi.Windows, System.Classes, Winapi.ActiveX, VirtualTrees;

const
  xlCsv = 6;
  xlXls = 43;
  xlHtml = 44;
  xlNxl = -2003;
  xlCell = -4143;
  xlText = -4158;
  xlLeft = -4131;
  xlRight = -4152;
  xlCenter = -4108;
  xlQualityStandard = $00000000;
  xlQualityMinimum = $00000001;
  xlTypePDF = $00000000;
  xlTypeXPS = $00000001;

type
  TFnExportExpert = reference to procedure(vExcel: Variant);

  XlFixedFormatType = TOleEnum;
  XlFixedFormatQuality = TOleEnum;

  TExportTargetKind = (etkAll, etkSelected, etkChecked);

function ExportCSV_VT(vt: TVirtualStringTree; sExportPath: String;
    aEncoding: TEncoding; aExpTgKind: TExportTargetKind = etkAll; pRootNode: PVirtualNode = nil): Boolean;
function ExportCSV_VT_Div(vt: TVirtualStringTree; sExportPath: String;
    aEncoding: TEncoding; nDivCount: Integer; aExpTgKind: TExportTargetKind = etkAll; bHitsCount: Boolean = false): Boolean;
function ExportXLS_VT(vt: TVirtualStringTree; const sSheetCaption, sExportPath: String;
    FnExportExpert: TFnExportExpert = nil; aExpTgKind: TExportTargetKind = etkAll): Boolean;
function ExportXLS_VT_Div(vt: TVirtualStringTree; const sSheetCaption, sExportPath: String; nDivCount: Integer;
    FnExportExpert: TFnExportExpert = nil; aExpTgKind: TExportTargetKind = etkAll): Boolean;
function ExportHanCell_VT(vt: TVirtualStringTree; const sSheetCaption, sExportPath: String;
    aExpTgKind: TExportTargetKind = etkAll): Boolean;

function ExportXlsFromCsv(const sCsvPath, sOutPath: String; aCsvEncoding: TEncoding): Boolean;

implementation

uses
  System.Win.ComObj, System.Variants, Vcl.Graphics, Tocsg.Safe, Tocsg.Exception,
  Tocsg.VTUtil, VirtualTrees.Types, Tocsg.Strings, Tocsg.Path;

function ExportCSV_VT(vt: TVirtualStringTree; sExportPath: String;
    aEncoding: TEncoding; aExpTgKind: TExportTargetKind = etkAll; pRootNode: PVirtualNode = nil): Boolean;
var
  pNode: PVirtualNode;
  StrList: TStringList;
  sData: String;
  i: Integer;
begin
  Result := false;

  Guard(StrList, TStringList.Create);

  sData := '';
  for i := 0 to vt.Header.Columns.Count - 1 do
  begin
    if coVisible in vt.Header.Columns[i].Options then
      SumString(sData, '"' + vt.Header.Columns[i].Text + '"', ',');
  end;
  StrList.Add(sData);

  case aExpTgKind of
    etkAll      : pNode := vt.GetFirst;
    etkSelected : pNode := vt.GetFirstSelected;
    etkChecked  : pNode := vt.GetFirstChecked;
    else          pNOde := nil;
  end;

  while pNode <> nil do
  begin
    sData := '';

    if (pRootNode = nil) or (pRootNode = pNode.Parent) then
    begin
      for i := 0 to vt.Header.Columns.Count - 1 do
        if coVisible in vt.Header.Columns[i].Options then
          SumString(sData, '"' + StringReplace(vt.Text[pNode, i], '"', '', [rfReplaceAll]) + '"', ',');

      if sData <> '' then
        StrList.Add(sData);
    end;

    case aExpTgKind of
      etkAll      : pNode := vt.GetNext(pNode);
      etkSelected : pNode := vt.GetNextSelected(pNode);
      etkChecked  : pNode := vt.GetNextChecked(pNode);
    end;
  end;

  if GetFileExt(sExportPath).ToUpper <> 'CSV' then
    sExportPath := sExportPath + '.csv';

  StrList.SaveToFile(sExportPath, aEncoding);

  Result := true;
end;

function ExportCSV_VT_Div(vt: TVirtualStringTree; sExportPath: String;
    aEncoding: TEncoding; nDivCount: Integer; aExpTgKind: TExportTargetKind = etkAll; bHitsCount: Boolean = false): Boolean;
var
  pNode: PVirtualNode;
  StrList: TStringList;
  sExt,
  sExcludeExtPath,
  sSavePath,
  sData: String;
  i, nFileNum,
  nAddedCnt: Integer;

  procedure InitData;
  var
    c: Integer;
  begin
    nAddedCnt := 0;
    StrList.Clear;

    sData := '';
    for c := 0 to vt.Header.Columns.Count - 1 do
    begin
      if coVisible in vt.Header.Columns[c].Options then
        SumString(sData, vt.Header.Columns[c].Text, ',');
    end;
    StrList.Add(sData);
  end;

  procedure SaveData;
  begin
    Inc(nFileNum);
    sSavePath := Format('%s - %.3d%s', [sExcludeExtPath, nFileNum, sExt]);
    StrList.SaveToFile(sSavePath, aEncoding);
    InitData;
  end;

begin
  Result := false;

  if GetFileExt(sExportPath).ToUpper <> 'CSV' then
    sExportPath := sExportPath + '.csv';

  sExt := ExtractFileExt(sExportPath);
  sExcludeExtPath := CutFileExt(sExportPath);
  nFileNum := 0;

  case aExpTgKind of
    etkAll      : pNode := vt.GetFirst;
    etkSelected : pNode := vt.GetFirstSelected;
    etkChecked  : pNode := vt.GetFirstChecked;
    else          pNode := nil;
  end;

  Guard(StrList, TStringList.Create);
  InitData;

  while pNode <> nil do
  begin
    sData := '';

    for i := 0 to vt.Header.Columns.Count - 1 do
      if coVisible in vt.Header.Columns[i].Options then
        SumString(sData, '"' + StringReplace(vt.Text[pNode, i], '"', '', [rfReplaceAll]) + '"', ',');

    if sData <> '' then
    begin
      StrList.Add(sData);
      Inc(nAddedCnt);
      if nAddedCnt >= nDivCount then
        SaveData;
    end;

    case aExpTgKind of
      etkAll      : pNode := vt.GetNext(pNode);
      etkSelected : pNode := vt.GetNextSelected(pNode);
      etkChecked  : pNode := vt.GetNextChecked(pNode);
    end;
  end;

  if nAddedCnt > 0 then
    SaveData;
  Result := true;
end;

function ExportXLS_VT(vt: TVirtualStringTree; const sSheetCaption, sExportPath: String; FnExportExpert: TFnExportExpert = nil; aExpTgKind: TExportTargetKind = etkAll): Boolean;
var
  vExcel, vWorkBook: Variant;
  vArrData: OleVariant;
  i, nRow, nCol: Integer;
  pNode: PVirtualNode;
  dwRecordCnt,
  dwColumnCnt: DWORD;
  ClassID: TCLSID;
begin
  Result := false;

  if not Succeeded(CLSIDFromProgID(PWideChar(WideString('Excel.Application')), ClassID)) then
    exit;

  try
    vExcel := CreateOleObject('Excel.Application');
  except
    on E: Exception do
    begin
      ETgException.TraceException(E, 'ExportXLS_VT() .. fail CreateOleObject(Excel.Application) ..');
      exit;
    end;
  end;

  try
    try
      vWorkBook := vExcel.WorkBooks.Add;

      if Assigned(FnExportExpert) then
        FnExportExpert(vExcel);

      dwRecordCnt := 0;
      case aExpTgKind of
        etkAll      : dwRecordCnt := VT_CountTotalNode(vt);
        etkSelected : dwRecordCnt := vt.SelectedCount;
        etkChecked  : dwRecordCnt := VT_CountVisibleCheckedNode(vt);
      end;

    // 엑셀 내보내기는 60000 미만으로 해야한다
      if dwRecordCnt > 60000 then
        exit;

      dwColumnCnt := 0;
      for nCol := 0 to vt.Header.Columns.Count - 1 do
        if coVisible in vt.Header.Columns[nCol].Options then
          Inc(dwColumnCnt);

      nRow := 0;
      vArrData := VarArrayCreate([0, dwRecordCnt, 0, dwColumnCnt - 1], varVariant);
      i := 0;
      for nCol := 0 to vt.Header.Columns.Count - 1 do
        if coVisible in vt.Header.Columns[nCol].Options then
        begin
          vArrData[nRow, i] := vt.Header.Columns[nCol].Text;
          Inc(i);
        end;

      case aExpTgKind of
        etkAll      : pNode := vt.GetFirst;
        etkSelected : pNode := vt.GetFirstSelected;
        etkChecked  : pNode := vt.GetFirstChecked;
        else          pNode := nil;
      end;

      while pNode <> nil do
      begin
        Inc(nRow);
        i := 0;
        for nCol := 0 to vt.Header.Columns.Count - 1 do
          if coVisible in vt.Header.Columns[nCol].Options then
          begin
            vArrData[nRow, i] := vt.Text[pNode, nCol];
            Inc(i);
          end;

        case aExpTgKind of
          etkAll      : pNode := vt.GetNext(pNode);
          etkSelected : pNode := vt.GetNextSelected(pNode);
          etkChecked  : pNode := vt.GetNextChecked(pNode);
        end;
      end;

      vWorkBook := vExcel.WorkSheets.Add;
      vExcel.Sheets[1].Name := DeleteChars(':\/?*[]', sSheetCaption);

      vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[1, dwColumnCnt]].Select;
      vExcel.Selection.Interior.ColorIndex := 15;
      vExcel.Selection.Interior.Pattern := 1;
      vExcel.Selection.Borders.LineStyle := 1;
      vExcel.Selection.Font.Name := 'Tahoma';//'굴림';
      vExcel.Selection.Font.Size := 10;
      vExcel.Selection.Font.Bold := True;
      vExcel.Selection.Font.Color := clBlack;
      // 가로 타이틀 정렬
      vExcel.Selection.VerticalAlignment := 2;
      vExcel.Selection.HorizontalAlignment := 3;

      vExcel.Range[vExcel.Cells[2, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Select;
      vExcel.Selection.Font.Size := 10;
      vExcel.Selection.Font.Name := 'Tahoma';//'굴림';
      vExcel.Selection.VerticalAlignment := 1;
      vExcel.Selection.HorizontalAlignment := 1;

      vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].NumberFormatLocal := WideString('@'); // 문자열 강제 지정
      vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Value := vArrData;

      vExcel.Range[vExcel.Cells[2, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Select;
      vExcel.Selection.VerticalAlignment := 2;
      vExcel.Selection.HorizontalAlignment := 2;

      vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Select;
      vExcel.Selection.Columns.AutoFit;
    finally
      vExcel.DisplayAlerts := False;
      vExcel.Visible := False;
      vWorkBook.Saveas(sExportPath);
//      vWorkBook.ExportAsFixedFormat(xlTypePDF, sExportPath);
      vExcel.Quit;
      vExcel := Unassigned;
    end;
  except
    on E: Exception do
    begin
      ETgException.TraceException(E, 'ExportXLS_VT() ..');
      exit;
    end;
  end;

  Result := true;
end;

function ExportXLS_VT_Div(vt: TVirtualStringTree; const sSheetCaption, sExportPath: String; nDivCount: Integer; FnExportExpert: TFnExportExpert = nil; aExpTgKind: TExportTargetKind = etkAll): Boolean;
var
  vExcel, vWorkBook: Variant;
  vArrData: OleVariant;
  i, nRow, nCol, nFileNum: Integer;
  pNode: PVirtualNode;
  dwRecordCnt,
  dwColumnCnt: DWORD;
  sExt,
  sSavePath,
  sExcludeExtPath: String;

  function InitData: Boolean;
  var
    c: Integer;
  begin
    Result := false;
    try
      vExcel := CreateOleObject('Excel.Application');
    except
      on E: Exception do
      begin
        ETgException.TraceException(E, 'Fail .. ExportXLS_VT_Div() .. fail CreateOleObject(Excel.Application) ..');
        exit;
      end;
    end;

    try
      vWorkBook := vExcel.WorkBooks.Add;

      if Assigned(FnExportExpert) then
        FnExportExpert(vExcel);

      case aExpTgKind of
        etkAll      : dwRecordCnt := VT_CountTotalNode(vt);
        etkSelected : dwRecordCnt := vt.SelectedCount;
        etkChecked  : dwRecordCnt := VT_CountVisibleCheckedNode(vt);
      end;

      dwColumnCnt := 0;
      for c := 0 to vt.Header.Columns.Count - 1 do
        if coVisible in vt.Header.Columns[c].Options then
          Inc(dwColumnCnt);

      nRow := 0;
      vArrData := VarArrayCreate([0, dwRecordCnt, 0, dwColumnCnt - 1], varVariant);
      i := 0;
      for c := 0 to vt.Header.Columns.Count - 1 do
        if coVisible in vt.Header.Columns[c].Options then
        begin
          vArrData[nRow, i] := vt.Header.Columns[c].Text;
          Inc(i);
        end;
    except
      on E: Exception do
      begin
        ETgException.TraceException(E, 'Fail .. ExportXLS_VT_Div() .. InitData()');
        exit;
      end;
    end;
    Result := true;
  end;

  function SaveData: Boolean;
  begin
    vWorkBook := vExcel.WorkSheets.Add;
    vExcel.Sheets[1].Name := DeleteChars(':\/?*[]', sSheetCaption);

    vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[1, dwColumnCnt]].Select;
    vExcel.Selection.Interior.ColorIndex := 15;
    vExcel.Selection.Interior.Pattern := 1;
    vExcel.Selection.Borders.LineStyle := 1;
    vExcel.Selection.Font.Name := 'Tahoma';//'굴림';
    vExcel.Selection.Font.Size := 10;
    vExcel.Selection.Font.Bold := True;
    vExcel.Selection.Font.Color := clBlack;
    // 가로 타이틀 정렬
    vExcel.Selection.VerticalAlignment := 2;
    vExcel.Selection.HorizontalAlignment := 3;

    vExcel.Range[vExcel.Cells[2, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Select;
    vExcel.Selection.Font.Size := 10;
    vExcel.Selection.Font.Name := 'Tahoma';//'굴림';
    vExcel.Selection.VerticalAlignment := 1;
    vExcel.Selection.HorizontalAlignment := 1;

    vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].NumberFormatLocal := WideString('@'); // 문자열 강제 지정
    vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Value := vArrData;

    vExcel.Range[vExcel.Cells[2, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Select;
    vExcel.Selection.VerticalAlignment := 2;
    vExcel.Selection.HorizontalAlignment := 2;

    vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Select;
    vExcel.Selection.Columns.AutoFit;


    Inc(nFileNum);
    sSavePath := Format('%s - %.3d.%s', [sExcludeExtPath, nFileNum, sExt]);


    vExcel.DisplayAlerts := False;
    vExcel.Visible := False;
    vWorkBook.Saveas(sSavePath);
    vExcel.Quit;
    vExcel := Unassigned;

    Result := InitData;
  end;

begin
  Result := false;

  sExt := ExtractFileExt(sExportPath);
  sExcludeExtPath := CutFileExt(sExportPath);

  if not InitData then
    exit;

  try
    pNode := nil;
    try
      case aExpTgKind of
        etkAll      : pNode := vt.GetFirst;
        etkSelected : pNode := vt.GetFirstSelected;
        etkChecked  : pNode := vt.GetFirstChecked;
      end;

      while pNode <> nil do
      begin
        Inc(nRow);
        i := 0;
        for nCol := 0 to vt.Header.Columns.Count - 1 do
          if coVisible in vt.Header.Columns[nCol].Options then
          begin
            vArrData[nRow, i] := vt.Text[pNode, nCol];
            Inc(i);
          end;

        if nRow >= nDivCount then
          if not SaveData then
            exit;

        case aExpTgKind of
          etkAll      : pNode := vt.GetNext(pNode);
          etkSelected : pNode := vt.GetNextSelected(pNode);
          etkChecked  : pNode := vt.GetNextChecked(pNode);
        end;
      end;

      if nRow > 1 then
        if not SaveData then
          exit;
    finally
      vExcel.Quit;
      vExcel := Unassigned;
    end;
  except
    on E: Exception do
    begin
      ETgException.TraceException(E, 'ExportXLS_VT_Div() ..');
      exit;
    end;
  end;

  Result := true;
end;

function ExportHanCell_VT(vt: TVirtualStringTree; const sSheetCaption, sExportPath: String; aExpTgKind: TExportTargetKind = etkAll): Boolean;
var
  vHancell: OleVariant;
  vWorkBook,
  vWorkSheet: Variant;
  i, nRow, nCol: Integer;
  pNode: PVirtualNode;
begin
  Result := false;
  try
    vHancell := CreateOleObject('HCell.Application'); // 한컴 오피스 네오 체험판의 한셀만 설치되어 있다면 작동하지 않음
  except
    on E: Exception do
    begin
      ETgException.TraceException(E, 'ExportHanCell_VT() .. fail CreateOleObject(HCell.Application) ..');
      exit;
    end;
  end;

  try
    vHancell.visible := False;
    vWorkBook := vHancell.Workbooks.Add;

    try
      vWorkSheet := vWorkBook.WorkSheets.Add;
    except
      vWorkSheet := vWorkBook.Sheets.Add;
    end;

    vWorkSheet.Name := sSheetCaption;

    pNode := nil;
    try
      i := 0;
      for nCol := 0 to vt.Header.Columns.Count - 1 do
        if coVisible in vt.Header.Columns[nCol].Options then
        begin
          vWorkSheet.Cells[1, 1 + i].Value := vt.Header.Columns[nCol].Text;
          Inc(i);
        end;

      case aExpTgKind of
        etkAll      : pNode := vt.GetFirst;
        etkSelected : pNode := vt.GetFirstSelected;
        etkChecked  : pNode := vt.GetFirstChecked;
      end;

      nRow := 0;
      while pNode <> nil do
      begin
        i := 0;
        for nCol := 0 to vt.Header.Columns.Count - 1 do
          if coVisible in vt.Header.Columns[nCol].Options then
          begin
            vWorkSheet.Cells[2 + nRow, 1 + i].Value := vt.Text[pNode, nCol];
            Inc(i);
          end;

        case aExpTgKind of
          etkAll      : pNode := vt.GetNext(pNode);
          etkSelected : pNode := vt.GetNextSelected(pNode);
          etkChecked  : pNode := vt.GetNextChecked(pNode);
        end;
        Inc(nRow);
      end;

      vWorkBook.SaveAs(OleVariant(sExportPath));
    finally
      vHancell.workbooks.close;
      vHancell.Quit;
      vHancell := Unassigned;
    end;
  except
    on E: Exception do
    begin
      ETgException.TraceException(E, 'ExportXLS_VT() ..');
      exit;
    end;
  end;

  Result := true;
end;

function ExportXlsFromCsv(const sCsvPath, sOutPath: String; aCsvEncoding: TEncoding): Boolean;
var
  CsvStrList,
  ColumnList: TStringList;
  vExcel, vWorkBook: Variant;
  vArrData: OleVariant;
  i, nRow, nCol: Integer;
  dwRecordCnt,
  dwColumnCnt: DWORD;
begin
  Result := false;

  if not FileExists(sCsvPath) then
    exit;

  Guard(CsvStrList, TStringList.Create);
  CsvStrList.LoadFromFile(sCsvPath, aCsvEncoding);

  if CsvStrList.Count = 0 then
    exit;

  Guard(ColumnList, TStringList.Create);

  try
    vExcel := CreateOleObject('Excel.Application');
  except
    on E: Exception do
    begin
      ETgException.TraceException(E, 'ExportXLS_fromCVS() .. fail CreateOleObject(Excel.Application) ..');
      exit;
    end;
  end;

  try
    try
      vWorkBook := vExcel.WorkBooks.Add;

      dwRecordCnt := CsvStrList.Count;

      SplitString2(CsvStrList[CsvStrList.Count-1], ',', ColumnList);
      dwColumnCnt := ColumnList.Count;

      vArrData := VarArrayCreate([0, dwRecordCnt, 0, dwColumnCnt-1], varVariant);
      for nRow := 0 to dwRecordCnt - 1 do
      begin
        i := 0;
        SplitString2(CsvStrList[nRow], ',', ColumnList);
        for nCol := 0 to ColumnList.Count - 1 do
        begin
          vArrData[nRow, i] := ColumnList[nCol];
          Inc(i);
        end;
      end;

      vWorkBook := vExcel.WorkSheets.Add;
      vExcel.Sheets[1].Name := 'Untitle';

      vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].NumberFormatLocal := WideString('@'); // 문자열 강제 지정
      vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Value := vArrData;

      vExcel.Range[vExcel.Cells[2, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Select;
      vExcel.Selection.VerticalAlignment := 2;
      vExcel.Selection.HorizontalAlignment := 2;

      vExcel.Range[vExcel.Cells[1, 1], vExcel.Cells[dwRecordCnt + 1, dwColumnCnt]].Select;
      vExcel.Selection.Columns.AutoFit;
    finally
      vExcel.DisplayAlerts := False;
      vExcel.Visible := False;
      vWorkBook.Saveas(sOutPath);
      vExcel.Quit;
      vExcel := Unassigned;
    end;
  except
    on E: Exception do
    begin
      ETgException.TraceException(E, 'ExportXLS_VT() ..');
      exit;
    end;
  end;

  Result := true;
end;

end.
