직접 만든 XLSX, Excel 복구 메시지 원인은 셀 순서였다

일본 급여 계산기에 급여명세서를 XLSX로 내려받는 기능을 추가했다. 파일 생성까지는 별문제가 없었는데, 실제로 내려받은 파일을 Windows Excel에서 열자 Excel이 파일을 복구했다.

복구 로그에는 이 문장이 남았다.

Removed Records: Cell information from /xl/worksheets/sheet1.xml

로그가 가리키는 곳은 sheet1.xml의 셀 정보였다.

코드에서는 E17, F17이 먼저 들어갈 수 있었다

이번 급여명세서는 기존 엑셀 템플릿에 값을 채우는 방식이 아니다. JavaScript에서 셀과 스타일, 수식 등을 구성한 뒤 XLSX를 직접 생성한다.

워크시트 XML은 대략 이런 구조다.

<worksheet>
  <sheetData>
    <row r="17">
      <c r="B17">...</c>
      <c r="C17">...</c>
      <c r="E17">...</c>
      <c r="F17">...</c>
    </row>
  </sheetData>
</worksheet>

그런데 실제 생성 코드는 왼쪽 셀부터 차례대로 만드는 구조가 아니었다. 일부 행에서는 오른쪽의 E17, F17을 먼저 추가하고 나중에 B17, C17을 추가했다.

정렬 과정이 없었기 때문에 개념상 다음과 같은 순서가 만들어질 수 있었다.

<row r="17">
  <c r="E17">...</c>
  <c r="F17">...</c>
  <c r="B17">...</c>
  <c r="C17">...</c>
</row>

이 XML은 문제가 발생한 XLSX에서 그대로 추출한 내용이 아니다. 당시 생성 코드에서 확인한 셀 추가 순서를 단순화한 예다.

Microsoft의 Open XML 예제에서도 같은 행에 셀을 넣을 때 CellReference 순서에 맞는 위치를 찾아 넣고 있었다. 생성 코드에서 빠져 있던 것도 이 순서 처리였다.

같은 행의 셀을 주소 순서로 정렬했다

기존에는 셀 XML 문자열 자체를 배열에 넣었다.

list.push('<c r="' + address + '" ...');

셀 주소를 별도로 보관하지 않으니 나중에 정렬하기도 어려웠다. 그래서 주소와 XML을 함께 저장하도록 바꿨다.

list.push({
  address: address,
  markup: '<c r="' + address + '" ...'
});

그리고 sheetData를 만들기 직전에 같은 행의 셀을 열 번호 기준으로 정렬했다.

문제가 있던 형태를 예로 들면,

E17 → F17 → B17 → C17

순서로 들어갈 수 있던 셀이 최종 XML에서는

B17 → C17 → E17 → F17

순서가 된다.

생성 테스트에도 행별 셀 주소가 오름차순인지 확인하는 검사를 추가했다. 당시 문제가 있었던 형태를 포함해 B17, C17, E17, F17 순서도 검사 대상에 넣었다.

마지막 확인은 Windows Excel에서 했다

코드 수정 후 별도의 테스트 환경에서 생성한 XLSX는 셀 주소가 순서대로 나왔고 기존 계산 결과와 수식도 유지됐다.

처음 문제가 발생한 곳은 Windows Excel이었으니 마지막 확인도 Excel에서 했다. 수정된 코드로 XLSX를 새로 생성해서 다시 열어봤고, 이전에 나오던 복구 메시지는 더 이상 나타나지 않았다.

광고 차단 알림

광고 클릭 제한을 초과하여 광고가 차단되었습니다.

단시간에 반복적인 광고 클릭은 시스템에 의해 감지되며, IP가 수집되어 사이트 관리자가 확인 가능합니다.