XLSX
このスキルは、スプレッドシート ファイルが主な入力または出力である場合にはいつでも使用できます。これは、ユーザーが次のことを行うタスクを意味します。既存の.xlsx、.xlsm、.csv、または.tsv ファイルを開く、読み取る、編集する、または修正する (列の追加、数式の計算、書式設定、グラフ作成、乱雑なデータのクリーニングなど)。新しいスプレッドシートを最初から作成するか、他のデータ ソースから作成します。または、表形式のファイル形式間で変換します。特に、ユーザーがスプレッドシート ファイルを名前またはパスで参照し (たとえ「ダウンロードした xlsx」など)、それに対して何かを実行したり、そこから生成したりしたい場合にトリガーされます。また、乱雑な表形式データ ファイル (不正な行、間違った配置のヘッダー、ジャンク データ) を適切なスプレッドシートにクリーンアップまたは再構築するためのトリガーでもあります。成果物はスプレッドシート ファイルである必要があります。主要な成果物が Word ドキュメント、HTML レポート、スタンドアロン Python スクリプト、データベース パイプライン、または Google Sheets API 統合である場合は、表形式のデータが含まれている場合でもトリガーしないでください。
出典: anthropics/skills (MIT) を基にしたコンテンツ。
すべての Excel ファイル
プロフェッショナルフォント
- ユーザーから別途指示がない限り、すべての成果物には一貫したプロフェッショナルなフォント (Arial、Times New Roman など) を使用してください。
ゼロ式エラー
- すべての Excel モデルは、数式エラー (#REF!、#DIV/0!、#VALUE!、#N/A、#NAME?) がゼロである必要があります。
既存のテンプレートを保持する (テンプレートを更新する場合)
- ファイルを変更する際は、既存の形式、スタイル、規則を調査し、正確に一致させます。
- 確立されたパターンを持つファイルに標準化されたフォーマットを強制しないでください
- 既存のテンプレート規則は常にこれらのガイドラインをオーバーライドします
財務モデル
色分け基準
ユーザーまたは既存のテンプレートによって別途指定がない限り、
業界標準の色の規則
- 青色のテキスト (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% 形式 (小数点 1 桁)
- 倍率: 評価倍率 (EV/EBITDA、P/E) を 0.0 倍としてフォーマットします。
- 負の数値: マイナス -123 ではなく括弧 (123) を使用してください。
式の構築ルール
仮定の配置
- すべての仮定 (成長率、マージン、倍数など) を別の仮定セルに配置します。
- 数式でハードコードされた値の代わりにセル参照を使用する
- 例: =B51.05 の代わりに =B5(1+$B$6) を使用します。
数式エラーの防止
- すべてのセル参照が正しいことを確認してください
- 範囲内のオフバイワンエラーをチェックする
- すべての予測期間にわたって一貫した計算式を確保する
- エッジケース (ゼロ値、負の数) を使用したテスト
- 意図しない循環参照がないことを確認する
ハードコードのドキュメント要件
- コメントまたは横のセル (表の終わりの場合)。形式: 「ソース: [システム/ドキュメント]、[日付]、[特定の参照]、[該当する場合は URL]」
- 例:
- 「出典: Company 10-K、FY2024、45 ページ、収益ノート、[SEC EDGAR URL]」
- 「出典: 会社 10-Q、2025 年第 2 四半期、資料 99.1、[SEC EDGAR URL]」
- 「出典: ブルームバーグ ターミナル、2025 年 8 月 15 日、AAPL 米国株式」
- 「出典: ファクトセット、2025 年 8 月 20 日、コンセンサス推定画面」
XLSXの作成、編集、分析
概要
ユーザーは、.xlsx ファイルの内容を作成、編集、または分析するように要求する場合があります。さまざまなタスクに使用できるさまざまなツールとワークフローがあります。
重要な要件
数式の再計算には LibreOffice が必要:scripts/recalc.pyスクリプトを使用して数式の値を再計算するには、LibreOffice がインストールされていると想定できます。このスクリプトは、Unix ソケットが制限されているサンドボックス環境を含め、初回実行時に LibreOffice を自動的に構成します (scripts/office/soffice.pyによって処理されます)。
データの読み取りと分析
パンダを使ったデータ分析
データ分析、視覚化、および基本的な操作には、強力なデータ操作機能を提供する 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!: ゼロ除算#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 値をチェックします - 右端の列: 会計年度データは多くの場合 50 列以上にあります
- 複数の一致: 最初だけでなく、すべての一致を検索します。
- ゼロ除算: 数式で
/を使用する前に分母を確認してください (#DIV/0!) - 間違った参照: すべてのセル参照が目的のセル (#REF!) を指していることを確認してください。
- クロスシート参照: シートをリンクするには正しい形式 (Sheet1!A1) を使用してください
フォーミュラテスト戦略
- 小規模から開始: 広範囲に適用する前に、2 ~ 3 個のセルで数式をテストします。
- 依存関係を確認: 数式で参照されているすべてのセルが存在することを確認します。
- エッジ ケースのテスト: ゼロ、負の値、および非常に大きな値を含めます
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 コードを作成します。
- 冗長な変数名と冗長な操作を避ける
- 不必要な print ステートメントを避ける
Excel ファイル自体の場合:
- 複雑な数式や重要な仮定を含むセルにコメントを追加する
- ハードコードされた値のドキュメント データ ソース
- 主要な計算とモデルのセクションに関するメモを含める
リソースファイル
ライセンス.txt
バイナリリソース
スクリプト/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
バイナリリソース
スクリプト/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 ファイルを開いたり、作成したり、操作したりする必要がある場合は、このスキルを使用してください。
ブランドガイドライン
クロード スキルが一貫したクリエイティブな成果物を生み出すように、ブランド ボイス、ビジュアル ルール、再利用可能なアセットを組み立てるためのエージェント スキル ハンドブック。
クロードスキルのドキュメント