기술 문서

HotXLS로 메타데이터 전용과 선택적 XLSX 로딩하기

"이 통합 문서가 여전히 TaxRate라는 이름을 정의하는가?"라는 질문은 XLSX 패키지의 작은 부분 하나로 답합니다. 시트 이름들과 그 hidden 또는 very-hidden 상태, 모든 통합 문서 수준의 정의된 이름은 xl/workbook.xml(ECMA-376 Part 1, §18.2)에 살며, 보통 몇 킬로바이트입니다. 셀들은 다른 곳에 삽니다. 시트마다 하나의 xl/worksheets/sheetN.xml 파트와 공유 문자열 테이블이며, 바이트의 거의 전부가 거기 있습니다. 뭔가를 물어보기 전에 완전한 통합 문서를 만들어야 한다고 고집하는 로더는, 첫 몇 킬로바이트가 이미 정리해 준 질문에 답하기 위해 폴더의 모든 파일에 대해 그 전부를 인플레이트하고 파싱합니다

중요한 분리는 통합 문서의 모양을 읽는 것과 그 무게를 읽는 것 사이입니다. 모양 뒤에서 멈출 수 있고, 선택된 시트 하위집합의 무게를 로드하고, 한 시트를 더 나중에 가져올 수 있는 로더는 "통합 문서 열기"를 전부 아니면 전무 단계에서 호출마다 범위를 정하는 것으로 바꿉니다. 함께 오는 함정도 둘 있습니다. 인덱스 기준에 관한 것과 소스 파일이 얼마나 오래 제자리에 있어야 하는지에 관한 것입니다

메타데이터 전용 Open이 읽는 것과 멈추는 지점

Open 전에 TXLSXWorkbook.MetadataOnly를 True로 설정하면 HotXLS는 통합 문서 파트, 스타일, 테마, 정의된 이름, 시트 이름과 가시성 상태, 연결, 외부 링크, docProps/core.xml과 docProps/app.xml의 문서 속성을 파싱합니다. 그다음 모든 워크시트의 셀 XML과 시트별 위성 파트, 즉 주석, 드로잉, 테이블을 건너뜁니다. 공유 문자열 테이블도 건너뛰는데, 이것은 들리는 것보다 중요합니다. 텍스트 많은 통합 문서에서 그 하나의 파트는 워크시트에 버금갈 수 있기 때문입니다

돌아오는 통합 문서는 구조적으로 완전합니다. Sheets.Count는 맞고 각 시트는 실제 이름과 Visibility를 지니며, DefinedNames는 채워져 있고, ExternalLinks는 연결된 모든 대상을 나열하고, Title, Author, LastModifiedBy는 파일이 선언한 것을 나릅니다. 모든 시트가 단지 셀이 0개일 뿐입니다

Delphi XLSX 아카이브에서 HotXLS MetadataOnly가 통합 문서 속성, 시트 이름, 스타일, 링크를 읽고 워크시트 셀 XML 전에 멈추는 모습
MetadataOnly는 아카이브의 모든 구조 파트를 읽고 워크시트 셀 XML 전에 멈춥니다. Delphi 통합 문서는 시트 이름, 가시성, 정의된 이름, 스타일과 0개의 셀로 돌아옵니다

감사가 도는 데이터가 정확히 이것입니다. 어떤 통합 문서가 시트를 숨기는가? 어느 것이 여전히 TaxRate를 정의하는가, 무엇으로? 작년에 폐기된 파일 공유로 링크하는 것은? 아래 스캐너가 하나의 파일에 대해 셋 모두에 답합니다. 두 인덱스 관례이 나란히 있는 점에 유의하십시오. Sheets[]는 XLS 엔진에 맞춘 1 기반이고, DefinedNames와 ExternalLinks는 평범한 0 기반 목록입니다

procedure ScanWorkbook(const FileName: WideString; Report: TStrings);
var
  Workbook: TXLSXWorkbook;
  Name: TXLSXDefinedName;
  I: Integer;
