기술 문서

HotXLS Delphi Component: Delphi에서 comments, hyperlinks, and review workflows

생성된 워크북에서 시트 이름을 "Summary"에서 "Overview"로 바꿔 보십시오. Summary!A1을 가리키던 모든 내부 하이퍼링크는 어디로도 향하지 못하게 됩니다. 저장할 때도, 열 때도 예외는 발생하지 않습니다. 링크는 여전히 렌더링되고 여전히 클릭 가능해 보이지만, 조용히 아무 데도 해석되지 않습니다. 저장 후 다른 이름으로 변환을 거치거나 .xls/.xlsx 왕복을 거친 뒤에도 같은 종류의 파손이 나타나서, 코멘트가 한 열 어긋난 곳에 안착하거나 상대 링크가 대상을 잃어버립니다. 두 기능 모두 실제 사람이 행동에 옮기는 리뷰 상태를 담고 있으므로, 이들이 망가지면 리뷰어가 클릭했는데 아무 일도 일어나지 않을 때까지 그 실패는 보이지 않습니다

이것이 코멘트와 하이퍼링크가 겉모습이 시사하는 것보다 더 많은 주의를 받을 가치가 있는 실질적인 이유입니다. HotXLS는 Delphi와 C++Builder 코드가 XLS와 XLSX 양쪽 모두에서 두 기능에 직접 쓰기로 접근할 수 있게 하며, Excel 자동화는 전혀 관여하지 않습니다. 그 통제력의 이면은 책임입니다. 라이브러리는 여러분이 건네준 대상을 정확히 그대로 기록할 뿐 어느 것도 검증하지 않으므로, 리뷰 워크플로를 온전하게 유지하는 것은 Excel의 일이 아니라 여러분의 코드가 할 일입니다

기계가 작성한 리뷰 기록으로서의 셀 코멘트

XLSX 클래스 모델에서 코멘트는 워크시트 수준의 객체입니다. 자신의 행, 열, 작성자, 텍스트 본문을 알고 있습니다. 작성자 필드는 제자리를 차지할 자격이 있습니다. 여러분의 코드가 생성한 워크북이 리뷰 체인을 거칠 때, 감사자가 처음 묻는 질문은 특정 노트를 누가 작성했는가이며, 작성자 없이 남겨진 노트는 그 질문에 빈칸으로 답합니다. 생성된 코멘트에는 서비스 신원을 찍어 두어 출처가 결코 모호해지지 않도록 하십시오

Delphi HotXLS 코멘트 재시도 다이어그램: FindAt 프로브는 기존 셀 노트를 갱신하는 반면, 무작정한 AddComment 재시도는 중복을 쌓음
AddComment를 무작정 호출하는 재시도는 같은 셀에 두 번째 노트를 쌓는 반면, FindAt 프로브는 이미 있는 노트를 편집합니다
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Note: TXLSXComment;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('reconciliation.xlsx');
    Sheet := Book.Sheets[0];

    // 조정된 수치에 작성자가 명시된 노트
    Sheet.AddComment(14, 4, 'Manual adjustment: late FX rate, see ticket FIN-2214',
      'recon-service');

    // 두 번째 노트를 쌓는 대신 기존 노트를 갱신함
    Note := Sheet.Comments.FindAt(14, 4);
    if Note <> nil then
      Note.Text := Note.Text + ' [verified 2026-06-11]';

    Book.SaveAs('reconciliation-reviewed.xlsx');
  finally
    Book.Free;
  end;
end;

FindAt 조회는 겉보기보다 더 큰 무게를 지닙니다. 일시적인 실패 후 재시도하는 배치 작업은 이미 주석을 단 셀에 AddComment를 서슴없이 두 번째로 호출하며, 그 셀에는 아무도 요청하지 않은 두 개의 노트가 쌓이게 됩니다. 먼저 FindAt으로 조회한 다음, 그것이 반환하는 객체를 갱신하십시오. Comments 컬렉션은 DeleteAtDeleteInRange도 노출합니다. 워크북이 건물을 떠나기 전에 정화할 때는 범위 변형을 사용해야 합니다. 영역 전체에서 내부 QA 주석을 지우는 것이 셀마다 손으로 작성한 루프 대신 한 번의 호출로 끝납니다

