기술 문서

HotXLS Delphi Component: Delphi에서 ODS open, save, and round-trip

수년간 .xlsx만 내보내던 Delphi 보고 백엔드에 새로운 요구사항이 생겼다고 가정해 봅시다. 공공 부문 고객의 조달 규정이 OpenDocument Spreadsheet 출력을 의무화하고, 그 계정을 담당하는 분석가들은 LibreOffice에서 저장한 .ods 파일로 수정 사항을 돌려보냅니다. 이제 같은 코드가 ODS를 쓰고 읽어야 합니다. Delphi와 C++Builder를 위한 losLab의 네이티브 Object Pascal 스프레드시트 라이브러리인 HotXLS는 Excel도 LibreOffice도 어디에도 설치되지 않은 상태에서 두 방향 모두를 처리합니다. 두 방향은 가깝지만 동일하지는 않습니다. HotXLS 2.88.229부터 가져오기 경로는 수식을 캐시된 결과와 나란히 살아 있는 수식으로 돌려주고, 최신 릴리스는 스타일, 숫자 서식, 병합된 범위, 코멘트, 이미지, 차트, 조건부 서식도 읽어들입니다. 여전히 살아남지 못하는 것은 완전한 OpenFormula 시맨틱스와 소수의 패키지 수준 세부 사항입니다. 따라서 완벽한 거울이라고 가정하는 팀은 여전히 고객의 수정과 다음 보고서 사이 어딘가에서 경계 사례를 만나게 되며, 보통 그것을 가리킬 오류도 나타나지 않습니다

ODS 지원은 XLS 파사드가 아니라 XLSX 파사드에 있습니다

HotXLS는 하나의 패키지 안에 두 개의 독립된 클래스 계층을 제공합니다. 바이너리 BIFF8 .xls 파일을 위한 lxHandle 유닛의 TXLSWorkbook과, OOXML .xlsx 패키지를 위한 lxHandleX 유닛의 TXLSXWorkbook입니다. OpenODS, SaveAsODS, GetODSSheetNames를 비롯한 모든 OpenDocument 진입점은 TXLSXWorkbook에 달려 있습니다. 이 배치는 임의적인 것이 아닙니다. OASIS ODF 1.3에 명시된 대로, ODS 패키지는 mimetype 멤버, 매니페스트, content.xml 본문을 담은 zip 아카이브이며, 이는 OOXML zip과 구조적으로 사촌 관계에 있습니다. 반면 BIFF8은 1990년대의 바이너리 레코드 스트림으로 이와는 공통점이 전혀 없습니다

이 배치에는 실용적인 함의가 있습니다. 레거시 .xls 워크북은 한 번의 호출로 .ods가 될 수 없습니다. 먼저 lxXlsxExport 유닛의 SaveXLSWorkbookAsXLSX로 BIFF 콘텐츠를 XLSX 모델로 다리 놓고, 결과를 TXLSXWorkbook으로 다시 연 다음, 거기서부터 내보내야 합니다. 이 브리지는 무손실이 아니며, 그 위에 쌓아 올리기 전에 빈틈을 알아둘 가치가 있습니다. 값, 수식, 숫자 서식, 글꼴, 채우기, 테두리, 병합된 범위, 열 너비, 행 높이는 복사됩니다. 코멘트, 차트, 조건부 서식은 버려집니다. 서식이 두꺼운 .xls 원본은 떠날 때보다 더 밋밋한 모습으로 ODS에 도착하며, 이는 ODS 라이터의 속성이 아니라 브리지의 속성입니다

가져오기 쪽의 감지는 자동입니다. 평범한 Open 메서드는 mimetype 멤버로 ODS 패키지를 인식하며, 그 멤버가 없으면 최상위 content.xml 확인으로 대체하므로, "사용자가 무엇을 업로드했든 연다"는 범용 코드 경로는 자체적인 확장자 스니핑이 필요 없습니다. 연 뒤에는 SourceFormat 속성이 어느 분기가 작동했는지 알려줍니다

