Xlsx
스프레드시트 파일이 주요 입력 또는 출력일 때마다 이 기술을 사용하십시오. 이는 사용자가 기존.xlsx,.xlsm,.csv 또는.tsv 파일 열기, 읽기, 편집 또는 수정(예: 열 추가, 수식 계산, 형식 지정, 차트 작성, 지저분한 데이터 정리)을 원하는 모든 작업을 의미합니다. 처음부터 또는 다른 데이터 소스로부터 새 스프레드시트를 만듭니다. 또는 표 형식 파일 형식 간에 변환할 수 있습니다. 특히 사용자가 이름이나 경로로 스프레드시트 파일을 참조하고("내 다운로드의 xlsx"와 같은) 아무 작업도 수행하거나 생성하기를 원할 때 트리거됩니다. 또한 지저분한 표 형식 데이터 파일(잘못된 행, 잘못된 헤더, 정크 데이터)을 정리하거나 적절한 스프레드시트로 재구성하기 위해 트리거됩니다. 결과물은 스프레드시트 파일이어야 합니다. 기본 결과물이 Word 문서, HTML 보고서, 독립 실행형 Python 스크립트, 데이터베이스 파이프라인 또는 Google Sheets API 통합인 경우 표 형식 데이터가 포함되어 있더라도 트리거하지 마세요.
출처: 인류학/기술(MIT)에서 채택한 콘텐츠.
모든 엑셀 파일
전문 글꼴
- 사용자가 달리 지시하지 않는 한 모든 결과물에 일관되고 전문적인 글꼴(예: Arial, Times New Roman)을 사용하십시오.
제로 수식 오류
- 모든 Excel 모델은 수식 오류(#REF!, #DIV/0!, #VALUE!, #N/A, #NAME?)가 0인 상태로 제공되어야 합니다.
기존 템플릿 유지(템플릿 업데이트 시)
- 파일을 수정할 때 기존 형식, 스타일 및 규칙을 연구하고 정확하게 일치시킵니다.
- 정해진 패턴이 있는 파일에 표준화된 형식을 적용하지 마세요.
- 기존 템플릿 규칙은 항상 이러한 지침보다 우선합니다.
재무 모델
색상 코딩 표준
사용자 또는 기존 템플릿이 달리 명시하지 않는 한
업계 표준 색상 규칙
- 파란색 텍스트(RGB: 0,0,255): 시나리오에 따라 사용자가 변경하게 될 하드코딩된 입력 및 숫자
- 검은색 텍스트(RGB: 0,0,0): 모든 수식 및 계산
- 녹색 텍스트(RGB: 0,128,0): 동일한 통합 문서 내의 다른 워크시트에서 가져오는 링크
- 빨간색 텍스트(RGB: 255,0,0): 다른 파일에 대한 외부 링크
- 노란색 배경(RGB: 255,255,0): 주의가 필요한 주요 가정 또는 업데이트가 필요한 셀
숫자 형식 표준
필수 형식 규칙
- 연도: 텍스트 문자열 형식(예: "2,024"가 아닌 "2024")
- 통화: $#,##0 형식을 사용합니다. 항상 헤더에 단위를 지정하세요("수익($mm)")
- 0: 숫자 형식을 사용하여 백분율을 포함하여 모든 0을 "-"로 만듭니다(예: "$#,##0;($#,##0);-")
- 백분율: 기본값은 0.0% 형식(소수점 첫째 자리)입니다.
- 배수: 평가 배수(EV/EBITDA, P/E)를 0.0x로 형식화합니다.
- 음수: -123이 아닌 괄호(123)를 사용하세요.
수식 구성 규칙
가정 배치
- 모든 가정(성장률, 마진, 배수 등)을 별도의 가정 셀에 배치합니다.
- 수식에 하드코딩된 값 대신 셀 참조 사용
- 예: =B51.05 대신 =B5(1+$B$6) 사용
수식 오류 방지
- 모든 셀 참조가 올바른지 확인하세요.
- 범위의 개별 오류 확인
- 모든 예측 기간에 걸쳐 일관된 공식 보장
- 극단적인 경우(0 값, 음수)로 테스트
- 의도하지 않은 순환 참조가 없는지 확인
하드코드에 대한 문서화 요구 사항
- 주석 또는 옆의 셀(테이블의 끝인 경우) 형식: "출처: [시스템/문서], [날짜], [특정 참조], [해당되는 경우 URL]"
- 예:
- "출처: Company 10-K, FY2024, 45페이지, 수익 메모, [SEC EDGAR URL]"
- "출처: Company 10-Q, Q2 2025, Exhibit 99.1, [SEC EDGAR URL]"
- "출처: Bloomberg Terminal, 2025년 8월 15일, AAPL US Equity"
- "출처: FactSet, 2025년 8월 20일, 합의 추정 화면"
XLSX 생성, 편집 및 분석
개요
사용자가.xlsx 파일의 내용을 생성, 편집 또는 분석하도록 요청할 수 있습니다. 다양한 작업에 사용할 수 있는 다양한 도구와 워크플로가 있습니다.
중요한 요구 사항
수식 재계산에 LibreOffice 필요:scripts/recalc.py스크립트를 사용하여 수식 값을 다시 계산하기 위해 LibreOffice가 설치되었다고 가정할 수 있습니다. 스크립트는 Unix 소켓이 제한된(scripts/office/soffice.py에서 처리) 샌드박스 환경을 포함하여 처음 실행 시 LibreOffice를 자동으로 구성합니다.
데이터 읽기 및 분석
팬더를 이용한 데이터 분석
데이터 분석, 시각화 및 기본 작업에는 강력한 데이터 조작 기능을 제공하는 pandas를 사용하세요.
import pandas as pd
# Read Excel
df = pd.read_excel('file.xlsx') # Default: first sheet
all_sheets = pd.read_excel('file.xlsx', sheet_name=None) # All sheets as dict
# Analyze
df.head() # Preview data
df.info() # Column info
df.describe() # Statistics
# Write Excel
df.to_excel('output.xlsx', index=False)Excel 파일 워크플로
중요: 하드코딩된 값이 아닌 수식을 사용하세요.
Python에서 값을 계산하고 하드코딩하는 대신 항상 Excel 수식을 사용하세요. 이렇게 하면 스프레드시트가 동적이며 업데이트 가능한 상태로 유지됩니다.
잘못됨 - 계산된 값을 하드코딩함
# Bad: Calculating in Python and hardcoding result
total = df['Sales'].sum()
sheet['B10'] = total # Hardcodes 5000
# Bad: Computing growth rate in Python
growth = (df.iloc[-1]['Revenue'] - df.iloc[0]['Revenue']) / df.iloc[0]['Revenue']
sheet['C5'] = growth # Hardcodes 0.15
# Bad: Python calculation for average
avg = sum(values) / len(values)
sheet['D20'] = avg # Hardcodes 42.5올바른 - Excel 수식 사용
# Good: Let Excel calculate the sum
sheet['B10'] = '=SUM(B2:B9)'
# Good: Growth rate as Excel formula
sheet['C5'] = '=(C4-C2)/C2'
# Good: Average using Excel function
sheet['D20'] = '=AVERAGE(D2:D19)'이는 합계, 백분율, 비율, 차이 등 모든 계산에 적용됩니다. 스프레드시트는 소스 데이터가 변경되면 다시 계산할 수 있어야 합니다.
일반적인 작업흐름
- 도구 선택: 데이터용 pandas, 수식/서식 지정용 openpyxl
- 만들기/로드: 새 통합 문서를 만들거나 기존 파일을 로드합니다.
- 수정: 데이터, 수식, 서식 추가/수정
- 저장: 파일에 쓰기
- 수식 다시 계산(수식을 사용하는 경우 필수): scripts/recalc.py 스크립트를 사용하세요.
python scripts/recalc.py output.xlsx - 오류 확인 및 수정:
- 스크립트는 오류 세부정보와 함께 JSON을 반환합니다.
status가errors_found인 경우error_summary에서 특정 오류 유형 및 위치를 확인하세요.- 식별된 오류를 수정하고 다시 계산하세요.
- 해결해야 할 일반적인 오류:
#REF!: 잘못된 셀 참조#DIV/0!: 0으로 나누기#VALUE!: 수식에 잘못된 데이터 유형이 있습니다.#NAME?: 인식할 수 없는 수식 이름
새 Excel 파일 만들기
# Using openpyxl for formulas and formatting
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
wb = Workbook()
sheet = wb.active
# Add data
sheet['A1'] = 'Hello'
sheet['B1'] = 'World'
sheet.append(['Row', 'of', 'data'])
# Add formula
sheet['B2'] = '=SUM(A1:A10)'
# Formatting
sheet['A1'].font = Font(bold=True, color='FF0000')
sheet['A1'].fill = PatternFill('solid', start_color='FFFF00')
sheet['A1'].alignment = Alignment(horizontal='center')
# Column width
sheet.column_dimensions['A'].width = 20
wb.save('output.xlsx')기존 Excel 파일 편집
# Using openpyxl to preserve formulas and formatting
from openpyxl import load_workbook
# Load existing file
wb = load_workbook('existing.xlsx')
sheet = wb.active # or wb['SheetName'] for specific sheet
# Working with multiple sheets
for sheet_name in wb.sheetnames:
sheet = wb[sheet_name]
print(f"Sheet: {sheet_name}")
# Modify cells
sheet['A1'] = 'New Value'
sheet.insert_rows(2) # Insert row at position 2
sheet.delete_cols(3) # Delete column 3
# Add new sheet
new_sheet = wb.create_sheet('NewSheet')
new_sheet['A1'] = 'Data'
wb.save('modified.xlsx')수식 다시 계산
openpyxl로 생성되거나 수정된 Excel 파일에는 수식이 문자열로 포함되어 있지만 계산된 값은 포함되어 있지 않습니다. 제공된scripts/recalc.py스크립트를 사용하여 수식을 다시 계산합니다.
python scripts/recalc.py <excel_file> [timeout_seconds]예:
python scripts/recalc.py output.xlsx 30스크립트:
- 처음 실행 시 LibreOffice 매크로를 자동으로 설정합니다.
- 모든 시트의 모든 수식을 다시 계산합니다.
- Excel 오류(#REF!, #DIV/0! 등)가 있는지 모든 셀을 검사합니다.
- 자세한 오류 위치 및 개수와 함께 JSON을 반환합니다.
- Linux와 macOS 모두에서 작동
공식 검증 체크리스트
수식이 올바르게 작동하는지 확인하는 빠른 검사:
필수 검증
- 2-3개의 샘플 참조 테스트: 전체 모델을 구축하기 전에 올바른 값을 가져오는지 확인합니다.
- 열 매핑: Excel 열 일치 확인(예: 열 64 = BK가 아닌 BL)
- 행 오프셋: Excel 행은 1부터 인덱싱됩니다(DataFrame 행 5 = Excel 행 6).
일반적인 함정
- NaN 처리:
pd.notna()를 사용하여 null 값을 확인합니다. - 맨 오른쪽 열: FY 데이터는 종종 50개 이상의 열에 있음
- 여러 일치: 첫 번째 항목뿐만 아니라 모든 항목을 검색합니다.
- 0으로 나누기: 수식에
/를 사용하기 전에 분모를 확인하세요(#DIV/0!) - 잘못된 참조: 모든 셀 참조가 의도한 셀을 가리키는지 확인하세요(#REF!)
- 시트 간 참조: 시트 연결에 올바른 형식(Sheet1!A1)을 사용합니다.
공식 테스트 전략
- 작게 시작: 광범위하게 적용하기 전에 2~3개의 셀에서 수식을 테스트합니다.
- 종속성 확인: 수식에서 참조된 모든 셀이 존재하는지 확인하세요.
- 임시 사례 테스트: 0, 음수 및 매우 큰 값을 포함합니다.
scripts/recalc.py 출력 해석
스크립트는 오류 세부정보와 함께 JSON을 반환합니다.
{
"status": "success", // or "errors_found"
"total_errors": 0, // Total error count
"total_formulas": 42, // Number of formulas in file
"error_summary": { // Only present if errors found
"#REF!": {
"count": 2,
"locations": ["Sheet1!B5", "Sheet1!C10"]
}
}
}모범 사례
도서관 선택
- pandas: 데이터 분석, 대량 작업 및 간단한 데이터 내보내기에 가장 적합
- openpyxl: 복잡한 서식, 수식 및 Excel 관련 기능에 가장 적합합니다.
openpyxl로 작업하기
- 셀 인덱스는 1부터 시작합니다(행=1, 열=1은 셀 A1을 나타냄).
data_only=True를 사용하여 계산된 값을 읽습니다.load_workbook('file.xlsx', data_only=True)- 경고:
data_only=True로 열고 저장하면 수식이 값으로 바뀌고 영구적으로 손실됩니다. - 대용량 파일의 경우: 읽기에는
read_only=True를 사용하고 쓰기에는write_only=True를 사용하세요. - 수식은 유지되지만 평가되지는 않습니다. 값을 업데이트하려면 scripts/recalc.py를 사용하세요.
팬더와 함께 일하기
- 추론 문제를 방지하기 위한 데이터 유형 지정:
pd.read_excel('file.xlsx', dtype={'id': str}) - 대용량 파일의 경우 특정 열을 읽으십시오:
pd.read_excel('file.xlsx', usecols=['A', 'C', 'E']) - 날짜를 올바르게 처리하세요:
pd.read_excel('file.xlsx', parse_dates=['date_column'])
코드 스타일 지침
중요: Excel 작업을 위한 Python 코드를 생성할 때:
- 불필요한 주석 없이 최소한의 간결한 Python 코드 작성
- 장황한 변수 이름과 중복 작업 방지
- 불필요한 인쇄문을 피하세요
Excel 파일 자체의 경우:
- 복잡한 수식이나 중요한 가정이 포함된 셀에 설명 추가
- 하드코딩된 값에 대한 문서 데이터 원본
- 주요 계산 및 모델 섹션에 대한 메모 포함
리소스 파일
라이센스.txt
바이너리 리소스
스크립트/사무실/helpers/init.py
스크립트/office/helpers/init.py 다운로드
바이너리 리소스
스크립트/사무실/helpers/merge_runs.py
스크립트 다운로드/office/helpers/merge_runs.py
"""Merge adjacent runs with identical formatting in DOCX.
Merges adjacent <w:r> elements that have identical <w:rPr> properties.
Works on runs in paragraphs and inside tracked changes (<w:ins>, <w:del>).
Also:
- Removes rsid attributes from runs (revision metadata that doesn't affect rendering)
- Removes proofErr elements (spell/grammar markers that block merging)
"""
from pathlib import Path
import defusedxml.minidom
def merge_runs(input_dir: str) -> tuple[int, str]:
doc_xml = Path(input_dir) / "word" / "document.xml"
if not doc_xml.exists():
return 0, f"Error: {doc_xml} not found"
try:
dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
root = dom.documentElement
_remove_elements(root, "proofErr")
_strip_run_rsid_attrs(root)
containers = {run.parentNode for run in _find_elements(root, "r")}
merge_count = 0
for container in containers:
merge_count += _merge_runs_in(container)
doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
return merge_count, f"Merged {merge_count} runs"
except Exception as e:
return 0, f"Error: {e}"
def _find_elements(root, tag: str) -> list:
results = []
def traverse(node):
if node.nodeType == node.ELEMENT_NODE:
name = node.localName or node.tagName
if name == tag or name.endswith(f":{tag}"):
results.append(node)
for child in node.childNodes:
traverse(child)
traverse(root)
return results
def _get_child(parent, tag: str):
for child in parent.childNodes:
if child.nodeType == child.ELEMENT_NODE:
name = child.localName or child.tagName
if name == tag or name.endswith(f":{tag}"):
return child
return None
def _get_children(parent, tag: str) -> list:
results = []
for child in parent.childNodes:
if child.nodeType == child.ELEMENT_NODE:
name = child.localName or child.tagName
if name == tag or name.endswith(f":{tag}"):
results.append(child)
return results
def _is_adjacent(elem1, elem2) -> bool:
node = elem1.nextSibling
while node:
if node == elem2:
return True
if node.nodeType == node.ELEMENT_NODE:
return False
if node.nodeType == node.TEXT_NODE and node.data.strip():
return False
node = node.nextSibling
return False
def _remove_elements(root, tag: str):
for elem in _find_elements(root, tag):
if elem.parentNode:
elem.parentNode.removeChild(elem)
def _strip_run_rsid_attrs(root):
for run in _find_elements(root, "r"):
for attr in list(run.attributes.values()):
if "rsid" in attr.name.lower():
run.removeAttribute(attr.name)
def _merge_runs_in(container) -> int:
merge_count = 0
run = _first_child_run(container)
while run:
while True:
next_elem = _next_element_sibling(run)
if next_elem and _is_run(next_elem) and _can_merge(run, next_elem):
_merge_run_content(run, next_elem)
container.removeChild(next_elem)
merge_count += 1
else:
break
_consolidate_text(run)
run = _next_sibling_run(run)
return merge_count
def _first_child_run(container):
for child in container.childNodes:
if child.nodeType == child.ELEMENT_NODE and _is_run(child):
return child
return None
def _next_element_sibling(node):
sibling = node.nextSibling
while sibling:
if sibling.nodeType == sibling.ELEMENT_NODE:
return sibling
sibling = sibling.nextSibling
return None
def _next_sibling_run(node):
sibling = node.nextSibling
while sibling:
if sibling.nodeType == sibling.ELEMENT_NODE:
if _is_run(sibling):
return sibling
sibling = sibling.nextSibling
return None
def _is_run(node) -> bool:
name = node.localName or node.tagName
return name == "r" or name.endswith(":r")
def _can_merge(run1, run2) -> bool:
rpr1 = _get_child(run1, "rPr")
rpr2 = _get_child(run2, "rPr")
if (rpr1 is None) != (rpr2 is None):
return False
if rpr1 is None:
return True
return rpr1.toxml() == rpr2.toxml()
def _merge_run_content(target, source):
for child in list(source.childNodes):
if child.nodeType == child.ELEMENT_NODE:
name = child.localName or child.tagName
if name != "rPr" and not name.endswith(":rPr"):
target.appendChild(child)
def _consolidate_text(run):
t_elements = _get_children(run, "t")
for i in range(len(t_elements) - 1, 0, -1):
curr, prev = t_elements[i], t_elements[i - 1]
if _is_adjacent(prev, curr):
prev_text = prev.firstChild.data if prev.firstChild else ""
curr_text = curr.firstChild.data if curr.firstChild else ""
merged = prev_text + curr_text
if prev.firstChild:
prev.firstChild.data = merged
else:
prev.appendChild(run.ownerDocument.createTextNode(merged))
if merged.startswith(" ") or merged.endswith(" "):
prev.setAttribute("xml:space", "preserve")
elif prev.hasAttribute("xml:space"):
prev.removeAttribute("xml:space")
run.removeChild(curr)스크립트/office/helpers/simplify_redlines.py
스크립트/office/helpers/simplify_redlines.py 다운로드
"""Simplify tracked changes by merging adjacent w:ins or w:del elements.
Merges adjacent <w:ins> elements from the same author into a single element.
Same for <w:del> elements. This makes heavily-redlined documents easier to
work with by reducing the number of tracked change wrappers.
Rules:
- Only merges w:ins with w:ins, w:del with w:del (same element type)
- Only merges if same author (ignores timestamp differences)
- Only merges if truly adjacent (only whitespace between them)
"""
import xml.etree.ElementTree as ET
import zipfile
from pathlib import Path
import defusedxml.minidom
WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
def simplify_redlines(input_dir: str) -> tuple[int, str]:
doc_xml = Path(input_dir) / "word" / "document.xml"
if not doc_xml.exists():
return 0, f"Error: {doc_xml} not found"
try:
dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
root = dom.documentElement
merge_count = 0
containers = _find_elements(root, "p") + _find_elements(root, "tc")
for container in containers:
merge_count += _merge_tracked_changes_in(container, "ins")
merge_count += _merge_tracked_changes_in(container, "del")
doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
return merge_count, f"Simplified {merge_count} tracked changes"
except Exception as e:
return 0, f"Error: {e}"
def _merge_tracked_changes_in(container, tag: str) -> int:
merge_count = 0
tracked = [
child
for child in container.childNodes
if child.nodeType == child.ELEMENT_NODE and _is_element(child, tag)
]
if len(tracked) < 2:
return 0
i = 0
while i < len(tracked) - 1:
curr = tracked[i]
next_elem = tracked[i + 1]
if _can_merge_tracked(curr, next_elem):
_merge_tracked_content(curr, next_elem)
container.removeChild(next_elem)
tracked.pop(i + 1)
merge_count += 1
else:
i += 1
return merge_count
def _is_element(node, tag: str) -> bool:
name = node.localName or node.tagName
return name == tag or name.endswith(f":{tag}")
def _get_author(elem) -> str:
author = elem.getAttribute("w:author")
if not author:
for attr in elem.attributes.values():
if attr.localName == "author" or attr.name.endswith(":author"):
return attr.value
return author
def _can_merge_tracked(elem1, elem2) -> bool:
if _get_author(elem1) != _get_author(elem2):
return False
node = elem1.nextSibling
while node and node != elem2:
if node.nodeType == node.ELEMENT_NODE:
return False
if node.nodeType == node.TEXT_NODE and node.data.strip():
return False
node = node.nextSibling
return True
def _merge_tracked_content(target, source):
while source.firstChild:
child = source.firstChild
source.removeChild(child)
target.appendChild(child)
def _find_elements(root, tag: str) -> list:
results = []
def traverse(node):
if node.nodeType == node.ELEMENT_NODE:
name = node.localName or node.tagName
if name == tag or name.endswith(f":{tag}"):
results.append(node)
for child in node.childNodes:
traverse(child)
traverse(root)
return results
def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]:
if not doc_xml_path.exists():
return {}
try:
tree = ET.parse(doc_xml_path)
root = tree.getroot()
except ET.ParseError:
return {}
namespaces = {"w": WORD_NS}
author_attr = f"{{{WORD_NS}}}author"
authors: dict[str, int] = {}
for tag in ["ins", "del"]:
for elem in root.findall(f".//w:{tag}", namespaces):
author = elem.get(author_attr)
if author:
authors[author] = authors.get(author, 0) + 1
return authors
def _get_authors_from_docx(docx_path: Path) -> dict[str, int]:
try:
with zipfile.ZipFile(docx_path, "r") as zf:
if "word/document.xml" not in zf.namelist():
return {}
with zf.open("word/document.xml") as f:
tree = ET.parse(f)
root = tree.getroot()
namespaces = {"w": WORD_NS}
author_attr = f"{{{WORD_NS}}}author"
authors: dict[str, int] = {}
for tag in ["ins", "del"]:
for elem in root.findall(f".//w:{tag}", namespaces):
author = elem.get(author_attr)
if author:
authors[author] = authors.get(author, 0) + 1
return authors
except (zipfile.BadZipFile, ET.ParseError):
return {}
def infer_author(modified_dir: Path, original_docx: Path, default: str = "Claude") -> str:
modified_xml = modified_dir / "word" / "document.xml"
modified_authors = get_tracked_change_authors(modified_xml)
if not modified_authors:
return default
original_authors = _get_authors_from_docx(original_docx)
new_changes: dict[str, int] = {}
for author, count in modified_authors.items():
original_count = original_authors.get(author, 0)
diff = count - original_count
if diff > 0:
new_changes[author] = diff
if not new_changes:
return default
if len(new_changes) == 1:
return next(iter(new_changes))
raise ValueError(
f"Multiple authors added new changes: {new_changes}. "
"Cannot infer which author to validate."
)스크립트/office/pack.py
"""Pack a directory into a DOCX, PPTX, or XLSX file.
Validates with auto-repair, condenses XML formatting, and creates the Office file.
Usage:
python pack.py <input_directory> <output_file> [--original <file>] [--validate true|false]
Examples:
python pack.py unpacked/ output.docx --original input.docx
python pack.py unpacked/ output.pptx --validate false
"""
import argparse
import sys
import shutil
import tempfile
import zipfile
from pathlib import Path
import defusedxml.minidom
from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
def pack(
input_directory: str,
output_file: str,
original_file: str | None = None,
validate: bool = True,
infer_author_func=None,
) -> tuple[None, str]:
input_dir = Path(input_directory)
output_path = Path(output_file)
suffix = output_path.suffix.lower()
if not input_dir.is_dir():
return None, f"Error: {input_dir} is not a directory"
if suffix not in {".docx", ".pptx", ".xlsx"}:
return None, f"Error: {output_file} must be a .docx, .pptx, or .xlsx file"
if validate and original_file:
original_path = Path(original_file)
if original_path.exists():
success, output = _run_validation(
input_dir, original_path, suffix, infer_author_func
)
if output:
print(output)
if not success:
return None, f"Error: Validation failed for {input_dir}"
with tempfile.TemporaryDirectory() as temp_dir:
temp_content_dir = Path(temp_dir) / "content"
shutil.copytree(input_dir, temp_content_dir)
for pattern in ["*.xml", "*.rels"]:
for xml_file in temp_content_dir.rglob(pattern):
_condense_xml(xml_file)
output_path.parent.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf:
for f in temp_content_dir.rglob("*"):
if f.is_file():
zf.write(f, f.relative_to(temp_content_dir))
return None, f"Successfully packed {input_dir} to {output_file}"
def _run_validation(
unpacked_dir: Path,
original_file: Path,
suffix: str,
infer_author_func=None,
) -> tuple[bool, str | None]:
output_lines = []
validators = []
if suffix == ".docx":
author = "Claude"
if infer_author_func:
try:
author = infer_author_func(unpacked_dir, original_file)
except ValueError as e:
print(f"Warning: {e} Using default author 'Claude'.", file=sys.stderr)
validators = [
DOCXSchemaValidator(unpacked_dir, original_file),
RedliningValidator(unpacked_dir, original_file, author=author),
]
elif suffix == ".pptx":
validators = [PPTXSchemaValidator(unpacked_dir, original_file)]
if not validators:
return True, None
total_repairs = sum(v.repair() for v in validators)
if total_repairs:
output_lines.append(f"Auto-repaired {total_repairs} issue(s)")
success = all(v.validate() for v in validators)
if success:
output_lines.append("All validations PASSED!")
return success, "\n".join(output_lines) if output_lines else None
def _condense_xml(xml_file: Path) -> None:
try:
with open(xml_file, encoding="utf-8") as f:
dom = defusedxml.minidom.parse(f)
for element in dom.getElementsByTagName("*"):
if element.tagName.endswith(":t"):
continue
for child in list(element.childNodes):
if (
child.nodeType == child.TEXT_NODE
and child.nodeValue
and child.nodeValue.strip() == ""
) or child.nodeType == child.COMMENT_NODE:
element.removeChild(child)
xml_file.write_bytes(dom.toxml(encoding="UTF-8"))
except Exception as e:
print(f"ERROR: Failed to parse {xml_file.name}: {e}", file=sys.stderr)
raise
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Pack a directory into a DOCX, PPTX, or XLSX file"
)
parser.add_argument("input_directory", help="Unpacked Office document directory")
parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)")
parser.add_argument(
"--original",
help="Original file for validation comparison",
)
parser.add_argument(
"--validate",
type=lambda x: x.lower() == "true",
default=True,
metavar="true|false",
help="Run validation with auto-repair (default: true)",
)
args = parser.parse_args()
_, message = pack(
args.input_directory,
args.output_file,
original_file=args.original,
validate=args.validate,
)
print(message)
if "Error" in message:
sys.exit(1)scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart드로잉.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/dml-chart드로잉.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ISO-IEC29500-4_2016/dml-diagram.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ISO-IEC29500-4_2016/dml-main.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ISO-IEC29500-4_2016/dml-picture.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheet드로잉.xsd
scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheet드로잉.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessing드로잉.xsd
scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessing드로잉.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ISO-IEC29500-4_2016/pml.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/pml.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ISO-IEC29500-4_2016/sml.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/sml.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ISO-IEC29500-4_2016/vml-main.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/vml-office드로잉.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/vml-office드로잉.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentation드로잉.xsd
[scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentation Drawing.xsd 다운로드](/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentation Drawing.xsd)
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheet드로잉.xsd
scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheet드로잉.xsd 다운로드
바이너리 리소스
scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessing드로잉.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessing드로잉.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ISO-IEC29500-4_2016/wml.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/wml.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ISO-IEC29500-4_2016/xml.xsd
스크립트/office/schemas/ISO-IEC29500-4_2016/xml.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ecma/fouth-edition/opc-contentTypes.xsd
스크립트/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ecma/fouth-edition/opc-coreProperties.xsd
스크립트/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/ecma/fouth-edition/opc-digSig.xsd
스크립트/office/schemas/ecma/fouth-edition/opc-digSig.xsd 다운로드
바이너리 리소스
스크립트/office/schemas/ecma/fouth-edition/opc-relationships.xsd
스크립트/office/schemas/ecma/fouth-edition/opc-relationships.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/mce/mc.xsd
스크립트 다운로드/office/schemas/mce/mc.xsd
바이너리 리소스
스크립트/사무실/스키마/microsoft/wml-2010.xsd
스크립트/office/schemas/microsoft/wml-2010.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/microsoft/wml-2012.xsd
스크립트/office/schemas/microsoft/wml-2012.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/microsoft/wml-2018.xsd
스크립트/office/schemas/microsoft/wml-2018.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/microsoft/wml-cex-2018.xsd
스크립트/office/schemas/microsoft/wml-cex-2018.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/microsoft/wml-cid-2016.xsd
스크립트/office/schemas/microsoft/wml-cid-2016.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/microsoft/wml-sdtdatahash-2020.xsd
스크립트/office/schemas/microsoft/wml-sdtdatahash-2020.xsd 다운로드
바이너리 리소스
스크립트/사무실/스키마/microsoft/wml-symex-2015.xsd
스크립트/office/schemas/microsoft/wml-symex-2015.xsd 다운로드
바이너리 리소스
스크립트/office/soffice.py
"""
Helper for running LibreOffice (soffice) in environments where AF_UNIX
sockets may be blocked (e.g., sandboxed VMs). Detects the restriction
at runtime and applies an LD_PRELOAD shim if needed.
Usage:
from office.soffice import run_soffice, get_soffice_env
# Option 1 – run soffice directly
result = run_soffice(["--headless", "--convert-to", "pdf", "input.docx"])
# Option 2 – get env dict for your own subprocess calls
env = get_soffice_env()
subprocess.run(["soffice", ...], env=env)
"""
import os
import socket
import subprocess
import tempfile
from pathlib import Path
def get_soffice_env() -> dict:
env = os.environ.copy()
env["SAL_USE_VCLPLUGIN"] = "svp"
if _needs_shim():
shim = _ensure_shim()
env["LD_PRELOAD"] = str(shim)
return env
def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess:
env = get_soffice_env()
return subprocess.run(["soffice"] + args, env=env, **kwargs)
_SHIM_SO = Path(tempfile.gettempdir()) / "lo_socket_shim.so"
def _needs_shim() -> bool:
try:
s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
s.close()
return False
except OSError:
return True
def _ensure_shim() -> Path:
if _SHIM_SO.exists():
return _SHIM_SO
src = Path(tempfile.gettempdir()) / "lo_socket_shim.c"
src.write_text(_SHIM_SOURCE)
subprocess.run(
["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"],
check=True,
capture_output=True,
)
src.unlink()
return _SHIM_SO
_SHIM_SOURCE = r"""
#define _GNU_SOURCE
#include <dlfcn.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <unistd.h>
static int (*real_socket)(int, int, int);
static int (*real_socketpair)(int, int, int, int[2]);
static int (*real_listen)(int, int);
static int (*real_accept)(int, struct sockaddr *, socklen_t *);
static int (*real_close)(int);
static int (*real_read)(int, void *, size_t);
/* Per-FD bookkeeping (FDs >= 1024 are passed through unshimmed). */
static int is_shimmed[1024];
static int peer_of[1024];
static int wake_r[1024]; /* accept() blocks reading this */
static int wake_w[1024]; /* close() writes to this */
static int listener_fd = -1; /* FD that received listen() */
__attribute__((constructor))
static void init(void) {
real_socket = dlsym(RTLD_NEXT, "socket");
real_socketpair = dlsym(RTLD_NEXT, "socketpair");
real_listen = dlsym(RTLD_NEXT, "listen");
real_accept = dlsym(RTLD_NEXT, "accept");
real_close = dlsym(RTLD_NEXT, "close");
real_read = dlsym(RTLD_NEXT, "read");
for (int i = 0; i < 1024; i++) {
peer_of[i] = -1;
wake_r[i] = -1;
wake_w[i] = -1;
}
}
/* ---- socket ---------------------------------------------------------- */
int socket(int domain, int type, int protocol) {
if (domain == AF_UNIX) {
int fd = real_socket(domain, type, protocol);
if (fd >= 0) return fd;
/* socket(AF_UNIX) blocked – fall back to socketpair(). */
int sv[2];
if (real_socketpair(domain, type, protocol, sv) == 0) {
if (sv[0] >= 0 && sv[0] < 1024) {
is_shimmed[sv[0]] = 1;
peer_of[sv[0]] = sv[1];
int wp[2];
if (pipe(wp) == 0) {
wake_r[sv[0]] = wp[0];
wake_w[sv[0]] = wp[1];
}
}
return sv[0];
}
errno = EPERM;
return -1;
}
return real_socket(domain, type, protocol);
}
/* ---- listen ---------------------------------------------------------- */
int listen(int sockfd, int backlog) {
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
listener_fd = sockfd;
return 0;
}
return real_listen(sockfd, backlog);
}
/* ---- accept ---------------------------------------------------------- */
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) {
if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) {
/* Block until close() writes to the wake pipe. */
if (wake_r[sockfd] >= 0) {
char buf;
real_read(wake_r[sockfd], &buf, 1);
}
errno = ECONNABORTED;
return -1;
}
return real_accept(sockfd, addr, addrlen);
}
/* ---- close ----------------------------------------------------------- */
int close(int fd) {
if (fd >= 0 && fd < 1024 && is_shimmed[fd]) {
int was_listener = (fd == listener_fd);
is_shimmed[fd] = 0;
if (wake_w[fd] >= 0) { /* unblock accept() */
char c = 0;
write(wake_w[fd], &c, 1);
real_close(wake_w[fd]);
wake_w[fd] = -1;
}
if (wake_r[fd] >= 0) { real_close(wake_r[fd]); wake_r[fd] = -1; }
if (peer_of[fd] >= 0) { real_close(peer_of[fd]); peer_of[fd] = -1; }
if (was_listener)
_exit(0); /* conversion done – exit */
}
return real_close(fd);
}
"""
if __name__ == "__main__":
import sys
result = run_soffice(sys.argv[1:])
sys.exit(result.returncode)스크립트/office/unpack.py
"""Unpack Office files (DOCX, PPTX, XLSX) for editing.
Extracts the ZIP archive, pretty-prints XML files, and optionally:
- Merges adjacent runs with identical formatting (DOCX only)
- Simplifies adjacent tracked changes from same author (DOCX only)
Usage:
python unpack.py <office_file> <output_dir> [options]
Examples:
python unpack.py document.docx unpacked/
python unpack.py presentation.pptx unpacked/
python unpack.py document.docx unpacked/ --merge-runs false
"""
import argparse
import sys
import zipfile
from pathlib import Path
import defusedxml.minidom
from helpers.merge_runs import merge_runs as do_merge_runs
from helpers.simplify_redlines import simplify_redlines as do_simplify_redlines
SMART_QUOTE_REPLACEMENTS = {
"\u201c": "“",
"\u201d": "”",
"\u2018": "‘",
"\u2019": "’",
}
def unpack(
input_file: str,
output_directory: str,
merge_runs: bool = True,
simplify_redlines: bool = True,
) -> tuple[None, str]:
input_path = Path(input_file)
output_path = Path(output_directory)
suffix = input_path.suffix.lower()
if not input_path.exists():
return None, f"Error: {input_file} does not exist"
if suffix not in {".docx", ".pptx", ".xlsx"}:
return None, f"Error: {input_file} must be a .docx, .pptx, or .xlsx file"
try:
output_path.mkdir(parents=True, exist_ok=True)
with zipfile.ZipFile(input_path, "r") as zf:
zf.extractall(output_path)
xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels"))
for xml_file in xml_files:
_pretty_print_xml(xml_file)
message = f"Unpacked {input_file} ({len(xml_files)} XML files)"
if suffix == ".docx":
if simplify_redlines:
simplify_count, _ = do_simplify_redlines(str(output_path))
message += f", simplified {simplify_count} tracked changes"
if merge_runs:
merge_count, _ = do_merge_runs(str(output_path))
message += f", merged {merge_count} runs"
for xml_file in xml_files:
_escape_smart_quotes(xml_file)
return None, message
except zipfile.BadZipFile:
return None, f"Error: {input_file} is not a valid Office file"
except Exception as e:
return None, f"Error unpacking: {e}"
def _pretty_print_xml(xml_file: Path) -> None:
try:
content = xml_file.read_text(encoding="utf-8")
dom = defusedxml.minidom.parseString(content)
xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="utf-8"))
except Exception:
pass
def _escape_smart_quotes(xml_file: Path) -> None:
try:
content = xml_file.read_text(encoding="utf-8")
for char, entity in SMART_QUOTE_REPLACEMENTS.items():
content = content.replace(char, entity)
xml_file.write_text(content, encoding="utf-8")
except Exception:
pass
if __name__ == "__main__":
parser = argparse.ArgumentParser(
description="Unpack an Office file (DOCX, PPTX, XLSX) for editing"
)
parser.add_argument("input_file", help="Office file to unpack")
parser.add_argument("output_directory", help="Output directory")
parser.add_argument(
"--merge-runs",
type=lambda x: x.lower() == "true",
default=True,
metavar="true|false",
help="Merge adjacent runs with identical formatting (DOCX only, default: true)",
)
parser.add_argument(
"--simplify-redlines",
type=lambda x: x.lower() == "true",
default=True,
metavar="true|false",
help="Merge adjacent tracked changes from same author (DOCX only, default: true)",
)
args = parser.parse_args()
_, message = unpack(
args.input_file,
args.output_directory,
merge_runs=args.merge_runs,
simplify_redlines=args.simplify_redlines,
)
print(message)
if "Error" in message:
sys.exit(1)스크립트/사무실/validate.py
"""
Command line tool to validate Office document XML files against XSD schemas and tracked changes.
Usage:
python validate.py <path> [--original <original_file>] [--auto-repair] [--author NAME]
The first argument can be either:
- An unpacked directory containing the Office document XML files
- A packed Office file (.docx/.pptx/.xlsx) which will be unpacked to a temp directory
Auto-repair fixes:
- paraId/durableId values that exceed OOXML limits
- Missing xml:space="preserve" on w:t elements with whitespace
"""
import argparse
import sys
import tempfile
import zipfile
from pathlib import Path
from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator
def main():
parser = argparse.ArgumentParser(description="Validate Office document XML files")
parser.add_argument(
"path",
help="Path to unpacked directory or packed Office file (.docx/.pptx/.xlsx)",
)
parser.add_argument(
"--original",
required=False,
default=None,
help="Path to original file (.docx/.pptx/.xlsx). If omitted, all XSD errors are reported and redlining validation is skipped.",
)
parser.add_argument(
"-v",
"--verbose",
action="store_true",
help="Enable verbose output",
)
parser.add_argument(
"--auto-repair",
action="store_true",
help="Automatically repair common issues (hex IDs, whitespace preservation)",
)
parser.add_argument(
"--author",
default="Claude",
help="Author name for redlining validation (default: Claude)",
)
args = parser.parse_args()
path = Path(args.path)
assert path.exists(), f"Error: {path} does not exist"
original_file = None
if args.original:
original_file = Path(args.original)
assert original_file.is_file(), f"Error: {original_file} is not a file"
assert original_file.suffix.lower() in [".docx", ".pptx", ".xlsx"], (
f"Error: {original_file} must be a .docx, .pptx, or .xlsx file"
)
file_extension = (original_file or path).suffix.lower()
assert file_extension in [".docx", ".pptx", ".xlsx"], (
f"Error: Cannot determine file type from {path}. Use --original or provide a .docx/.pptx/.xlsx file."
)
if path.is_file() and path.suffix.lower() in [".docx", ".pptx", ".xlsx"]:
temp_dir = tempfile.mkdtemp()
with zipfile.ZipFile(path, "r") as zf:
zf.extractall(temp_dir)
unpacked_dir = Path(temp_dir)
else:
assert path.is_dir(), f"Error: {path} is not a directory or Office file"
unpacked_dir = path
match file_extension:
case ".docx":
validators = [
DOCXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
]
if original_file:
validators.append(
RedliningValidator(unpacked_dir, original_file, verbose=args.verbose, author=args.author)
)
case ".pptx":
validators = [
PPTXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose),
]
case _:
print(f"Error: Validation not supported for file type {file_extension}")
sys.exit(1)
if args.auto_repair:
total_repairs = sum(v.repair() for v in validators)
if total_repairs:
print(f"Auto-repaired {total_repairs} issue(s)")
success = all(v.validate() for v in validators)
if success:
print("All validations PASSED!")
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()스크립트/사무실/검증기/init.py
스크립트/office/validators/init.py 다운로드
"""
Validation modules for Word document processing.
"""
from .base import BaseSchemaValidator
from .docx import DOCXSchemaValidator
from .pptx import PPTXSchemaValidator
from .redlining import RedliningValidator
__all__ = [
"BaseSchemaValidator",
"DOCXSchemaValidator",
"PPTXSchemaValidator",
"RedliningValidator",
]스크립트/사무실/검증기/base.py
스크립트 다운로드/office/validators/base.py
바이너리 리소스
스크립트/office/validators/docx.py
스크립트 다운로드/office/validators/docx.py
바이너리 리소스
스크립트/사무실/검증기/pptx.py
스크립트 다운로드/office/validators/pptx.py
바이너리 리소스
스크립트/사무실/검증기/redlining.py
스크립트 다운로드/office/validators/redlining.py
바이너리 리소스
스크립트/recalc.py
"""
Excel Formula Recalculation Script
Recalculates all formulas in an Excel file using LibreOffice
"""
import json
import os
import platform
import subprocess
import sys
from pathlib import Path
from office.soffice import get_soffice_env
from openpyxl import load_workbook
MACRO_DIR_MACOS = "~/Library/Application Support/LibreOffice/4/user/basic/Standard"
MACRO_DIR_LINUX = "~/.config/libreoffice/4/user/basic/Standard"
MACRO_FILENAME = "Module1.xba"
RECALCULATE_MACRO = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE script:module PUBLIC "-//OpenOffice.org//DTD OfficeDocument 1.0//EN" "module.dtd">
<script:module xmlns:script="http://openoffice.org/2000/script" script:name="Module1" script:language="StarBasic">
Sub RecalculateAndSave()
ThisComponent.calculateAll()
ThisComponent.store()
ThisComponent.close(True)
End Sub
</script:module>"""
def has_gtimeout():
try:
subprocess.run(
["gtimeout", "--version"], capture_output=True, timeout=1, check=False
)
return True
except (FileNotFoundError, subprocess.TimeoutExpired):
return False
def setup_libreoffice_macro():
macro_dir = os.path.expanduser(
MACRO_DIR_MACOS if platform.system() == "Darwin" else MACRO_DIR_LINUX
)
macro_file = os.path.join(macro_dir, MACRO_FILENAME)
if (
os.path.exists(macro_file)
and "RecalculateAndSave" in Path(macro_file).read_text()
):
return True
if not os.path.exists(macro_dir):
subprocess.run(
["soffice", "--headless", "--terminate_after_init"],
capture_output=True,
timeout=10,
env=get_soffice_env(),
)
os.makedirs(macro_dir, exist_ok=True)
try:
Path(macro_file).write_text(RECALCULATE_MACRO)
return True
except Exception:
return False
def recalc(filename, timeout=30):
if not Path(filename).exists():
return {"error": f"File {filename} does not exist"}
abs_path = str(Path(filename).absolute())
if not setup_libreoffice_macro():
return {"error": "Failed to setup LibreOffice macro"}
cmd = [
"soffice",
"--headless",
"--norestore",
"vnd.sun.star.script:Standard.Module1.RecalculateAndSave?language=Basic&location=application",
abs_path,
]
if platform.system() == "Linux":
cmd = ["timeout", str(timeout)] + cmd
elif platform.system() == "Darwin" and has_gtimeout():
cmd = ["gtimeout", str(timeout)] + cmd
result = subprocess.run(cmd, capture_output=True, text=True, env=get_soffice_env())
if result.returncode != 0 and result.returncode != 124:
error_msg = result.stderr or "Unknown error during recalculation"
if "Module1" in error_msg or "RecalculateAndSave" not in error_msg:
return {"error": "LibreOffice macro not configured properly"}
return {"error": error_msg}
try:
wb = load_workbook(filename, data_only=True)
excel_errors = [
"#VALUE!",
"#DIV/0!",
"#REF!",
"#NAME?",
"#NULL!",
"#NUM!",
"#N/A",
]
error_details = {err: [] for err in excel_errors}
total_errors = 0
for sheet_name in wb.sheetnames:
ws = wb[sheet_name]
for row in ws.iter_rows():
for cell in row:
if cell.value is not None and isinstance(cell.value, str):
for err in excel_errors:
if err in cell.value:
location = f"{sheet_name}!{cell.coordinate}"
error_details[err].append(location)
total_errors += 1
break
wb.close()
result = {
"status": "success" if total_errors == 0 else "errors_found",
"total_errors": total_errors,
"error_summary": {},
}
for err_type, locations in error_details.items():
if locations:
result["error_summary"][err_type] = {
"count": len(locations),
"locations": locations[:20],
}
wb_formulas = load_workbook(filename, data_only=False)
formula_count = 0
for sheet_name in wb_formulas.sheetnames:
ws = wb_formulas[sheet_name]
for row in ws.iter_rows():
for cell in row:
if (
cell.value
and isinstance(cell.value, str)
and cell.value.startswith("=")
):
formula_count += 1
wb_formulas.close()
result["total_formulas"] = formula_count
return result
except Exception as e:
return {"error": str(e)}
def main():
if len(sys.argv) < 2:
print("Usage: python recalc.py <excel_file> [timeout_seconds]")
print("\nRecalculates all formulas in an Excel file using LibreOffice")
print("\nReturns JSON with error details:")
print(" - status: 'success' or 'errors_found'")
print(" - total_errors: Total number of Excel errors found")
print(" - total_formulas: Number of formulas in the file")
print(" - error_summary: Breakdown by error type with locations")
print(" - #VALUE!, #DIV/0!, #REF!, #NAME?, #NULL!, #NUM!, #N/A")
sys.exit(1)
filename = sys.argv[1]
timeout = int(sys.argv[2]) if len(sys.argv) > 2 else 30
result = recalc(filename, timeout)
print(json.dumps(result, indent=2))
if __name__ == "__main__":
main()GitHub에서 보기
Pptx
.pptx 파일이 어떤 방식으로든(입력, 출력 또는 둘 다) 관련될 때마다 이 기술을 사용하세요. 여기에는 슬라이드 데크, 피치 데크 또는 프레젠테이션 만들기가 포함됩니다..pptx 파일에서 텍스트 읽기, 구문 분석 또는 추출(추출된 콘텐츠가 이메일이나 요약과 같은 다른 곳에서 사용되는 경우에도) 기존 프리젠테이션을 편집, 수정 또는 업데이트합니다. 슬라이드 파일을 결합하거나 분할합니다. 템플릿, 레이아웃, 발표자 노트 또는 댓글 작업. 나중에 콘텐츠로 무엇을 할 계획인지에 관계없이 사용자가 "데크", "슬라이드", "프레젠테이션"을 언급하거나.pptx 파일 이름을 참조할 때마다 트리거됩니다..pptx 파일을 열거나 생성하거나 터치해야 하는 경우 이 기술을 사용하세요.
브랜드 가이드라인
Claude Skills가 일관되고 창의적인 결과물을 생산할 수 있도록 브랜드 음성, 시각적 규칙 및 재사용 가능한 자산을 조합하기 위한 상담원 기술 핸드북입니다.
클로데스킬스 문서