외부 URL과 워크북 내부 점프는 서로 다른 API입니다

OOXML은 두 종류의 링크를 서로 다른 곳에 보관합니다. 외부 URL은 시트의 .rels 파트에 관계 항목이 되며, 셀은 id로 그 관계를 가리킵니다. 내부 점프는 관계 계층을 아예 건드리지 않습니다. Summary!A1 같은 단순한 위치 문자열이 링크에 직접 저장될 뿐입니다. HotXLS는 이 구분을 단일 메서드로 뭉뚱그리는 대신 API에서 그대로 드러내므로, 대상이 어디에 사는지 알면 올바른 호출을 고를 수 있습니다:

Delphi 생성 통합문서에서 HotXLS가 외부 URL을 rels 파트의 relationship으로 저장하는 것과, 내부 점프를 단순 location 문자열로 저장하는 것 대비 다이어그램
외부 URL은 관계 계층을 거치고 내부 점프는 단순 텍스트이므로, 각 종류는 저마다의 방식으로 실패하고 저마다의 감사 규칙이 필요합니다
Sheet.Cells[2, 1].Value := 'Source record';
Sheet.AddHyperlink(2, 1, 'https://intranet.example.com/records/2214',
  'Open record 2214', 'ERP source entry');

Sheet.Cells[3, 1].Value := 'Totals';
Sheet.AddHyperlinkToCell(3, 1, 'Overview!B12', 'Jump to totals');

결과로 만들어진 TXLSXHyperlink 객체에서 UrlLocation은 서로 배타적이며, IsInternal이 둘 중 어느 쪽이 채워져 있는지 알려줍니다. 이 플래그는 이미 열려 있는 워크북의 링크를 조사하면서 "파일을 벗어난다"와 "파일 안에 머문다"를 서로 다른 규칙으로 다뤄야 할 때 확인하는 값입니다. 외부 호스트는 허용 목록의 대상이 될 수 있지만, 내부 대상은 존재하는 시트를 지목하기만 하면 됩니다. 내부 링크는 그 뒤에 관계 파트를 전혀 지니지 않으므로, 대량으로 다시 쓰기에도 더 저렴합니다

이 문서 서두에서 소개한 파손은 전적으로 내부 쪽에 있으며, 한 가지 사실에서 비롯됩니다. 위치 문자열은 파싱된 참조가 아닙니다. HotXLS는 여러분이 건네준 텍스트를 정확히 그대로 기록하며, 나중에 시트 이름이 바뀌어도 그 텍스트를 다시 가리키게 해 주는 것은 아무것도 없습니다. 실전에서 통하는 방어책은 둘입니다. 첫 번째는 순서에 관한 규율입니다. 단 하나의 링크라도 생성하기 전에 모든 시트 이름을 바꾸고, 그 뒤로는 시트 이름을 고정된 식별자로 취급하십시오. 두 번째는 더 견고하며 나중에 이루어진 이름 변경도 견뎌냅니다. 링크를 원시 Sheet!Cell 주소가 아니라 워크북 수준의 정의된 이름을 가리키게 하십시오. 기저의 시트가 바뀌면 Excel이 그 이름의 정의를 다시 쓰므로, 링크도 자동으로 함께 따라갑니다. 이 두 번째 접근 방식은 HotXLS의 정의된 이름과 시트 간 수식에서 다루는 기법과 자연스럽게 짝을 이룹니다

XLS 쪽: 같은 개념, 더 오래된 배관