Delphi에서 HotXLS 클래스 배치 다이어그램: 모든 ODS 진입점이 TXLSXWorkbook 위에 살고, SaveXLSWorkbookAsXLSX 브리지가 BIFF8 .xls 콘텐츠를 건너 보냄
모든 OpenDocument 진입점은 TXLSXWorkbook에 매달리고, 레거시 .xls는 손실 있는 BIFF→XLSX 브리지를 통해서만 ODS에 도달합니다

TODSExportOptions로 ODS 내보내기

내보내기 호출 자체는 한 줄이지만, 그 주위의 옵션 객체는 나중에 리뷰어가 물어볼 결정 사항들을 담고 있습니다:

var
  Book: TXLSXWorkbook;
  Opts: TODSExportOptions;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('quarterly-report.xlsx');
    Opts := TODSExportOptions.Create;        // 호출자가 소유하고 해제함
    try
      Opts.Generator := 'ReportService 4.2'; // meta:generator를 덮어씀
      Opts.IncludeCharts := True;
      Opts.IncludeImages := True;
      Book.SaveAsODS('quarterly-report.ods', Opts);
    finally
      Opts.Free;
    end;
  finally
    Book.Free;
  end;
end;

옵션 객체는 호출자가 소유합니다. HotXLS는 이를 해제하지 않으며, 이것이 안쪽의 try..finally가 선택 사항이 아니라 반드시 있어야 하는 이유입니다. 단순히 출력에 라벨을 붙이는 데 그치지 않고 실제로 출력을 바꾸는 두 속성은 더 자세히 살펴볼 가치가 있습니다. IncludeCharts := False를 설정하면 차트를 숨기는 것 이상의 일이 일어납니다. 차트 하위 문서와 그 매니페스트 항목을 패키지에서 완전히 제거하며, 소비자가 이를 만나면 걸려 넘어질 데이터 파이프라인일 때 정확히 원하는 동작입니다. Generator는 그렇지 않으면 HotXLS/<version>로 읽히는 ODF meta:generator 문자열을 덮어씁니다. 다운스트림 도구가 파일 생성자를 지문 삼아 지원 경로를 결정한다면 이를 재정의하십시오. 이 중 어느 것도 해당하지 않는다면 옵션 객체를 아예 생략하십시오. SaveAs(FileName, xlsxOpenDocumentSpreadsheet)를 호출하는 것은 기본값을 사용하는 SaveAsODS와 동일하며, 양쪽 모두의 스트림 오버로드를 사용하면 임시 파일 없이 패키지를 곧바로 HTTP 응답에 기록할 수 있습니다

가져오기 경로가 읽는 것과 여전히 건너뛰는 것

누군가에게 왕복 충실도를 약속하기 전에 이 부분을 주의 깊게 읽으십시오. HotXLS의 ODS 가져오기는 스칼라 값과 캐시된 수식 결과만 담아 두던 가벼운 경로로 시작했지만, 지금은 그 한계를 크게 넘어섰습니다. 최신 릴리스는 content.xml과 함께 styles.xml, settings.xml을 읽습니다. 스칼라 값과 캐시된 수식 결과, 그리드로 펼쳐 넣은 반복 행과 열, 병합된 범위, 리치 텍스트, 하이퍼링크, 코멘트, 내장 이미지와 차트, 상속·기본 셀 스타일, 숫자·날짜 서식, 행 높이와 열 너비, 명명된 범위, 틀 고정(freeze panes), AutoFilter 범위, 데이터 유효성 검사, 조건부 서식까지입니다. OpenODS가 여전히 변환하지 않는 것은 완전한 OpenFormula 시맨틱스, ODS 보호 키, 코멘트 기하 정보, 고급 코멘트 서식, 스프레드시트 콘텐츠 밖의 패키지 기능입니다

