야간 작업이 코드로 청구서 워크북을 만들고 이를 다운스트림 시스템이 가져갈 CSV로 내보낸다고 가정해 봅시다. Excel에서 보면 숫자는 맞아 보입니다. CSV는 텍스트 편집기에서도 깔끔하게 열립니다. 그런데 임포터가 합계 열에서 멈춰 버립니다. 42행의 금액 필드가 계산되어야 할 수치가 아니라 수식 텍스트 그대로인 =SUM(D2:D41)로 읽히기 때문입니다. 아무것도 고장 나지 않았습니다. 이것은 문서화된 동작이며, HotXLS에서 내보내기를 할 때 가장 먼저 이해해야 할 사실입니다. 이 라이터는 셀 모델을 있는 그대로 직렬화하며, 값이 한 번도 계산된 적 없는 수식 셀은 넘겨줄 것이라고는 수식 텍스트뿐입니다
CSV에 숫자 대신 수식이 담기는 이유
HotXLS는 수식 텍스트와 계산된 값을 서로 다른 두 가지로 저장합니다. SaveAsCSV는 설계상 내보내는 과정에서 계산 엔진을 실행하지 않습니다. 내보내기가 워크북을 변형시켜서는 안 되고, 병적인 수식 체인에서 멈춰 버릴 위험을 감수해서도 안 되기 때문입니다. Excel이 직접 저장한 파일은 수식 옆에 캐시된 결과를 함께 지니고 있으므로, 이런 파일을 다시 내보낼 때는 예상한 대로 동작합니다. 함정은 코드가 직접 생성한 워크북, 즉 수식은 작성되었지만 한 번도 평가된 적 없는 워크북에 특히 해당됩니다. 해결책은 시트 간 참조와 사용자 정의 함수를 해석하는 것과 동일한 Calculate 엔진을 사용해, 내보내기 전에 값이 존재하도록 만드는 것입니다:
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
R: Integer;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('invoice-run.xlsx');
Sheet := Book.Sheets[0];
// 수식 결과를 값으로 구체화해 CSV에 '=...' 텍스트가 아닌 숫자가 담기도록 합니다
for R := 2 to 41 do
if Sheet.Cells[R, 4].Formula <> '' then
Sheet.Cells[R, 4].Value := Book.Calculate(Sheet.Cells[R, 4].Formula);
Book.SaveAsCSV('feed.csv', 0, ','); // 0번 시트, 쉼표
Book.SaveAsCSV('feed.tsv', 0, #9); // 같은 시트를 TSV로
finally
Book.Free;
end;
end;
이 루프가 실제로 무엇을 하는지 눈여겨봐야 합니다. 수식 셀을 계산된 값으로 덮어씁니다. 일회성 내보내기 패스라면 정확히 옳은 동작이지만, 이후 워크북을 다시 .xlsx로 저장할 생각이라면 잘못된 동작입니다. 살아 있던 수식을 방금 고정된 숫자로 대체해 버렸기 때문입니다. 사본에서 내보내거나, 내보내기 실행에만 영향을 미치도록 다시 쓰기 범위를 한정하십시오. Calculate 뒤에 있는 엔진은 여기서 더 나아가 사용자 정의 함수 등록까지 지원하며, 이는 HotXLS 수식 엔진과 사용자 정의 함수 문서의 주제입니다
구분자 기반 라이터가 보장하는 것
CSV 경로는 바이트 순서 표시(BOM)가 있는 UTF-8, CRLF 줄바꿈, RFC 4180 인용 규칙을 따르는 결과를 만듭니다. 구분자, 인용부호, 줄바꿈을 포함하는 필드는 감싸이며, 내부에 포함된 인용부호는 두 번 반복됩니다. 날짜는 셀의 화면 표시 서식과 무관하게 yyyy-mm-dd hh:nn:ss로 렌더링됩니다. 기계가 읽는 소비자에게는 옳은 선택이지만, 화면상의 서식이 그대로 이어질 것이라 기대한 사람에게는 놀라울 수 있습니다. 리치 텍스트 셀은 각 런을 이어 붙여 평탄화됩니다
이러한 기본값들은 임포터와의 논쟁 대부분을 시작되기도 전에 정리해 주지만, 그중 둘은 인터페이스 계약에도 명시해 둘 가치가 있습니다. 첫 번째는 BOM입니다. 이는 악센트 문자가 그대로 보존된 채 Excel이 파일을 열 수 있게 해 주지만, 일부 엄격한 파서는 이 세 바이트를 데이터로 취급합니다. 여러분의 파서가 그중 하나라면 전달 과정에서 제거해야 합니다. 두 번째는 TSV입니다. 이는 별개의 기능이 전혀 아니며, 단지 같은 라이터를 구분자로 #9를 넘겨 호출한 것일 뿐이므로 위의 모든 내용이 그대로 적용됩니다. 내보낼 시트는 다중 인수 오버로드에서는 0부터 시작하는 인덱스로 선택되며, 단일 인수인 SaveAsCSV(FileName) 축약형은 활성 시트를 사용합니다
HTML 내보내기는 상호 교환 포맷이 아니라 스냅숏입니다
CSV가 값 외의 모든 것을 버리는 반면, SaveAsHTML은 외관을 유지하려 시도합니다. 시트당 하나의 <table>, colspan과 rowspan으로 표현되는 병합 영역, CSS로 인라인 처리되는 기본 셀 스타일이 그것입니다. 테마 상대적인 색상은 해석되지 않고 건너뛰어지므로, 테마 슬롯에 의존하는 템플릿은 Excel에서 보이는 것보다 밋밋하게 나옵니다. 변환 과정에서 살아남아야 하는 요소에는 명시적인 RGB 색상을 설정하십시오. 옵션 객체가 봉투 전체를 제어합니다:
var
Opts: TXLSXHtmlExportOptions;
begin
Opts := TXLSXHtmlExportOptions.Create;
try
Opts.Title := 'Weekly settlement';
Opts.TableClass := 'report-grid'; // 호스트 페이지 스타일시트를 위한 후크
Opts.WriteDocument := True; // 조각이 아니라 전체 페이지
if Book.SaveAsHTML('settlement.html', 0, Opts) <> 0 then
raise Exception.Create('Sheet index out of range');
finally
Opts.Free;
end;
end;
이 코드 조각에서 눈여겨볼 세부사항이 둘 있습니다. WriteDocument를 False로 뒤집으면 출력은 전체 페이지 대신 단순한 테이블 조각이 되며, 이는 기존 레이아웃에 미리보기를 삽입할 때 원하는 형태입니다. TableClass를 설정하고 테마 적용은 호스트 스타일시트에 맡기십시오. 반환값 관례 또한 대부분의 HotXLS 호출과는 반대입니다. SaveAsHTML은 성공 시 0을, 시트 인덱스가 잘못되었을 때 -1을 반환하므로, 습관적으로 = 1을 확인하는 검사는 성공한 모든 내보내기를 실패로 보고하게 됩니다. 시트 전체가 아니라 특정 영역, 이를테면 이메일로 보내거나 하나의 블록만 임베드해야 할 때는 TXLSXRange.SaveAsHTML이 동일한 렌더링 규칙 아래에서 임의의 직사각형 범위를 내보냅니다
RTF 출력과 그것이 여전히 제자리를 지키는 이유
네 번째 대상은 SaveAsRTF를 통해 호출당 한 시트씩 RTF 1.6 테이블을 기록합니다. 열 너비는 열 너비 문자당 대략 96트윕으로 근사됩니다. 알아두어야 할 구조적 한계는 병합된 셀이 출력에서 걸쳐지지 않는다는 점입니다. 앵커 셀만 내용을 지니며, 덮인 셀들은 빈 칸으로 나타납니다. 이는 레이아웃 비중이 큰 템플릿에서 RTF를 배제하는 이유입니다. 그럼에도 표 형태 결과를 워드 프로세서나 HTML 수용 이전 시대의 레거시 문서 관리 시스템에 던져 넣을 때 가장 저항이 적은 경로로서 여전히 제자리를 지킵니다
왕복 변환: CSV 가져오기는 설계상 파괴적입니다
CSV를 다시 읽어 들이는 데에는 그 나름의 계약이 있습니다. OpenCSV는 워크북 전체를 지우고 Sheet1이라는 이름의 단일 시트로 다시 만듭니다. 이는 정신적으로 병합이 아니라 생성자이므로, 저장되지 않은 내용을 아직 담고 있는 워크북에는 절대 호출해서는 안 됩니다. 구분자로 #0을 넘기면 자동 구분자 감지가 작동합니다. ADetectTypes 플래그는 타입 승격을 제어합니다. 이를 켜면 숫자 문자열은 숫자가 되고, ISO-8601 문자열은 날짜가 되며, true/false는 불리언이 됩니다. 앞자리가 0인 식별자나 우편번호, 제품 코드를 담고 있는 피드라면 이를 꺼야 합니다. 승격은 이들을 조용히 숫자로 망가뜨리기 때문입니다(앞자리 0은 00123이 123이 되는 순간 그냥 사라집니다). 두 파사드 모두 동일한 가져오기 기능을 노출합니다. 이를 위의 내보내기 호출과 짝지으면 파이프라인 어디에도 Excel 설치가 필요 없는 포맷 브리지를 얻게 되며, 이는 HotXLS를 이용한 데이터베이스-Excel 보고서 생성 문서에서 다루는 시나리오입니다
스트림으로 곧바로 내보내기
여기 나온 모든 라이터에는 파일명 버전 바로 옆에 스트림 오버로드가 있습니다. CSV, HTML, RTF, 그리고 워크북 형식 자체 모두 그렇습니다. 서버 코드에서는 이러한 오버로드를 사용해야 합니다. CSV 다운로드를 제공하는 웹 엔드포인트는 TMemoryStream에 기록하고 이를 곧바로 응답 객체로 넘길 수 있으며, 임시 파일도, 정리 작업도, 같은 생성 이름을 우연히 고른 두 요청 사이의 충돌도 없습니다. 내보낸 결과를 블롭 스토리지에 넣거나 발신 메일에 첨부하는 경우도 마찬가지입니다. 파일 시스템은 완전히 그림에서 빠집니다
이 패턴은 라이브러리가 배포되는 방식과 맞물려 배가됩니다. 두 파사드 모두 네이티브 Object Pascal 리더이자 라이터이므로, Excel 설치도, COM 자동화도, 서버에서 요청을 직렬화하는 프로세스당 병목도 없습니다. 각 요청은 자신의 워크북 객체를 소유하고, 앞 절의 계산 다시 쓰기를 실행하며, 이웃 요청들과 병렬로 자신의 내보내기를 스트리밍할 수 있습니다. 주의를 기울여야 할 유일한 자원은 메모리입니다. 워크북 모델은 내보내는 동안 RAM에 상주하므로, 매우 큰 파일을 열어 CSV로 다시 내보내기만 하는 서비스라면 트래픽 급증이 작업 집합을 결정하게 두는 대신 동시 작업 수를 제한하거나 과대한 작업을 큐에 넣어야 합니다
더 작은 스위치도 하나 있습니다. 조각이 다운스트림의 어떤 도구가 인코딩을 스니핑하는 독립 파일로 저장될 때는 HTML 옵션의 IncludeBOM을 설정하십시오. HTML을 HTTP로 직접 서빙할 때는 문자 집합 선언을 응답 헤더에 맡기십시오
바이트가 여전히 잘못 나올 때
CSV 내보내기에 관해 가장 흔한 지원 질문은 다른 옷을 입은, 앞서 나온 문제와 같은 것입니다. Excel이 악센트 문자 대신 깨진 문자를 보여줍니다. 라이터를 탓하고 싶은 본능이 들지만, 라이터는 바로 이 이유 때문에 UTF-8 BOM을 내보내며, 코드를 떠나는 순간 파일은 거의 항상 올바릅니다. 그 지점과 Excel 사이의 무언가가 BOM을 먹어 치운 것입니다. 텍스트 모드의 FTP 전송, 처음 세 바이트를 건너뛰는 스트림 복사, 도중에 재인코딩하는 프록시 등 무엇이든 이 표시를 벗겨내 Excel이 인코딩을 추측하게 만들 수 있으며, 그 추측은 형편없습니다. 이는 내보내기 호출이 아니라 경계 지점에서 진단해야 합니다. 전달된 파일을 헥스 뷰어로 열어 첫 바이트가 여전히 EF BB BF인지 확인하십시오
이것이 네 가지 포맷 모두를 관통하는 흐름입니다. 내보내기 호출 자체는 쉬운 부분이며, HotXLS는 라이터가 마주하는 각 결정마다 방어할 수 있는 선택을 합니다. 실패는 이음매에서 일어납니다. 수식 텍스트가 숫자를 원하는 파서를 만나는 지점, BOM이 이를 보존하지 않는 전송 수단을 만나는 지점, 병합된 셀이 RTF의 평탄한 테이블 모델을 만나는 지점이 그렇습니다. 이들 각각은 내보내기 코드와 그 결과를 소비하는 무언가 사이의 계약에 반드시 명시해야 할 사실입니다. 소비자는 바이트만 보고 의도를 읽어낼 수 없기 때문입니다. 두 워크북 파사드 전체에 걸친 완전한 메서드 목록은 HotXLS Delphi Component 제품 페이지에서 전체 레퍼런스를 확인할 수 있습니다