BIFF8 파사드는 코멘트를 워크시트 수준의 컬렉션이 아니라 범위에 매답니다. IXLSRange에 대해 AddComment를 호출하면 TXLSComment를 돌려받으며, 범위의 Comment 속성은 기존 노트를 읽고, ClearComments는 이를 지웁니다. 여기서 날카로운 부분은 위치와 관련이 있습니다. TXLSComment는 자신의 행과 열을 공개적으로 노출하지 않으므로, "모든 코멘트를 순회하며 어디에 있는지 보고하라"는 자연스러운 루프는 이 API에 대해서는 거꾸로 작동합니다. 셀에서부터 시작해야 합니다. 주석을 단 주소 목록으로부터 감사를 진행하거나, 기록하는 동안 스스로 위치 로그를 유지하십시오. 나중에 코멘트 객체는 자신이 어디에 사는지 알려주지 않기 때문입니다

var
  Book: IXLSWorkbook;
  Sheet: IXLSWorksheet;
  Remark: TXLSComment;
begin
  Book := TXLSWorkbook.Create;
  Sheet := Book.Sheets.Add;
  Sheet.Name := 'Review';
  Sheet.Cells.Item[5, 2].Value := 4821.50;

  Remark := Sheet.Cells.Item[5, 2].AddComment('Awaiting sign-off from controller');
  Remark.Visible := True;   // 처음 볼 때부터 노트가 펼쳐져 있도록 함

  Sheet.AddHyperlink(7, 2, 'https://intranet.example.com/signoff/4821',
    'Sign-off form', 'Opens the controller queue');
  Book.SaveAs('review.xls');
end;

Visible을 True로 설정하는 것은 노트를 절대 지나칠 수 없게 만드는 전통적인 방법입니다. 노란 상자가 마우스 오버를 기다리는 대신 시트 위에 펼쳐진 채로 남습니다. TXLSCommentTextRuns를 노출함으로써 XLSX 대응물보다 한 걸음 더 나아가며, 이 덕분에 하나의 노트가 평범한 설명 옆에 굵은 경고를 함께 담을 수 있습니다. 이는 XLSX 코멘트 API가 같은 방식으로 노출하지 않는 서식입니다. 이 쪽의 하이퍼링크는 세 단계의 점진적인 오버로드(주소만, 표시 텍스트 포함, 화면 팁 포함)로 만들어지며, 워크시트의 HyperLinks 컬렉션을 통해 다시 읽을 수 있고, 각 링크는 Address, SubAddress, DisplayText, ScreenTip을 노출합니다

흩어진 노트보다 리뷰 색인 시트가 낫습니다

대략 십여 개가 넘는 주석부터는 마우스를 올려 읽는 방식이 조용히 확장성을 잃습니다. 노트는 리뷰어가 절대 열지 않는 시트에 쌓이고, 가장 중요한 노트야말로 놓치기 가장 쉬운 것들입니다. 가장 잘 버텨온 구조는 생성된 색인 시트입니다. 주석이 달린 위치마다 한 행씩, 시트 이름, 셀 주소, 작성자, 노트의 짧은 발췌를 나열합니다. 마지막 열은 AddHyperlinkToCell로 만든 내부 하이퍼링크를 담고 있어 주석이 달린 셀로 곧장 점프합니다. 이제 리뷰어는 그리드를 뒤지는 대신 목록을 아래로 읽어 내려가며, 그 색인의 행 수는 아래에서 다룰 감사 단계의 코멘트 목록 역할도 겸합니다

이 색인은 저렴하게 만들 수 있습니다. 여러분의 생성기는 이미 자신이 건드린 모든 위치를 알고 있기 때문입니다. 각 코멘트를 쓸 때마다 (시트, 행, 열, 작성자, 요약) 튜플을 목록에 추가한 다음, 저장하기 전에 행 수가 확정되도록 색인 시트를 마지막에 내보내십시오. 두 가지 개선이 보답합니다. 삽입 순서가 아니라 심각도나 시트별로 색인을 정렬하고, 각 항목을 본 뒤 리뷰어가 위로 돌아갈 수 있도록 색인 헤더에 복귀 링크를 넣으십시오. 내부 링크는 그 뒤에 관계 계층이 전혀 없는 단순한 위치 문자열이므로, 천 행짜리 색인이라도 파일 크기나 저장 시간에는 거의 아무것도 더하지 않습니다

