기술 문서

델파이 HotXLS 조건부 서식과 서식 있는 텍스트

OOXML의 조건부 서식 규칙은 하나의 이름을 쓰고 있는 두 개의 별개 사물입니다. 조건(비교, 수식, 텍스트 일치)은 어느 셀이 자격을 갖추는지 정합니다. 겉모습(차등 서식 레코드, ECMA-376 용어로 dxf)은 그 셀들이 어떻게 보일지 정합니다. Excel의 대화 상자는 둘을 한꺼번에 채우게 만들어 그 이음매를 감춥니다. HotXLS는 감추지 않습니다. 델파이에서 cellIs 규칙을 만들고 스타일을 건너뛰면, 규칙은 유효하고 범위도 맞고 수식은 정확히 옳은 셀들에서 참으로 평가되는데 아무 색도 바뀌지 않습니다. 규칙의 지시가 "참이다, 아무것도 칠하지 마라"였기 때문입니다. 조건과 결과 사이의 그 틈이 가장 먼저 바로잡아야 할 것이고, 규칙 관리 대화 상자에서는 맞아 보이는데 아무것도 강조하지 않는 규칙 대부분이 여기서 나옵니다

HotXLS는 조건부 서식을 BIFF8 .xls와 OOXML .xlsx 양쪽에 네이티브로 쓰고, 서식 있는 텍스트 런과 풀링된 셀 스타일 모델도 똑같이 다룹니다. 이 세 기능은 평평해 보이는 API 표면이 시사하는 것보다 더 많은 배선을 공유하며, 출력이 의도에서 어긋나는 자리는 대개 그 셋 사이의 이음매입니다

조건에는 결과가 필요합니다: dxf 스타일

XLSX 워크시트에서 비교 규칙은 AddConditionalFormat에서 나옵니다. 이 메서드는 범위, TXLSXCfOperator의 연산자, 그리고 수식이나 리터럴을 받은 다음 시트의 ConditionalFormats 컬렉션 안에 새로 생긴 규칙의 인덱스를 돌려줍니다. 그 인덱스의 규칙 객체는 Style 속성을 드러내고, 강조는 바로 거기에 삽니다. 거기에 채우기를 설정하면 자격을 갖춘 셀이 그 채우기를 받습니다. 손대지 않고 두면 위에서 말한 보이지 않는 규칙을 만든 것입니다

델파이에서 만든 HotXLS cellIs 규칙을 두 반쪽으로 보여 주는 그림. AddConditionalFormat이 조건에 해당하는 규칙 인덱스를 돌려주고, ConditionalFormats[Idx].Style.SetFillBgColor가 dxf 결과를 공급하며, 스타일을 끝내 설정하지 않은 규칙은 검증은 통과하면서 아무것도 칠하지 않는다
조건은 어느 셀이 자격을 갖추는지 정하고 dxf 스타일은 그 셀이 어떻게 보일지 정하므로, 스타일을 건너뛰면 보이지 않는 규칙이 만들어집니다
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Idx: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('kpi.xlsx');
    Sheet := Book.Sheets[0];

    // 음의 편차: 연한 빨강 채우기
    Idx := Sheet.AddConditionalFormat('D2:D200', xlsxCfOpLessThan, '0');
    Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);

    // 중복 주문 ID도 같은 방식으로 표시된다
    Idx := Sheet.AddCondFormatDuplicateValues('A2:A200');
    Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFEB9C);

    // 사용자 정의 수식 규칙: 실적이 목표의 90%에 못 미치는 행을 강조
    Idx := Sheet.AddCondFormatExpression('B2:B200', '$C2<$B2*0.9');
    Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);

    Book.SaveAs('kpi-flagged.xlsx');
  finally
    Book.Free;
  end;
end;