가장 물릴 가능성이 높은 부분은 수식입니다. ODF 셀은 두 가지를 나란히 저장합니다. ODF 1.3 Part 4에 정의된 OpenFormula 방언으로 작성된 수식 표현식과, 생성 애플리케이션이 마지막으로 계산한 값입니다. 초기 릴리스는 캐시된 값만 읽었습니다. HotXLS 2.88.229부터 OpenODS는 표현식도 가져옵니다. of:= 또는 oooc:= 접두사로 쓰인 셀은 캐시된 값을 유지하면서 정규화된 Excel 수식을 받고, [.A1:.B1] 같은 대괄호 참조와 세미콜론 인수 구분자가 Excel 문법으로 변환됩니다. 2.384.32부터는 인라인 배열 행({1;2|3;4}가 {1,2;3,4}로)과 ~ 참조 합집합 연산자도 변환하며, 2.384.55부터는 그런 합집합이 괄호를 한 겹 더 입지 않고 읽혀 돌아옵니다. OpenFormula를 Excel 문법으로 번역하는 것은 여전히 함수 어휘, 참조 문법, 오류 모델을 둘러싼 진짜 경계 사례가 있는 방언 변환 문제입니다. 그래서 캐시된 값이 함께 오는 것입니다. 여러분이 가져오는 숫자는 발신자가 마지막으로 본 숫자와 정확히 같습니다

설계 시 고려해야 할 실패 양상도 그에 맞게 좁아졌습니다. 2.88.229 이전에는 LibreOffice가 마지막으로 저장했을 때 합계가 정확했던 스프레드시트가 정확한 숫자로 가져와졌지만 그 숫자들은 이제 상수였습니다. 입력 셀을 수정하고 재계산해도 아무것도 움직이지 않았죠. 최신 릴리스에서는 수식이 살아 있는 채로 돌아오므로, 남은 위험은 번역기가 모델링하지 못하는 구성입니다. 이 경우 수식이 생성 애플리케이션과 다르게 재계산될 수 있는데, 캐시된 값은 여전히 발신자의 결과를 보여 줍니다. 파이프라인이 의존하는 수식에 대해 재계산 결과를 가져온 캐시 값과 비교해 보고, 어긋나는 수식은 XLSX 파사드에서 등호 접두사 없이 표현식을 받는 Cell.Formula를 통해 여러분의 비즈니스 규칙으로 다시 세우십시오

남은 왕복 빈틈을 고려한 설계

내보내기는 값, 수식, 스타일, 그리고 요청하면 차트와 이미지까지 포함해 메모리에 있는 워크북 모델 전체로부터 렌더링합니다. 수식 내보내기는 릴리스마다 다듬어져 왔습니다. 2.384.55부터 인라인 배열, 참조 합집합, 절대 참조가 OpenFormula에서 의미를 유지합니다. 2.384.56부터 HotXLS는 수식 접두사의 네임스페이스를 선언해서 LibreOffice가 모든 수식 셀에 #VALUE!를 보여주는 대신 수식을 실제로 평가합니다. 그리고 2.384.65부터 열 전체·행 전체 참조가 OpenFormula 형태인 [.A:.A]로 기록되어 Excel이 더 이상 #NAME?으로 열지 않습니다. 조건부 서식도 뒤따랐습니다. 2.384.66부터 수식 기반 규칙이 기준 셀과 함께 LibreOffice가 인식하는 formula-is(...)로 기록되고, 2.384.69부터 값·수식 규칙이 Excel이 읽는 유일한 형태인 표준 style:map 조건으로도 기록됩니다. 가져오기 쪽은 두 형태 모두 읽습니다. 따라서 최신 릴리스에서 .xlsx에서 .ods로, 다시 .xlsx로 완전히 한 바퀴 도는 사이클은 값, 수식, 스타일, 조건부 서식을 유지합니다. 여전히 잃을 수 있는 것은 이전 절에 나열한 빈틈 안에 있습니다