같은 시트는 돌아오는 여정에서도 다시 보답합니다. 리뷰가 끝난 워크북이 돌아오면, 여러분의 코드는 바뀌었을지도 모르는 코멘트를 찾아 모든 시트를 다시 스캔하는 대신 색인 행 옆의 셀에 입력된 상태 값을 읽습니다. 구조화된 상태 셀들의 열은 깔끔하게 파싱되지만, 흩어진 자유 형식 텍스트 노트는 그렇지 않습니다

실제로 파손을 잡아내는 배포 전 감사 단계

이 API들 중 어느 것도 대상을 검증하지 않습니다. 삭제한 시트를 가리키는 링크, 오타가 난 인트라넷 호스트, 지난 분기에 폐기된 파일 공유, 이 모든 것이 아무런 소리도 없이 저장됩니다. ECMA-376은 링크가 어떻게 저장되는지를 명시할 뿐, 그것이 무언가로 해석된다는 것은 명시하지 않습니다. 따라서 리뷰 메타데이터를 담은 워크북은 SaveAs 바로 직전에 실행하는 짧은 자체 감사 단계를 갖출 가치가 있습니다:

Delphi에서 SaveAs 전에 내부 대상, URL 허용 목록, 코멘트 개수, 수신자 스크러빙을 확인하는 HotXLS 사전 배포 감사 패스 다이어그램
네 가지 검사가 SaveAs 직전에 실행되며, 그 모두가 라이브러리 자신은 결코 던지지 않을 실패를 잡아냅니다
  • 생성 중에 기록된 모든 내부 위치를 수집하고, 느낌표 앞의 시트 이름이 워크북의 시트 컬렉션에 여전히 존재하는지 확인합니다
  • 외부 URL을 스킴과 호스트의 허용 목록에 대조하여 확인합니다. 순수 file://과 UNC 경로는 환경 세부 정보를 노출하며 파일이 네트워크를 벗어나는 순간 깨집니다
  • 시트별 코멘트 수를 세어 생성기가 원래 기록하려던 것과 비교합니다. 노트를 두 배로 늘린 재시도는 리뷰어의 받은 편지함이 아니라 여기서 드러납니다
  • 수신자가 조직 바깥에 있을 때는 DeleteInRange로 내부 전용 주석을 제거합니다

데이터 계층으로부터 워크북을 구축하는 팀은 이 단계를 이미 데이터를 검증하는 같은 파이프라인 단계에 접어 넣을 수 있으므로, 메타데이터 검사가 공짜로 함께 실려 갑니다. 그 메커니즘은 데이터베이스 쿼리 결과를 Excel 보고서로 내보내기에서 설명한 것과 같으며, 행이 아니라 링크와 코멘트를 향하도록 돌려 놓은 것뿐입니다

위치 문자열을 손으로 만들 때 사람들을 걸려 넘어지게 하는 인용 관련 세부사항이 하나 있습니다. 이름에 공백이 포함된 시트는 수식 입력줄이 인용하는 것과 정확히 같은 방식으로 위치 문자열 안에서 인용되어야 합니다. Quarterly Totals!A1이 아니라 'Quarterly Totals'!A1입니다. HotXLS는 수식 엔진이 시트 간 참조에 사용하는 것과 같은 규칙을 적용하므로, 링크가 워크시트 수식에서 동작한다면 여기서도 그 인용 방식이 동작합니다. 공백이 있는 이름을 인용 없이 건네주면 이 문서 서두에서 경고한 것과 똑같은 조용한 죽은 링크를 얻게 됩니다

코멘트와 하이퍼링크는 리뷰어가 두 번 살펴보지 않고 곧바로 행동에 옮기는, 생성된 워크북의 부분들이며, 바로 그 때문에 아무 데도 가리키지 않는 대상은 누군가 알아채기 전에 진짜 피해를 입힙니다. 검증 단계를 한 번 만들어 배포되는 모든 워크북에서 실행하면, 리뷰 워크플로는 이름 변경과 변환을 거치는 내내 온전하게 유지됩니다. XLS와 XLSX 파사드 양쪽의 전체 API 표면은 HotXLS Delphi Component 제품 페이지에 문서화되어 있습니다