Pptx
.pptx 파일이 어떤 방식으로든(입력, 출력 또는 둘 다) 관련될 때마다 이 기술을 사용하세요. 여기에는 슬라이드 데크, 피치 데크 또는 프레젠테이션 만들기가 포함됩니다..pptx 파일에서 텍스트 읽기, 구문 분석 또는 추출(추출된 콘텐츠가 이메일이나 요약과 같은 다른 곳에서 사용되는 경우에도) 기존 프리젠테이션을 편집, 수정 또는 업데이트합니다. 슬라이드 파일을 결합하거나 분할합니다. 템플릿, 레이아웃, 발표자 노트 또는 댓글 작업. 나중에 콘텐츠로 무엇을 할 계획인지에 관계없이 사용자가 "데크", "슬라이드", "프레젠테이션"을 언급하거나.pptx 파일 이름을 참조할 때마다 트리거됩니다..pptx 파일을 열거나 생성하거나 터치해야 하는 경우 이 기술을 사용하세요.
출처: 인류학/기술(MIT)에서 채택한 콘텐츠.
빠른 참조
| 작업 | 가이드 |
|---|---|
| 콘텐츠 읽기/분석 | python -m markitdown presentation.pptx |
| 템플릿에서 편집 또는 만들기 | editing.md 읽기 |
| 처음부터 만들기 | pptxgenjs.md 읽기 |
콘텐츠 읽기
# Text extraction
python -m markitdown presentation.pptx
# Visual overview
python scripts/thumbnail.py presentation.pptx
# Raw XML
python scripts/office/unpack.py presentation.pptx unpacked/작업 흐름 편집
자세한 내용은 editing.md를 참조하세요.
thumbnail.py로 템플릿 분석- 압축 풀기 -> 슬라이드 조작 -> 콘텐츠 편집 -> 정리 -> 압축
처음부터 만들기
자세한 내용은 pptxgenjs.md를 참조하세요.
템플릿이나 참조 프리젠테이션을 사용할 수 없을 때 사용합니다.
디자인 아이디어
지루한 슬라이드를 만들지 마세요. 흰색 배경에 평범한 글머리 기호는 누구에게도 깊은 인상을 주지 않습니다. 각 슬라이드에 대해 이 목록의 아이디어를 고려하십시오.
시작하기 전에
- 대담하고 내용에 맞는 색상 팔레트를 선택하세요: 팔레트는 이 주제에 맞게 디자인되어야 합니다. 색상을 완전히 다른 표현으로 바꾸는 것이 여전히 "작동"한다면, 구체적인 선택을 충분히 하지 않은 것입니다.
- 평등보다 우위: 하나의 색상이 지배적이어야 하며(시각적 비중 60
70%), 12개의 보조 톤과 하나의 선명한 액센트가 있어야 합니다. 모든 색상에 동일한 가중치를 부여하지 마십시오. - 어두운/밝은 대비: 제목 + 결론 슬라이드의 배경은 어둡고 내용의 배경은 밝습니다("샌드위치" 구조). 또는 프리미엄 느낌을 위해 전체적으로 어둡게 만듭니다.
- 시각적 모티프에 전념: 둥근 이미지 프레임, 컬러 원으로 둘러싸인 아이콘, 두꺼운 단면 테두리 등 하나의 독특한 요소를 선택하고 반복합니다. 모든 슬라이드에서 휴대하세요.
색상 팔레트
주제에 맞는 색상을 선택하세요. 기본 색상은 일반적인 파란색이 아닙니다. 다음 팔레트를 영감으로 사용하세요.
| 테마 | 기본 | 보조 | 악센트 |
|---|---|---|---|
| 미드나잇 이그제큐티브 | 1E2761(네이비) | CADCFC(아이스 블루) | FFFFFF(화이트) |
| 숲과 이끼 | 2C5F2D(숲) | 97BC62(이끼) | F5F5F5(크림) |
| 산호에너지 | F96167(산호) | F9E795(골드) | 2F3C7E(네이비) |
| 따뜻한 테라코타 | B85042(테라코타) | E7E8D1(모래) | A7BEAE(세이지) |
| 바다 그라데이션 | 065A82(딥블루) | 1C7293(청록색) | 21295C(자정) |
| 차콜 미니멀 | 36454F(챠콜) | F2F2F2(오프화이트) | 212121(블랙) |
| 청록 신뢰 | 028090(청록색) | 00A896(해포) | 02C39A(민트) |
| 베리 앤 크림 | 6D2E46(베리) | A26769(더스티 로즈) | ECE2D0(크림) |
| 세이지 캄 | 84B59F(세이지) | 69A297(유칼립투스) | 50808E(슬레이트) |
| 체리 볼드 | 990011(체리) | FCF6F5(오프화이트) | 2F3C7E(네이비) |
각 슬라이드에 대해
모든 슬라이드에는 시각적 요소(이미지, 차트, 아이콘 또는 모양)가 필요합니다. 텍스트만 있는 슬라이드는 잊어버릴 수 있습니다.
레이아웃 옵션:
- 2열(왼쪽 텍스트, 오른쪽 그림)
- 아이콘 + 텍스트 행(색상 원 안의 아이콘, 굵은 헤더, 아래 설명)
- 2x2 또는 2x3 그리드(한쪽은 이미지, 다른 쪽은 콘텐츠 블록 그리드)
- 콘텐츠 오버레이가 포함된 하프 블리드 이미지(전체 왼쪽 또는 오른쪽)
데이터 표시:
- 큰 통계 설명(아래에 작은 라벨이 있는 큰 숫자 60-72pt)
- 비교 열(전/후, 장단점, 병렬 옵션)
- 타임라인 또는 프로세스 흐름(번호가 매겨진 단계, 화살표)
시각적 개선:
- 섹션 헤더 옆에 있는 작은 색상의 원 아이콘
- 주요 통계 또는 태그라인에 대한 기울임꼴 악센트 텍스트
타이포그래피
흥미로운 글꼴 조합을 선택하세요 - 기본적으로 Arial을 사용하지 마세요. 개성이 있는 헤더 글꼴을 선택하고 깔끔한 바디 글꼴과 결합하세요.
| 헤더 글꼴 | 본문 글꼴 |
|---|---|
| 조지아 | 칼리브리 |
| 아리알 블랙 | 굴림 |
| 칼리브리 | 칼리브리 라이트 |
| 캠브리아 | 칼리브리 |
| 트레뷰셋 MS | 칼리브리 |
| 영향 | 굴림 |
| 팔라티노 | 가라몬드 |
| 콘솔라 | 칼리브리 |
| 요소 | 사이즈 |
|---|---|
| 슬라이드 제목 | 36-44pt 굵은 글씨 |
| 섹션 헤더 | 20-24pt 굵은 글씨 |
| 본문 | 14-16포인트 |
| 캡션 | 10-12pt 음소거 |
간격
- 0.5" 최소 여백
- 콘텐츠 블록 사이 0.3-0.5"
- 호흡 공간을 남겨두세요. 모든 공간을 채우지 마세요.
피하십시오(일반적인 실수)
- 동일한 레이아웃을 반복하지 마세요 - 슬라이드 전체에서 열, 카드, 설명선을 다양하게 변경
- 본문 텍스트를 가운데에 맞추지 마세요 - 단락과 목록을 왼쪽 정렬합니다. 중앙에만 제목
- 크기 대비를 인색하지 마세요 - 제목이 14~16pt 본문에서 눈에 띄도록 하려면 36pt 이상이 필요합니다.
- 기본적으로 파란색을 사용하지 마세요 - 특정 주제를 반영하는 색상을 선택하세요.
- 간격을 무작위로 혼합하지 마세요 - 0.3인치 또는 0.5인치 간격을 선택하고 일관되게 사용하세요.
- 한 슬라이드의 스타일을 지정하고 나머지는 그대로 두지 마세요 - 완전히 적용하거나 전체적으로 단순하게 유지하세요.
- 텍스트 전용 슬라이드를 만들지 마세요 - 이미지, 아이콘, 차트 또는 시각적 요소를 추가하세요. 일반 제목과 글머리 기호는 피하세요.
- 텍스트 상자 패딩을 잊지 마세요 - 텍스트 가장자리에 맞춰 선이나 모양을 정렬할 때 텍스트 상자에
margin: 0를 설정하거나 패딩을 고려하여 모양을 오프셋하세요. - 대비가 낮은 요소를 사용하지 마세요 - 아이콘과 텍스트는 배경과 강한 대비가 필요합니다. 밝은 배경에 밝은 텍스트, 어두운 배경에 어두운 텍스트를 피하세요.
- 제목 아래에 강조선을 사용하지 마세요 - 이는 AI 생성 슬라이드의 특징입니다. 대신 공백이나 배경색을 사용하세요.
품질보증(필수)
문제가 있다고 가정합니다. 당신의 임무는 그들을 찾는 것입니다.
첫 번째 렌더링은 거의 정확하지 않습니다. 확인 단계가 아닌 버그 찾기로 QA에 접근하세요. 첫 번째 검사에서 문제가 전혀 발견되지 않았다면 충분히 자세히 살펴보지 않은 것입니다.
콘텐츠 QA
python -m markitdown output.pptx내용 누락, 오타, 잘못된 순서를 확인하세요.
템플릿을 사용할 때 남은 자리 표시자 텍스트를 확인하세요.
python -m markitdown output.pptx | grep -iE "xxxx|lorem|ipsum|this.*(page|slide).*layout"grep이 결과를 반환하면 성공을 선언하기 전에 결과를 수정하세요.
시각적 QA
** 하위 에이전트 사용** - 2~3개 슬라이드에도 가능합니다. 당신은 코드를 쳐다보고 거기에 있는 것이 아니라 기대하는 것을 보게 될 것입니다. 서브 에이전트는 신선한 눈을 가지고 있습니다.
슬라이드를 이미지로 변환한 후(이미지로 변환 참조) 다음 프롬프트를 사용하세요.
Visually inspect these slides. Assume there are issues - find them.
Look for:
- Overlapping elements (text through shapes, lines through words, stacked elements)
- Text overflow or cut off at edges/box boundaries
- Decorative lines positioned for single-line text but title wrapped to two lines
- Source citations or footers colliding with content above
- Elements too close (< 0.3" gaps) or cards/sections nearly touching
- Uneven gaps (large empty area in one place, cramped in another)
- Insufficient margin from slide edges (< 0.5")
- Columns or similar elements not aligned consistently
- Low-contrast text (e.g., light gray text on cream-colored background)
- Low-contrast icons (e.g., dark icons on dark backgrounds without a contrasting circle)
- Text boxes too narrow causing excessive wrapping
- Leftover placeholder content
For each slide, list issues or areas of concern, even if minor.
Read and analyze these images:
1. /path/to/slide-01.jpg (Expected: [brief description])
2. /path/to/slide-02.jpg (Expected: [brief description])
Report ALL issues found, including minor ones.검증 루프
- 슬라이드 생성 -> 이미지로 변환 -> 검사
- 발견된 문제 나열(발견된 문제가 없으면 더욱 비판적으로 다시 살펴보세요)
- 문제 해결
- 영향을 받은 슬라이드를 다시 확인하세요 - 하나의 수정으로 또 다른 문제가 발생하는 경우가 많습니다.
- 전체 패스에서 새로운 문제가 발견되지 않을 때까지 반복합니다.
최소 한 번의 수정 및 확인 주기를 완료할 때까지 성공을 선언하지 마세요.
이미지로 변환
육안 검사를 위해 프레젠테이션을 개별 슬라이드 이미지로 변환:
python scripts/office/soffice.py --headless --convert-to pdf output.pptx
pdftoppm -jpeg -r 150 output.pdf slide그러면slide-01.jpg,slide-02.jpg등이 생성됩니다.
수정 후 특정 슬라이드를 다시 렌더링하려면:
pdftoppm -jpeg -r 150 -f N -l N output.pdf slide-fixed종속성
pip install "markitdown[pptx]"- 텍스트 추출pip install Pillow- 썸네일 그리드npm install -g pptxgenjs- 처음부터 생성- LibreOffice(
soffice) - PDF 변환(scripts/office/soffice.py를 통해 샌드박스 환경에 대해 자동 구성) - 포플러(
pdftoppm) - PDF를 이미지로 변환
리소스 파일
라이센스.txt
바이너리 리소스
편집.md
바이너리 리소스
pptxgenjs.md
바이너리 리소스
스크립트/init.py
바이너리 리소스
스크립트/add_slide.py
바이너리 리소스
스크립트/clean.py
바이너리 리소스
스크립트/사무실/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
scripts/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/pptx/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
scripts/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
바이너리 리소스
스크립트/thumbnail.py
바이너리 리소스
GitHub에서 보기
사용자가 PDF 파일로 무엇이든 하고 싶을 때마다 이 기술을 사용하세요. 여기에는 PDF에서 텍스트/표 읽기 또는 추출, 여러 PDF를 하나로 결합 또는 병합, PDF 분할, 페이지 회전, 워터마크 추가, 새 PDF 생성, PDF 양식 채우기, PDF 암호화/암호 해독, 이미지 추출 및 스캔한 PDF에서 OCR을 포함하여 검색 가능하게 만듭니다. 사용자가.pdf 파일을 언급하거나 파일 생성을 요청하는 경우 이 기술을 사용하세요.
Xlsx
스프레드시트 파일이 주요 입력 또는 출력일 때마다 이 기술을 사용하십시오. 이는 사용자가 기존.xlsx,.xlsm,.csv 또는.tsv 파일 열기, 읽기, 편집 또는 수정(예: 열 추가, 수식 계산, 형식 지정, 차트 작성, 지저분한 데이터 정리)을 원하는 모든 작업을 의미합니다. 처음부터 또는 다른 데이터 소스로부터 새 스프레드시트를 만듭니다. 또는 표 형식 파일 형식 간에 변환할 수 있습니다. 특히 사용자가 이름이나 경로로 스프레드시트 파일을 참조하고("내 다운로드의 xlsx"와 같은) 아무 작업도 수행하거나 생성하기를 원할 때 트리거됩니다. 또한 지저분한 표 형식 데이터 파일(잘못된 행, 잘못된 헤더, 정크 데이터)을 정리하거나 적절한 스프레드시트로 재구성하기 위해 트리거됩니다. 결과물은 스프레드시트 파일이어야 합니다. 기본 결과물이 Word 문서, HTML 보고서, 독립 실행형 Python 스크립트, 데이터베이스 파이프라인 또는 Google Sheets API 통합인 경우 표 형식 데이터가 포함되어 있더라도 트리거하지 마세요.
클로데스킬스 문서