기술 문서

HotXLS를 사용한 Delphi에서의 R1C1 수식 표기법

HotXLS는 TXLSCalculator.GetCompiledFormulaR1C1을 통해 R1C1 참조 표기법으로 작성된 수식을 컴파일합니다. 이 메서드는 R2C3(절대), R[-1]C[2](상대 오프셋) 및 RC(현재 셀)와 같은 형식을 허용하고, 수식이 배치된 셀의 행과 열을 기준으로 A1 표기법으로 변환한 다음 일반 A1 수식을 처리하는 동일한 컴파일러로 전달합니다. 수백 행에 걸쳐 동일한 수식을 생성하는 Delphi 및 C++Builder 코드의 경우, 이 단일 메서드를 사용하여 문자열 연결 작업에서 발생하는 다양한 버그를 완전히 방지할 수 있습니다

프로그래밍 방식으로 열 데이터를 채워 본 개발자라면 이러한 종류의 버그가 낯설지 않을 것입니다. 행을 루프하면서 각 행에 대해 Format('D%d*E%d', [Row, Row])와 같은 형태로 A1 수식 문자열을 생성하게 됩니다. 매 반복마다 텍스트에 행 번호를 결합하게 되며, 행 번호만이 유일하게 변경되는 부분입니다. 오프셋 계산을 단 한 번이라도 잘못하거나, 0부터 시작하는 루프 카운터와 1부터 시작하는 A1 행 번호를 혼동하거나, 헤더 행 추가로 인해 데이터 블록이 한 행씩 밀리면 해당 열의 모든 수식이 한 행씩 어긋나게 됩니다. 예외는 발생하지 않지만 최종 수치들이 잘못 산출됩니다. 실제로 의도했던 수식은 "내 왼쪽의 두 셀을 서로 곱하기"이며 행 번호를 전혀 언급할 필요가 없는데, R1C1 표기법을 사용하면 이처럼 구현하는 것이 가능해집니다

R1C1 표기법이란 무엇이며 언제 사용해야 합니까?

R1C1 표기법은 열 문자 및 행 번호 대신 행 번호와 열 번호로 셀의 주소를 나타내며, 상대 참조를 수식 셀 기준의 명시적 오프셋으로 표현합니다. R2C3은 2행 3열의 절대 셀을 의미하며, A1 표기법으로는 $C$2로 씁니다. R[-1]C[2]는 수식이 위치한 셀을 기준으로 한 행 위 및 두 열 오른쪽의 셀을 나타냅니다. RC는 수식 셀 자체를 의미합니다. 대괄호 오프셋의 주요 이점은 다음과 같습니다: R1C1 표기법의 상대 참조는 수식이 배치되는 셀의 위치와 무관하게 동일한 문자열로 기술되는 반면, 동일한 참조를 나타내는 A1 표기법은 행마다 문자열이 변경됩니다

이 표기법은 특정 시나리오에서 특히 유용하며, 가장 대표적인 것이 템플릿 생성 작업입니다. 이 작업에서는 동일한 상대 수식을 데이터 영역의 모든 행에 주입해야 합니다. A1 표기법의 경우 행마다 수식 텍스트를 다시 렌더링해야 하지만, R1C1 표기법에서는 수식 문자열이 상수로 유지됩니다. 이는 스프레드시트 파일 포맷의 내부 논리 구조와도 일치합니다: 공유 수식 레코드는 상대 참조를 호스트 셀 기준의 행/열 오프셋으로 저장하므로, 행마다 다른 A1 문자열을 조립하는 작업은 파서가 이를 다시 오프셋으로 분해하게 만드는 불필요한 번거로움을 유발합니다. R1C1은 이러한 불필요한 변환을 생략해 줍니다. 일반 사람이 직접 상호 작용하며 작성하는 수식의 경우에는 A1이 더 직관적이므로, HotXLS에서도 기본 표기법은 A1으로 유지되고 있습니다

HotXLS는 R1C1 수식을 어떻게 컴파일합니까?

HotXLS는 v2.175.0에 추가된 두 가지 레벨에서 이 기능을 제공합니다. 가장 자주 호출되는 TXLSCalculator.GetCompiledFormulaR1C1(UncompiledFormula: String; SheetID, CurRow, CurCol: Integer): TXLSCompiledFormula는 A1 기반의 GetCompiledFormula와 유사하게 평가 준비가 완료된 컴파일된 수식을 반환하며, 수식이 속한 셀의 0부터 시작하는 행 및 열을 지정하는 두 개의 추가 매개변수를 수용합니다. 내부적으로 TXLSFormula.GetCompiledR1C1은 원시 구문 트리를 생성하고, 독립형 함수인 R1C1ToA1(const AFormula: String; CurRow, CurCol: Integer): String이 실제 표기법 변환을 수행합니다. 작동 구조는 매우 직관적입니다: 호스트 셀 좌표를 사용하여 R1C1 텍스트를 해당하는 A1 텍스트로 변환한 다음, 정의된 이름 및 시트 간 참조를 해석하고 사용자 정의 워크시트 함수를 처리하는 기존 컴파일러 엔진으로 전달합니다