여기서 색은 32비트 ARGB 값이므로 $FFFFC7CE는 대화 상자에서 익숙한 Excel의 "연한 빨강"이고, RGB 앞에 완전 불투명 알파 바이트가 앉아 있습니다. 셀 단위 조건으로 발동하는 규칙 종류는 전부 같은 생성 후 스타일 지정 모양을 따릅니다. 텍스트 매처(AddCondFormatContainsText, AddCondFormatBeginsWith, AddCondFormatEndsWith)는 나중에 스타일을 입힐 인덱스를 돌려주고, AddCondFormatTop10AddCondFormatAboveAverage, 그리고 공백·오류 검출기도 마찬가지입니다. 이 패턴을 한 번 익히면 텍스트와 비교 계열 전체가 똑같이 움직입니다

데이터 막대, 색조, 아이콘 집합은 스스로를 칠합니다

시각적 규칙 종류는 정반대로 동작합니다. 이들은 겉모습을 규칙 정의 안에 담고 다니며 Style 속성을 완전히 무시합니다. 데이터 막대 규칙에 채우기를 지정해도 아무 일도 일어나지 않는데, 분류 체계를 이해하기 전까지는 버그처럼 읽힙니다. AddCondFormatDataBar는 막대 색을 직접 인자로 받고, 2점·3점 색조도 끝점 색을 같은 방식으로 받으며, AddCondFormatIconSeticsTrafficLights3 같은 26가지 아이콘 집합 유형 중 하나를 고릅니다. 여기서는 잊어버릴 별도의 스타일 레코드가 없습니다. 애초에 별도의 스타일 레코드 자체가 없기 때문입니다

이 호출들에서 생각해 볼 가치가 있는 매개변수는 TXLSCfValueKind로 타입이 지정된 값 앵커입니다. 막대나 색조의 끝점은 범위의 최솟값이나 최댓값에, 리터럴 숫자에, 백분율이나 백분위수에, 혹은 수식의 결과에 앉을 수 있습니다. 기본값인 범위 최솟값과 범위 최댓값은 잘 정돈된 데모 데이터에서는 얌전하다가 이상치가 있는 실제 데이터에서 여러분을 배신합니다. 튀는 값 하나가 눈금을 늘려 다른 모든 막대를 그루터기로 납작하게 만듭니다. 대시보드를 기간에 걸쳐 읽어야 한다면 끝점을 고정된 숫자나 백분위수에 고정하십시오. 그래야 3월의 막대 절반이 4월의 막대 절반과 같은 양을 뜻합니다. 자동으로 눈금이 잡히는 막대는 자기 자신하고만 비교할 수 있습니다

XLS 기록기는 규칙 종류 넷만 다룹니다

레거시 BIFF8 쪽은 XLSX 쪽의 축소판 거울이 아니라 의도된 부분집합입니다. XLS 파사드는 정확히 네 가지 조건부 규칙 모양, 즉 데이터 막대, 2색 색조, 3색 색조, 아이콘 집합을 만들 수 있고 이를 CF12 레코드로 스트림에 내보냅니다. cellIs, 표현식, 텍스트 규칙을 만드는 API는 없습니다. 여러분이 여는 파일에 이미 살고 있는 그런 종류의 규칙은 읽히고 유지되고 그대로 다시 쓰이므로, 고객의 .xls를 열었다 다시 저장하는 것이 그 안에 실려 있던 서식을 망가뜨리지는 않습니다. 할 수 없는 일은 .xls에 임계값 강조를 처음부터 생성해 넣는 것입니다. 그 경우 선택지는 코드로 계산한 평범한 셀 채우기로 흉내 내거나, 산출물을 .xlsx로 삼아 규칙 계열 전체를 쓰는 것입니다

이것은 데이터 계층이 생긴 뒤가 아니라 생기기 전에 정리할 제약입니다. 대시보드 모양을 한 모든 것에 대해 파일 형식 결정을 바꾸기 때문입니다. 호환성 때문에 .xls를 고른 팀이 그다음 cellIs 임계값이 들어간 KPI 보고서를 명세하면 서로 맞지 않는 두 가지를 고른 것이고, 알아채기에 더 싼 시점은 구축 3주 차가 아니라 형식을 결정하는 자리입니다

규칙 쌓기, 우선순위, 겹치는 범위