Delphi에서 HotXLS ODS 왕복 다이어그램: 인메모리 통합문서 모델에서의 내보내기와, HotXLS 2.88.229 이전까지 수식을 상수로 남겨둔 값 전용 임포트
초기 릴리스는 값과 캐시된 결과만 가져와서 .xlsx→.ods→.xlsx 왕복 한 바퀴가 스타일과 살아 있는 수식을 버렸습니다. 최신 릴리스는 둘 다 읽어들입니다
Book := TXLSXWorkbook.Create;
try
  Book.Open('vendor-revision.ods');          // 형식은 자동 감지됨
  if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
  begin
    // HotXLS 2.88.229부터 수식은 캐시된 결과와 나란히 살아 있는
    // 채로 돌아옵니다. 저장하기 전에 다운스트림 파이프라인이
    // 번역을 신뢰할 수 없는 수식은 다시 세우십시오.
    Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
    Book.SaveAs('vendor-revision.xlsx');
  end;
finally
  Book.Free;
end;

여기서 나오는 아키텍처 패턴은 이렇습니다. 들어오는 .ods 파일을 제자리에서 편집할 문서가 아니라 데이터 피드로 취급하십시오. 정본 워크북은 .xlsx로 유지하고, 고객의 수정본에서 값만 읽어내며, 정본 사본으로부터 필요할 때마다 새로운 ODS를 내보내십시오. 검증은 양쪽 진영 모두에 필요합니다. 내보낸 파일을 참조 ODF 소비자인 LibreOffice Calc에서도, 오랫동안 ODS를 읽어 왔지만 차트와 스타일 지원의 가장자리에서는 LibreOffice와 의견이 갈리는 Excel에서도 열어 보십시오. 시트 수, 몇 개의 핵심 셀, 차트 유무 확인이면 내보내기 프로필마다 충분한 스모크 체크가 됩니다

가져오기를 결정하기 전에 ODS 파일 선별하기

엔드포인트가 업로드를 받아들일 때, 시트 이름을 나열하는 것은 완전한 파싱보다 훨씬 저렴하며 구조적인 이상을 일찍 잡아냅니다:

Delphi에서 HotXLS 업로드 트리아지 게이트 다이어그램: GetODSSheetNames가 전체 임포트가 실행되기 전에 읽을 수 없는 ODS 패키지와 없는 시트를 거부
GetODSSheetNames 프로브는 전체 파싱보다 훨씬 싸고, 오류가 아직 파일 이름을 말해 줄 수 있는 동안 이름 바뀐 시트 실패를 잡습니다
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
  if Book.GetODSSheetNames('incoming.ods', Names) <= 0 then
    raise Exception.Create('not a readable ODS package');
  if Names.IndexOf('Data') < 0 then
    raise Exception.Create('revision is missing the Data sheet');
finally
  Book.Free;
  Names.Free;
end;

반환값 관례가 사람들을 걸려 넘어지게 합니다. HotXLS 호출은 일반적으로 성공 시 양수 개수 또는 1을, 실패 시 -1을 반환하며 실패할 때 목록을 비우므로, 특정한 양수 하나와 비교하는 대신 <= 0으로 검사하십시오. GetODSSheetNames는 워크북 인스턴스를 초기화하지도 채우지도 않으므로, 단 하나의 프로브 객체로 들어오는 파일 디렉터리 전체를 검사할 수 있습니다. 이런 구조적 검사는 가장 흔한 실전 실패, 즉 분석가가 수정본을 돌려보내기 전에 시트 이름을 바꾸거나 삭제하는 경우를 세 계층 아래에서 nil 참조로 드러나는 대신, 오류 메시지가 여전히 파일 이름과 빠진 시트를 지목할 수 있는 관문에서 잡아냅니다

이를 중심으로 더 폭넓은 변환 파이프라인을 구축하고 있다면, 워크북 감사 및 변환 워크벤치 패턴은 목표 형식을 선택하기 전에 파일의 기능을 목록화하는 방법을 보여주며, 대규모 워크북 성능 가이드는 배치 내보내기를 합리적인 메모리 범위 안에 유지하는 방법을 다룹니다

HotXLS는 전체 소스 코드를 갖춘 네이티브 Delphi 및 C++Builder 스프레드시트 라이브러리입니다. 전체 기능 목록과 라이선스 세부사항은 HotXLS Delphi Component 제품 페이지에 있습니다