컴파일 이전에 변환 작업이 먼저 수행되므로, 하위의 모든 단계는 사용자가 처음부터 A1 수식을 직접 작성한 것처럼 처리합니다. R1C1ToA1('R2C3', 4, 3)은 두 좌표가 모두 절대 좌표이므로 호스트 셀 위치와 상관없이 항상 '$C$2'를 반환합니다. 0부터 시작하는 CurRow = 4CurCol = 3 즉 D5 셀에 놓일 수식인 R1C1ToA1('SUM(R[-3]C[0]:R[-1]C[0])', 4, 3)'SUM(D2:D4)'를 반환합니다: 오프셋이 D5 셀을 기준으로 해석되어 일반 상대 A1 참조로 출력되는 것입니다. 범위(range) 참조의 경우 특별한 처리 없이 콜론(:) 기호가 그대로 보존되며 양쪽 종단점이 독립적으로 변환됩니다

// The formula lives in D5: CurRow = 4, CurCol = 3 (both 0-based)
S := R1C1ToA1('R2C3', 4, 3);
// S = '$C$2'  (absolute row and column)

S := R1C1ToA1('SUM(R[-3]C[0]:R[-1]C[0])', 4, 3);
// S = 'SUM(D2:D4)'  (offsets resolved against D5)

S := R1C1ToA1('ROUND(R[-1]C[0], 2)', 4, 3);
// S = 'ROUND(D4, 2)'  (the R in ROUND is left alone)

단 하나의 상대 수식으로 열 채우기

가장 유용하게 사용되는 곳은 루프 처리 영역입니다. 루프의 매 반복마다 수식 텍스트를 재합성하는 A1 버전과, 수식이 상수로 고정되고 호스트 좌표 정보만 변경되는 R1C1 버전을 비교해 보십시오. 두 방식 모두 TXLSCalculator를 통해 컴파일되고 GetValue로 평가됩니다; 계산기는 생성 시 셀 공급자 콜백을 수용하여 평가기 엔진이 데이터 소스로부터 셀 값을 읽어올 수 있도록 처리합니다

// A1 style: a different formula string for every row
for Row := 1 to 500 do
begin
  FormulaText := Format('D%d*E%d', [Row + 1, Row + 1]);  // 1-based A1 rows
  Compiled := Calc.GetCompiledFormula(FormulaText, 0);
  // ... evaluate, store, free ...
end;
const
  AmountFormula = 'RC[-2]*RC[-1]';  // two cells to the left, same row
var
  Calc: TXLSCalculator;
  Compiled: TXLSCompiledFormula;
  Value: Variant;
  Row: Integer;
begin
  Calc := TXLSCalculator.Create(nil, Provider.GetValue);
  try
    for Row := 1 to 500 do
    begin
      Compiled := Calc.GetCompiledFormulaR1C1(AmountFormula, 0, Row, 5);
      try
        if Calc.GetValue(0, Compiled, Row, 5, Value, 1) = lxOk then
          StoreResult(Row, 5, Value);
      finally
        Compiled.Free;
      end;
    end;
  finally
    Calc.Free;
  end;
end;

R1C1 루프의 경우 수식 텍스트 내부에 행 관련 연산 수식이 전혀 포함되지 않습니다. 'RC[-2]*RC[-1]'은 2행이든 500행이든 일관되게 "동일 행 두 열 왼쪽 셀 곱하기 동일 행 한 열 왼쪽 셀"을 의미하며, 데이터 블록이 나중에 헤더 행 추가 등으로 아래로 이동하더라도 수식 상수는 달라지지 않고 단지 루프 경계만 변경됩니다. A1 버전은 두 군데에서 + 1 조정 오류가 생길 여지가 있으나, R1C1 버전은 그러한 위험성이 전혀 없습니다

변환 엔진이 수용하는 R1C1 형식