실제 대시보드가 범위당 규칙 하나만 돌리는 일은 드뭅니다. 편차 열 하나가 크기를 나타내는 데이터 막대, 하드 임계값을 위한 cellIs 규칙, 그리고 그 둘 위에 에스컬레이션을 위한 행 수준 표현식 규칙을 함께 실을 수 있습니다. 각 TXLSXConditionalFormatPriority 값을 드러내고, Excel은 경합하는 규칙을 우선순위 순서로 해소합니다. 두 규칙이 같은 셀을 칠하려 할 때 승자는 여러분이 설정한 숫자가 정하지, 검토자가 규칙 관리 대화 상자에서 어쩌다 스크롤한 순서가 정하지 않습니다

우선순위는 그리기 프로그램이 z-순서를 다루듯 다루십시오. 두 규칙이 같은 셀에 닿을 수 있는 곳마다 의도적으로 부여하고, 나중 규칙이 나머지를 다시 번호 매기지 않고 끼어들 수 있도록 값 사이에 간격을 남기십시오. 규칙끼리 충돌할 수 없는 곳, 이를테면 E열에 갇힌 데이터 막대와 G열에 갇힌 텍스트 규칙이라면 생성 순서로 충분하고 우선순위는 신경 쓸 값어치가 없습니다. 그 주의력은 범위 경계에 쓰십시오. 여기서 비싼 버그는 거의 언제나 우선순위 역전이 아니기 때문입니다. 비싼 버그는 350행으로 자란 보고서에 걸린 B2:B200 같은 범위입니다. 덮이지 않은 꼬리가 건강한 데이터와 똑같이 보이는 평범한 셀로 렌더링됩니다. 모든 규칙 범위를 통합 문서의 다른 곳에서 차트 계열과 유효성 검사 범위를 이끄는 것과 같은 최종 행 수 값에서 파생시키면 꼬리가 떨어져 나가지 않습니다

검증 습관 하나는 제 몫을 합니다. 생성 후 파일을 Excel에서 열고 서식이 적용된 범위를 선택한 다음, 템플릿을 바꿀 때마다 규칙 관리를 한 번 훑으십시오. 조건부 서식은 권위 있는 렌더러가 파일을 소비하는 애플리케이션뿐인 몇 안 되는 영역이라, XML을 대상으로 한 단위 테스트는 규칙이 쓰였다는 것만 증명하지 Excel이 여러분 뜻대로 칠한다는 것은 증명하지 않습니다. 눈으로 훑는 1분이 그 틈을 메웁니다

서식 있는 텍스트: 셀 하나 안의 여러 서식

XLSX 모델의 서식 있는 텍스트 셀은 런의 목록을 담고, 각 런은 텍스트 한 구간에 자기 자신의 글꼴 속성을 더한 것입니다. 목록은 TXLSXRichText 객체로 옆에서 만들고 거기에 런을 더한 다음, 전체를 셀에 붙입니다. 물리는 부분은 소유권 규칙입니다. Cell.RichText에 대입하면 그 객체의 소유권이 셀로 넘어가고, 셀은 자기 파괴 과정에서 그것을 해제합니다. 여러분이 또 해제하면 이중 해제가 되는데, 원인이 된 실행 내내 조용히 있다가 한참 뒤 전혀 상관없는 곳에서 크래시로 드러나는 종류입니다

델파이에서의 HotXLS 서식 있는 텍스트 런 그림. TXLSXRichText 객체를 Cell.RichText에 대입하면 소유권이 셀로 옮겨져 두 번째 Free가 한참 뒤 힙을 망가뜨리고, 런 색은 ColorIsAuto를 지운 뒤에만 반영된다
대입하는 순간 런 목록의 소유권이 셀로 옮겨지고, 색 지정은 ColorIsAuto를 지운 뒤에야 붙습니다
var
  Rich: TXLSXRichText;
  Run: TXLSXRichTextRun;
begin
  Rich := TXLSXRichText.Create;
  Rich.AddRunText('Status: ');
  Run := Rich.AddRunText('OVERDUE');
  Run.Bold := True;
  Run.Color := $FFC00000;
  Run.ColorIsAuto := False;
  Run := Rich.AddRunText(' (escalated to regional manager)');
  Run.Italic := True;
  Sheet.Cells[2, 7].RichText := Rich;   // 소유권이 셀로 넘어간다: Free 하지 말 것