begin
  Workbook := TXLSXWorkbook.Create;
  try
    Workbook.MetadataOnly := True;           // Open이 읽으므로 먼저 설정
    if Workbook.Open(FileName) <> 1 then
    begin
      Report.Add(FileName + ': open failed');
      Exit;
    end;
    for I := 1 to Workbook.Sheets.Count do   // Sheets[]는 1 기반
      case Workbook.Sheets[I].Visibility of
        xlsxSheetHidden:
          Report.Add(FileName + ': hidden sheet ' + Workbook.Sheets[I].Name);
        xlsxSheetVeryHidden:
          Report.Add(FileName + ': very hidden sheet ' + Workbook.Sheets[I].Name);
      end;
    Name := Workbook.DefinedNames.FindByName('TaxRate');
    if Name <> nil then
      Report.Add(FileName + ': TaxRate = ' + Name.Formula);
    for I := 0 to Workbook.ExternalLinks.Count - 1 do   // 0 기반 목록
      Report.Add(FileName + ': links to ' + Workbook.ExternalLinks[I].Target);
  finally
    Workbook.Free;
  end;
end;

스캐너가 불리언 Visible이 아니라 Visibility를 읽는 것은 의도입니다. Visible은 두 숨김 상태 모두에서 False이며, 감사가 가장 지적하고 싶은 것은 very hidden 시트입니다. Excel의 숨기기 해제 대화상자는 그것을 나열하지 않고 VBA나 파일 직접 편집만이 되살리기 때문입니다

SelectedSheets로 마흔 중 두 시트 로드하기

SelectedSheets는 통합 문서가 소유한 TStringList입니다. 이름을 최소 하나 담고 있으면 Open은 그것이 지목한 시트의 셀 XML만 파싱합니다. 다른 모든 시트는 올바른 이름과 가시성으로, 셀 없이 만들어집니다. 비어 있는 목록, 즉 기본값은 모든 시트를 로드합니다. 이름은 목록 자신의 IndexOf로 찾으므로, 기본 설정에서는 매칭이 대소문자를 무시합니다. 이는 하나의 통합 문서에서 두 시트 이름이 대소문자만으로 갈릴 수 없다는 Excel의 규칙과 일치합니다

Workbook := TXLSXWorkbook.Create;
Workbook.SelectedSheets.Add('Summary');
Workbook.SelectedSheets.Add('Q3 Detail');
if Workbook.Open('consolidated-2026.xlsx') = 1 then
  ShowSheet(Workbook.Sheets[1]);   // ShowSheet는 여러분 자신의 UI 코드

두 스위치는 한 방향으로만 상호작용합니다. MetadataOnly = True는 선택을 무시하고 모든 시트를 건너뛰므로, 둘 다 설정된 통합 문서는 어디에도 셀 없이 돌아오고, 선택은 단순히 서열에서 밀렸을 뿐인데 고장난 것처럼 보입니다. Open 하나에 하나씩 쓰십시오. 발견에는 메타데이터 전용, 표적 작업에는 선택을

선택적 로딩은 ParallelParse가 켜져 있을 때 병렬 파스 단계도 통과합니다. 선택되지 않은 시트는 워커 작업 안에서 건너뛰어지므로, 스레드는 여러분이 요청한 시트에 시간을 씁니다. 같은 두 프로퍼티가 소스가 바이너리 .xlsb 통합 문서일 때도 적용되며, 범위 규칙이 하나 있습니다. VBA 프로젝트를 지닌 .xlsb는 이 두 옵션 없이 열어야 합니다. 둘 중 하나라도 설정되면 Open은 선택적 읽기를 지원하지 않는다고 보고합니다

Delphi에서 HotXLS SelectedSheets가 마흔 중 이름 지어진 두 시트의 셀을 파싱하는 동안 LoadSheet가 변하지 않은 소스 파일에서 건너뛰어진 시트를 구체화하는 모습
SelectedSheets는 지목된 시트의 셀 XML만 파싱하고 나머지는 이름과 가시성을 유지하는 빈 스텁으로 둡니다. LoadSheet는 소스 파일이 디스크에 그대로 남아 있다면 나중에 스텁을 구체화합니다

LoadSheet(1)은 왜 잘못된 시트를 로드하나?

LoadSheet는 0 기반 위치를 받는 반면 Sheets[]는 1 기반이기 때문입니다. LoadSheet(1)은 통합 문서의 두 번째 시트를 구체화하고, 이미 셀을 지닌 시트를 로드하는 것은 True를 돌려주는 no-op이므로, 뒤섞인 인덱스는 사용자가 클릭한 시트가 빈 채로 남아 있는 동안 성공처럼 보일 수 있습니다. 변환은 여러분 코드의 정확히 한 곳에 들어가야 합니다

