PPTX
このスキルは、.pptx ファイルが入力、出力、またはその両方として何らかの形で関与するときにいつでも使用できます。これには、スライドデッキ、ピッチデッキ、またはプレゼンテーションの作成が含まれます。任意の.pptx ファイルからのテキストの読み取り、解析、抽出 (抽出されたコンテンツが電子メールや概要などの他の場所で使用される場合でも)。既存のプレゼンテーションを編集、変更、または更新する。スライド ファイルの結合または分割。テンプレート、レイアウト、講演者ノート、またはコメントを使用して作業します。ユーザーが後でコンテンツをどうするかに関係なく、ユーザーが「デッキ」、「スライド」、「プレゼンテーション」に言及したり、.pptx ファイル名を参照したりするとトリガーされます。.pptx ファイルを開いたり、作成したり、操作したりする必要がある場合は、このスキルを使用してください。
出典: anthropics/skills (MIT) を基にしたコンテンツ。
クイックリファレンス
| タスク | ガイド |
|---|---|
| コンテンツの読み取り/分析 | python -m markitdown presentation.pptx |
| テンプレートから編集または作成 | 編集.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 をお読みください。
テンプレートや参照プレゼンテーションが利用できない場合に使用します。
デザインのアイデア
退屈なスライドは作成しないでください。 白い背景に単純な箇条書きでは、誰の印象にも残りません。各スライドについて、このリストからアイデアを検討してください。
始める前に
- 内容を反映した大胆なカラー パレットを選択してください: パレットは、このトピック向けにデザインされたものである必要があります。色を完全に異なるプレゼンテーションに交換しても「機能する」場合は、具体的な十分な選択ができていません。
- 平等よりも優勢: 1 つの色が優勢 (視覚的な重みの 60 ~ 70%) で、1 ~ 2 つのサポート トーンと 1 つのシャープなアクセントが必要です。すべての色に同じ重みを与えないでください。
- 暗い/明るいコントラスト: タイトルと結論のスライドは暗い背景、コンテンツは明るい (「サンドイッチ」構造)。または、プレミアム感を得るために全体を暗くすることもできます。
- 視覚的なモチーフにこだわります: 丸い画像フレーム、色付きの円内のアイコン、太い片面境界線など、特徴的な要素を 1 つ選択して繰り返します。すべてのスライドにわたってそれを実行します。
カラーパレット
トピックに一致する色を選択してください。デフォルトで一般的な青を選択しないでください。これらのパレットをインスピレーションとして使用してください。
| テーマ | プライマリー | 二次 | アクセント |
|---|---|---|---|
| ミッドナイト エグゼクティブ | 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 ~ 72 ポイントの大きな数字とその下に小さなラベル)
- 比較欄(前/後、長所/短所、並列オプション)
- タイムラインまたはプロセス フロー (番号付きのステップ、矢印)
ビジュアル磨き:
- セクションヘッダーの横にある小さな色付きの円内のアイコン
- 主要な統計またはキャッチフレーズの斜体のアクセント テキスト
タイポグラフィー
興味深いフォントの組み合わせを選択してください - デフォルトで Arial にしないでください。個性のあるヘッダー フォントを選択し、すっきりとした本文フォントと組み合わせます。
| ヘッダーのフォント | 本文フォント |
|---|---|
| ジョージア | カリブリ |
| エリアルブラック | エリアル |
| カリブリ | カリブリライト |
| カンブリア | カリブリ |
| トレビュシェット MS | カリブリ |
| 影響 | エリアル |
| パラティーノ | ガラモンド |
| コンソラス | カリブリ |
| 要素 | サイズ |
|---|---|
| スライドのタイトル | 36-44pt 太字 |
| セクションヘッダー | 20~24pt 太字 |
| 本文 | 14-16pt |
| キャプション | 10-12pt ミュート |
間隔
- 最小マージン 0.5 インチ
- コンテンツブロック間は0.3~0.5インチ
- 呼吸できる空間を残してください - 隅々まで埋めないでください
避ける(よくある間違い)
- 同じレイアウトを繰り返さないでください - スライド間で列、カード、吹き出しを変更します。
- 本文テキストを中央揃えにしないでください - 段落とリストを左揃えにします。センターのみのタイトル
- サイズのコントラストを軽視しないでください - 14 ~ 16 ポイントの本文からタイトルを目立たせるには、36 ポイント以上が必要です
- デフォルトを青にしないでください - 特定のトピックを反映する色を選択してください
- 間隔をランダムに混ぜないでください - 0.3 インチまたは 0.5 インチの間隔を選択し、一貫して使用してください。
- 1 つのスライドにスタイルを付けて残りをプレーンのままにしないでください - 完全にコミットするか、全体を通してシンプルに保ちます
- テキストのみのスライドを作成しないでください - 画像、アイコン、グラフ、または視覚要素を追加します。単純なタイトルと箇条書きを避ける
- テキスト ボックスのパディングを忘れないでください - 線や図形をテキストの端に揃えるときは、テキスト ボックスに
margin: 0を設定するか、パディングを考慮して図形をオフセットします。 - コントラストの低い要素は使用しないでください - アイコンとテキストは背景に対して強いコントラストを必要とします。明るい背景に明るいテキスト、暗い背景に暗いテキストを避ける
- タイトルの下にアクセント線を決して使用しないでください - これは AI によって生成されたスライドの特徴です。代わりに空白または背景色を使用してください
QA(必須)
問題があると仮定します。あなたの仕事はそれらを見つけることです。
最初のレンダリングが正確になることはほとんどありません。 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.検証ループ
- スライドの生成 -> 画像に変換 -> 検査
- 見つかった問題をリストします (何も見つからなかった場合は、より厳密にもう一度調べます)
- 問題を修正する
- 影響を受けたスライドを再確認します - 1 つの修正が別の問題を引き起こすことがよくあります
- フルパスで新たな問題がなくなるまで繰り返します
修正と検証のサイクルを少なくとも 1 回完了するまでは、成功を宣言しないでください。
画像への変換
プレゼンテーションを視覚的に検査できるように個々のスライド画像に変換します。
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
バイナリリソース
スクリプト/office/helpers/init.py
スクリプトをダウンロード/office/helpers/init.py
バイナリリソース
script/office/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)scripts/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)スクリプト/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-chartDrawing.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
バイナリリソース
スクリプト/office/schemas/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
バイナリリソース
スクリプト/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd
バイナリリソース
scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
バイナリリソース
scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd
バイナリリソース
scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.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
バイナリリソース
scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd
バイナリリソース
scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
バイナリリソース
scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd
バイナリリソース
scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd
バイナリリソース
scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd
スクリプトをダウンロード/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.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
バイナリリソース
scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd
スクリプトをダウンロード/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd
バイナリリソース
scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd
スクリプトをダウンロード/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd
バイナリリソース
scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd
スクリプトをダウンロード/office/schemas/ecma/fouth-edition/opc-digSig.xsd
バイナリリソース
scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd
スクリプトをダウンロード/office/schemas/ecma/fouth-edition/opc-relationships.xsd
バイナリリソース
スクリプト/オフィス/スキーマ/mce/mc.xsd
スクリプトをダウンロード/office/schemas/mce/mc.xsd
バイナリリソース
スクリプト/office/schemas/microsoft/wml-2010.xsd
スクリプトをダウンロード/office/schemas/microsoft/wml-2010.xsd
バイナリリソース
スクリプト/office/schemas/microsoft/wml-2012.xsd
スクリプトをダウンロード/office/schemas/microsoft/wml-2012.xsd
バイナリリソース
スクリプト/office/schemas/microsoft/wml-2018.xsd
スクリプトをダウンロード/office/schemas/microsoft/wml-2018.xsd
バイナリリソース
スクリプト/office/schemas/microsoft/wml-cex-2018.xsd
スクリプトをダウンロード/office/schemas/microsoft/wml-cex-2018.xsd
バイナリリソース
スクリプト/office/schemas/microsoft/wml-cid-2016.xsd
スクリプトをダウンロード/office/schemas/microsoft/wml-cid-2016.xsd
バイナリリソース
scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd
スクリプトをダウンロード/office/schemas/microsoft/wml-sdtdatahash-2020.xsd
バイナリリソース
スクリプト/office/schemas/microsoft/wml-symex-2015.xsd
スクリプトをダウンロード/office/schemas/microsoft/wml-symex-2015.xsd
バイナリリソース
スクリプト/オフィス/soffice.py
スクリプトをダウンロード/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)スクリプト/office/validate.py
スクリプトをダウンロード/office/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()スクリプト/office/validators/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",
]スクリプト/office/validators/base.py
スクリプトをダウンロード/office/validators/base.py
バイナリリソース
スクリプト/office/validators/docx.py
スクリプトをダウンロード/office/validators/docx.py
バイナリリソース
スクリプト/office/validators/pptx.py
スクリプトをダウンロード/office/validators/pptx.py
バイナリリソース
スクリプト/office/validators/redlining.py
スクリプトをダウンロード/office/validators/redlining.py
バイナリリソース
スクリプト/サムネイル.py
バイナリリソース
GitHub で見る
ユーザーが PDF ファイルに対して何かを行う場合は、このスキルを使用してください。これには、PDF からのテキスト/表の読み取りまたは抽出、複数の PDF の 1 つの結合またはマージ、PDF の分割、ページの回転、透かしの追加、新しい PDF の作成、PDF フォームの入力、PDF の暗号化/復号化、画像の抽出、スキャンされた PDF を検索可能にするための OCR が含まれます。ユーザーが.pdf ファイルについて言及した場合、またはファイルの作成を要求した場合は、このスキルを使用します。
XLSX
このスキルは、スプレッドシート ファイルが主な入力または出力である場合にはいつでも使用できます。これは、ユーザーが次のことを行うタスクを意味します。既存の.xlsx、.xlsm、.csv、または.tsv ファイルを開く、読み取る、編集する、または修正する (列の追加、数式の計算、書式設定、グラフ作成、乱雑なデータのクリーニングなど)。新しいスプレッドシートを最初から作成するか、他のデータ ソースから作成します。または、表形式のファイル形式間で変換します。特に、ユーザーがスプレッドシート ファイルを名前またはパスで参照し (たとえ「ダウンロードした xlsx」など)、それに対して何かを実行したり、そこから生成したりしたい場合にトリガーされます。また、乱雑な表形式データ ファイル (不正な行、間違った配置のヘッダー、ジャンク データ) を適切なスプレッドシートにクリーンアップまたは再構築するためのトリガーでもあります。成果物はスプレッドシート ファイルである必要があります。主要な成果物が Word ドキュメント、HTML レポート、スタンドアロン Python スクリプト、データベース パイプライン、または Google Sheets API 統合である場合は、表形式のデータが含まれている場合でもトリガーしないでください。
クロードスキルのドキュメント