XLSX 구조 비교 실습
==================
모든 Excel 파일은 합성 예제입니다. 실제 고객/개인 데이터가 아닙니다.
이 도구는 .xlsx ZIP/XML을 읽습니다. Excel을 열거나 통합문서를 저장하지 않습니다.
파일을 밖으로 전송하거나 ZIP 멤버를 디스크에 풀지 않고 매크로/외부 링크를 실행하지 않습니다.

1. ZIP의 압축을 본인이 선택한 새 폴더에 풉니다.
2. Python 3.12 이상이 설치되어 있는지 확인합니다.
   Windows: py --version
   macOS/Linux: python3 --version
3. 터미널에서 이 README.txt가 있는 폴더로 이동합니다.
4. 먼저 합성 파일 검산을 실행합니다.
   Windows: py verify_practice.py
   macOS/Linux: python3 verify_practice.py
   마지막에 PASS가 나와야 합니다. 진단 도구 실행에는 추가 패키지 설치가 없습니다.
5. 두 파일을 비교합니다.
   Windows:
   py xlsx_structure.py fixtures/01_basic.xlsx fixtures/02_unique_styles.xlsx --out my_report.json
   macOS/Linux:
   python3 xlsx_structure.py fixtures/01_basic.xlsx fixtures/02_unique_styles.xlsx --out my_report.json
6. 자신의 파일을 비교할 때는 위 두 경로를 바꾸고, 공백이 있는 경로는 따옴표로 묶습니다.
   출력 이름이 이미 있으면 오류로 멈춥니다. 새 이름을 지정하세요. 덮어쓰기 옵션은 없습니다.

파일 구성
- xlsx_structure.py: 표준 라이브러리만 사용하는 읽기 전용 진단
- verify_practice.py: 첨부한 5개 파일 SHA-256와 정확한 측정 결과, CLI 출력 보존 검산
- test_xlsx_structure.py: unittest 회귀 테스트
- fixtures/01_basic.xlsx: A1:J10에 1~100, 기본 서식
- fixtures/02_unique_styles.xlsx: 같은 100개 값에 서로 다른 채우기 100종
- fixtures/03_distant_blank.xlsx: 같은 100개 값 + XFD1048576에 채우기만 적용
- fixtures/04_image.xlsx: 같은 100개 값 + 384x384 합성 노이즈 PNG 삽입
- fixtures/05_empty.xlsx: 값/수식/저장 셀이 없는 단일 시트
- fixtures/synthetic_noise.png: 이미지 파일의 출처인 직접 생성 합성 노이즈
- expected_results.json: 첨부한 실제 파일의 전체 측정값과 SHA-256
- make_fixtures.py: 새 폴더에 예제를 재생성하는 선택 도구 (openpyxl 3.1.5, Pillow 12.3.0 필요)

검증 범위
2026-10-08 Linux 클라우드의 Python 3.12.14에서 실행했습니다.
Windows/macOS의 네이티브 Excel, 재계산 시간, 실제 UsedRange는 측정하지 않았습니다.
새 예제를 재생성하면 ZIP 메타데이터/패키지 버전에 따라 바이트/SHA-256가 달라질 수 있습니다.
첨부된 파일을 기준으로 verify_practice.py를 실행하세요.

숫자 정의
archive_bytes: 디스크상의 전체 .xlsx 바이트. ZIP 헤더/중앙 디렉터리 포함.
zip_compressed_payload_bytes: ZIP 각 멤버의 압축 데이터 크기의 합. 헤더는 제외.
zip_uncompressed_declared_bytes: ZIP 중앙 디렉터리에 기록된 압축 전 크기의 합.
zip_uncompressed_actual_bytes: 실제 스트림으로 읽은 압축 전 바이트의 합. 위 선언값과 대조.
value_cells: 수식 없이 비어 있지 않은 v 또는 inline 문자열이 있는 저장 셀.
formula_cells: f 요소가 있는 저장 셀. 재계산/수식 평가를 하지 않습니다.
blank_stored_cells: 값/수식이 없는 저장 c 요소. 저장=값+수식+이 수치.
cellXfs_definitions: styles.xml의 cellXfs 아래 실제 xf 요소 수. count 속성과 별도로 기록.
referenced_cell_styles: 저장 c 요소의 서로 다른 s 인덱스 수. s 생략은 기본 인덱스0.
row_column_style_ids: 행/열에만 지정된 기본 서식 인덱스. 위 셀 참조 수에는 포함하지 않음.
cellXfs가 없으면 실제 정의0으로 표시. 암묵적인 기본0은 별도로 셀 참조에 나타날 수 있음.
색상/채우기/보이는 모습의 종류를 세는 도구가 아닙니다. 조건부서식 dxfs도 위 정의에 포함하지 않음.
xml_dimension: 파일에 저장된 dimension ref 원문. 빈 파일에서도 A1:A1일 수 있음.
stored_cell_bounds: 실제 저장 c 요소 좌표로 계산한 경계. 빈 시트는 null.
value_or_formula_bounds: 값/수식이 있는 저장 셀의 경계. Excel의 UsedRange 속성과 다름.

보수적인 지원 범위/한도
.xlsx, transitional OOXML의 worksheet 시트, UTF-8 XML(ASCII 내용 허용), stored/deflate ZIP.
.xls/.xlsb/.xlsm, Strict OOXML, 차트 전용 시트, UTF-16/32 XML 등은 지원하지 않음.
입력 파일64MiB, ZIP멤버2000개, 멤버별 압축 전16MiB, 압축 전 총64MiB,
저장 셀 총500000개, XML 파일별 노드1000000개 제한.
선언 크기는 먼저, 실제 압축 전 바이트는 읽는 동안 검사. XML 노드는 파싱 중,
저장 셀 수는 XML을 파싱한 뒤 집계 중 검사합니다.
이 수치는 Python 프로세스 메모리 사용량의 상한이 아닙니다.
중복 경로/암호화/미지원 압축/위험한 멤버 경로/CRC 실패/잘못된 XML/DTD/ENTITY는 거부.
OOXML 전체 스키마 유효성 검사, 보안 검사, 파일 손상 원인 판정 도구는 아닙니다.
경고/오류가 나면 입력을 자동 수정하지 않고 보고서 생성을 멈춥니다.
출력은 같은 폴더에 임시 JSON을 완성한 뒤 하드 링크로 새 이름에 게시합니다.
대상이 이미 있거나 파일시스템이 하드 링크를 지원하지 않으면 기존 결과를 유지하며 실패합니다.

회귀 테스트
Windows: py -m unittest -v
macOS/Linux: python3 -m unittest -v

공식 참고문서 (2026-10-08 확인)
https://openpyxl.readthedocs.io/en/stable/styles.html
https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.cellformats?view=openxml-3.0.1
https://learn.microsoft.com/en-us/dotnet/api/documentformat.openxml.spreadsheet.sheetdimension?view=openxml-3.0.1
https://learn.microsoft.com/en-us/office/vba/api/excel.worksheet.usedrange
https://docs.python.org/3.12/library/zipfile.html