두 번째 함정은 파일 수명입니다. LoadSheet는 호출 사이에 아카이브를 열어 두지 않습니다. 원본 파일을 이름으로 다시 열고, 통합 문서 파트, 스타일, 공유 문자열을 파싱한 다음 요청된 워크시트를 파싱합니다. 그 파일을 믿기 전에 HotXLS는 크기와 최종 기록 시각을 Open 시점에 잡아둔 값과 비교합니다. 그 사이 다른 프로세스가 통합 문서 위에 저장했다면, LoadSheet는 다른 버전의 셀을 이미 쥐고 있는 모델에 섞는 대신 False를 돌려줍니다

procedure TReportForm.ShowSheetNo(SheetNo: Integer);  // SheetNo는 Sheets[]처럼 1 기반
var
  Sheet: TXLSXWorksheet;
begin
  Sheet := FWorkbook.Sheets[SheetNo];
  if Sheet.Cells.Count = 0 then
    if not FWorkbook.LoadSheet(SheetNo - 1) then      // LoadSheet는 0 기반 위치를 원한다
    begin
      StatusBar.SimpleText := 'The workbook changed on disk; reopen it';
      Exit;
    end;
  Grid.ShowSheet(Sheet);                              // 여러분 자신의 그리드 코드
end;

그 설계에서 몇 가지 결과가 따라옵니다. TStream에서 열린 통합 문서는 돌아갈 소스 파일이 없으므로, 스트림으로 연 .xlsx에 대해 LoadSheet는 False를 돌려줍니다. 파싱 전에 메모리로 복호화되는 암호화 통합 문서도 마찬가지입니다. 스트림에서 선택적으로 연 바이너리 .xlsb는 예외인데, HotXLS가 지연된 시트를 위해 스트림의 임시 사본을 준비하기 때문입니다. 그리고 진짜로 빈 시트는 매 호출마다 다시 읽힙니다. 셀이 0개라는 것은 건너뛰어진 시트의 모습이기도 하기 때문입니다

비용 모델이 사용 방식을 정합니다. 모든 LoadSheet 호출은 공유 문자열 테이블을 다시 파싱하므로, 열 시트를 하나씩 구체화하면 그 파트 값을 열 번 치릅니다. 사용자가 이따금 탭을 하나 더 여는 뷰어라면 괜찮은 가격입니다. 시트 집합을 이미 알고 있다면 대신 SelectedSheets로 다시 여십시오. 아카이브 패스 한 번, 공유 문자열 파스 한 번입니다. 콘텐츠 차이도 있습니다. LoadSheet는 시트의 셀, 값, 수식, 형식을 가져오는 반면, 주석, 드로잉, 테이블은 Open과 함께 도착합니다. 코드가 주석이나 테이블을 읽는 시트는 처음부터 선택에 넣어야 합니다

왕복은 왜 손대지 않은 파트까지 바꾸나?

모든 것을 재생성하는 저장은 바뀐 적 없는 바이트를 다시 씁니다. 테마를 압축 해제하고, 객체 모델을 만들고, 직렬화하고, 다시 압축하면, 아무도 테마를 편집하지 않았는데도 결과는 원본과 달라집니다. VBA 프로젝트와 피벗 캐시에서는 비용이 시끄러운 diff 그 이상입니다. VBA 프로젝트는 자체 내부 스트림을 지닌 바이너리 복합 파일이고, 피벗 캐시는 피벗 테이블 정의가 위치로 지목하는 소스 데이터의 스냅샷을 지니므로, 아무도 바꾸지 않았다면 가장 안전한 일은 그 바이트를 그대로 두는 것입니다

기본값이 True인 PreserveRawParts는 SaveAs가 바뀌지 않은 파트의 압축된 바이트를 소스 아카이브에서 곧장 복사하게 만듭니다. 테마, VBA 프로젝트, 연결, 차트, 임베드된 미디어, 프린터 설정, 피벗 테이블과 피벗 캐시 정의와 레코드가 대상이며, 각각은 모델이 수정되지 않았다고 말할 때만 그렇습니다. 그 파트들은 지나가는 길에 인플레이트되거나 재 deflate되지 않습니다. 프로퍼티를 False로 설정하면 모든 파트의 완전한 재직렬화가 강제되는데, 출력이 객체 모델이 담은 것만 반영하기를 원할 때 손 뻗는 스위치입니다

원시 복사 경로는 LoadSheet와 같은 파일 검사를 씁니다. 소스는 Open 시점에 본 크기와 타임스탬프로 여전히 존재해야 합니다. 그렇지 않으면 SaveAs는 그 파트들을 평범하게 직렬화하고 파일은 여전히 완전합니다. 두 사례는 언제나 직렬화 경로를 킵니다. 소스가 바이너리 .xlsb였던 통합 문서(읽을 때 XML로 변환됨)와, 파트 네임스페이스가 바뀌는 StrictOOXML 설정 저장입니다

