XLSX
Utilisez cette compétence chaque fois qu’un fichier de feuille de calcul constitue la principale entrée ou sortie. Cela signifie toute tâche dans laquelle l'utilisateur souhaite: ouvrir, lire, modifier ou corriger un fichier.xlsx,.xlsm,.csv ou.tsv existant (par exemple, ajouter des colonnes, calculer des formules, formater, créer des graphiques, nettoyer des données désordonnées); créer une nouvelle feuille de calcul à partir de zéro ou à partir d'autres sources de données; ou convertir entre des formats de fichiers tabulaires. Déclenchez-le en particulier lorsque l'utilisateur fait référence à un fichier de feuille de calcul par son nom ou son chemin - même avec désinvolture (comme "le xlsx dans mes téléchargements") - et souhaite que quelque chose y soit fait ou produit à partir de celui-ci. Déclenche également le nettoyage ou la restructuration des fichiers de données tabulaires désordonnés (lignes mal formées, en-têtes mal placés, données indésirables) dans des feuilles de calcul appropriées. Le livrable doit être un fichier tableur. Ne déclenchez PAS lorsque le livrable principal est un document Word, un rapport HTML, un script Python autonome, un pipeline de base de données ou une intégration d'API Google Sheets, même si des données tabulaires sont impliquées.
Source: Contenu adapté de anthropics/skills (MIT).
Tous les fichiers Excel
Police professionnelle
- Utiliser une police cohérente et professionnelle (par exemple, Arial, Times New Roman) pour tous les livrables, sauf indication contraire de l'utilisateur.
Zéro erreur de formule
- Chaque modèle Excel DOIT être livré avec ZÉRO erreurs de formule (#REF!, #DIV/0!, #VALUE!, #N/A, #NAME?)
Conserver les modèles existants (lors de la mise à jour des modèles)
- Étudiez et faites correspondre EXACTEMENT le format, le style et les conventions existants lors de la modification de fichiers
- N'imposez jamais un formatage standardisé aux fichiers avec des modèles établis
- Les conventions de modèles existantes remplacent TOUJOURS ces directives
Modèles financiers
Normes de codage couleur
Sauf indication contraire de l'utilisateur ou d'un modèle existant
Conventions de couleur standard de l'industrie
- Texte bleu (RVB: 0,0,255): entrées codées en dur et nombres que les utilisateurs modifieront pour les scénarios
- Texte noir (RVB: 0,0,0): TOUTES les formules et calculs
- Texte vert (RVB: 0,128,0): liens extraits d'autres feuilles de calcul dans le même classeur
- Texte rouge (RVB: 255,0,0): Liens externes vers d'autres fichiers
- Fond jaune (RVB: 255 255,0): hypothèses clés nécessitant une attention particulière ou cellules nécessitant une mise à jour
Normes de formatage des nombres
Règles de format requises
- Années: formater sous forme de chaînes de texte (par exemple, "2024" et non "2 024")
- Devise: utilisez le format $#,##0; TOUJOURS préciser les unités dans les en-têtes ("Revenu ($ mm)")
- Zéros: utilisez le formatage des nombres pour que tous les zéros soient "-", y compris les pourcentages (par exemple, "$#,##0;($#,##0);-")
- Pourcentages: format par défaut de 0,0 % (une décimale)
- Multiples: format 0,0x pour les multiples de valorisation (EV/EBITDA, P/E)
- Nombres négatifs: utilisez des parenthèses (123) et non moins -123
Règles de construction de formule
Placement des hypothèses
- Placez TOUTES les hypothèses (taux de croissance, marges, multiples, etc.) dans des cellules d'hypothèses distinctes
- Utilisez des références de cellules au lieu de valeurs codées en dur dans les formules
- Exemple: utilisez =B5*(1+$B$6) au lieu de =B5*1.05
Prévention des erreurs de formule
- Vérifiez que toutes les références de cellules sont correctes
- Vérifiez les erreurs ponctuelles dans les plages
- Garantir des formules cohérentes sur toutes les périodes de projection
- Test avec des cas extrêmes (valeurs nulles, nombres négatifs)
- Vérifiez qu'il n'y a pas de références circulaires involontaires
Exigences de documentation pour les codes durs
- Commentaire ou dans les cellules à côté (si fin du tableau). Format: "Source: [Système/Document], [Date], [Référence spécifique], [URL le cas échéant]"
- Exemples:
- "Source: Société 10-K, FY2024, page 45, note sur les revenus, [URL SEC EDGAR]"
- "Source: Société 10-Q, T2 2025, pièce 99.1, [URL SEC EDGAR]"
- "Source: Bloomberg Terminal, 15/08/2025, AAPL US Equity"
- "Source: FactSet, 20/08/2025, écran d'estimations consensuelles"
Création, édition et analyse XLSX
Aperçu
Un utilisateur peut vous demander de créer, modifier ou analyser le contenu d'un fichier.xlsx. Vous disposez de différents outils et flux de travail disponibles pour différentes tâches.
Exigences importantes
LibreOffice requis pour le recalcul de formule: Vous pouvez supposer que LibreOffice est installé pour recalculer les valeurs de formule à l'aide du scriptscripts/recalc.py. Le script configure automatiquement LibreOffice lors de la première exécution, y compris dans les environnements sandbox où les sockets Unix sont restreints (géré parscripts/office/soffice.py)
Lire et analyser des données
Analyse de données avec des pandas
Pour l'analyse des données, la visualisation et les opérations de base, utilisez pandas qui offre de puissantes fonctionnalités de manipulation des données:
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)Flux de travail de fichiers Excel
CRITIQUE: utilisez des formules et non des valeurs codées en dur
Utilisez toujours des formules Excel au lieu de calculer les valeurs en Python et de les coder en dur. Cela garantit que la feuille de calcul reste dynamique et modifiable.
FAUX - Codage en dur des valeurs calculées
# 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.5CORRECT – Utiliser des formules 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)'Cela s'applique à TOUS les calculs: totaux, pourcentages, ratios, différences, etc. La feuille de calcul doit pouvoir recalculer lorsque les données sources changent.
Flux de travail commun
- Choisissez l'outil: pandas pour les données, openpyxl pour les formules/formatages
- Créer/Charger: créer un nouveau classeur ou charger un fichier existant
- Modifier: ajouter/modifier des données, des formules et un formatage
- Enregistrer: écrire dans un fichier
- Recalculer les formules (OBLIGATOIRE SI VOUS UTILISEZ DES FORMULES): Utilisez le script scripts/recalc.py
python scripts/recalc.py output.xlsx - Vérifiez et corrigez les erreurs:
- Le script renvoie JSON avec les détails de l'erreur
- Si
statusesterrors_found, vérifiezerror_summarypour les types et emplacements d'erreur spécifiques. - Corrigez les erreurs identifiées et recalculez à nouveau
- Erreurs courantes à corriger:
#REF!: références de cellule non valides#DIV/0!: Division par zéro#VALUE!: type de données incorrect dans la formule#NAME?: nom de formule non reconnu
Création de nouveaux fichiers 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')Modification de fichiers Excel existants
# 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')Recalculer des formules
Les fichiers Excel créés ou modifiés par openpyxl contiennent des formules sous forme de chaînes mais pas de valeurs calculées. Utilisez le scriptscripts/recalc.pyfourni pour recalculer les formules:
python scripts/recalc.py <excel_file> [timeout_seconds]Exemple:
python scripts/recalc.py output.xlsx 30Le scénario:
- Configure automatiquement la macro LibreOffice lors de la première exécution
- Recalcule toutes les formules dans toutes les feuilles
- Analyse TOUTES les cellules à la recherche d'erreurs Excel (#REF!, #DIV/0!, etc.)
- Renvoie JSON avec les emplacements et le nombre d'erreurs détaillés
- Fonctionne sur Linux et macOS
Liste de contrôle de vérification de formule
Vérifications rapides pour garantir le bon fonctionnement des formules:
Vérification essentielle
- Testez 2 à 3 exemples de références: vérifiez qu'ils extraient les valeurs correctes avant de créer le modèle complet
- Mappage de colonnes: confirmez que les colonnes Excel correspondent (par exemple, colonne 64 = BL, pas BK)
- Décalage de ligne: n'oubliez pas que les lignes Excel sont indexées à 1 (ligne DataFrame 5 = ligne Excel 6)
Pièges courants
- Gestion NaN: recherchez les valeurs nulles avec
pd.notna() - Colonnes d'extrême droite: données fiscales souvent dans les colonnes 50+
- Correspondances multiples: recherchez toutes les occurrences, pas seulement en premier
- Division par zéro: Vérifiez les dénominateurs avant d'utiliser
/dans les formules (#DIV/0!) - Références erronées: Vérifiez que toutes les références de cellules pointent vers les cellules prévues (#REF!)
- Références entre feuilles: utilisez le format correct (Feuille1! A1) pour relier les feuilles
Stratégie de test de formule
- Commencez petit: testez les formules sur 2 à 3 cellules avant de les appliquer largement.
- Vérifier les dépendances: Vérifiez que toutes les cellules référencées dans les formules existent
- Tester les cas extrêmes: inclure des valeurs nulles, négatives et très élevées
Interprétation des scripts/recalc.py Sortie
Le script renvoie JSON avec les détails de l'erreur:
{
"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"]
}
}
}Meilleures pratiques
Sélection de bibliothèque
- pandas: idéal pour l'analyse des données, les opérations groupées et l'exportation simple de données
- openpyxl: Idéal pour le formatage complexe, les formules et les fonctionnalités spécifiques à Excel
Travailler avec openpyxl
- Les indices de cellule sont basés sur 1 (ligne = 1, colonne = 1 fait référence à la cellule A1)
- Utilisez
data_only=Truepour lire les valeurs calculées:load_workbook('file.xlsx', data_only=True) - Attention: Si elles sont ouvertes avec
data_only=Trueet enregistrées, les formules sont remplacées par des valeurs et définitivement perdues. - Pour les fichiers volumineux: utilisez
read_only=Truepour la lecture ouwrite_only=Truepour l'écriture. - Les formules sont conservées mais non évaluées - utilisez scripts/recalc.py pour mettre à jour les valeurs
Travailler avec des pandas
- Spécifiez les types de données pour éviter les problèmes d'inférence:
pd.read_excel('file.xlsx', dtype={'id': str}) - Pour les fichiers volumineux, lisez les colonnes spécifiques:
pd.read_excel('file.xlsx', usecols=['A', 'C', 'E']) - Gérer correctement les dates:
pd.read_excel('file.xlsx', parse_dates=['date_column'])
Directives de style de code
IMPORTANT: lors de la génération de code Python pour les opérations Excel:
- Écrivez du code Python minimal et concis sans commentaires inutiles
- Évitez les noms de variables verbeux et les opérations redondantes
- Évitez les déclarations d'impression inutiles
Pour les fichiers Excel eux-mêmes:
- Ajoutez des commentaires aux cellules contenant des formules complexes ou des hypothèses importantes
- Documenter les sources de données pour les valeurs codées en dur
- Inclure des notes pour les calculs clés et les sections du modèle
Fichiers de ressources
LICENCE.txt
Ressource binaire
scripts/office/helpers/init.py
Télécharger scripts/office/helpers/init.py
Ressource binaire
scripts/office/helpers/merge_runs.py
Télécharger scripts/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
Télécharger scripts/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."
)scripts/office/pack.py
Télécharger scripts/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
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd
Ressource binaire
scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd
Télécharger scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd
Ressource binaire
scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd
Télécharger scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd
Ressource binaire
scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd
Télécharger scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd
Ressource binaire
scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd
Télécharger scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd
Ressource binaire
scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd
Télécharger scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd
Ressource binaire
scripts/office/schemas/mce/mc.xsd
Télécharger scripts/office/schemas/mce/mc.xsd
Ressource binaire
scripts/office/schemas/microsoft/wml-2010.xsd
Télécharger scripts/office/schemas/microsoft/wml-2010.xsd
Ressource binaire
scripts/office/schemas/microsoft/wml-2012.xsd
Télécharger scripts/office/schemas/microsoft/wml-2012.xsd
Ressource binaire
scripts/office/schemas/microsoft/wml-2018.xsd
Télécharger scripts/office/schemas/microsoft/wml-2018.xsd
Ressource binaire
scripts/office/schemas/microsoft/wml-cex-2018.xsd
Télécharger scripts/office/schemas/microsoft/wml-cex-2018.xsd
Ressource binaire
scripts/office/schemas/microsoft/wml-cid-2016.xsd
Télécharger scripts/office/schemas/microsoft/wml-cid-2016.xsd
Ressource binaire
scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd
Télécharger scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd
Ressource binaire
scripts/office/schemas/microsoft/wml-symex-2015.xsd
Télécharger scripts/office/schemas/microsoft/wml-symex-2015.xsd
Ressource binaire
scripts/office/soffice.py
Télécharger scripts/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)scripts/office/unpack.py
Télécharger scripts/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)scripts/office/validate.py
Télécharger scripts/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()scripts/office/validateurs/init.py
Télécharger scripts/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",
]scripts/office/validateurs/base.py
Télécharger scripts/office/validators/base.py
Ressource binaire
scripts/office/validateurs/docx.py
Télécharger scripts/office/validators/docx.py
Ressource binaire
scripts/office/validateurs/pptx.py
Télécharger scripts/office/validators/pptx.py
Ressource binaire
scripts/office/validateurs/redlining.py
Télécharger scripts/office/validators/redlining.py
Ressource binaire
scripts/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()Voir dans GitHub
Pptx
Utilisez cette compétence chaque fois qu'un fichier.pptx est impliqué de quelque manière que ce soit - en entrée, en sortie ou les deux. Cela inclut: la création de diaporamas, de pitch decks ou de présentations; lire, analyser ou extraire du texte de n'importe quel fichier.pptx (même si le contenu extrait sera utilisé ailleurs, comme dans un e-mail ou un résumé); éditer, modifier ou mettre à jour des présentations existantes; combiner ou diviser des fichiers de diapositives; travailler avec des modèles, des mises en page, des notes du présentateur ou des commentaires. Déclenchez chaque fois que l'utilisateur mentionne « deck », « diapositives », « présentation » ou fait référence à un nom de fichier.pptx, quel que soit ce qu'il envisage de faire avec le contenu par la suite. Si un fichier.pptx doit être ouvert, créé ou modifié, utilisez cette compétence.
Lignes directrices de la marque
Manuel de compétences d'agent pour assembler la voix de la marque, les règles visuelles et les actifs réutilisables afin que Claude Skills produise des livrables créatifs cohérents.
claudeskills Docs