@preapexis/piexis 0.1.2
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/.github/workflows/npm-publish.yml +63 -0
- package/AGENTS.md +58 -0
- package/PERMISSION_MODES_PLAN.md +279 -0
- package/README.md +207 -0
- package/WHAT_YOU_GET.md +153 -0
- package/eval/README.md +72 -0
- package/eval/permission-cases.json +28 -0
- package/eval/permissions.mjs +103 -0
- package/extensions/ask-question.ts +111 -0
- package/extensions/background-tasks.ts +433 -0
- package/extensions/background-worker.ts +50 -0
- package/extensions/glob.ts +32 -0
- package/extensions/mode.ts +397 -0
- package/extensions/ui.ts +134 -0
- package/lib/bash-approvals.ts +113 -0
- package/lib/bash-policy.ts +164 -0
- package/lib/mode-policy.ts +174 -0
- package/lib/path-policy.ts +182 -0
- package/lib/permission-classifier.ts +128 -0
- package/lib/permission-config.ts +91 -0
- package/lib/permission-denials.ts +32 -0
- package/lib/permission-intent.ts +128 -0
- package/lib/permission-taxonomy.ts +15 -0
- package/lib/task-broker.ts +65 -0
- package/lib/task-checkout.ts +39 -0
- package/lib/task-git.ts +44 -0
- package/lib/task-patch.ts +106 -0
- package/lib/task-permissions.ts +51 -0
- package/lib/task-report.ts +62 -0
- package/lib/task-worktree-policy.ts +56 -0
- package/package.json +51 -0
- package/prompts/commit.md +7 -0
- package/prompts/init.md +66 -0
- package/prompts/summary.md +14 -0
- package/skills/claude-api/LICENSE.txt +202 -0
- package/skills/claude-api/SKILL.md +572 -0
- package/skills/claude-api/csharp/claude-api/README.md +362 -0
- package/skills/claude-api/csharp/claude-api/batches.md +14 -0
- package/skills/claude-api/csharp/claude-api/files-api.md +25 -0
- package/skills/claude-api/csharp/claude-api/streaming.md +28 -0
- package/skills/claude-api/csharp/claude-api/tool-use.md +164 -0
- package/skills/claude-api/curl/examples.md +256 -0
- package/skills/claude-api/curl/managed-agents.md +371 -0
- package/skills/claude-api/go/claude-api/README.md +188 -0
- package/skills/claude-api/go/claude-api/files-api.md +23 -0
- package/skills/claude-api/go/claude-api/streaming.md +43 -0
- package/skills/claude-api/go/claude-api/tool-use.md +220 -0
- package/skills/claude-api/go/managed-agents/README.md +564 -0
- package/skills/claude-api/java/claude-api/README.md +238 -0
- package/skills/claude-api/java/claude-api/files-api.md +27 -0
- package/skills/claude-api/java/claude-api/streaming.md +24 -0
- package/skills/claude-api/java/claude-api/tool-use.md +228 -0
- package/skills/claude-api/java/managed-agents/README.md +443 -0
- package/skills/claude-api/php/claude-api/README.md +174 -0
- package/skills/claude-api/php/claude-api/batches.md +15 -0
- package/skills/claude-api/php/claude-api/files-api.md +13 -0
- package/skills/claude-api/php/claude-api/streaming.md +27 -0
- package/skills/claude-api/php/claude-api/tool-use.md +253 -0
- package/skills/claude-api/php/managed-agents/README.md +443 -0
- package/skills/claude-api/python/claude-api/README.md +565 -0
- package/skills/claude-api/python/claude-api/batches.md +198 -0
- package/skills/claude-api/python/claude-api/files-api.md +170 -0
- package/skills/claude-api/python/claude-api/sdk-upgrade.md +286 -0
- package/skills/claude-api/python/claude-api/streaming.md +179 -0
- package/skills/claude-api/python/claude-api/tool-use.md +627 -0
- package/skills/claude-api/python/managed-agents/README.md +335 -0
- package/skills/claude-api/ruby/claude-api/README.md +134 -0
- package/skills/claude-api/ruby/claude-api/streaming.md +16 -0
- package/skills/claude-api/ruby/claude-api/tool-use.md +41 -0
- package/skills/claude-api/ruby/managed-agents/README.md +394 -0
- package/skills/claude-api/shared/admin-api.md +179 -0
- package/skills/claude-api/shared/agent-design.md +101 -0
- package/skills/claude-api/shared/anthropic-cli.md +269 -0
- package/skills/claude-api/shared/claude-platform-on-aws.md +59 -0
- package/skills/claude-api/shared/cost-optimization.md +233 -0
- package/skills/claude-api/shared/error-codes.md +262 -0
- package/skills/claude-api/shared/live-sources.md +166 -0
- package/skills/claude-api/shared/managed-agents-api-reference.md +455 -0
- package/skills/claude-api/shared/managed-agents-client-patterns.md +220 -0
- package/skills/claude-api/shared/managed-agents-core.md +359 -0
- package/skills/claude-api/shared/managed-agents-environments.md +226 -0
- package/skills/claude-api/shared/managed-agents-events.md +290 -0
- package/skills/claude-api/shared/managed-agents-memory.md +201 -0
- package/skills/claude-api/shared/managed-agents-multiagent.md +260 -0
- package/skills/claude-api/shared/managed-agents-onboarding.md +82 -0
- package/skills/claude-api/shared/managed-agents-outcomes.md +108 -0
- package/skills/claude-api/shared/managed-agents-overview.md +77 -0
- package/skills/claude-api/shared/managed-agents-scheduled-deployments.md +156 -0
- package/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +282 -0
- package/skills/claude-api/shared/managed-agents-tools.md +504 -0
- package/skills/claude-api/shared/managed-agents-webhooks.md +143 -0
- package/skills/claude-api/shared/model-migration.md +1872 -0
- package/skills/claude-api/shared/models.md +142 -0
- package/skills/claude-api/shared/platform-availability.md +53 -0
- package/skills/claude-api/shared/prompt-audit.md +223 -0
- package/skills/claude-api/shared/prompt-caching.md +292 -0
- package/skills/claude-api/shared/token-counting.md +56 -0
- package/skills/claude-api/shared/tool-use-concepts.md +526 -0
- package/skills/claude-api/typescript/claude-api/README.md +405 -0
- package/skills/claude-api/typescript/claude-api/batches.md +106 -0
- package/skills/claude-api/typescript/claude-api/files-api.md +98 -0
- package/skills/claude-api/typescript/claude-api/streaming.md +178 -0
- package/skills/claude-api/typescript/claude-api/tool-use.md +586 -0
- package/skills/claude-api/typescript/managed-agents/README.md +359 -0
- package/skills/code-review/SKILL.md +34 -0
- package/skills/code-review/agents/openai.yaml +3 -0
- package/skills/codebase-design/DEEPENING.md +37 -0
- package/skills/codebase-design/DESIGN-IT-TWICE.md +44 -0
- package/skills/codebase-design/SKILL.md +40 -0
- package/skills/diagnosing-bugs/SKILL.md +38 -0
- package/skills/diagnosing-bugs/agents/openai.yaml +3 -0
- package/skills/diagnosing-bugs/scripts/hitl-loop.template.sh +44 -0
- package/skills/docx/LICENSE.txt +30 -0
- package/skills/docx/SKILL.md +93 -0
- package/skills/docx/scripts/__init__.py +1 -0
- package/skills/docx/scripts/accept_changes.py +135 -0
- package/skills/docx/scripts/comment.py +368 -0
- package/skills/docx/scripts/merge_runs.py +310 -0
- package/skills/docx/scripts/office/helpers/__init__.py +111 -0
- package/skills/docx/scripts/office/helpers/pptx_chart.py +170 -0
- package/skills/docx/scripts/office/helpers/pptx_slide.py +60 -0
- package/skills/docx/scripts/office/helpers/pptx_theme.py +114 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/docx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/docx/scripts/office/soffice.py +192 -0
- package/skills/docx/scripts/office/validate.py +173 -0
- package/skills/docx/scripts/office/validators/__init__.py +15 -0
- package/skills/docx/scripts/office/validators/base.py +875 -0
- package/skills/docx/scripts/office/validators/docx.py +466 -0
- package/skills/docx/scripts/office/validators/pptx.py +441 -0
- package/skills/docx/scripts/office/validators/redlining.py +299 -0
- package/skills/docx/scripts/templates/comments.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
- package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
- package/skills/docx/scripts/templates/commentsIds.xml +3 -0
- package/skills/docx/scripts/templates/people.xml +3 -0
- package/skills/frontend-design/LICENSE.txt +177 -0
- package/skills/frontend-design/SKILL.md +27 -0
- package/skills/grilling/SKILL.md +41 -0
- package/skills/lean-build/SKILL.md +22 -0
- package/skills/lean-build/agents/openai.yaml +4 -0
- package/skills/mcp-builder/LICENSE.txt +202 -0
- package/skills/mcp-builder/SKILL.md +235 -0
- package/skills/mcp-builder/reference/evaluation.md +602 -0
- package/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/skills/mcp-builder/scripts/connections.py +151 -0
- package/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/skills/migration/SKILL.md +17 -0
- package/skills/migration/agents/openai.yaml +4 -0
- package/skills/pdf/LICENSE.txt +30 -0
- package/skills/pdf/SKILL.md +27 -0
- package/skills/pdf/forms.md +294 -0
- package/skills/pdf/reference.md +612 -0
- package/skills/pdf/scripts/check_bounding_boxes.py +65 -0
- package/skills/pdf/scripts/check_fillable_fields.py +11 -0
- package/skills/pdf/scripts/convert_pdf_to_images.py +33 -0
- package/skills/pdf/scripts/create_validation_image.py +37 -0
- package/skills/pdf/scripts/extract_form_field_info.py +122 -0
- package/skills/pdf/scripts/extract_form_structure.py +115 -0
- package/skills/pdf/scripts/fill_fillable_fields.py +98 -0
- package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +107 -0
- package/skills/pptx/LICENSE.txt +30 -0
- package/skills/pptx/SKILL.md +240 -0
- package/skills/pptx/scripts/__init__.py +0 -0
- package/skills/pptx/scripts/add_slide.py +367 -0
- package/skills/pptx/scripts/clean.py +309 -0
- package/skills/pptx/scripts/office/helpers/__init__.py +111 -0
- package/skills/pptx/scripts/office/helpers/pptx_chart.py +170 -0
- package/skills/pptx/scripts/office/helpers/pptx_slide.py +60 -0
- package/skills/pptx/scripts/office/helpers/pptx_theme.py +114 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/pptx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/pptx/scripts/office/soffice.py +192 -0
- package/skills/pptx/scripts/office/validate.py +173 -0
- package/skills/pptx/scripts/office/validators/__init__.py +15 -0
- package/skills/pptx/scripts/office/validators/base.py +875 -0
- package/skills/pptx/scripts/office/validators/docx.py +466 -0
- package/skills/pptx/scripts/office/validators/pptx.py +441 -0
- package/skills/pptx/scripts/office/validators/redlining.py +299 -0
- package/skills/pptx/scripts/thumbnail.py +311 -0
- package/skills/safe-refactor/SKILL.md +16 -0
- package/skills/safe-refactor/agents/openai.yaml +4 -0
- package/skills/tdd/SKILL.md +18 -0
- package/skills/tdd/agents/openai.yaml +3 -0
- package/skills/tdd/mocking.md +59 -0
- package/skills/tdd/tests.md +77 -0
- package/skills/verify-and-stop/SKILL.md +16 -0
- package/skills/verify-and-stop/agents/openai.yaml +4 -0
- package/skills/webapp-testing/LICENSE.txt +202 -0
- package/skills/webapp-testing/SKILL.md +96 -0
- package/skills/webapp-testing/examples/console_logging.py +35 -0
- package/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/skills/webapp-testing/scripts/with_server.py +106 -0
- package/skills/writing-for-agents/SKILL-MECHANICS.md +22 -0
- package/skills/writing-for-agents/SKILL.md +28 -0
- package/skills/writing-for-agents/agents/openai.yaml +3 -0
- package/skills/xlsx/LICENSE.txt +30 -0
- package/skills/xlsx/SKILL.md +101 -0
- package/skills/xlsx/scripts/office/helpers/__init__.py +111 -0
- package/skills/xlsx/scripts/office/helpers/pptx_chart.py +170 -0
- package/skills/xlsx/scripts/office/helpers/pptx_slide.py +60 -0
- package/skills/xlsx/scripts/office/helpers/pptx_theme.py +114 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
- package/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
- package/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
- package/skills/xlsx/scripts/office/schemas/mce/mc.xsd +75 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +560 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +67 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +14 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +20 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +13 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
- package/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +8 -0
- package/skills/xlsx/scripts/office/soffice.py +192 -0
- package/skills/xlsx/scripts/office/validate.py +173 -0
- package/skills/xlsx/scripts/office/validators/__init__.py +15 -0
- package/skills/xlsx/scripts/office/validators/base.py +875 -0
- package/skills/xlsx/scripts/office/validators/docx.py +466 -0
- package/skills/xlsx/scripts/office/validators/pptx.py +441 -0
- package/skills/xlsx/scripts/office/validators/redlining.py +299 -0
- package/skills/xlsx/scripts/recalc.py +308 -0
- package/skills-lock.json +359 -0
- package/tests/auto-permissions.test.mjs +315 -0
- package/tests/background-tasks.test.mjs +27 -0
- package/tests/background-worker.test.mjs +60 -0
- package/tests/bash-permissions.test.mjs +276 -0
- package/tests/glob.test.mjs +31 -0
- package/tests/mode.test.mjs +62 -0
- package/tests/permission-evaluation.test.mjs +70 -0
- package/tests/permission-foundation.test.mjs +194 -0
- package/tests/permission-policy.test.mjs +158 -0
- package/tests/runtime/README.md +51 -0
- package/tests/runtime/harness.mjs +190 -0
- package/tests/runtime/permissions.test.mjs +192 -0
- package/tests/runtime/tui.test.mjs +60 -0
- package/tests/skills-routing.test.mjs +51 -0
- package/tests/task-integration.test.mjs +324 -0
- package/tests/task-permissions.test.mjs +178 -0
- package/tests/task-worktree-policy.test.mjs +53 -0
- package/themes/orange.json +82 -0
- package/tsconfig.json +13 -0
|
@@ -0,0 +1,309 @@
|
|
|
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 posixpath
|
|
19
|
+
import re
|
|
20
|
+
import sys
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
import defusedxml.minidom
|
|
24
|
+
|
|
25
|
+
from office.helpers import SLIDE_REL_TYPE, opc_target, rels_source_part
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _slide_rids(pres_rels_path: Path, unpacked_dir: Path) -> dict[str, str]:
|
|
29
|
+
source_part = rels_source_part(pres_rels_path, unpacked_dir)
|
|
30
|
+
rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
|
|
31
|
+
|
|
32
|
+
rids: dict[str, str] = {}
|
|
33
|
+
for rel in rels_dom.getElementsByTagName("Relationship"):
|
|
34
|
+
if rel.getAttribute("Type") != SLIDE_REL_TYPE:
|
|
35
|
+
continue
|
|
36
|
+
part = opc_target(
|
|
37
|
+
rel.getAttribute("Target"), source_part, rel.getAttribute("TargetMode")
|
|
38
|
+
)
|
|
39
|
+
if part is not None:
|
|
40
|
+
rids[rel.getAttribute("Id")] = part
|
|
41
|
+
return rids
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def get_slides_in_sldidlst(unpacked_dir: Path) -> set[str]:
|
|
45
|
+
pres_path = unpacked_dir / "ppt" / "presentation.xml"
|
|
46
|
+
pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
|
|
47
|
+
|
|
48
|
+
if not pres_path.exists() or not pres_rels_path.exists():
|
|
49
|
+
return set()
|
|
50
|
+
|
|
51
|
+
rid_to_slide = _slide_rids(pres_rels_path, unpacked_dir)
|
|
52
|
+
|
|
53
|
+
pres_content = pres_path.read_text(encoding="utf-8")
|
|
54
|
+
referenced_rids = set(re.findall(r'<p:sldId[^>]*r:id="([^"]+)"', pres_content))
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
posixpath.basename(rid_to_slide[rid])
|
|
58
|
+
for rid in referenced_rids
|
|
59
|
+
if rid in rid_to_slide
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class RefusedToClean(Exception):
|
|
64
|
+
"""The package does not look the way a readable package should."""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def remove_orphaned_slides(unpacked_dir: Path) -> list[str]:
|
|
68
|
+
slides_dir = unpacked_dir / "ppt" / "slides"
|
|
69
|
+
slides_rels_dir = slides_dir / "_rels"
|
|
70
|
+
pres_rels_path = unpacked_dir / "ppt" / "_rels" / "presentation.xml.rels"
|
|
71
|
+
|
|
72
|
+
if not slides_dir.exists():
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
referenced_slides = get_slides_in_sldidlst(unpacked_dir)
|
|
76
|
+
on_disk = sorted(slides_dir.glob("slide*.xml"))
|
|
77
|
+
|
|
78
|
+
if on_disk and not any(s.name in referenced_slides for s in on_disk):
|
|
79
|
+
listed = re.findall(
|
|
80
|
+
r'<p:sldId[^>]*r:id="([^"]+)"',
|
|
81
|
+
(unpacked_dir / "ppt" / "presentation.xml").read_text(encoding="utf-8")
|
|
82
|
+
if (unpacked_dir / "ppt" / "presentation.xml").exists()
|
|
83
|
+
else "",
|
|
84
|
+
)
|
|
85
|
+
if listed:
|
|
86
|
+
raise RefusedToClean(
|
|
87
|
+
f"<p:sldIdLst> lists {len(listed)} slide(s) and none of the "
|
|
88
|
+
f"{len(on_disk)} slide(s) on disk match any of them. Refusing to "
|
|
89
|
+
f"delete them all — this is a parse failure, not an empty deck."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
removed = []
|
|
93
|
+
|
|
94
|
+
for slide_file in on_disk:
|
|
95
|
+
if slide_file.name not in referenced_slides:
|
|
96
|
+
rel_path = slide_file.relative_to(unpacked_dir)
|
|
97
|
+
slide_file.unlink()
|
|
98
|
+
removed.append(str(rel_path))
|
|
99
|
+
|
|
100
|
+
rels_file = slides_rels_dir / f"{slide_file.name}.rels"
|
|
101
|
+
if rels_file.exists():
|
|
102
|
+
rels_file.unlink()
|
|
103
|
+
removed.append(str(rels_file.relative_to(unpacked_dir)))
|
|
104
|
+
|
|
105
|
+
if removed and pres_rels_path.exists():
|
|
106
|
+
rels_dom = defusedxml.minidom.parse(str(pres_rels_path))
|
|
107
|
+
source_part = rels_source_part(pres_rels_path, unpacked_dir)
|
|
108
|
+
changed = False
|
|
109
|
+
|
|
110
|
+
for rel in list(rels_dom.getElementsByTagName("Relationship")):
|
|
111
|
+
if rel.getAttribute("Type") != SLIDE_REL_TYPE:
|
|
112
|
+
continue
|
|
113
|
+
part = opc_target(
|
|
114
|
+
rel.getAttribute("Target"), source_part, rel.getAttribute("TargetMode")
|
|
115
|
+
)
|
|
116
|
+
if part is None:
|
|
117
|
+
continue
|
|
118
|
+
if posixpath.basename(part) not in referenced_slides:
|
|
119
|
+
if rel.parentNode:
|
|
120
|
+
rel.parentNode.removeChild(rel)
|
|
121
|
+
changed = True
|
|
122
|
+
|
|
123
|
+
if changed:
|
|
124
|
+
with open(pres_rels_path, "wb") as f:
|
|
125
|
+
f.write(rels_dom.toxml(encoding="utf-8"))
|
|
126
|
+
|
|
127
|
+
return removed
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def remove_trash_directory(unpacked_dir: Path) -> list[str]:
|
|
131
|
+
trash_dir = unpacked_dir / "[trash]"
|
|
132
|
+
removed = []
|
|
133
|
+
|
|
134
|
+
if trash_dir.exists() and trash_dir.is_dir():
|
|
135
|
+
for file_path in trash_dir.iterdir():
|
|
136
|
+
if file_path.is_file():
|
|
137
|
+
rel_path = file_path.relative_to(unpacked_dir)
|
|
138
|
+
removed.append(str(rel_path))
|
|
139
|
+
file_path.unlink()
|
|
140
|
+
trash_dir.rmdir()
|
|
141
|
+
|
|
142
|
+
return removed
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _referenced_by(rels_files, unpacked_dir: Path) -> set:
|
|
146
|
+
referenced = set()
|
|
147
|
+
|
|
148
|
+
for rels_file in rels_files:
|
|
149
|
+
source_part = rels_source_part(rels_file, unpacked_dir)
|
|
150
|
+
dom = defusedxml.minidom.parse(str(rels_file))
|
|
151
|
+
for rel in dom.getElementsByTagName("Relationship"):
|
|
152
|
+
part = opc_target(
|
|
153
|
+
rel.getAttribute("Target"), source_part, rel.getAttribute("TargetMode")
|
|
154
|
+
)
|
|
155
|
+
if part is not None:
|
|
156
|
+
referenced.add(Path(part))
|
|
157
|
+
|
|
158
|
+
return referenced
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def remove_orphaned_rels_files(unpacked_dir: Path) -> list[str]:
|
|
162
|
+
resource_dirs = ["charts", "diagrams", "drawings"]
|
|
163
|
+
removed = []
|
|
164
|
+
|
|
165
|
+
for dir_name in resource_dirs:
|
|
166
|
+
rels_dir = unpacked_dir / "ppt" / dir_name / "_rels"
|
|
167
|
+
if not rels_dir.exists():
|
|
168
|
+
continue
|
|
169
|
+
|
|
170
|
+
for rels_file in rels_dir.glob("*.rels"):
|
|
171
|
+
resource_file = rels_dir.parent / rels_file.name.replace(".rels", "")
|
|
172
|
+
if not resource_file.exists():
|
|
173
|
+
rels_file.unlink()
|
|
174
|
+
removed.append(str(rels_file.relative_to(unpacked_dir)))
|
|
175
|
+
|
|
176
|
+
return removed
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def get_referenced_files(unpacked_dir: Path) -> set:
|
|
180
|
+
return _referenced_by(sorted(unpacked_dir.rglob("*.rels")), unpacked_dir)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def remove_orphaned_files(unpacked_dir: Path, referenced: set) -> list[str]:
|
|
184
|
+
resource_dirs = ["media", "embeddings", "charts", "diagrams", "tags", "drawings", "ink"]
|
|
185
|
+
removed = []
|
|
186
|
+
|
|
187
|
+
for dir_name in resource_dirs:
|
|
188
|
+
dir_path = unpacked_dir / "ppt" / dir_name
|
|
189
|
+
if not dir_path.exists():
|
|
190
|
+
continue
|
|
191
|
+
|
|
192
|
+
for file_path in dir_path.glob("*"):
|
|
193
|
+
if not file_path.is_file():
|
|
194
|
+
continue
|
|
195
|
+
rel_path = file_path.relative_to(unpacked_dir)
|
|
196
|
+
if rel_path not in referenced:
|
|
197
|
+
file_path.unlink()
|
|
198
|
+
removed.append(str(rel_path))
|
|
199
|
+
|
|
200
|
+
theme_dir = unpacked_dir / "ppt" / "theme"
|
|
201
|
+
if theme_dir.exists():
|
|
202
|
+
for file_path in theme_dir.glob("theme*.xml"):
|
|
203
|
+
rel_path = file_path.relative_to(unpacked_dir)
|
|
204
|
+
if rel_path not in referenced:
|
|
205
|
+
file_path.unlink()
|
|
206
|
+
removed.append(str(rel_path))
|
|
207
|
+
theme_rels = theme_dir / "_rels" / f"{file_path.name}.rels"
|
|
208
|
+
if theme_rels.exists():
|
|
209
|
+
theme_rels.unlink()
|
|
210
|
+
removed.append(str(theme_rels.relative_to(unpacked_dir)))
|
|
211
|
+
|
|
212
|
+
notes_dir = unpacked_dir / "ppt" / "notesSlides"
|
|
213
|
+
if notes_dir.exists():
|
|
214
|
+
for file_path in notes_dir.glob("*.xml"):
|
|
215
|
+
if not file_path.is_file():
|
|
216
|
+
continue
|
|
217
|
+
rel_path = file_path.relative_to(unpacked_dir)
|
|
218
|
+
if rel_path not in referenced:
|
|
219
|
+
file_path.unlink()
|
|
220
|
+
removed.append(str(rel_path))
|
|
221
|
+
|
|
222
|
+
notes_rels_dir = notes_dir / "_rels"
|
|
223
|
+
if notes_rels_dir.exists():
|
|
224
|
+
for file_path in notes_rels_dir.glob("*.rels"):
|
|
225
|
+
notes_file = notes_dir / file_path.name.replace(".rels", "")
|
|
226
|
+
if not notes_file.exists():
|
|
227
|
+
file_path.unlink()
|
|
228
|
+
removed.append(str(file_path.relative_to(unpacked_dir)))
|
|
229
|
+
|
|
230
|
+
return removed
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
def update_content_types(unpacked_dir: Path, removed_files: list[str]) -> None:
|
|
234
|
+
ct_path = unpacked_dir / "[Content_Types].xml"
|
|
235
|
+
if not ct_path.exists():
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
dom = defusedxml.minidom.parse(str(ct_path))
|
|
239
|
+
changed = False
|
|
240
|
+
|
|
241
|
+
for override in list(dom.getElementsByTagName("Override")):
|
|
242
|
+
part_name = override.getAttribute("PartName").lstrip("/")
|
|
243
|
+
if part_name in removed_files:
|
|
244
|
+
if override.parentNode:
|
|
245
|
+
override.parentNode.removeChild(override)
|
|
246
|
+
changed = True
|
|
247
|
+
|
|
248
|
+
if changed:
|
|
249
|
+
with open(ct_path, "wb") as f:
|
|
250
|
+
f.write(dom.toxml(encoding="utf-8"))
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
def clean_unused_files(unpacked_dir: Path) -> list[str]:
|
|
254
|
+
all_removed = []
|
|
255
|
+
|
|
256
|
+
if list(unpacked_dir.rglob("*.rels")) and not get_referenced_files(unpacked_dir):
|
|
257
|
+
raise RefusedToClean(
|
|
258
|
+
"no relationship in this package names a part we can resolve. "
|
|
259
|
+
"Refusing to treat every file as unreferenced."
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
slides_removed = remove_orphaned_slides(unpacked_dir)
|
|
263
|
+
all_removed.extend(slides_removed)
|
|
264
|
+
|
|
265
|
+
trash_removed = remove_trash_directory(unpacked_dir)
|
|
266
|
+
all_removed.extend(trash_removed)
|
|
267
|
+
|
|
268
|
+
while True:
|
|
269
|
+
removed_rels = remove_orphaned_rels_files(unpacked_dir)
|
|
270
|
+
referenced = get_referenced_files(unpacked_dir)
|
|
271
|
+
removed_files = remove_orphaned_files(unpacked_dir, referenced)
|
|
272
|
+
|
|
273
|
+
total_removed = removed_rels + removed_files
|
|
274
|
+
if not total_removed:
|
|
275
|
+
break
|
|
276
|
+
|
|
277
|
+
all_removed.extend(total_removed)
|
|
278
|
+
|
|
279
|
+
if all_removed:
|
|
280
|
+
update_content_types(unpacked_dir, all_removed)
|
|
281
|
+
|
|
282
|
+
return all_removed
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
if __name__ == "__main__":
|
|
286
|
+
if len(sys.argv) != 2:
|
|
287
|
+
print("Usage: python clean.py <unpacked_dir>", file=sys.stderr)
|
|
288
|
+
print("Example: python clean.py unpacked/", file=sys.stderr)
|
|
289
|
+
sys.exit(1)
|
|
290
|
+
|
|
291
|
+
unpacked_dir = Path(sys.argv[1])
|
|
292
|
+
|
|
293
|
+
if not unpacked_dir.exists():
|
|
294
|
+
print(f"Error: {unpacked_dir} not found", file=sys.stderr)
|
|
295
|
+
sys.exit(1)
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
removed = clean_unused_files(unpacked_dir)
|
|
299
|
+
except (RefusedToClean, ValueError) as e:
|
|
300
|
+
print(f"Error: {e}", file=sys.stderr)
|
|
301
|
+
print("Nothing was deleted.", file=sys.stderr)
|
|
302
|
+
sys.exit(1)
|
|
303
|
+
|
|
304
|
+
if removed:
|
|
305
|
+
print(f"Removed {len(removed)} unreferenced files:")
|
|
306
|
+
for f in removed:
|
|
307
|
+
print(f" {f}")
|
|
308
|
+
else:
|
|
309
|
+
print("No unreferenced files found")
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import posixpath
|
|
3
|
+
import re
|
|
4
|
+
import stat
|
|
5
|
+
import tempfile
|
|
6
|
+
import urllib.parse
|
|
7
|
+
import zipfile
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
OOXML_FAMILY = {
|
|
11
|
+
".docx": "docx",
|
|
12
|
+
".dotx": "docx",
|
|
13
|
+
".pptx": "pptx",
|
|
14
|
+
".potx": "pptx",
|
|
15
|
+
".xlsx": "xlsx",
|
|
16
|
+
".xltx": "xlsx",
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
_SCHEME_RE = re.compile(r"^[A-Za-z][A-Za-z0-9+.\-]*:")
|
|
20
|
+
|
|
21
|
+
SLIDE_REL_TYPE = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def opc_target(target: str, source_part: str, target_mode: str = "") -> str | None:
|
|
25
|
+
if not target:
|
|
26
|
+
return None
|
|
27
|
+
if target_mode.lower() == "external":
|
|
28
|
+
return None
|
|
29
|
+
if _SCHEME_RE.match(target):
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
target = urllib.parse.unquote(target)
|
|
33
|
+
|
|
34
|
+
if "\\" in target:
|
|
35
|
+
raise ValueError(f"relationship target is not a POSIX part name: {target!r}")
|
|
36
|
+
|
|
37
|
+
if target.startswith("/"):
|
|
38
|
+
joined = target.lstrip("/")
|
|
39
|
+
else:
|
|
40
|
+
joined = posixpath.join(posixpath.dirname(source_part), target)
|
|
41
|
+
|
|
42
|
+
parts: list[str] = []
|
|
43
|
+
for segment in posixpath.normpath(joined).split("/"):
|
|
44
|
+
if segment in ("", "."):
|
|
45
|
+
continue
|
|
46
|
+
if segment == "..":
|
|
47
|
+
if not parts:
|
|
48
|
+
raise ValueError(f"relationship target escapes the package: {target!r}")
|
|
49
|
+
parts.pop()
|
|
50
|
+
else:
|
|
51
|
+
parts.append(segment)
|
|
52
|
+
|
|
53
|
+
if not parts:
|
|
54
|
+
raise ValueError(f"relationship target resolves to nothing: {target!r}")
|
|
55
|
+
return "/".join(parts)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def rels_source_part(rels_file: Path, unpacked_dir: Path) -> str:
|
|
59
|
+
owner_dir = rels_file.parent.parent.relative_to(unpacked_dir)
|
|
60
|
+
return posixpath.join(owner_dir.as_posix(), rels_file.name[: -len(".rels")]).lstrip("./")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def part_text(data: bytes) -> str:
|
|
64
|
+
return data.decode("utf-8", "surrogateescape")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
XML_SPACE = " \t\r\n"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def rendered_text(text: str, preserve: bool) -> str:
|
|
71
|
+
return text if preserve else text.strip(XML_SPACE)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def safe_extract(zf: zipfile.ZipFile, dest: Path) -> None:
|
|
75
|
+
dest = dest.resolve()
|
|
76
|
+
for m in zf.infolist():
|
|
77
|
+
if stat.S_ISLNK(m.external_attr >> 16):
|
|
78
|
+
raise ValueError(f"symlink archive entry not allowed: {m.filename!r}")
|
|
79
|
+
target = (dest / m.filename).resolve()
|
|
80
|
+
if not target.is_relative_to(dest):
|
|
81
|
+
raise ValueError(f"unsafe archive entry: {m.filename!r}")
|
|
82
|
+
zf.extract(m, dest)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def rezip(src_dir: Path, out_path: Path) -> None:
|
|
86
|
+
files = sorted(p for p in src_dir.rglob("*") if p.is_file())
|
|
87
|
+
ct = src_dir / "[Content_Types].xml"
|
|
88
|
+
fd, tmp_name = tempfile.mkstemp(
|
|
89
|
+
prefix=out_path.name + ".", suffix=".tmp", dir=out_path.parent
|
|
90
|
+
)
|
|
91
|
+
tmp_out = Path(tmp_name)
|
|
92
|
+
try:
|
|
93
|
+
with os.fdopen(fd, "wb") as fh:
|
|
94
|
+
with zipfile.ZipFile(fh, "w", zipfile.ZIP_DEFLATED) as zf:
|
|
95
|
+
if ct.exists():
|
|
96
|
+
zf.write(ct, ct.relative_to(src_dir), compress_type=zipfile.ZIP_STORED)
|
|
97
|
+
for f in files:
|
|
98
|
+
if f == ct:
|
|
99
|
+
continue
|
|
100
|
+
zf.write(f, f.relative_to(src_dir))
|
|
101
|
+
if out_path.exists():
|
|
102
|
+
mode = out_path.stat().st_mode & 0o777
|
|
103
|
+
else:
|
|
104
|
+
umask = os.umask(0)
|
|
105
|
+
os.umask(umask)
|
|
106
|
+
mode = 0o666 & ~umask
|
|
107
|
+
os.chmod(tmp_out, mode)
|
|
108
|
+
os.replace(tmp_out, out_path)
|
|
109
|
+
finally:
|
|
110
|
+
if tmp_out.exists():
|
|
111
|
+
tmp_out.unlink()
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Find chart XML that PowerPoint refuses but the schema accepts.
|
|
2
|
+
|
|
3
|
+
Detection only: for either fault more than one repair is valid, and only the
|
|
4
|
+
author knows which was meant.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from typing import Mapping
|
|
12
|
+
|
|
13
|
+
from . import part_text
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_CHART_PART_RE = re.compile(r"ppt/charts/chart\d+\.xml")
|
|
17
|
+
|
|
18
|
+
_GROUPING_RE = re.compile(r"""<c:grouping\b[^>]*?\bval=["'](\w+)["']""")
|
|
19
|
+
_DLBL_POS_RE = re.compile(r"""<c:dLblPos\b[^>]*?\bval=["'](\w+)["']""")
|
|
20
|
+
|
|
21
|
+
def _strip_ext_lst(text: str) -> str:
|
|
22
|
+
out, cursor = [], 0
|
|
23
|
+
for lo, hi in _ext_lst_spans(text):
|
|
24
|
+
out.append(text[cursor:lo])
|
|
25
|
+
cursor = hi
|
|
26
|
+
out.append(text[cursor:])
|
|
27
|
+
return "".join(out)
|
|
28
|
+
|
|
29
|
+
_BAR_GROUP_RE = re.compile(r"<c:(bar3DChart|barChart)\b[^>]*(?<!/)>.*?</c:\1\s*>", re.DOTALL)
|
|
30
|
+
|
|
31
|
+
STACKED_GROUPINGS = frozenset({"stacked", "percentStacked"})
|
|
32
|
+
ILLEGAL_ON_STACKED = frozenset({"outEnd"})
|
|
33
|
+
LEGAL_ON_STACKED = ("ctr", "inEnd", "inBase")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _check_stacked_label_positions(part: str, xml: str) -> list[str]:
|
|
37
|
+
problems: list[str] = []
|
|
38
|
+
for match in _BAR_GROUP_RE.finditer(xml):
|
|
39
|
+
block = _strip_ext_lst(match.group(0))
|
|
40
|
+
group = match.group(1)
|
|
41
|
+
|
|
42
|
+
grouping = _GROUPING_RE.search(block)
|
|
43
|
+
if grouping is None or grouping.group(1) not in STACKED_GROUPINGS:
|
|
44
|
+
continue
|
|
45
|
+
|
|
46
|
+
bad = [p for p in _DLBL_POS_RE.findall(block) if p in ILLEGAL_ON_STACKED]
|
|
47
|
+
for pos in sorted(set(bad)):
|
|
48
|
+
problems.append(
|
|
49
|
+
f'{part}: {bad.count(pos)} data label(s) use dLblPos="{pos}" on a '
|
|
50
|
+
f"{grouping.group(1)} {group}; PowerPoint allows only "
|
|
51
|
+
f"{', '.join(LEGAL_ON_STACKED)} there"
|
|
52
|
+
)
|
|
53
|
+
return problems
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
_ANY_CHART_GROUP_RE = re.compile(r"<c:(\w+Chart)\b[^>]*(?<!/)>.*?</c:\1\s*>", re.DOTALL)
|
|
58
|
+
|
|
59
|
+
_AXID_RE = re.compile(
|
|
60
|
+
r"""\s*<c:axId\b[^>]*?\bval=["'](-?\d+)["']\s*(?:/>|>\s*</c:axId\s*>)"""
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
_AXIS_DECL_RE = re.compile(
|
|
64
|
+
r"""<c:(catAx|valAx|serAx|dateAx)\b[^>]*(?<!/)>\s*<c:axId\b[^>]*?\bval=["'](-?\d+)["']"""
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
AXID_LIMIT = {
|
|
68
|
+
"barChart": 2, "lineChart": 2, "areaChart": 2, "scatterChart": 2,
|
|
69
|
+
"bubbleChart": 2, "radarChart": 2, "stockChart": 2,
|
|
70
|
+
"bar3DChart": 3, "line3DChart": 3, "area3DChart": 3,
|
|
71
|
+
"surfaceChart": 3, "surface3DChart": 3,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
AXID_MINIMUM = {
|
|
75
|
+
"barChart": 2, "lineChart": 2, "areaChart": 2, "scatterChart": 2,
|
|
76
|
+
"bubbleChart": 2, "radarChart": 2, "stockChart": 2,
|
|
77
|
+
"bar3DChart": 2, "area3DChart": 2, "surfaceChart": 2,
|
|
78
|
+
"line3DChart": 3, "surface3DChart": 3,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _declared_axes(xml: str) -> dict[str, list[str]]:
|
|
83
|
+
axes: dict[str, list[str]] = {}
|
|
84
|
+
for kind, axid in _AXIS_DECL_RE.findall(xml):
|
|
85
|
+
axes.setdefault(kind, []).append(axid)
|
|
86
|
+
return axes
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _canonical_ids(axes: dict[str, list[str]], limit: int) -> list[str] | None:
|
|
90
|
+
category = axes.get("catAx", []) + axes.get("dateAx", [])
|
|
91
|
+
value = axes.get("valAx", [])
|
|
92
|
+
series = axes.get("serAx", [])
|
|
93
|
+
if len(category) != 1 or len(value) != 1 or len(series) > 1:
|
|
94
|
+
return None
|
|
95
|
+
ids = [category[0], value[0]]
|
|
96
|
+
if limit >= 3 and series:
|
|
97
|
+
ids.append(series[0])
|
|
98
|
+
return ids
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _undeclared_axes(kind: str, block: str, axes: dict[str, list[str]]) -> list[str] | None:
|
|
102
|
+
if kind not in AXID_LIMIT:
|
|
103
|
+
return None
|
|
104
|
+
ids = _AXID_RE.findall(block)
|
|
105
|
+
declared = {i for group in axes.values() for i in group}
|
|
106
|
+
if len([i for i in ids if i in declared]) >= 2:
|
|
107
|
+
return None
|
|
108
|
+
return ids
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _check_chart_axis_references(part: str, xml: str) -> list[str]:
|
|
112
|
+
axes = _declared_axes(xml)
|
|
113
|
+
problems: list[str] = []
|
|
114
|
+
declared = {i for group in axes.values() for i in group}
|
|
115
|
+
for match in _ANY_CHART_GROUP_RE.finditer(xml):
|
|
116
|
+
kind, block = match.group(1), match.group(0)
|
|
117
|
+
ids = _undeclared_axes(kind, block, axes)
|
|
118
|
+
if ids is None:
|
|
119
|
+
continue
|
|
120
|
+
if not ids:
|
|
121
|
+
problems.append(
|
|
122
|
+
f"{part}: <c:{kind}> declares no <c:axId> this part can resolve; a chart "
|
|
123
|
+
f"group needs {AXID_MINIMUM[kind]}, and PowerPoint discards one with fewer"
|
|
124
|
+
)
|
|
125
|
+
continue
|
|
126
|
+
dead = [i for i in ids if i not in declared]
|
|
127
|
+
canonical = _canonical_ids(axes, AXID_LIMIT[kind])
|
|
128
|
+
if canonical is not None and len(canonical) >= AXID_MINIMUM[kind]:
|
|
129
|
+
hint = f"Fix: point them at the axes this part declares ({', '.join(canonical)})"
|
|
130
|
+
else:
|
|
131
|
+
hint = ("Fix: the part declares several axes of a kind -- declare the "
|
|
132
|
+
"secondary axes the series expects, or drop them")
|
|
133
|
+
detail = (f"of which {', '.join(dead)} name no declared axis"
|
|
134
|
+
if dead else f"only {len(ids)} of which this part declares")
|
|
135
|
+
problems.append(
|
|
136
|
+
f"{part}: <c:{kind}> references axId {', '.join(ids)}, {detail}, "
|
|
137
|
+
f"leaving fewer than two live axes; PowerPoint discards the chart. {hint}"
|
|
138
|
+
)
|
|
139
|
+
return problems
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _ext_lst_spans(text: str) -> list[tuple[int, int]]:
|
|
143
|
+
spans: list[tuple[int, int]] = []
|
|
144
|
+
depth = 0
|
|
145
|
+
start = 0
|
|
146
|
+
for match in re.finditer(r"<(/?)c:extLst\b[^>]*?(/?)>", text):
|
|
147
|
+
closing, self_closing = match.group(1), match.group(2)
|
|
148
|
+
if self_closing:
|
|
149
|
+
continue
|
|
150
|
+
if closing:
|
|
151
|
+
depth -= 1
|
|
152
|
+
if depth == 0:
|
|
153
|
+
spans.append((start, match.end()))
|
|
154
|
+
else:
|
|
155
|
+
if depth == 0:
|
|
156
|
+
start = match.start()
|
|
157
|
+
depth += 1
|
|
158
|
+
return spans
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
CHART_CHECKS = (_check_stacked_label_positions, _check_chart_axis_references)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def find_chart_problems(files: Mapping[str, bytes]) -> list[str]:
|
|
165
|
+
problems: list[str] = []
|
|
166
|
+
for part in sorted(n for n in files if _CHART_PART_RE.fullmatch(n)):
|
|
167
|
+
xml = part_text(files[part])
|
|
168
|
+
for check in CHART_CHECKS:
|
|
169
|
+
problems.extend(check(part, xml))
|
|
170
|
+
return problems
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Pick the slide-XML schema errors PowerPoint refuses the file over.
|
|
2
|
+
|
|
3
|
+
A denylist over lxml's messages, so an unrecognised error class is a miss rather
|
|
4
|
+
than a false alarm.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
|
|
12
|
+
SLIDE_PART_RE = re.compile(
|
|
13
|
+
r"ppt/(slides|slideLayouts|slideMasters|notesSlides|notesMasters|handoutMasters)"
|
|
14
|
+
r"/[^/]+\.xml"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
FATAL_SLIDE_ERRORS: tuple[tuple[re.Pattern[str], str], ...] = (
|
|
18
|
+
(
|
|
19
|
+
re.compile(r"\}tableStyleId': This element is not expected"),
|
|
20
|
+
"two <a:tableStyleId> in one <a:tblPr> (the schema allows one)",
|
|
21
|
+
),
|
|
22
|
+
(
|
|
23
|
+
re.compile(r"\}srgbClr', attribute 'val'"),
|
|
24
|
+
"a colour that is not six hex digits",
|
|
25
|
+
),
|
|
26
|
+
(
|
|
27
|
+
re.compile(r"\}txBody': Missing child element"),
|
|
28
|
+
"a <p:txBody> with no children",
|
|
29
|
+
),
|
|
30
|
+
(
|
|
31
|
+
re.compile(r"\}miter', attribute 'lim'"),
|
|
32
|
+
'a line join with lim="NaN"',
|
|
33
|
+
),
|
|
34
|
+
(
|
|
35
|
+
re.compile(r"\}uLnTx': This element is not expected"),
|
|
36
|
+
"<a:uLnTx> in a position the schema forbids",
|
|
37
|
+
),
|
|
38
|
+
(
|
|
39
|
+
re.compile(r"\}overrideClrMapping': This element is not expected"),
|
|
40
|
+
"<p:overrideClrMapping> in a position the schema forbids",
|
|
41
|
+
),
|
|
42
|
+
(
|
|
43
|
+
re.compile(r"\}nvGrpSpPr': Missing child element"),
|
|
44
|
+
"a <p:nvGrpSpPr> with no children",
|
|
45
|
+
),
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def is_schema_verdict(error: str) -> bool:
|
|
50
|
+
return error.startswith("Element ")
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def fatal_slide_errors(errors: set[str]) -> list[str]:
|
|
54
|
+
out = []
|
|
55
|
+
for error in sorted(errors):
|
|
56
|
+
for pattern, meaning in FATAL_SLIDE_ERRORS:
|
|
57
|
+
if pattern.search(error):
|
|
58
|
+
out.append(f"{meaning}: {error}")
|
|
59
|
+
break
|
|
60
|
+
return out
|