wormclaude 1.0.119 → 1.0.121
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/theme.js +1 -1
- package/dist/tui.js +6 -1
- package/package.json +1 -1
- package/skills/build-mcp-app/SKILL.md +0 -393
- package/skills/build-mcp-app/references/abuse-protection.md +0 -60
- package/skills/build-mcp-app/references/apps-sdk-messages.md +0 -227
- package/skills/build-mcp-app/references/directory-checklist.md +0 -18
- package/skills/build-mcp-app/references/iframe-sandbox.md +0 -164
- package/skills/build-mcp-app/references/payload-budgeting.md +0 -54
- package/skills/build-mcp-app/references/widget-templates.md +0 -249
- package/skills/build-mcp-server/SKILL.md +0 -222
- package/skills/build-mcp-server/references/auth.md +0 -108
- package/skills/build-mcp-server/references/deploy-cloudflare-workers.md +0 -106
- package/skills/build-mcp-server/references/elicitation.md +0 -129
- package/skills/build-mcp-server/references/remote-http-scaffold.md +0 -211
- package/skills/build-mcp-server/references/resources-and-prompts.md +0 -122
- package/skills/build-mcp-server/references/server-capabilities.md +0 -164
- package/skills/build-mcp-server/references/tool-design.md +0 -189
- package/skills/build-mcp-server/references/versions.md +0 -25
- package/skills/build-mcpb/SKILL.md +0 -200
- package/skills/build-mcpb/references/local-security.md +0 -149
- package/skills/build-mcpb/references/manifest-schema.md +0 -156
- package/skills/docx/script/__init__.py +0 -1
- package/skills/docx/script/accept_chages.py +0 -135
- package/skills/docx/script/comment.py +0 -318
- package/skills/docx/script/office/helpers/__init__.py +0 -0
- package/skills/docx/script/office/helpers/merge_runs.py +0 -199
- package/skills/docx/script/office/helpers/simplify_redlines.py +0 -197
- package/skills/docx/script/office/pack.py +0 -159
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/skills/docx/script/office/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/skills/docx/script/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/skills/docx/script/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/skills/docx/script/office/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/skills/docx/script/office/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/skills/docx/script/office/schemas/mce/mc.xsd +0 -75
- package/skills/docx/script/office/schemas/microsoft/wml-2010.xsd +0 -560
- package/skills/docx/script/office/schemas/microsoft/wml-2012.xsd +0 -67
- package/skills/docx/script/office/schemas/microsoft/wml-2018.xsd +0 -14
- package/skills/docx/script/office/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/skills/docx/script/office/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/skills/docx/script/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/skills/docx/script/office/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/skills/docx/script/office/soffice.py +0 -183
- package/skills/docx/script/office/unpack.py +0 -132
- package/skills/docx/script/office/validate.py +0 -117
- package/skills/docx/script/office/validators/__init__.py +0 -15
- package/skills/docx/script/office/validators/base.py +0 -851
- package/skills/docx/script/office/validators/docx.py +0 -446
- package/skills/docx/script/office/validators/pptx.py +0 -275
- package/skills/docx/script/office/validators/redlining.py +0 -247
- package/skills/docx/script/templates/comments.xml +0 -3
- package/skills/docx/script/templates/commentsExtended.xml +0 -3
- package/skills/docx/script/templates/commentsExtensible.xml +0 -3
- package/skills/docx/script/templates/commentsIds.xml +0 -3
- package/skills/docx/script/templates/people.xml +0 -3
- package/skills/docx/skill.md +0 -593
- package/skills/explain.md +0 -14
- package/skills/frontend-design/SKILL.md +0 -42
- package/skills/pdf/FORMS.md +0 -294
- package/skills/pdf/REFERENCE.md +0 -612
- package/skills/pdf/SKILL.md +0 -314
- package/skills/pdf/scripts/check_bounding_boxes.py +0 -65
- package/skills/pdf/scripts/check_fillable_fields.py +0 -11
- package/skills/pdf/scripts/convert_pdf_to_images.py +0 -33
- package/skills/pdf/scripts/create_validation_image.py +0 -37
- package/skills/pdf/scripts/extract_form_field_info.py +0 -122
- package/skills/pdf/scripts/extract_form_structure.py +0 -115
- package/skills/pdf/scripts/fill_fillable_fields.py +0 -98
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -107
- package/skills/playground/SKILL.md +0 -77
- package/skills/playground/templates/code-map.md +0 -158
- package/skills/playground/templates/concept-map.md +0 -73
- package/skills/playground/templates/data-explorer.md +0 -67
- package/skills/playground/templates/design-playground.md +0 -67
- package/skills/playground/templates/diff-review.md +0 -179
- package/skills/playground/templates/document-critique.md +0 -171
- package/skills/pptx/SKILL.md +0 -230
- package/skills/pptx/editing.md +0 -205
- package/skills/pptx/pptxgenjs.md +0 -437
- package/skills/pptx/scripts/__init__.py +0 -0
- package/skills/pptx/scripts/add_slide.py +0 -195
- package/skills/pptx/scripts/clean.py +0 -286
- package/skills/pptx/scripts/office/helpers/__init__.py +0 -0
- package/skills/pptx/scripts/office/helpers/merge_runs.py +0 -199
- package/skills/pptx/scripts/office/helpers/simplify_redlines.py +0 -197
- package/skills/pptx/scripts/office/pack.py +0 -159
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/skills/pptx/scripts/office/schemas/mce/mc.xsd +0 -75
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +0 -560
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +0 -67
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +0 -14
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/skills/pptx/scripts/office/soffice.py +0 -183
- package/skills/pptx/scripts/office/unpack.py +0 -132
- package/skills/pptx/scripts/office/validate.py +0 -117
- package/skills/pptx/scripts/office/validators/__init__.py +0 -15
- package/skills/pptx/scripts/office/validators/base.py +0 -851
- package/skills/pptx/scripts/office/validators/docx.py +0 -446
- package/skills/pptx/scripts/office/validators/pptx.py +0 -275
- package/skills/pptx/scripts/office/validators/redlining.py +0 -247
- package/skills/pptx/scripts/thumbnail.py +0 -289
- package/skills/recon.md +0 -16
- package/skills/security-audit/SKILL.md +0 -26
- package/skills/talent-creator/SKILL.md +0 -486
- package/skills/talent-creator/agents/analyzer.md +0 -274
- package/skills/talent-creator/agents/comparator.md +0 -202
- package/skills/talent-creator/agents/grader.md +0 -223
- package/skills/talent-creator/assets/eval_review.html +0 -146
- package/skills/talent-creator/eval-viewer/generate_review.py +0 -471
- package/skills/talent-creator/eval-viewer/viewer.html +0 -1325
- package/skills/talent-creator/references/schemas.md +0 -430
- package/skills/talent-creator/scripts/__init__.py +0 -0
- package/skills/talent-creator/scripts/aggregate_benchmark.py +0 -401
- package/skills/talent-creator/scripts/generate_report.py +0 -326
- package/skills/talent-creator/scripts/improve_description.py +0 -247
- package/skills/talent-creator/scripts/package_skill.py +0 -136
- package/skills/talent-creator/scripts/quick_validate.py +0 -146
- package/skills/talent-creator/scripts/run_eval.py +0 -310
- package/skills/talent-creator/scripts/run_loop.py +0 -328
- package/skills/talent-creator/scripts/utils.py +0 -47
- package/skills/xlsx/SKILL.md +0 -300
- package/skills/xlsx/scripts/office/helpers/__init__.py +0 -0
- package/skills/xlsx/scripts/office/helpers/merge_runs.py +0 -199
- package/skills/xlsx/scripts/office/helpers/simplify_redlines.py +0 -197
- package/skills/xlsx/scripts/office/pack.py +0 -159
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
- package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +0 -75
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +0 -560
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +0 -67
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +0 -14
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +0 -20
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +0 -13
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +0 -8
- package/skills/xlsx/scripts/office/soffice.py +0 -183
- package/skills/xlsx/scripts/office/unpack.py +0 -132
- package/skills/xlsx/scripts/office/validate.py +0 -117
- package/skills/xlsx/scripts/office/validators/__init__.py +0 -15
- package/skills/xlsx/scripts/office/validators/base.py +0 -851
- package/skills/xlsx/scripts/office/validators/docx.py +0 -446
- package/skills/xlsx/scripts/office/validators/pptx.py +0 -275
- package/skills/xlsx/scripts/office/validators/redlining.py +0 -247
- package/skills/xlsx/scripts/recalc.py +0 -184
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
"""Add a new slide to an unpacked PPTX directory.
|
|
2
|
-
|
|
3
|
-
Usage: python add_slide.py <unpacked_dir> <source>
|
|
4
|
-
|
|
5
|
-
The source can be:
|
|
6
|
-
- A slide file (e.g., slide2.xml) - duplicates the slide
|
|
7
|
-
- A layout file (e.g., slideLayout2.xml) - creates from layout
|
|
8
|
-
|
|
9
|
-
Examples:
|
|
10
|
-
python add_slide.py unpacked/ slide2.xml
|
|
11
|
-
# Duplicates slide2, creates slide5.xml
|
|
12
|
-
|
|
13
|
-
python add_slide.py unpacked/ slideLayout2.xml
|
|
14
|
-
# Creates slide5.xml from slideLayout2.xml
|
|
15
|
-
|
|
16
|
-
To see available layouts: ls unpacked/ppt/slideLayouts/
|
|
17
|
-
|
|
18
|
-
Prints the <p:sldId> element to add to presentation.xml.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
import re
|
|
22
|
-
import shutil
|
|
23
|
-
import sys
|
|
24
|
-
from pathlib import Path
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def get_next_slide_number(slides_dir: Path) -> int:
|
|
28
|
-
existing = [int(m.group(1)) for f in slides_dir.glob("slide*.xml")
|
|
29
|
-
if (m := re.match(r"slide(\d+)\.xml", f.name))]
|
|
30
|
-
return max(existing) + 1 if existing else 1
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
def create_slide_from_layout(unpacked_dir: Path, layout_file: str) -> None:
|
|
34
|
-
slides_dir = unpacked_dir / "ppt" / "slides"
|
|
35
|
-
rels_dir = slides_dir / "_rels"
|
|
36
|
-
layouts_dir = unpacked_dir / "ppt" / "slideLayouts"
|
|
37
|
-
|
|
38
|
-
layout_path = layouts_dir / layout_file
|
|
39
|
-
if not layout_path.exists():
|
|
40
|
-
print(f"Error: {layout_path} not found", file=sys.stderr)
|
|
41
|
-
sys.exit(1)
|
|
42
|
-
|
|
43
|
-
next_num = get_next_slide_number(slides_dir)
|
|
44
|
-
dest = f"slide{next_num}.xml"
|
|
45
|
-
dest_slide = slides_dir / dest
|
|
46
|
-
dest_rels = rels_dir / f"{dest}.rels"
|
|
47
|
-
|
|
48
|
-
slide_xml = '''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
49
|
-
<p:sld xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:p="http://schemas.openxmlformats.org/presentationml/2006/main">
|
|
50
|
-
<p:cSld>
|
|
51
|
-
<p:spTree>
|
|
52
|
-
<p:nvGrpSpPr>
|
|
53
|
-
<p:cNvPr id="1" name=""/>
|
|
54
|
-
<p:cNvGrpSpPr/>
|
|
55
|
-
<p:nvPr/>
|
|
56
|
-
</p:nvGrpSpPr>
|
|
57
|
-
<p:grpSpPr>
|
|
58
|
-
<a:xfrm>
|
|
59
|
-
<a:off x="0" y="0"/>
|
|
60
|
-
<a:ext cx="0" cy="0"/>
|
|
61
|
-
<a:chOff x="0" y="0"/>
|
|
62
|
-
<a:chExt cx="0" cy="0"/>
|
|
63
|
-
</a:xfrm>
|
|
64
|
-
</p:grpSpPr>
|
|
65
|
-
</p:spTree>
|
|
66
|
-
</p:cSld>
|
|
67
|
-
<p:clrMapOvr>
|
|
68
|
-
<a:masterClrMapping/>
|
|
69
|
-
</p:clrMapOvr>
|
|
70
|
-
</p:sld>'''
|
|
71
|
-
dest_slide.write_text(slide_xml, encoding="utf-8")
|
|
72
|
-
|
|
73
|
-
rels_dir.mkdir(exist_ok=True)
|
|
74
|
-
rels_xml = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
75
|
-
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
|
|
76
|
-
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/{layout_file}"/>
|
|
77
|
-
</Relationships>'''
|
|
78
|
-
dest_rels.write_text(rels_xml, encoding="utf-8")
|
|
79
|
-
|
|
80
|
-
_add_to_content_types(unpacked_dir, dest)
|
|
81
|
-
|
|
82
|
-
rid = _add_to_presentation_rels(unpacked_dir, dest)
|
|
83
|
-
|
|
84
|
-
next_slide_id = _get_next_slide_id(unpacked_dir)
|
|
85
|
-
|
|
86
|
-
print(f"Created {dest} from {layout_file}")
|
|
87
|
-
print(f'Add to presentation.xml <p:sldIdLst>: <p:sldId id="{next_slide_id}" r:id="{rid}"/>')
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def duplicate_slide(unpacked_dir: Path, source: str) -> None:
|
|
91
|
-
slides_dir = unpacked_dir / "ppt" / "slides"
|
|
92
|
-
rels_dir = slides_dir / "_rels"
|
|
93
|
-
|
|
94
|
-
source_slide = slides_dir / source
|
|
95
|
-
|
|
96
|
-
if not source_slide.exists():
|
|
97
|
-
print(f"Error: {source_slide} not found", file=sys.stderr)
|
|
98
|
-
sys.exit(1)
|
|
99
|
-
|
|
100
|
-
next_num = get_next_slide_number(slides_dir)
|
|
101
|
-
dest = f"slide{next_num}.xml"
|
|
102
|
-
dest_slide = slides_dir / dest
|
|
103
|
-
|
|
104
|
-
source_rels = rels_dir / f"{source}.rels"
|
|
105
|
-
dest_rels = rels_dir / f"{dest}.rels"
|
|
106
|
-
|
|
107
|
-
shutil.copy2(source_slide, dest_slide)
|
|
108
|
-
|
|
109
|
-
if source_rels.exists():
|
|
110
|
-
shutil.copy2(source_rels, dest_rels)
|
|
111
|
-
|
|
112
|
-
rels_content = dest_rels.read_text(encoding="utf-8")
|
|
113
|
-
rels_content = re.sub(
|
|
114
|
-
r'\s*<Relationship[^>]*Type="[^"]*notesSlide"[^>]*/>\s*',
|
|
115
|
-
"\n",
|
|
116
|
-
rels_content,
|
|
117
|
-
)
|
|
118
|
-
dest_rels.write_text(rels_content, encoding="utf-8")
|
|
119
|
-
|
|
120
|
-
_add_to_content_types(unpacked_dir, dest)
|
|
121
|
-
|
|
122
|
-
rid = _add_to_presentation_rels(unpacked_dir, dest)
|
|
123
|
-
|
|
124
|
-
next_slide_id = _get_next_slide_id(unpacked_dir)
|
|
125
|
-
|
|
126
|
-
print(f"Created {dest} from {source}")
|
|
127
|
-
print(f'Add to presentation.xml <p:sldIdLst>: <p:sldId id="{next_slide_id}" r:id="{rid}"/>')
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
def _add_to_content_types(unpacked_dir: Path, dest: str) -> None:
|
|
131
|
-
content_types_path = unpacked_dir / "[Content_Types].xml"
|
|
132
|
-
content_types = content_types_path.read_text(encoding="utf-8")
|
|
133
|
-
|
|
134
|
-
new_override = f'<Override PartName="/ppt/slides/{dest}" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>'
|
|
135
|
-
|
|
136
|
-
if f"/ppt/slides/{dest}" not in content_types:
|
|
137
|
-
content_types = content_types.replace("</Types>", f" {new_override}\n</Types>")
|
|
138
|
-
content_types_path.write_text(content_types, encoding="utf-8")
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
def _add_to_presentation_rels(unpacked_dir: Path, dest: str) -> str:
|
|
142
|
-
pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
|
|
143
|
-
pres_rels = pres_rels_path.read_text(encoding="utf-8")
|
|
144
|
-
|
|
145
|
-
rids = [int(m) for m in re.findall(r'Id="rId(\d+)"', pres_rels)]
|
|
146
|
-
next_rid = max(rids) + 1 if rids else 1
|
|
147
|
-
rid = f"rId{next_rid}"
|
|
148
|
-
|
|
149
|
-
new_rel = f'<Relationship Id="{rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide" Target="slides/{dest}"/>'
|
|
150
|
-
|
|
151
|
-
if f"slides/{dest}" not in pres_rels:
|
|
152
|
-
pres_rels = pres_rels.replace("</Relationships>", f" {new_rel}\n</Relationships>")
|
|
153
|
-
pres_rels_path.write_text(pres_rels, encoding="utf-8")
|
|
154
|
-
|
|
155
|
-
return rid
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
def _get_next_slide_id(unpacked_dir: Path) -> int:
|
|
159
|
-
pres_path = unpacked_dir / "ppt" / "presentation.xml"
|
|
160
|
-
pres_content = pres_path.read_text(encoding="utf-8")
|
|
161
|
-
slide_ids = [int(m) for m in re.findall(r'<p:sldId[^>]*id="(\d+)"', pres_content)]
|
|
162
|
-
return max(slide_ids) + 1 if slide_ids else 256
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def parse_source(source: str) -> tuple[str, str | None]:
|
|
166
|
-
if source.startswith("slideLayout") and source.endswith(".xml"):
|
|
167
|
-
return ("layout", source)
|
|
168
|
-
|
|
169
|
-
return ("slide", None)
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if __name__ == "__main__":
|
|
173
|
-
if len(sys.argv) != 3:
|
|
174
|
-
print("Usage: python add_slide.py <unpacked_dir> <source>", file=sys.stderr)
|
|
175
|
-
print("", file=sys.stderr)
|
|
176
|
-
print("Source can be:", file=sys.stderr)
|
|
177
|
-
print(" slide2.xml - duplicate an existing slide", file=sys.stderr)
|
|
178
|
-
print(" slideLayout2.xml - create from a layout template", file=sys.stderr)
|
|
179
|
-
print("", file=sys.stderr)
|
|
180
|
-
print("To see available layouts: ls <unpacked_dir>/ppt/slideLayouts/", file=sys.stderr)
|
|
181
|
-
sys.exit(1)
|
|
182
|
-
|
|
183
|
-
unpacked_dir = Path(sys.argv[1])
|
|
184
|
-
source = sys.argv[2]
|
|
185
|
-
|
|
186
|
-
if not unpacked_dir.exists():
|
|
187
|
-
print(f"Error: {unpacked_dir} not found", file=sys.stderr)
|
|
188
|
-
sys.exit(1)
|
|
189
|
-
|
|
190
|
-
source_type, layout_file = parse_source(source)
|
|
191
|
-
|
|
192
|
-
if source_type == "layout" and layout_file is not None:
|
|
193
|
-
create_slide_from_layout(unpacked_dir, layout_file)
|
|
194
|
-
else:
|
|
195
|
-
duplicate_slide(unpacked_dir, source)
|
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
"""Remove unreferenced files from an unpacked PPTX directory.
|
|
2
|
-
|
|
3
|
-
Usage: python clean.py <unpacked_dir>
|
|
4
|
-
|
|
5
|
-
Example:
|
|
6
|
-
python clean.py unpacked/
|
|
7
|
-
|
|
8
|
-
This script removes:
|
|
9
|
-
- Orphaned slides (not in sldIdLst) and their relationships
|
|
10
|
-
- [trash] directory (unreferenced files)
|
|
11
|
-
- Orphaned .rels files for deleted resources
|
|
12
|
-
- Unreferenced media, embeddings, charts, diagrams, drawings, ink files
|
|
13
|
-
- Unreferenced theme files
|
|
14
|
-
- Unreferenced notes slides
|
|
15
|
-
- Content-Type overrides for deleted files
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
import sys
|
|
19
|
-
from pathlib import Path
|
|
20
|
-
|
|
21
|
-
import defusedxml.minidom
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
import re
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def get_slides_in_sldidlst(unpacked_dir: Path) -> set[str]:
|
|
28
|
-
pres_path = unpacked_dir / "ppt" / "presentation.xml"
|
|
29
|
-
pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
|
|
30
|
-
|
|
31
|
-
if not pres_path.exists() or not pres_rels_path.exists():
|
|
32
|
-
return set()
|
|
33
|
-
|
|
34
|
-
rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
|
|
35
|
-
rid_to_slide = {}
|
|
36
|
-
for rel in rels_dom.getElementsByTagName("Relationship"):
|
|
37
|
-
rid = rel.getAttribute("Id")
|
|
38
|
-
target = rel.getAttribute("Target")
|
|
39
|
-
rel_type = rel.getAttribute("Type")
|
|
40
|
-
if "slide" in rel_type and target.startswith("slides/"):
|
|
41
|
-
rid_to_slide[rid] = target.replace("slides/", "")
|
|
42
|
-
|
|
43
|
-
pres_content = pres_path.read_text(encoding="utf-8")
|
|
44
|
-
referenced_rids = set(re.findall(r'<p:sldId[^>]*r:id="([^"]+)"', pres_content))
|
|
45
|
-
|
|
46
|
-
return {rid_to_slide[rid] for rid in referenced_rids if rid in rid_to_slide}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
def remove_orphaned_slides(unpacked_dir: Path) -> list[str]:
|
|
50
|
-
slides_dir = unpacked_dir / "ppt" / "slides"
|
|
51
|
-
slides_rels_dir = slides_dir / "_rels"
|
|
52
|
-
pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
|
|
53
|
-
|
|
54
|
-
if not slides_dir.exists():
|
|
55
|
-
return []
|
|
56
|
-
|
|
57
|
-
referenced_slides = get_slides_in_sldidlst(unpacked_dir)
|
|
58
|
-
removed = []
|
|
59
|
-
|
|
60
|
-
for slide_file in slides_dir.glob("slide*.xml"):
|
|
61
|
-
if slide_file.name not in referenced_slides:
|
|
62
|
-
rel_path = slide_file.relative_to(unpacked_dir)
|
|
63
|
-
slide_file.unlink()
|
|
64
|
-
removed.append(str(rel_path))
|
|
65
|
-
|
|
66
|
-
rels_file = slides_rels_dir / f"{slide_file.name}.rels"
|
|
67
|
-
if rels_file.exists():
|
|
68
|
-
rels_file.unlink()
|
|
69
|
-
removed.append(str(rels_file.relative_to(unpacked_dir)))
|
|
70
|
-
|
|
71
|
-
if removed and pres_rels_path.exists():
|
|
72
|
-
rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
|
|
73
|
-
changed = False
|
|
74
|
-
|
|
75
|
-
for rel in list(rels_dom.getElementsByTagName("Relationship")):
|
|
76
|
-
target = rel.getAttribute("Target")
|
|
77
|
-
if target.startswith("slides/"):
|
|
78
|
-
slide_name = target.replace("slides/", "")
|
|
79
|
-
if slide_name not in referenced_slides:
|
|
80
|
-
if rel.parentNode:
|
|
81
|
-
rel.parentNode.removeChild(rel)
|
|
82
|
-
changed = True
|
|
83
|
-
|
|
84
|
-
if changed:
|
|
85
|
-
with open(pres_rels_path, "wb") as f:
|
|
86
|
-
f.write(rels_dom.toxml(encoding="utf-8"))
|
|
87
|
-
|
|
88
|
-
return removed
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def remove_trash_directory(unpacked_dir: Path) -> list[str]:
|
|
92
|
-
trash_dir = unpacked_dir / "[trash]"
|
|
93
|
-
removed = []
|
|
94
|
-
|
|
95
|
-
if trash_dir.exists() and trash_dir.is_dir():
|
|
96
|
-
for file_path in trash_dir.iterdir():
|
|
97
|
-
if file_path.is_file():
|
|
98
|
-
rel_path = file_path.relative_to(unpacked_dir)
|
|
99
|
-
removed.append(str(rel_path))
|
|
100
|
-
file_path.unlink()
|
|
101
|
-
trash_dir.rmdir()
|
|
102
|
-
|
|
103
|
-
return removed
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def get_slide_referenced_files(unpacked_dir: Path) -> set:
|
|
107
|
-
referenced = set()
|
|
108
|
-
slides_rels_dir = unpacked_dir / "ppt" / "slides" / "_rels"
|
|
109
|
-
|
|
110
|
-
if not slides_rels_dir.exists():
|
|
111
|
-
return referenced
|
|
112
|
-
|
|
113
|
-
for rels_file in slides_rels_dir.glob("*.rels"):
|
|
114
|
-
dom = defusedxml.minidom.parse(str(rels_file))
|
|
115
|
-
for rel in dom.getElementsByTagName("Relationship"):
|
|
116
|
-
target = rel.getAttribute("Target")
|
|
117
|
-
if not target:
|
|
118
|
-
continue
|
|
119
|
-
target_path = (rels_file.parent.parent / target).resolve()
|
|
120
|
-
try:
|
|
121
|
-
referenced.add(target_path.relative_to(unpacked_dir.resolve()))
|
|
122
|
-
except ValueError:
|
|
123
|
-
pass
|
|
124
|
-
|
|
125
|
-
return referenced
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def remove_orphaned_rels_files(unpacked_dir: Path) -> list[str]:
|
|
129
|
-
resource_dirs = ["charts", "diagrams", "drawings"]
|
|
130
|
-
removed = []
|
|
131
|
-
slide_referenced = get_slide_referenced_files(unpacked_dir)
|
|
132
|
-
|
|
133
|
-
for dir_name in resource_dirs:
|
|
134
|
-
rels_dir = unpacked_dir / "ppt" / dir_name / "_rels"
|
|
135
|
-
if not rels_dir.exists():
|
|
136
|
-
continue
|
|
137
|
-
|
|
138
|
-
for rels_file in rels_dir.glob("*.rels"):
|
|
139
|
-
resource_file = rels_dir.parent / rels_file.name.replace(".rels", "")
|
|
140
|
-
try:
|
|
141
|
-
resource_rel_path = resource_file.resolve().relative_to(unpacked_dir.resolve())
|
|
142
|
-
except ValueError:
|
|
143
|
-
continue
|
|
144
|
-
|
|
145
|
-
if not resource_file.exists() or resource_rel_path not in slide_referenced:
|
|
146
|
-
rels_file.unlink()
|
|
147
|
-
rel_path = rels_file.relative_to(unpacked_dir)
|
|
148
|
-
removed.append(str(rel_path))
|
|
149
|
-
|
|
150
|
-
return removed
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
def get_referenced_files(unpacked_dir: Path) -> set:
|
|
154
|
-
referenced = set()
|
|
155
|
-
|
|
156
|
-
for rels_file in unpacked_dir.rglob("*.rels"):
|
|
157
|
-
dom = defusedxml.minidom.parse(str(rels_file))
|
|
158
|
-
for rel in dom.getElementsByTagName("Relationship"):
|
|
159
|
-
target = rel.getAttribute("Target")
|
|
160
|
-
if not target:
|
|
161
|
-
continue
|
|
162
|
-
target_path = (rels_file.parent.parent / target).resolve()
|
|
163
|
-
try:
|
|
164
|
-
referenced.add(target_path.relative_to(unpacked_dir.resolve()))
|
|
165
|
-
except ValueError:
|
|
166
|
-
pass
|
|
167
|
-
|
|
168
|
-
return referenced
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
def remove_orphaned_files(unpacked_dir: Path, referenced: set) -> list[str]:
|
|
172
|
-
resource_dirs = ["media", "embeddings", "charts", "diagrams", "tags", "drawings", "ink"]
|
|
173
|
-
removed = []
|
|
174
|
-
|
|
175
|
-
for dir_name in resource_dirs:
|
|
176
|
-
dir_path = unpacked_dir / "ppt" / dir_name
|
|
177
|
-
if not dir_path.exists():
|
|
178
|
-
continue
|
|
179
|
-
|
|
180
|
-
for file_path in dir_path.glob("*"):
|
|
181
|
-
if not file_path.is_file():
|
|
182
|
-
continue
|
|
183
|
-
rel_path = file_path.relative_to(unpacked_dir)
|
|
184
|
-
if rel_path not in referenced:
|
|
185
|
-
file_path.unlink()
|
|
186
|
-
removed.append(str(rel_path))
|
|
187
|
-
|
|
188
|
-
theme_dir = unpacked_dir / "ppt" / "theme"
|
|
189
|
-
if theme_dir.exists():
|
|
190
|
-
for file_path in theme_dir.glob("theme*.xml"):
|
|
191
|
-
rel_path = file_path.relative_to(unpacked_dir)
|
|
192
|
-
if rel_path not in referenced:
|
|
193
|
-
file_path.unlink()
|
|
194
|
-
removed.append(str(rel_path))
|
|
195
|
-
theme_rels = theme_dir / "_rels" / f"{file_path.name}.rels"
|
|
196
|
-
if theme_rels.exists():
|
|
197
|
-
theme_rels.unlink()
|
|
198
|
-
removed.append(str(theme_rels.relative_to(unpacked_dir)))
|
|
199
|
-
|
|
200
|
-
notes_dir = unpacked_dir / "ppt" / "notesSlides"
|
|
201
|
-
if notes_dir.exists():
|
|
202
|
-
for file_path in notes_dir.glob("*.xml"):
|
|
203
|
-
if not file_path.is_file():
|
|
204
|
-
continue
|
|
205
|
-
rel_path = file_path.relative_to(unpacked_dir)
|
|
206
|
-
if rel_path not in referenced:
|
|
207
|
-
file_path.unlink()
|
|
208
|
-
removed.append(str(rel_path))
|
|
209
|
-
|
|
210
|
-
notes_rels_dir = notes_dir / "_rels"
|
|
211
|
-
if notes_rels_dir.exists():
|
|
212
|
-
for file_path in notes_rels_dir.glob("*.rels"):
|
|
213
|
-
notes_file = notes_dir / file_path.name.replace(".rels", "")
|
|
214
|
-
if not notes_file.exists():
|
|
215
|
-
file_path.unlink()
|
|
216
|
-
removed.append(str(file_path.relative_to(unpacked_dir)))
|
|
217
|
-
|
|
218
|
-
return removed
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def update_content_types(unpacked_dir: Path, removed_files: list[str]) -> None:
|
|
222
|
-
ct_path = unpacked_dir / "[Content_Types].xml"
|
|
223
|
-
if not ct_path.exists():
|
|
224
|
-
return
|
|
225
|
-
|
|
226
|
-
dom = defusedxml.minidom.parse(str(ct_path))
|
|
227
|
-
changed = False
|
|
228
|
-
|
|
229
|
-
for override in list(dom.getElementsByTagName("Override")):
|
|
230
|
-
part_name = override.getAttribute("PartName").lstrip("/")
|
|
231
|
-
if part_name in removed_files:
|
|
232
|
-
if override.parentNode:
|
|
233
|
-
override.parentNode.removeChild(override)
|
|
234
|
-
changed = True
|
|
235
|
-
|
|
236
|
-
if changed:
|
|
237
|
-
with open(ct_path, "wb") as f:
|
|
238
|
-
f.write(dom.toxml(encoding="utf-8"))
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
def clean_unused_files(unpacked_dir: Path) -> list[str]:
|
|
242
|
-
all_removed = []
|
|
243
|
-
|
|
244
|
-
slides_removed = remove_orphaned_slides(unpacked_dir)
|
|
245
|
-
all_removed.extend(slides_removed)
|
|
246
|
-
|
|
247
|
-
trash_removed = remove_trash_directory(unpacked_dir)
|
|
248
|
-
all_removed.extend(trash_removed)
|
|
249
|
-
|
|
250
|
-
while True:
|
|
251
|
-
removed_rels = remove_orphaned_rels_files(unpacked_dir)
|
|
252
|
-
referenced = get_referenced_files(unpacked_dir)
|
|
253
|
-
removed_files = remove_orphaned_files(unpacked_dir, referenced)
|
|
254
|
-
|
|
255
|
-
total_removed = removed_rels + removed_files
|
|
256
|
-
if not total_removed:
|
|
257
|
-
break
|
|
258
|
-
|
|
259
|
-
all_removed.extend(total_removed)
|
|
260
|
-
|
|
261
|
-
if all_removed:
|
|
262
|
-
update_content_types(unpacked_dir, all_removed)
|
|
263
|
-
|
|
264
|
-
return all_removed
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if __name__ == "__main__":
|
|
268
|
-
if len(sys.argv) != 2:
|
|
269
|
-
print("Usage: python clean.py <unpacked_dir>", file=sys.stderr)
|
|
270
|
-
print("Example: python clean.py unpacked/", file=sys.stderr)
|
|
271
|
-
sys.exit(1)
|
|
272
|
-
|
|
273
|
-
unpacked_dir = Path(sys.argv[1])
|
|
274
|
-
|
|
275
|
-
if not unpacked_dir.exists():
|
|
276
|
-
print(f"Error: {unpacked_dir} not found", file=sys.stderr)
|
|
277
|
-
sys.exit(1)
|
|
278
|
-
|
|
279
|
-
removed = clean_unused_files(unpacked_dir)
|
|
280
|
-
|
|
281
|
-
if removed:
|
|
282
|
-
print(f"Removed {len(removed)} unreferenced files:")
|
|
283
|
-
for f in removed:
|
|
284
|
-
print(f" {f}")
|
|
285
|
-
else:
|
|
286
|
-
print("No unreferenced files found")
|
|
File without changes
|
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
"""Merge adjacent runs with identical formatting in DOCX.
|
|
2
|
-
|
|
3
|
-
Merges adjacent <w:r> elements that have identical <w:rPr> properties.
|
|
4
|
-
Works on runs in paragraphs and inside tracked changes (<w:ins>, <w:del>).
|
|
5
|
-
|
|
6
|
-
Also:
|
|
7
|
-
- Removes rsid attributes from runs (revision metadata that doesn't affect rendering)
|
|
8
|
-
- Removes proofErr elements (spell/grammar markers that block merging)
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from pathlib import Path
|
|
12
|
-
|
|
13
|
-
import defusedxml.minidom
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def merge_runs(input_dir: str) -> tuple[int, str]:
|
|
17
|
-
doc_xml = Path(input_dir) / "word" / "document.xml"
|
|
18
|
-
|
|
19
|
-
if not doc_xml.exists():
|
|
20
|
-
return 0, f"Error: {doc_xml} not found"
|
|
21
|
-
|
|
22
|
-
try:
|
|
23
|
-
dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8"))
|
|
24
|
-
root = dom.documentElement
|
|
25
|
-
|
|
26
|
-
_remove_elements(root, "proofErr")
|
|
27
|
-
_strip_run_rsid_attrs(root)
|
|
28
|
-
|
|
29
|
-
containers = {run.parentNode for run in _find_elements(root, "r")}
|
|
30
|
-
|
|
31
|
-
merge_count = 0
|
|
32
|
-
for container in containers:
|
|
33
|
-
merge_count += _merge_runs_in(container)
|
|
34
|
-
|
|
35
|
-
doc_xml.write_bytes(dom.toxml(encoding="UTF-8"))
|
|
36
|
-
return merge_count, f"Merged {merge_count} runs"
|
|
37
|
-
|
|
38
|
-
except Exception as e:
|
|
39
|
-
return 0, f"Error: {e}"
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
def _find_elements(root, tag: str) -> list:
|
|
45
|
-
results = []
|
|
46
|
-
|
|
47
|
-
def traverse(node):
|
|
48
|
-
if node.nodeType == node.ELEMENT_NODE:
|
|
49
|
-
name = node.localName or node.tagName
|
|
50
|
-
if name == tag or name.endswith(f":{tag}"):
|
|
51
|
-
results.append(node)
|
|
52
|
-
for child in node.childNodes:
|
|
53
|
-
traverse(child)
|
|
54
|
-
|
|
55
|
-
traverse(root)
|
|
56
|
-
return results
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def _get_child(parent, tag: str):
|
|
60
|
-
for child in parent.childNodes:
|
|
61
|
-
if child.nodeType == child.ELEMENT_NODE:
|
|
62
|
-
name = child.localName or child.tagName
|
|
63
|
-
if name == tag or name.endswith(f":{tag}"):
|
|
64
|
-
return child
|
|
65
|
-
return None
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def _get_children(parent, tag: str) -> list:
|
|
69
|
-
results = []
|
|
70
|
-
for child in parent.childNodes:
|
|
71
|
-
if child.nodeType == child.ELEMENT_NODE:
|
|
72
|
-
name = child.localName or child.tagName
|
|
73
|
-
if name == tag or name.endswith(f":{tag}"):
|
|
74
|
-
results.append(child)
|
|
75
|
-
return results
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def _is_adjacent(elem1, elem2) -> bool:
|
|
79
|
-
node = elem1.nextSibling
|
|
80
|
-
while node:
|
|
81
|
-
if node == elem2:
|
|
82
|
-
return True
|
|
83
|
-
if node.nodeType == node.ELEMENT_NODE:
|
|
84
|
-
return False
|
|
85
|
-
if node.nodeType == node.TEXT_NODE and node.data.strip():
|
|
86
|
-
return False
|
|
87
|
-
node = node.nextSibling
|
|
88
|
-
return False
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
def _remove_elements(root, tag: str):
|
|
94
|
-
for elem in _find_elements(root, tag):
|
|
95
|
-
if elem.parentNode:
|
|
96
|
-
elem.parentNode.removeChild(elem)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def _strip_run_rsid_attrs(root):
|
|
100
|
-
for run in _find_elements(root, "r"):
|
|
101
|
-
for attr in list(run.attributes.values()):
|
|
102
|
-
if "rsid" in attr.name.lower():
|
|
103
|
-
run.removeAttribute(attr.name)
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
def _merge_runs_in(container) -> int:
|
|
109
|
-
merge_count = 0
|
|
110
|
-
run = _first_child_run(container)
|
|
111
|
-
|
|
112
|
-
while run:
|
|
113
|
-
while True:
|
|
114
|
-
next_elem = _next_element_sibling(run)
|
|
115
|
-
if next_elem and _is_run(next_elem) and _can_merge(run, next_elem):
|
|
116
|
-
_merge_run_content(run, next_elem)
|
|
117
|
-
container.removeChild(next_elem)
|
|
118
|
-
merge_count += 1
|
|
119
|
-
else:
|
|
120
|
-
break
|
|
121
|
-
|
|
122
|
-
_consolidate_text(run)
|
|
123
|
-
run = _next_sibling_run(run)
|
|
124
|
-
|
|
125
|
-
return merge_count
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def _first_child_run(container):
|
|
129
|
-
for child in container.childNodes:
|
|
130
|
-
if child.nodeType == child.ELEMENT_NODE and _is_run(child):
|
|
131
|
-
return child
|
|
132
|
-
return None
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
def _next_element_sibling(node):
|
|
136
|
-
sibling = node.nextSibling
|
|
137
|
-
while sibling:
|
|
138
|
-
if sibling.nodeType == sibling.ELEMENT_NODE:
|
|
139
|
-
return sibling
|
|
140
|
-
sibling = sibling.nextSibling
|
|
141
|
-
return None
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
def _next_sibling_run(node):
|
|
145
|
-
sibling = node.nextSibling
|
|
146
|
-
while sibling:
|
|
147
|
-
if sibling.nodeType == sibling.ELEMENT_NODE:
|
|
148
|
-
if _is_run(sibling):
|
|
149
|
-
return sibling
|
|
150
|
-
sibling = sibling.nextSibling
|
|
151
|
-
return None
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
def _is_run(node) -> bool:
|
|
155
|
-
name = node.localName or node.tagName
|
|
156
|
-
return name == "r" or name.endswith(":r")
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def _can_merge(run1, run2) -> bool:
|
|
160
|
-
rpr1 = _get_child(run1, "rPr")
|
|
161
|
-
rpr2 = _get_child(run2, "rPr")
|
|
162
|
-
|
|
163
|
-
if (rpr1 is None) != (rpr2 is None):
|
|
164
|
-
return False
|
|
165
|
-
if rpr1 is None:
|
|
166
|
-
return True
|
|
167
|
-
return rpr1.toxml() == rpr2.toxml()
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
def _merge_run_content(target, source):
|
|
171
|
-
for child in list(source.childNodes):
|
|
172
|
-
if child.nodeType == child.ELEMENT_NODE:
|
|
173
|
-
name = child.localName or child.tagName
|
|
174
|
-
if name != "rPr" and not name.endswith(":rPr"):
|
|
175
|
-
target.appendChild(child)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
def _consolidate_text(run):
|
|
179
|
-
t_elements = _get_children(run, "t")
|
|
180
|
-
|
|
181
|
-
for i in range(len(t_elements) - 1, 0, -1):
|
|
182
|
-
curr, prev = t_elements[i], t_elements[i - 1]
|
|
183
|
-
|
|
184
|
-
if _is_adjacent(prev, curr):
|
|
185
|
-
prev_text = prev.firstChild.data if prev.firstChild else ""
|
|
186
|
-
curr_text = curr.firstChild.data if curr.firstChild else ""
|
|
187
|
-
merged = prev_text + curr_text
|
|
188
|
-
|
|
189
|
-
if prev.firstChild:
|
|
190
|
-
prev.firstChild.data = merged
|
|
191
|
-
else:
|
|
192
|
-
prev.appendChild(run.ownerDocument.createTextNode(merged))
|
|
193
|
-
|
|
194
|
-
if merged.startswith(" ") or merged.endswith(" "):
|
|
195
|
-
prev.setAttribute("xml:space", "preserve")
|
|
196
|
-
elif prev.hasAttribute("xml:space"):
|
|
197
|
-
prev.removeAttribute("xml:space")
|
|
198
|
-
|
|
199
|
-
run.removeChild(curr)
|