R1C1ToA1 변환 엔진은 v2.175.0 문서에 명시된 양방향 상대 오프셋 형식 R[n]C[m], 절대 행열 형식 RnCm, 음수 오프셋 형식 R[-n]C[m], 수식 셀 자체를 나타내는 RC, 그리고 R1C1:R3C3R[-1]C:R[1]C와 같은 범위를 지원합니다. 행 및 열 성분이 독립적으로 파싱되므로 상대 행과 절대 열이 결합된 R[1]C3과 같은 혼합 형식도 지원되며 대소문자를 구분하지 않으므로 r[-1]c[2]도 대문자 형태와 동일하게 컴파일됩니다. 대괄호로 둘러싸인 부분은 고정 기호가 없는(상대) A1 좌표로 변환되고, 숫자가 단독으로 쓰인 부분은 $ 고정 기호가 붙은 절대 좌표로 변환됩니다

여기서 한 가지 궁금한 점은 R과 C가 매우 흔하게 쓰이는 문자인데도 변환기가 수식 내의 다른 텍스트를 손상시키지 않고 어떻게 구분해 내는가 하는 점입니다. 이 구분 규칙은 토큰 기반으로 작동합니다: R 기호 앞에 다른 문자가 붙지 않은 경우에만 참조의 시작으로 고려되며, 선택적인 행 성분, 필수 C 성분, 선택적인 열 성분으로 구문 분석이 정상적으로 완료되어야 참조로 식별되고 그렇지 않으면 텍스트가 그대로 복구됩니다. ROUND(R[-1]C[0], 2) 수식에서 내부 참조만 올바르게 변환되는 것도 이 때문입니다: ROUNDR 뒤에는 숫자나 대괄호 또는 C가 아닌 O 문자가 이어지므로 참조 해석에 실패하고 함수 이름이 그대로 보존됩니다. ROW() 함수도 이와 동일한 논리로 보호되며, 참조는 항상 R로만 시작되므로 C로 시작하는 함수들은 처음부터 오인될 소지가 없습니다. v2.175.0 릴리스 노트 기준 문자열 리터럴 및 식별자도 마찬가지로 검사에서 건너뜁니다. 그럼에도 불구하고 수식 내의 큰따옴표 문자열 리터럴이 R1C1 참조 형상과 정확히 일치하는 데이터를 담고 있는 경우에는 실제 프로덕션 환경에 사용하기 전에 컴파일된 결과물을 한 번 확인하는 것이 권장됩니다

알아두어야 할 좌표 기준 및 앵커링 정보

이 API 내부에서는 두 가지 규칙이 병존하므로, 이를 명확히 조율해야 유일한 함정을 피할 수 있습니다. GetCompiledFormulaR1C1CurRowCurCol 매개변수는 계산기 API 사양에 따라 0부터 시작하는 반면, 수식 텍스트 내부의 R1C1 숫자는 Excel에서 표시하는 기준과 동일하게 1부터 시작합니다: R2C3은 2행 3열을 가리키며, 이는 $D$3이 아닌 $C$2가 됩니다. 루프 카운터가 이미 0부터 시작하도록 코딩되어 있는 경우에는 CurRow에 그대로 전달하면 되며, + 1 오프셋 처리는 사용자 코드가 아닌 변환기 엔진 내부에서 자체적으로 처리됩니다

컴파일된 수식이 대상 셀의 수명보다 더 오래 유지되어야 하는 경우 추가적인 세부 규칙이 중요하게 작용합니다. 행이나 열 성분을 생략하는 형식(RC[-1] 또는 R[2]C)의 경우, 생략된 좌표는 호스트 셀의 좌표로 결정되며 변환된 A1 텍스트에는 절대 좌표인 $ 고정형 기호로 출력됩니다. 평가 시점에는 어느 쪽이든 값이 동일하므로 영향이 없습니다. 그러나 절대 참조와 상대 참조 구분 여부는 연관 기사인 수식 참조 조정 문서에서 다룬 것과 같이 행/열 삽입 및 삭제 등의 구조 편집 시 참조 좌표가 어떻게 이동하는지를 결정짓는 핵심 요소입니다. 따라서 구조적 편집이 일어나도 좌표를 상대 참조로 계속 유지하려면 RC[-1] 대신 오프셋을 명시적으로 적어 R[0]C[-1]로 기술함으로써 변환 엔진이 앵커가 없는 A1 상대 참조로 출력하도록 유도해야 합니다

R1C1 컴파일 기능은 A1 컴파일러, 재계산 의존성 그래프 및 위에 설명된 평가 API와 함께 HotXLS Delphi Excel Component의 수식 처리 엔진의 일부로 탑재되어 있습니다. 루프를 돌며 열에 수식을 채워 넣는 방식으로 스프레드시트를 작성 중이라면, 결합 문자열 기반의 A1 방식에서 단일 R1C1 상수를 사용하는 루프로 전환하는 것이 코드 안정성을 개선하는 가장 간단하면서도 효과적인 업그레이드가 될 것입니다