부분적으로 로드된 통합 문서의 저장에는 결정이 필요합니다. .xlsb 소스에서는 어떤 시트든 여전히 지연 중이면 SaveAs가 거부하고 Diagnostics에 "저장하기 전에 지연된 모든 바이너리 워크시트를 로드하십시오"를 기록합니다. .xlsx에서는 선택적으로 로드된 통합 문서를 읽기 전용 뷰로 취급하십시오. 스텁 시트는 작성기가 내보낼 셀이 없으므로, 편집하고 저장하기 전에 모든 스텁에 대해 LoadSheet를 호출하거나 선택 없이 다시 여십시오

의도적으로 다시 여는 발견 파이프라인

버티는 패턴은 두 패스입니다. 패스 하나는 모든 파일을 메타데이터 전용으로 열어 판단합니다. 패스 둘은 맞은 파일만 시트 선택과 함께 다시 열어 작업합니다. 다시 열기가 낭비처럼 보이는 것은 패스 하나가 피한 것을 세기 전까지입니다. 두 번째 Open이 셀을 파싱하는 유일한 것이고, 그것도 패스 하나가 고른 시트만 파싱합니다

Delphi의 2패스 HotXLS 발견 파이프라인. 메타데이터 전용 패스가 어느 통합 문서와 시트가 중요한지 판단하고, 표적 패스가 선택된 시트만 파싱함
패스 하나는 디렉터리를 메타데이터 전용으로 훑고 어느 파일과 시트가 중요한지 판단하며, 패스 둘은 시트 선택으로 맞은 것들을 다시 엽니다. 셀 XML을 파싱하는 것은 두 번째 열기뿐입니다
procedure ProcessInvoiceFile(const FileName: WideString);
var
  Probe, Workbook: TXLSXWorkbook;
  Wanted: TStringList;
  I: Integer;
begin
  Wanted := TStringList.Create;
  try
    // 패스 1: 발견, 셀 없음
    Probe := TXLSXWorkbook.Create;
    try
      Probe.MetadataOnly := True;
      if Probe.Open(FileName) <> 1 then
        Exit;
      for I := 1 to Probe.Sheets.Count do
        if Pos('Invoice', Probe.Sheets[I].Name) > 0 then
          Wanted.Add(Probe.Sheets[I].Name);
    finally
      Probe.Free;
    end;
    if Wanted.Count = 0 then
      Exit;                               // 이 파일에는 파싱할 가치가 있는 것이 없다

    // 패스 2: 선택된 시트들만의 셀
    Workbook := TXLSXWorkbook.Create;
    try
      Workbook.SelectedSheets.Assign(Wanted);
      if Workbook.Open(FileName) = 1 then
        PostInvoices(Workbook);           // 여러분 자신의 처리 코드
    finally
      Workbook.Free;
    end;
  finally
    Wanted.Free;
  end;
end;

Open 뒤에 MetadataOnly를 지운다고 프로브가 온전한 통합 문서로 변하지는 않습니다. 플래그는 열는 동안 읽히고, 그 뒤의 변경은 그 객체의 다음 Open에만 영향을 줍니다. 프로브는 LoadSheet로 한 번에 한 시트씩 자랄 수 있고 한두 시트라면 그게 합리적이지만, 파이프라인에는 깔끔한 재열기가 더 낫습니다. 각 시트의 주석과 테이블도 함께 데려옵니다

다른 대용량 파일 레버와 어디에 맞는가

선택적 로딩은 여러분이 파싱하는 것을 줄입니다. 로드된 시트의 비용에는 아무것도 하지 않고, 작업이 모든 시트의 모든 셀을 필요로 할 때는 도울 수 없습니다. 그 사례는 저장소의 문제이며 행 블록 셀 저장과 범위 기반 스타일 글이 다루고, 파스 처리량의 문제이며 병렬 XLSX 파싱과 메모리 할당자가 다룹니다. 작업이 편집 없는 순수 읽기라면 스트리밍 다이렉트 리더가 통합 문서 객체 모델을 전혀 만들지 않고, 그것을 만드는 어떤 로드 전략보다 나은 도구입니다

이 모든 것은 스캐닝을 하는 서버에 Excel 설치 없이 여러분 자신의 Delphi 또는 C++Builder 실행 파일 안에서 돌아갑니다. 지원 형식, 에디션, 라이선스는 HotXLS Delphi 스프레드시트 컴포넌트 페이지에 정리되어 있습니다