end;

명시적인 ColorIsAuto := False는 선택적인 장식이 아닙니다. 런은 자동 색 플래그를 지니고 다니며, 색 지정은 그 플래그가 지워진 뒤에만 존중됩니다. Color를 설정하고 ColorIsAuto를 잊으면 런은 굵게 나오지만 고집스럽게 검은색이고, 원인을 가리킬 오류도 없습니다. 런은 취소선, 밑줄 변형, 위 첨자와 아래 첨자를 위한 세로 맞춤도 지원하며, PlainText는 텍스트 내용을 내보내거나 비교해야 할 때 목록 전체를 문자열 하나로 납작하게 만들어 줍니다

셀 수준의 서식 있는 텍스트는 XLSX 전용입니다. XLS 파사드에는 그것을 쓰는 공개 API가 없지만, 런 자체는 TextRuns를 통해 주석과 텍스트 상자에서 쓸 수 있고, 기존 .xls에서 읽은 서식 있는 문자열은 왕복을 온전히 살아남습니다. 끌리는 방향은 조건부 서식과 같습니다. 셀 안에서 서식을 섞는 것은 무엇이든 XLSX 기록기에 속합니다

스타일 풀과 출고되는 하나 차이 오류

XLSX 모델의 평범한 셀 스타일 지정은 통합 문서의 풀링된 컬렉션을 거칩니다. Fonts.Add, Fills.AddSolid, Borders.Add는 각각 정의를 등록하고 풀 안의 인덱스를 돌려줍니다. 그 인덱스는 0 기반입니다. 그것을 소비하는 FontIndex 같은 셀 쪽 속성은 0을 "기본값"으로 예약하므로, 셀에 대입하는 값은 풀 인덱스에 1을 더한 것입니다:

HotXLS XLSX 스타일 풀의 하나 차이 오류 그림. Fonts.Add는 0 기반 풀 인덱스를 돌려주는 반면 셀의 FontIndex는 0을 기본값으로 예약한 1 기반이라, 더하기 1을 빠뜨리면 모든 머리글이 조용히 스타일 없이 렌더링된다
풀 인덱스는 0에서 시작하고 셀 인덱스는 0을 기본값으로 예약하므로, 셀 쪽은 언제나 1을 더합니다
HeaderFont := Book.Fonts.Add('Calibri', 11, True, False);  // 풀 인덱스, 0 기반
for Col := 1 to 6 do
  Sheet.Cells[1, Col].FontIndex := HeaderFont + 1;          // 셀 인덱스, 1 기반

+ 1을 빠뜨리면 모든 머리글이 기본 글꼴로 되돌아갑니다. 예외도 경고도 없고, 아무도 스타일을 입히지 않은 것처럼 보이는 통합 문서만 남습니다. 두 번째 실수는 루프 안에 숨습니다. 행마다 Fonts.Add를 부르는 것입니다. 동일한 글꼴 정의는 중복 제거되므로 파일이 망가지지는 않지만 일은 낭비되고, 특히 맞춤 풀은 호출마다 중복을 접는 대신 새 객체를 돌려줍니다. 몇 안 되는 스타일은 루프 전에 한 번 만들어 두고 그 인덱스를 재사용하십시오. 10만 행짜리 보고서에서 그 변경 하나는 HotXLS의 대형 통합 문서 성능 튜닝에서 다루는 지렛대 중 하나입니다. 기성 의미 스타일만 필요하다면 두 파사드 모두 범위에 ApplyBuiltinStyle을 드러내며, 이는 풀을 전혀 건드리지 않고 Excel의 기본 제공 Good, Bad, Neutral과 강조 스타일로 매핑됩니다

조건부 서식, 서식 있는 텍스트, 풀링된 스타일은 보고서의 마지막 한 구간으로 데이터 모델과 레이아웃이 정리된 뒤에 적용되며, 그 앞 단계는 HotXLS를 이용한 템플릿 기반 보고서 생성의 주제입니다. 규칙과 런과 스타일의 전체 레퍼런스는 HotXLS Delphi Component 제품 페이지에 있습니다