From 6234ba6139d2e0e62a4bb36c34a119be52c0cf52 Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Sun, 15 Feb 2026 01:31:53 +0800 Subject: [PATCH 1/3] refactor(skills): deduplicate office/ module across docx, xlsx, pptx Move the shared office/ directory (pack/unpack, validators, schemas, soffice wrapper) to skills/_shared/office/ and replace the three identical copies with symlinks. Update skill loader to dereference symlinks during copy to managed directory, and skip _-prefixed directories in the bundled skills scan. Co-Authored-By: Claude Opus 4.6 --- packages/core/src/agent/skills/loader.ts | 8 +- .../office/helpers/__init__.py | 0 .../office/helpers/merge_runs.py | 0 .../office/helpers/simplify_redlines.py | 0 .../{docx/scripts => _shared}/office/pack.py | 0 .../schemas/ISO-IEC29500-4_2016/dml-chart.xsd | 0 .../ISO-IEC29500-4_2016/dml-chartDrawing.xsd | 0 .../ISO-IEC29500-4_2016/dml-diagram.xsd | 0 .../ISO-IEC29500-4_2016/dml-lockedCanvas.xsd | 0 .../schemas/ISO-IEC29500-4_2016/dml-main.xsd | 0 .../ISO-IEC29500-4_2016/dml-picture.xsd | 0 .../dml-spreadsheetDrawing.xsd | 0 .../dml-wordprocessingDrawing.xsd | 0 .../schemas/ISO-IEC29500-4_2016/pml.xsd | 0 .../shared-additionalCharacteristics.xsd | 0 .../shared-bibliography.xsd | 0 .../shared-commonSimpleTypes.xsd | 0 .../shared-customXmlDataProperties.xsd | 0 .../shared-customXmlSchemaProperties.xsd | 0 .../shared-documentPropertiesCustom.xsd | 0 .../shared-documentPropertiesExtended.xsd | 0 .../shared-documentPropertiesVariantTypes.xsd | 0 .../ISO-IEC29500-4_2016/shared-math.xsd | 0 .../shared-relationshipReference.xsd | 0 .../schemas/ISO-IEC29500-4_2016/sml.xsd | 0 .../schemas/ISO-IEC29500-4_2016/vml-main.xsd | 0 .../ISO-IEC29500-4_2016/vml-officeDrawing.xsd | 0 .../vml-presentationDrawing.xsd | 0 .../vml-spreadsheetDrawing.xsd | 0 .../vml-wordprocessingDrawing.xsd | 0 .../schemas/ISO-IEC29500-4_2016/wml.xsd | 0 .../schemas/ISO-IEC29500-4_2016/xml.xsd | 0 .../ecma/fouth-edition/opc-contentTypes.xsd | 0 .../ecma/fouth-edition/opc-coreProperties.xsd | 0 .../schemas/ecma/fouth-edition/opc-digSig.xsd | 0 .../ecma/fouth-edition/opc-relationships.xsd | 0 .../office/schemas/mce/mc.xsd | 0 .../office/schemas/microsoft/wml-2010.xsd | 0 .../office/schemas/microsoft/wml-2012.xsd | 0 .../office/schemas/microsoft/wml-2018.xsd | 0 .../office/schemas/microsoft/wml-cex-2018.xsd | 0 .../office/schemas/microsoft/wml-cid-2016.xsd | 0 .../microsoft/wml-sdtdatahash-2020.xsd | 0 .../schemas/microsoft/wml-symex-2015.xsd | 0 .../scripts => _shared}/office/soffice.py | 0 .../scripts => _shared}/office/unpack.py | 0 .../scripts => _shared}/office/validate.py | 0 .../office/validators/__init__.py | 0 .../__pycache__/__init__.cpython-314.pyc | Bin 0 -> 523 bytes .../__pycache__/base.cpython-314.pyc | Bin 0 -> 41902 bytes .../__pycache__/docx.cpython-314.pyc | Bin 0 -> 22029 bytes .../__pycache__/pptx.cpython-314.pyc | Bin 0 -> 13284 bytes .../__pycache__/redlining.cpython-314.pyc | Bin 0 -> 11816 bytes .../office/validators/base.py | 0 .../office/validators/docx.py | 0 .../office/validators/pptx.py | 0 .../office/validators/redlining.py | 0 skills/docx/scripts/office | 1 + skills/pptx/scripts/office | 1 + .../pptx/scripts/office/helpers/__init__.py | 0 .../pptx/scripts/office/helpers/merge_runs.py | 199 - .../office/helpers/simplify_redlines.py | 197 - skills/pptx/scripts/office/pack.py | 159 - .../schemas/ISO-IEC29500-4_2016/dml-chart.xsd | 1499 ------ .../ISO-IEC29500-4_2016/dml-chartDrawing.xsd | 146 - .../ISO-IEC29500-4_2016/dml-diagram.xsd | 1085 ---- .../ISO-IEC29500-4_2016/dml-lockedCanvas.xsd | 11 - .../schemas/ISO-IEC29500-4_2016/dml-main.xsd | 3081 ------------ .../ISO-IEC29500-4_2016/dml-picture.xsd | 23 - .../dml-spreadsheetDrawing.xsd | 185 - .../dml-wordprocessingDrawing.xsd | 287 -- .../schemas/ISO-IEC29500-4_2016/pml.xsd | 1676 ------- .../shared-additionalCharacteristics.xsd | 28 - .../shared-bibliography.xsd | 144 - .../shared-commonSimpleTypes.xsd | 174 - .../shared-customXmlDataProperties.xsd | 25 - .../shared-customXmlSchemaProperties.xsd | 18 - .../shared-documentPropertiesCustom.xsd | 59 - .../shared-documentPropertiesExtended.xsd | 56 - .../shared-documentPropertiesVariantTypes.xsd | 195 - .../ISO-IEC29500-4_2016/shared-math.xsd | 582 --- .../shared-relationshipReference.xsd | 25 - .../schemas/ISO-IEC29500-4_2016/sml.xsd | 4439 ----------------- .../schemas/ISO-IEC29500-4_2016/vml-main.xsd | 570 --- .../ISO-IEC29500-4_2016/vml-officeDrawing.xsd | 509 -- .../vml-presentationDrawing.xsd | 12 - .../vml-spreadsheetDrawing.xsd | 108 - .../vml-wordprocessingDrawing.xsd | 96 - .../schemas/ISO-IEC29500-4_2016/wml.xsd | 3646 -------------- .../schemas/ISO-IEC29500-4_2016/xml.xsd | 116 - .../ecma/fouth-edition/opc-contentTypes.xsd | 42 - .../ecma/fouth-edition/opc-coreProperties.xsd | 50 - .../schemas/ecma/fouth-edition/opc-digSig.xsd | 49 - .../ecma/fouth-edition/opc-relationships.xsd | 33 - skills/pptx/scripts/office/schemas/mce/mc.xsd | 75 - .../office/schemas/microsoft/wml-2010.xsd | 560 --- .../office/schemas/microsoft/wml-2012.xsd | 67 - .../office/schemas/microsoft/wml-2018.xsd | 14 - .../office/schemas/microsoft/wml-cex-2018.xsd | 20 - .../office/schemas/microsoft/wml-cid-2016.xsd | 13 - .../microsoft/wml-sdtdatahash-2020.xsd | 4 - .../schemas/microsoft/wml-symex-2015.xsd | 8 - skills/pptx/scripts/office/soffice.py | 183 - skills/pptx/scripts/office/unpack.py | 132 - skills/pptx/scripts/office/validate.py | 111 - .../scripts/office/validators/__init__.py | 15 - skills/pptx/scripts/office/validators/base.py | 847 ---- skills/pptx/scripts/office/validators/docx.py | 446 -- skills/pptx/scripts/office/validators/pptx.py | 275 - .../scripts/office/validators/redlining.py | 247 - skills/xlsx/scripts/office | 1 + .../xlsx/scripts/office/helpers/__init__.py | 0 .../xlsx/scripts/office/helpers/merge_runs.py | 199 - .../office/helpers/simplify_redlines.py | 197 - skills/xlsx/scripts/office/pack.py | 159 - .../schemas/ISO-IEC29500-4_2016/dml-chart.xsd | 1499 ------ .../ISO-IEC29500-4_2016/dml-chartDrawing.xsd | 146 - .../ISO-IEC29500-4_2016/dml-diagram.xsd | 1085 ---- .../ISO-IEC29500-4_2016/dml-lockedCanvas.xsd | 11 - .../schemas/ISO-IEC29500-4_2016/dml-main.xsd | 3081 ------------ .../ISO-IEC29500-4_2016/dml-picture.xsd | 23 - .../dml-spreadsheetDrawing.xsd | 185 - .../dml-wordprocessingDrawing.xsd | 287 -- .../schemas/ISO-IEC29500-4_2016/pml.xsd | 1676 ------- .../shared-additionalCharacteristics.xsd | 28 - .../shared-bibliography.xsd | 144 - .../shared-commonSimpleTypes.xsd | 174 - .../shared-customXmlDataProperties.xsd | 25 - .../shared-customXmlSchemaProperties.xsd | 18 - .../shared-documentPropertiesCustom.xsd | 59 - .../shared-documentPropertiesExtended.xsd | 56 - .../shared-documentPropertiesVariantTypes.xsd | 195 - .../ISO-IEC29500-4_2016/shared-math.xsd | 582 --- .../shared-relationshipReference.xsd | 25 - .../schemas/ISO-IEC29500-4_2016/sml.xsd | 4439 ----------------- .../schemas/ISO-IEC29500-4_2016/vml-main.xsd | 570 --- .../ISO-IEC29500-4_2016/vml-officeDrawing.xsd | 509 -- .../vml-presentationDrawing.xsd | 12 - .../vml-spreadsheetDrawing.xsd | 108 - .../vml-wordprocessingDrawing.xsd | 96 - .../schemas/ISO-IEC29500-4_2016/wml.xsd | 3646 -------------- .../schemas/ISO-IEC29500-4_2016/xml.xsd | 116 - .../ecma/fouth-edition/opc-contentTypes.xsd | 42 - .../ecma/fouth-edition/opc-coreProperties.xsd | 50 - .../schemas/ecma/fouth-edition/opc-digSig.xsd | 49 - .../ecma/fouth-edition/opc-relationships.xsd | 33 - skills/xlsx/scripts/office/schemas/mce/mc.xsd | 75 - .../office/schemas/microsoft/wml-2010.xsd | 560 --- .../office/schemas/microsoft/wml-2012.xsd | 67 - .../office/schemas/microsoft/wml-2018.xsd | 14 - .../office/schemas/microsoft/wml-cex-2018.xsd | 20 - .../office/schemas/microsoft/wml-cid-2016.xsd | 13 - .../microsoft/wml-sdtdatahash-2020.xsd | 4 - .../schemas/microsoft/wml-symex-2015.xsd | 8 - skills/xlsx/scripts/office/soffice.py | 183 - skills/xlsx/scripts/office/unpack.py | 132 - skills/xlsx/scripts/office/validate.py | 111 - .../scripts/office/validators/__init__.py | 15 - skills/xlsx/scripts/office/validators/base.py | 847 ---- skills/xlsx/scripts/office/validators/docx.py | 446 -- skills/xlsx/scripts/office/validators/pptx.py | 275 - .../scripts/office/validators/redlining.py | 247 - 162 files changed, 7 insertions(+), 45086 deletions(-) rename skills/{docx/scripts => _shared}/office/helpers/__init__.py (100%) rename skills/{docx/scripts => _shared}/office/helpers/merge_runs.py (100%) rename skills/{docx/scripts => _shared}/office/helpers/simplify_redlines.py (100%) rename skills/{docx/scripts => _shared}/office/pack.py (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/pml.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/sml.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/wml.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ISO-IEC29500-4_2016/xml.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ecma/fouth-edition/opc-digSig.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/ecma/fouth-edition/opc-relationships.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/mce/mc.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/microsoft/wml-2010.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/microsoft/wml-2012.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/microsoft/wml-2018.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/microsoft/wml-cex-2018.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/microsoft/wml-cid-2016.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/microsoft/wml-sdtdatahash-2020.xsd (100%) rename skills/{docx/scripts => _shared}/office/schemas/microsoft/wml-symex-2015.xsd (100%) rename skills/{docx/scripts => _shared}/office/soffice.py (100%) rename skills/{docx/scripts => _shared}/office/unpack.py (100%) rename skills/{docx/scripts => _shared}/office/validate.py (100%) rename skills/{docx/scripts => _shared}/office/validators/__init__.py (100%) create mode 100644 skills/_shared/office/validators/__pycache__/__init__.cpython-314.pyc create mode 100644 skills/_shared/office/validators/__pycache__/base.cpython-314.pyc create mode 100644 skills/_shared/office/validators/__pycache__/docx.cpython-314.pyc create mode 100644 skills/_shared/office/validators/__pycache__/pptx.cpython-314.pyc create mode 100644 skills/_shared/office/validators/__pycache__/redlining.cpython-314.pyc rename skills/{docx/scripts => _shared}/office/validators/base.py (100%) rename skills/{docx/scripts => _shared}/office/validators/docx.py (100%) rename skills/{docx/scripts => _shared}/office/validators/pptx.py (100%) rename skills/{docx/scripts => _shared}/office/validators/redlining.py (100%) create mode 120000 skills/docx/scripts/office create mode 120000 skills/pptx/scripts/office delete mode 100644 skills/pptx/scripts/office/helpers/__init__.py delete mode 100644 skills/pptx/scripts/office/helpers/merge_runs.py delete mode 100644 skills/pptx/scripts/office/helpers/simplify_redlines.py delete mode 100755 skills/pptx/scripts/office/pack.py delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd delete mode 100644 skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd delete mode 100644 skills/pptx/scripts/office/schemas/mce/mc.xsd delete mode 100644 skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd delete mode 100644 skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd delete mode 100644 skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd delete mode 100644 skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd delete mode 100644 skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd delete mode 100644 skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd delete mode 100644 skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd delete mode 100644 skills/pptx/scripts/office/soffice.py delete mode 100755 skills/pptx/scripts/office/unpack.py delete mode 100755 skills/pptx/scripts/office/validate.py delete mode 100644 skills/pptx/scripts/office/validators/__init__.py delete mode 100644 skills/pptx/scripts/office/validators/base.py delete mode 100644 skills/pptx/scripts/office/validators/docx.py delete mode 100644 skills/pptx/scripts/office/validators/pptx.py delete mode 100644 skills/pptx/scripts/office/validators/redlining.py create mode 120000 skills/xlsx/scripts/office delete mode 100644 skills/xlsx/scripts/office/helpers/__init__.py delete mode 100644 skills/xlsx/scripts/office/helpers/merge_runs.py delete mode 100644 skills/xlsx/scripts/office/helpers/simplify_redlines.py delete mode 100755 skills/xlsx/scripts/office/pack.py delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/mce/mc.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd delete mode 100644 skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd delete mode 100644 skills/xlsx/scripts/office/soffice.py delete mode 100755 skills/xlsx/scripts/office/unpack.py delete mode 100755 skills/xlsx/scripts/office/validate.py delete mode 100644 skills/xlsx/scripts/office/validators/__init__.py delete mode 100644 skills/xlsx/scripts/office/validators/base.py delete mode 100644 skills/xlsx/scripts/office/validators/docx.py delete mode 100644 skills/xlsx/scripts/office/validators/pptx.py delete mode 100644 skills/xlsx/scripts/office/validators/redlining.py diff --git a/packages/core/src/agent/skills/loader.ts b/packages/core/src/agent/skills/loader.ts index d5dc59be..b193c0aa 100644 --- a/packages/core/src/agent/skills/loader.ts +++ b/packages/core/src/agent/skills/loader.ts @@ -185,8 +185,8 @@ export function initializeManagedSkills(): void { try { const entries = readdirSync(BUNDLED_DIR); for (const skillName of entries) { - // Skip hidden directories - if (skillName.startsWith(".")) continue; + // Skip hidden directories and shared/internal directories + if (skillName.startsWith(".") || skillName.startsWith("_")) continue; const src = join(BUNDLED_DIR, skillName); const dest = join(MANAGED_DIR, skillName); @@ -199,7 +199,7 @@ export function initializeManagedSkills(): void { // Check if skill exists in managed if (!existsSync(dest)) { // Skill doesn't exist, copy it - cpSync(src, dest, { recursive: true }); + cpSync(src, dest, { recursive: true, dereference: true }); continue; } @@ -216,7 +216,7 @@ export function initializeManagedSkills(): void { if (compareVersions(bundledVersion, managedVersion) > 0) { // Remove old and copy new rmSync(dest, { recursive: true }); - cpSync(src, dest, { recursive: true }); + cpSync(src, dest, { recursive: true, dereference: true }); } } diff --git a/skills/docx/scripts/office/helpers/__init__.py b/skills/_shared/office/helpers/__init__.py similarity index 100% rename from skills/docx/scripts/office/helpers/__init__.py rename to skills/_shared/office/helpers/__init__.py diff --git a/skills/docx/scripts/office/helpers/merge_runs.py b/skills/_shared/office/helpers/merge_runs.py similarity index 100% rename from skills/docx/scripts/office/helpers/merge_runs.py rename to skills/_shared/office/helpers/merge_runs.py diff --git a/skills/docx/scripts/office/helpers/simplify_redlines.py b/skills/_shared/office/helpers/simplify_redlines.py similarity index 100% rename from skills/docx/scripts/office/helpers/simplify_redlines.py rename to skills/_shared/office/helpers/simplify_redlines.py diff --git a/skills/docx/scripts/office/pack.py b/skills/_shared/office/pack.py similarity index 100% rename from skills/docx/scripts/office/pack.py rename to skills/_shared/office/pack.py diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/pml.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/pml.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/sml.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/sml.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/wml.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/wml.xsd diff --git a/skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd b/skills/_shared/office/schemas/ISO-IEC29500-4_2016/xml.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd rename to skills/_shared/office/schemas/ISO-IEC29500-4_2016/xml.xsd diff --git a/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd b/skills/_shared/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd rename to skills/_shared/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd diff --git a/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd b/skills/_shared/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd rename to skills/_shared/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd diff --git a/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd b/skills/_shared/office/schemas/ecma/fouth-edition/opc-digSig.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd rename to skills/_shared/office/schemas/ecma/fouth-edition/opc-digSig.xsd diff --git a/skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd b/skills/_shared/office/schemas/ecma/fouth-edition/opc-relationships.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd rename to skills/_shared/office/schemas/ecma/fouth-edition/opc-relationships.xsd diff --git a/skills/docx/scripts/office/schemas/mce/mc.xsd b/skills/_shared/office/schemas/mce/mc.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/mce/mc.xsd rename to skills/_shared/office/schemas/mce/mc.xsd diff --git a/skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd b/skills/_shared/office/schemas/microsoft/wml-2010.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/microsoft/wml-2010.xsd rename to skills/_shared/office/schemas/microsoft/wml-2010.xsd diff --git a/skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd b/skills/_shared/office/schemas/microsoft/wml-2012.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/microsoft/wml-2012.xsd rename to skills/_shared/office/schemas/microsoft/wml-2012.xsd diff --git a/skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd b/skills/_shared/office/schemas/microsoft/wml-2018.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/microsoft/wml-2018.xsd rename to skills/_shared/office/schemas/microsoft/wml-2018.xsd diff --git a/skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd b/skills/_shared/office/schemas/microsoft/wml-cex-2018.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/microsoft/wml-cex-2018.xsd rename to skills/_shared/office/schemas/microsoft/wml-cex-2018.xsd diff --git a/skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd b/skills/_shared/office/schemas/microsoft/wml-cid-2016.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/microsoft/wml-cid-2016.xsd rename to skills/_shared/office/schemas/microsoft/wml-cid-2016.xsd diff --git a/skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd b/skills/_shared/office/schemas/microsoft/wml-sdtdatahash-2020.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd rename to skills/_shared/office/schemas/microsoft/wml-sdtdatahash-2020.xsd diff --git a/skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd b/skills/_shared/office/schemas/microsoft/wml-symex-2015.xsd similarity index 100% rename from skills/docx/scripts/office/schemas/microsoft/wml-symex-2015.xsd rename to skills/_shared/office/schemas/microsoft/wml-symex-2015.xsd diff --git a/skills/docx/scripts/office/soffice.py b/skills/_shared/office/soffice.py similarity index 100% rename from skills/docx/scripts/office/soffice.py rename to skills/_shared/office/soffice.py diff --git a/skills/docx/scripts/office/unpack.py b/skills/_shared/office/unpack.py similarity index 100% rename from skills/docx/scripts/office/unpack.py rename to skills/_shared/office/unpack.py diff --git a/skills/docx/scripts/office/validate.py b/skills/_shared/office/validate.py similarity index 100% rename from skills/docx/scripts/office/validate.py rename to skills/_shared/office/validate.py diff --git a/skills/docx/scripts/office/validators/__init__.py b/skills/_shared/office/validators/__init__.py similarity index 100% rename from skills/docx/scripts/office/validators/__init__.py rename to skills/_shared/office/validators/__init__.py diff --git a/skills/_shared/office/validators/__pycache__/__init__.cpython-314.pyc b/skills/_shared/office/validators/__pycache__/__init__.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a05a40995982cdc2a742c9975e82387e1d055cf5 GIT binary patch literal 523 zcmZuuOHRWu5VceHLlGds0U8N`6rt7RH5?+VK#X=`xe$abKxLY#nWu;4&M zV#Ni3ianD+S#@MRJ&)$S@r<{YmzoIT_2o$nFhcLeWfsmKneG93Kt8hQ0{gg!yN-2Q z1$TOtF0rKKm0q=5v$c|wUcK9}jTY)4zt+cg!LP0(uYSMXJmpe^+zOSru?jO88F#3( zd!}^ghAPP7D6wv;RS+2?l2NDW;e2h6o9HMQM=}4CD_x`y`upc|(u2YAob;+6g;FHo z`R{S)5narvD+Q1OssLUyuQLXZVJs(?(7G(j31lW^X-?AAPD(+(pl7zFwoq~@8GA-k zutIrEPr$&?E5YwFp4^XlGNOS>!Yr^#(_5vlP0C?fYO*xa?KqQG1e_YK+9}VZqUKsi zX(%&e4!x)v4n+{ro8LVuux1R#vW#`oyS%aUyVYI20^Q+5k!b;ZR4~S$j)Tc}1L4g# Rv{4@Ki&Ph#$GTpEnjdg$me&9P literal 0 HcmV?d00001 diff --git a/skills/_shared/office/validators/__pycache__/base.cpython-314.pyc b/skills/_shared/office/validators/__pycache__/base.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0d8c5eaf9fd19e7d32be0d8e9f2be07fad6dcc6a GIT binary patch literal 41902 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�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 jKw)6*S&@e(Guf4P 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 literal 0 HcmV?d00001 diff --git a/skills/_shared/office/validators/__pycache__/redlining.cpython-314.pyc b/skills/_shared/office/validators/__pycache__/redlining.cpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..295144e3cfab1cd818949bcb1e33c9a65cdcc734 GIT binary patch literal 11816 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

4;A5$y?yZQ)^yE@4eLn^e^`d^1^CZ) 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 elements that have identical properties. -Works on runs in paragraphs and inside tracked changes (, ). - -Also: -- Removes rsid attributes from runs (revision metadata that doesn't affect rendering) -- Removes proofErr elements (spell/grammar markers that block merging) -""" - -from pathlib import Path - -import defusedxml.minidom - - -def merge_runs(input_dir: str) -> tuple[int, str]: - doc_xml = Path(input_dir) / "word" / "document.xml" - - if not doc_xml.exists(): - return 0, f"Error: {doc_xml} not found" - - try: - dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8")) - root = dom.documentElement - - _remove_elements(root, "proofErr") - _strip_run_rsid_attrs(root) - - containers = {run.parentNode for run in _find_elements(root, "r")} - - merge_count = 0 - for container in containers: - merge_count += _merge_runs_in(container) - - doc_xml.write_bytes(dom.toxml(encoding="UTF-8")) - return merge_count, f"Merged {merge_count} runs" - - except Exception as e: - return 0, f"Error: {e}" - - - - -def _find_elements(root, tag: str) -> list: - results = [] - - def traverse(node): - if node.nodeType == node.ELEMENT_NODE: - name = node.localName or node.tagName - if name == tag or name.endswith(f":{tag}"): - results.append(node) - for child in node.childNodes: - traverse(child) - - traverse(root) - return results - - -def _get_child(parent, tag: str): - for child in parent.childNodes: - if child.nodeType == child.ELEMENT_NODE: - name = child.localName or child.tagName - if name == tag or name.endswith(f":{tag}"): - return child - return None - - -def _get_children(parent, tag: str) -> list: - results = [] - for child in parent.childNodes: - if child.nodeType == child.ELEMENT_NODE: - name = child.localName or child.tagName - if name == tag or name.endswith(f":{tag}"): - results.append(child) - return results - - -def _is_adjacent(elem1, elem2) -> bool: - node = elem1.nextSibling - while node: - if node == elem2: - return True - if node.nodeType == node.ELEMENT_NODE: - return False - if node.nodeType == node.TEXT_NODE and node.data.strip(): - return False - node = node.nextSibling - return False - - - - -def _remove_elements(root, tag: str): - for elem in _find_elements(root, tag): - if elem.parentNode: - elem.parentNode.removeChild(elem) - - -def _strip_run_rsid_attrs(root): - for run in _find_elements(root, "r"): - for attr in list(run.attributes.values()): - if "rsid" in attr.name.lower(): - run.removeAttribute(attr.name) - - - - -def _merge_runs_in(container) -> int: - merge_count = 0 - run = _first_child_run(container) - - while run: - while True: - next_elem = _next_element_sibling(run) - if next_elem and _is_run(next_elem) and _can_merge(run, next_elem): - _merge_run_content(run, next_elem) - container.removeChild(next_elem) - merge_count += 1 - else: - break - - _consolidate_text(run) - run = _next_sibling_run(run) - - return merge_count - - -def _first_child_run(container): - for child in container.childNodes: - if child.nodeType == child.ELEMENT_NODE and _is_run(child): - return child - return None - - -def _next_element_sibling(node): - sibling = node.nextSibling - while sibling: - if sibling.nodeType == sibling.ELEMENT_NODE: - return sibling - sibling = sibling.nextSibling - return None - - -def _next_sibling_run(node): - sibling = node.nextSibling - while sibling: - if sibling.nodeType == sibling.ELEMENT_NODE: - if _is_run(sibling): - return sibling - sibling = sibling.nextSibling - return None - - -def _is_run(node) -> bool: - name = node.localName or node.tagName - return name == "r" or name.endswith(":r") - - -def _can_merge(run1, run2) -> bool: - rpr1 = _get_child(run1, "rPr") - rpr2 = _get_child(run2, "rPr") - - if (rpr1 is None) != (rpr2 is None): - return False - if rpr1 is None: - return True - return rpr1.toxml() == rpr2.toxml() - - -def _merge_run_content(target, source): - for child in list(source.childNodes): - if child.nodeType == child.ELEMENT_NODE: - name = child.localName or child.tagName - if name != "rPr" and not name.endswith(":rPr"): - target.appendChild(child) - - -def _consolidate_text(run): - t_elements = _get_children(run, "t") - - for i in range(len(t_elements) - 1, 0, -1): - curr, prev = t_elements[i], t_elements[i - 1] - - if _is_adjacent(prev, curr): - prev_text = prev.firstChild.data if prev.firstChild else "" - curr_text = curr.firstChild.data if curr.firstChild else "" - merged = prev_text + curr_text - - if prev.firstChild: - prev.firstChild.data = merged - else: - prev.appendChild(run.ownerDocument.createTextNode(merged)) - - if merged.startswith(" ") or merged.endswith(" "): - prev.setAttribute("xml:space", "preserve") - elif prev.hasAttribute("xml:space"): - prev.removeAttribute("xml:space") - - run.removeChild(curr) diff --git a/skills/pptx/scripts/office/helpers/simplify_redlines.py b/skills/pptx/scripts/office/helpers/simplify_redlines.py deleted file mode 100644 index db963bb9..00000000 --- a/skills/pptx/scripts/office/helpers/simplify_redlines.py +++ /dev/null @@ -1,197 +0,0 @@ -"""Simplify tracked changes by merging adjacent w:ins or w:del elements. - -Merges adjacent elements from the same author into a single element. -Same for elements. This makes heavily-redlined documents easier to -work with by reducing the number of tracked change wrappers. - -Rules: -- Only merges w:ins with w:ins, w:del with w:del (same element type) -- Only merges if same author (ignores timestamp differences) -- Only merges if truly adjacent (only whitespace between them) -""" - -import xml.etree.ElementTree as ET -import zipfile -from pathlib import Path - -import defusedxml.minidom - -WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" - - -def simplify_redlines(input_dir: str) -> tuple[int, str]: - doc_xml = Path(input_dir) / "word" / "document.xml" - - if not doc_xml.exists(): - return 0, f"Error: {doc_xml} not found" - - try: - dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8")) - root = dom.documentElement - - merge_count = 0 - - containers = _find_elements(root, "p") + _find_elements(root, "tc") - - for container in containers: - merge_count += _merge_tracked_changes_in(container, "ins") - merge_count += _merge_tracked_changes_in(container, "del") - - doc_xml.write_bytes(dom.toxml(encoding="UTF-8")) - return merge_count, f"Simplified {merge_count} tracked changes" - - except Exception as e: - return 0, f"Error: {e}" - - -def _merge_tracked_changes_in(container, tag: str) -> int: - merge_count = 0 - - tracked = [ - child - for child in container.childNodes - if child.nodeType == child.ELEMENT_NODE and _is_element(child, tag) - ] - - if len(tracked) < 2: - return 0 - - i = 0 - while i < len(tracked) - 1: - curr = tracked[i] - next_elem = tracked[i + 1] - - if _can_merge_tracked(curr, next_elem): - _merge_tracked_content(curr, next_elem) - container.removeChild(next_elem) - tracked.pop(i + 1) - merge_count += 1 - else: - i += 1 - - return merge_count - - -def _is_element(node, tag: str) -> bool: - name = node.localName or node.tagName - return name == tag or name.endswith(f":{tag}") - - -def _get_author(elem) -> str: - author = elem.getAttribute("w:author") - if not author: - for attr in elem.attributes.values(): - if attr.localName == "author" or attr.name.endswith(":author"): - return attr.value - return author - - -def _can_merge_tracked(elem1, elem2) -> bool: - if _get_author(elem1) != _get_author(elem2): - return False - - node = elem1.nextSibling - while node and node != elem2: - if node.nodeType == node.ELEMENT_NODE: - return False - if node.nodeType == node.TEXT_NODE and node.data.strip(): - return False - node = node.nextSibling - - return True - - -def _merge_tracked_content(target, source): - while source.firstChild: - child = source.firstChild - source.removeChild(child) - target.appendChild(child) - - -def _find_elements(root, tag: str) -> list: - results = [] - - def traverse(node): - if node.nodeType == node.ELEMENT_NODE: - name = node.localName or node.tagName - if name == tag or name.endswith(f":{tag}"): - results.append(node) - for child in node.childNodes: - traverse(child) - - traverse(root) - return results - - -def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]: - if not doc_xml_path.exists(): - return {} - - try: - tree = ET.parse(doc_xml_path) - root = tree.getroot() - except ET.ParseError: - return {} - - namespaces = {"w": WORD_NS} - author_attr = f"{{{WORD_NS}}}author" - - authors: dict[str, int] = {} - for tag in ["ins", "del"]: - for elem in root.findall(f".//w:{tag}", namespaces): - author = elem.get(author_attr) - if author: - authors[author] = authors.get(author, 0) + 1 - - return authors - - -def _get_authors_from_docx(docx_path: Path) -> dict[str, int]: - try: - with zipfile.ZipFile(docx_path, "r") as zf: - if "word/document.xml" not in zf.namelist(): - return {} - with zf.open("word/document.xml") as f: - tree = ET.parse(f) - root = tree.getroot() - - namespaces = {"w": WORD_NS} - author_attr = f"{{{WORD_NS}}}author" - - authors: dict[str, int] = {} - for tag in ["ins", "del"]: - for elem in root.findall(f".//w:{tag}", namespaces): - author = elem.get(author_attr) - if author: - authors[author] = authors.get(author, 0) + 1 - return authors - except (zipfile.BadZipFile, ET.ParseError): - return {} - - -def infer_author(modified_dir: Path, original_docx: Path, default: str = "Claude") -> str: - modified_xml = modified_dir / "word" / "document.xml" - modified_authors = get_tracked_change_authors(modified_xml) - - if not modified_authors: - return default - - original_authors = _get_authors_from_docx(original_docx) - - new_changes: dict[str, int] = {} - for author, count in modified_authors.items(): - original_count = original_authors.get(author, 0) - diff = count - original_count - if diff > 0: - new_changes[author] = diff - - if not new_changes: - return default - - if len(new_changes) == 1: - return next(iter(new_changes)) - - raise ValueError( - f"Multiple authors added new changes: {new_changes}. " - "Cannot infer which author to validate." - ) diff --git a/skills/pptx/scripts/office/pack.py b/skills/pptx/scripts/office/pack.py deleted file mode 100755 index db29ed8b..00000000 --- a/skills/pptx/scripts/office/pack.py +++ /dev/null @@ -1,159 +0,0 @@ -"""Pack a directory into a DOCX, PPTX, or XLSX file. - -Validates with auto-repair, condenses XML formatting, and creates the Office file. - -Usage: - python pack.py [--original ] [--validate true|false] - -Examples: - python pack.py unpacked/ output.docx --original input.docx - python pack.py unpacked/ output.pptx --validate false -""" - -import argparse -import sys -import shutil -import tempfile -import zipfile -from pathlib import Path - -import defusedxml.minidom - -from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator - -def pack( - input_directory: str, - output_file: str, - original_file: str | None = None, - validate: bool = True, - infer_author_func=None, -) -> tuple[None, str]: - input_dir = Path(input_directory) - output_path = Path(output_file) - suffix = output_path.suffix.lower() - - if not input_dir.is_dir(): - return None, f"Error: {input_dir} is not a directory" - - if suffix not in {".docx", ".pptx", ".xlsx"}: - return None, f"Error: {output_file} must be a .docx, .pptx, or .xlsx file" - - if validate and original_file: - original_path = Path(original_file) - if original_path.exists(): - success, output = _run_validation( - input_dir, original_path, suffix, infer_author_func - ) - if output: - print(output) - if not success: - return None, f"Error: Validation failed for {input_dir}" - - with tempfile.TemporaryDirectory() as temp_dir: - temp_content_dir = Path(temp_dir) / "content" - shutil.copytree(input_dir, temp_content_dir) - - for pattern in ["*.xml", "*.rels"]: - for xml_file in temp_content_dir.rglob(pattern): - _condense_xml(xml_file) - - output_path.parent.mkdir(parents=True, exist_ok=True) - with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf: - for f in temp_content_dir.rglob("*"): - if f.is_file(): - zf.write(f, f.relative_to(temp_content_dir)) - - return None, f"Successfully packed {input_dir} to {output_file}" - - -def _run_validation( - unpacked_dir: Path, - original_file: Path, - suffix: str, - infer_author_func=None, -) -> tuple[bool, str | None]: - output_lines = [] - validators = [] - - if suffix == ".docx": - author = "Claude" - if infer_author_func: - try: - author = infer_author_func(unpacked_dir, original_file) - except ValueError as e: - print(f"Warning: {e} Using default author 'Claude'.", file=sys.stderr) - - validators = [ - DOCXSchemaValidator(unpacked_dir, original_file), - RedliningValidator(unpacked_dir, original_file, author=author), - ] - elif suffix == ".pptx": - validators = [PPTXSchemaValidator(unpacked_dir, original_file)] - - if not validators: - return True, None - - total_repairs = sum(v.repair() for v in validators) - if total_repairs: - output_lines.append(f"Auto-repaired {total_repairs} issue(s)") - - success = all(v.validate() for v in validators) - - if success: - output_lines.append("All validations PASSED!") - - return success, "\n".join(output_lines) if output_lines else None - - -def _condense_xml(xml_file: Path) -> None: - try: - with open(xml_file, encoding="utf-8") as f: - dom = defusedxml.minidom.parse(f) - - for element in dom.getElementsByTagName("*"): - if element.tagName.endswith(":t"): - continue - - for child in list(element.childNodes): - if ( - child.nodeType == child.TEXT_NODE - and child.nodeValue - and child.nodeValue.strip() == "" - ) or child.nodeType == child.COMMENT_NODE: - element.removeChild(child) - - xml_file.write_bytes(dom.toxml(encoding="UTF-8")) - except Exception as e: - print(f"ERROR: Failed to parse {xml_file.name}: {e}", file=sys.stderr) - raise - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Pack a directory into a DOCX, PPTX, or XLSX file" - ) - parser.add_argument("input_directory", help="Unpacked Office document directory") - parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)") - parser.add_argument( - "--original", - help="Original file for validation comparison", - ) - parser.add_argument( - "--validate", - type=lambda x: x.lower() == "true", - default=True, - metavar="true|false", - help="Run validation with auto-repair (default: true)", - ) - args = parser.parse_args() - - _, message = pack( - args.input_directory, - args.output_file, - original_file=args.original, - validate=args.validate, - ) - print(message) - - if "Error" in message: - sys.exit(1) diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd deleted file mode 100644 index 6454ef9a..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +++ /dev/null @@ -1,1499 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd deleted file mode 100644 index afa4f463..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd deleted file mode 100644 index 64e66b8a..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +++ /dev/null @@ -1,1085 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd deleted file mode 100644 index 687eea82..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd deleted file mode 100644 index 6ac81b06..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +++ /dev/null @@ -1,3081 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd deleted file mode 100644 index 1dbf0514..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd deleted file mode 100644 index f1af17db..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd deleted file mode 100644 index 0a185ab6..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd deleted file mode 100644 index 14ef4888..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +++ /dev/null @@ -1,1676 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd deleted file mode 100644 index c20f3bf1..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd deleted file mode 100644 index ac602522..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd deleted file mode 100644 index 424b8ba8..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd deleted file mode 100644 index 2bddce29..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd deleted file mode 100644 index 8a8c18ba..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd deleted file mode 100644 index 5c42706a..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd deleted file mode 100644 index 853c341c..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd deleted file mode 100644 index da835ee8..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd deleted file mode 100644 index 87ad2658..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +++ /dev/null @@ -1,582 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd deleted file mode 100644 index 9e86f1b2..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd deleted file mode 100644 index d0be42e7..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +++ /dev/null @@ -1,4439 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd deleted file mode 100644 index 8821dd18..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +++ /dev/null @@ -1,570 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd deleted file mode 100644 index ca2575c7..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +++ /dev/null @@ -1,509 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd deleted file mode 100644 index dd079e60..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd deleted file mode 100644 index 3dd6cf62..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd deleted file mode 100644 index f1041e34..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd deleted file mode 100644 index 9c5b7a63..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +++ /dev/null @@ -1,3646 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd b/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd deleted file mode 100644 index 0f13678d..00000000 --- a/skills/pptx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - See http://www.w3.org/XML/1998/namespace.html and - http://www.w3.org/TR/REC-xml for information about this namespace. - - This schema document describes the XML namespace, in a form - suitable for import by other schema documents. - - Note that local names in this namespace are intended to be defined - only by the World Wide Web Consortium or its subgroups. The - following names are currently defined in this namespace and should - not be used with conflicting semantics by any Working Group, - specification, or document instance: - - base (as an attribute name): denotes an attribute whose value - provides a URI to be used as the base for interpreting any - relative URIs in the scope of the element on which it - appears; its value is inherited. This name is reserved - by virtue of its definition in the XML Base specification. - - lang (as an attribute name): denotes an attribute whose value - is a language code for the natural language of the content of - any element; its value is inherited. This name is reserved - by virtue of its definition in the XML specification. - - space (as an attribute name): denotes an attribute whose - value is a keyword indicating what whitespace processing - discipline is intended for the content of the element; its - value is inherited. This name is reserved by virtue of its - definition in the XML specification. - - Father (in any context at all): denotes Jon Bosak, the chair of - the original XML Working Group. This name is reserved by - the following decision of the W3C XML Plenary and - XML Coordination groups: - - In appreciation for his vision, leadership and dedication - the W3C XML Plenary on this 10th day of February, 2000 - reserves for Jon Bosak in perpetuity the XML name - xml:Father - - - - - This schema defines attributes and an attribute group - suitable for use by - schemas wishing to allow xml:base, xml:lang or xml:space attributes - on elements they define. - - To enable this, such a schema must import this schema - for the XML namespace, e.g. as follows: - <schema . . .> - . . . - <import namespace="http://www.w3.org/XML/1998/namespace" - schemaLocation="http://www.w3.org/2001/03/xml.xsd"/> - - Subsequently, qualified reference to any of the attributes - or the group defined below will have the desired effect, e.g. - - <type . . .> - . . . - <attributeGroup ref="xml:specialAttrs"/> - - will define a type which will schema-validate an instance - element with any of those attributes - - - - In keeping with the XML Schema WG's standard versioning - policy, this schema document will persist at - http://www.w3.org/2001/03/xml.xsd. - At the date of issue it can also be found at - http://www.w3.org/2001/xml.xsd. - The schema document at that URI may however change in the future, - in order to remain compatible with the latest version of XML Schema - itself. In other words, if the XML Schema namespace changes, the version - of this document at - http://www.w3.org/2001/xml.xsd will change - accordingly; the version at - http://www.w3.org/2001/03/xml.xsd will not change. - - - - - - In due course, we should install the relevant ISO 2- and 3-letter - codes as the enumerated possible values . . . - - - - - - - - - - - - - - - See http://www.w3.org/TR/xmlbase/ for - information about this attribute. - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd b/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd deleted file mode 100644 index a6de9d27..00000000 --- a/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd b/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd deleted file mode 100644 index 10e978b6..00000000 --- a/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd b/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd deleted file mode 100644 index 4248bf7a..00000000 --- a/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd b/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd deleted file mode 100644 index 56497467..00000000 --- a/skills/pptx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/mce/mc.xsd b/skills/pptx/scripts/office/schemas/mce/mc.xsd deleted file mode 100644 index ef725457..00000000 --- a/skills/pptx/scripts/office/schemas/mce/mc.xsd +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd b/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd deleted file mode 100644 index f65f7777..00000000 --- a/skills/pptx/scripts/office/schemas/microsoft/wml-2010.xsd +++ /dev/null @@ -1,560 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd b/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd deleted file mode 100644 index 6b00755a..00000000 --- a/skills/pptx/scripts/office/schemas/microsoft/wml-2012.xsd +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd b/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd deleted file mode 100644 index f321d333..00000000 --- a/skills/pptx/scripts/office/schemas/microsoft/wml-2018.xsd +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd b/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd deleted file mode 100644 index 364c6a9b..00000000 --- a/skills/pptx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd b/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd deleted file mode 100644 index fed9d15b..00000000 --- a/skills/pptx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd b/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd deleted file mode 100644 index 680cf154..00000000 --- a/skills/pptx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd b/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd deleted file mode 100644 index 89ada908..00000000 --- a/skills/pptx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/skills/pptx/scripts/office/soffice.py b/skills/pptx/scripts/office/soffice.py deleted file mode 100644 index c7f7e328..00000000 --- a/skills/pptx/scripts/office/soffice.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Helper for running LibreOffice (soffice) in environments where AF_UNIX -sockets may be blocked (e.g., sandboxed VMs). Detects the restriction -at runtime and applies an LD_PRELOAD shim if needed. - -Usage: - from office.soffice import run_soffice, get_soffice_env - - # Option 1 – run soffice directly - result = run_soffice(["--headless", "--convert-to", "pdf", "input.docx"]) - - # Option 2 – get env dict for your own subprocess calls - env = get_soffice_env() - subprocess.run(["soffice", ...], env=env) -""" - -import os -import socket -import subprocess -import tempfile -from pathlib import Path - - -def get_soffice_env() -> dict: - env = os.environ.copy() - env["SAL_USE_VCLPLUGIN"] = "svp" - - if _needs_shim(): - shim = _ensure_shim() - env["LD_PRELOAD"] = str(shim) - - return env - - -def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess: - env = get_soffice_env() - return subprocess.run(["soffice"] + args, env=env, **kwargs) - - - -_SHIM_SO = Path(tempfile.gettempdir()) / "lo_socket_shim.so" - - -def _needs_shim() -> bool: - try: - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - s.close() - return False - except OSError: - return True - - -def _ensure_shim() -> Path: - if _SHIM_SO.exists(): - return _SHIM_SO - - src = Path(tempfile.gettempdir()) / "lo_socket_shim.c" - src.write_text(_SHIM_SOURCE) - subprocess.run( - ["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"], - check=True, - capture_output=True, - ) - src.unlink() - return _SHIM_SO - - - -_SHIM_SOURCE = r""" -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include - -static int (*real_socket)(int, int, int); -static int (*real_socketpair)(int, int, int, int[2]); -static int (*real_listen)(int, int); -static int (*real_accept)(int, struct sockaddr *, socklen_t *); -static int (*real_close)(int); -static int (*real_read)(int, void *, size_t); - -/* Per-FD bookkeeping (FDs >= 1024 are passed through unshimmed). */ -static int is_shimmed[1024]; -static int peer_of[1024]; -static int wake_r[1024]; /* accept() blocks reading this */ -static int wake_w[1024]; /* close() writes to this */ -static int listener_fd = -1; /* FD that received listen() */ - -__attribute__((constructor)) -static void init(void) { - real_socket = dlsym(RTLD_NEXT, "socket"); - real_socketpair = dlsym(RTLD_NEXT, "socketpair"); - real_listen = dlsym(RTLD_NEXT, "listen"); - real_accept = dlsym(RTLD_NEXT, "accept"); - real_close = dlsym(RTLD_NEXT, "close"); - real_read = dlsym(RTLD_NEXT, "read"); - for (int i = 0; i < 1024; i++) { - peer_of[i] = -1; - wake_r[i] = -1; - wake_w[i] = -1; - } -} - -/* ---- socket ---------------------------------------------------------- */ -int socket(int domain, int type, int protocol) { - if (domain == AF_UNIX) { - int fd = real_socket(domain, type, protocol); - if (fd >= 0) return fd; - /* socket(AF_UNIX) blocked – fall back to socketpair(). */ - int sv[2]; - if (real_socketpair(domain, type, protocol, sv) == 0) { - if (sv[0] >= 0 && sv[0] < 1024) { - is_shimmed[sv[0]] = 1; - peer_of[sv[0]] = sv[1]; - int wp[2]; - if (pipe(wp) == 0) { - wake_r[sv[0]] = wp[0]; - wake_w[sv[0]] = wp[1]; - } - } - return sv[0]; - } - errno = EPERM; - return -1; - } - return real_socket(domain, type, protocol); -} - -/* ---- listen ---------------------------------------------------------- */ -int listen(int sockfd, int backlog) { - if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) { - listener_fd = sockfd; - return 0; - } - return real_listen(sockfd, backlog); -} - -/* ---- accept ---------------------------------------------------------- */ -int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { - if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) { - /* Block until close() writes to the wake pipe. */ - if (wake_r[sockfd] >= 0) { - char buf; - real_read(wake_r[sockfd], &buf, 1); - } - errno = ECONNABORTED; - return -1; - } - return real_accept(sockfd, addr, addrlen); -} - -/* ---- close ----------------------------------------------------------- */ -int close(int fd) { - if (fd >= 0 && fd < 1024 && is_shimmed[fd]) { - int was_listener = (fd == listener_fd); - is_shimmed[fd] = 0; - - if (wake_w[fd] >= 0) { /* unblock accept() */ - char c = 0; - write(wake_w[fd], &c, 1); - real_close(wake_w[fd]); - wake_w[fd] = -1; - } - if (wake_r[fd] >= 0) { real_close(wake_r[fd]); wake_r[fd] = -1; } - if (peer_of[fd] >= 0) { real_close(peer_of[fd]); peer_of[fd] = -1; } - - if (was_listener) - _exit(0); /* conversion done – exit */ - } - return real_close(fd); -} -""" - - - -if __name__ == "__main__": - import sys - result = run_soffice(sys.argv[1:]) - sys.exit(result.returncode) diff --git a/skills/pptx/scripts/office/unpack.py b/skills/pptx/scripts/office/unpack.py deleted file mode 100755 index 00152533..00000000 --- a/skills/pptx/scripts/office/unpack.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Unpack Office files (DOCX, PPTX, XLSX) for editing. - -Extracts the ZIP archive, pretty-prints XML files, and optionally: -- Merges adjacent runs with identical formatting (DOCX only) -- Simplifies adjacent tracked changes from same author (DOCX only) - -Usage: - python unpack.py [options] - -Examples: - python unpack.py document.docx unpacked/ - python unpack.py presentation.pptx unpacked/ - python unpack.py document.docx unpacked/ --merge-runs false -""" - -import argparse -import sys -import zipfile -from pathlib import Path - -import defusedxml.minidom - -from helpers.merge_runs import merge_runs as do_merge_runs -from helpers.simplify_redlines import simplify_redlines as do_simplify_redlines - -SMART_QUOTE_REPLACEMENTS = { - "\u201c": "“", - "\u201d": "”", - "\u2018": "‘", - "\u2019": "’", -} - - -def unpack( - input_file: str, - output_directory: str, - merge_runs: bool = True, - simplify_redlines: bool = True, -) -> tuple[None, str]: - input_path = Path(input_file) - output_path = Path(output_directory) - suffix = input_path.suffix.lower() - - if not input_path.exists(): - return None, f"Error: {input_file} does not exist" - - if suffix not in {".docx", ".pptx", ".xlsx"}: - return None, f"Error: {input_file} must be a .docx, .pptx, or .xlsx file" - - try: - output_path.mkdir(parents=True, exist_ok=True) - - with zipfile.ZipFile(input_path, "r") as zf: - zf.extractall(output_path) - - xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels")) - for xml_file in xml_files: - _pretty_print_xml(xml_file) - - message = f"Unpacked {input_file} ({len(xml_files)} XML files)" - - if suffix == ".docx": - if simplify_redlines: - simplify_count, _ = do_simplify_redlines(str(output_path)) - message += f", simplified {simplify_count} tracked changes" - - if merge_runs: - merge_count, _ = do_merge_runs(str(output_path)) - message += f", merged {merge_count} runs" - - for xml_file in xml_files: - _escape_smart_quotes(xml_file) - - return None, message - - except zipfile.BadZipFile: - return None, f"Error: {input_file} is not a valid Office file" - except Exception as e: - return None, f"Error unpacking: {e}" - - -def _pretty_print_xml(xml_file: Path) -> None: - try: - content = xml_file.read_text(encoding="utf-8") - dom = defusedxml.minidom.parseString(content) - xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="utf-8")) - except Exception: - pass - - -def _escape_smart_quotes(xml_file: Path) -> None: - try: - content = xml_file.read_text(encoding="utf-8") - for char, entity in SMART_QUOTE_REPLACEMENTS.items(): - content = content.replace(char, entity) - xml_file.write_text(content, encoding="utf-8") - except Exception: - pass - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Unpack an Office file (DOCX, PPTX, XLSX) for editing" - ) - parser.add_argument("input_file", help="Office file to unpack") - parser.add_argument("output_directory", help="Output directory") - parser.add_argument( - "--merge-runs", - type=lambda x: x.lower() == "true", - default=True, - metavar="true|false", - help="Merge adjacent runs with identical formatting (DOCX only, default: true)", - ) - parser.add_argument( - "--simplify-redlines", - type=lambda x: x.lower() == "true", - default=True, - metavar="true|false", - help="Merge adjacent tracked changes from same author (DOCX only, default: true)", - ) - args = parser.parse_args() - - _, message = unpack( - args.input_file, - args.output_directory, - merge_runs=args.merge_runs, - simplify_redlines=args.simplify_redlines, - ) - print(message) - - if "Error" in message: - sys.exit(1) diff --git a/skills/pptx/scripts/office/validate.py b/skills/pptx/scripts/office/validate.py deleted file mode 100755 index 03b01f6e..00000000 --- a/skills/pptx/scripts/office/validate.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Command line tool to validate Office document XML files against XSD schemas and tracked changes. - -Usage: - python validate.py [--original ] [--auto-repair] [--author NAME] - -The first argument can be either: -- An unpacked directory containing the Office document XML files -- A packed Office file (.docx/.pptx/.xlsx) which will be unpacked to a temp directory - -Auto-repair fixes: -- paraId/durableId values that exceed OOXML limits -- Missing xml:space="preserve" on w:t elements with whitespace -""" - -import argparse -import sys -import tempfile -import zipfile -from pathlib import Path - -from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator - - -def main(): - parser = argparse.ArgumentParser(description="Validate Office document XML files") - parser.add_argument( - "path", - help="Path to unpacked directory or packed Office file (.docx/.pptx/.xlsx)", - ) - parser.add_argument( - "--original", - required=False, - default=None, - help="Path to original file (.docx/.pptx/.xlsx). If omitted, all XSD errors are reported and redlining validation is skipped.", - ) - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Enable verbose output", - ) - parser.add_argument( - "--auto-repair", - action="store_true", - help="Automatically repair common issues (hex IDs, whitespace preservation)", - ) - parser.add_argument( - "--author", - default="Claude", - help="Author name for redlining validation (default: Claude)", - ) - args = parser.parse_args() - - path = Path(args.path) - assert path.exists(), f"Error: {path} does not exist" - - original_file = None - if args.original: - original_file = Path(args.original) - assert original_file.is_file(), f"Error: {original_file} is not a file" - assert original_file.suffix.lower() in [".docx", ".pptx", ".xlsx"], ( - f"Error: {original_file} must be a .docx, .pptx, or .xlsx file" - ) - - file_extension = (original_file or path).suffix.lower() - assert file_extension in [".docx", ".pptx", ".xlsx"], ( - f"Error: Cannot determine file type from {path}. Use --original or provide a .docx/.pptx/.xlsx file." - ) - - if path.is_file() and path.suffix.lower() in [".docx", ".pptx", ".xlsx"]: - temp_dir = tempfile.mkdtemp() - with zipfile.ZipFile(path, "r") as zf: - zf.extractall(temp_dir) - unpacked_dir = Path(temp_dir) - else: - assert path.is_dir(), f"Error: {path} is not a directory or Office file" - unpacked_dir = path - - match file_extension: - case ".docx": - validators = [ - DOCXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose), - ] - if original_file: - validators.append( - RedliningValidator(unpacked_dir, original_file, verbose=args.verbose, author=args.author) - ) - case ".pptx": - validators = [ - PPTXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose), - ] - case _: - print(f"Error: Validation not supported for file type {file_extension}") - sys.exit(1) - - if args.auto_repair: - total_repairs = sum(v.repair() for v in validators) - if total_repairs: - print(f"Auto-repaired {total_repairs} issue(s)") - - success = all(v.validate() for v in validators) - - if success: - print("All validations PASSED!") - - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main() diff --git a/skills/pptx/scripts/office/validators/__init__.py b/skills/pptx/scripts/office/validators/__init__.py deleted file mode 100644 index db092ece..00000000 --- a/skills/pptx/scripts/office/validators/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Validation modules for Word document processing. -""" - -from .base import BaseSchemaValidator -from .docx import DOCXSchemaValidator -from .pptx import PPTXSchemaValidator -from .redlining import RedliningValidator - -__all__ = [ - "BaseSchemaValidator", - "DOCXSchemaValidator", - "PPTXSchemaValidator", - "RedliningValidator", -] diff --git a/skills/pptx/scripts/office/validators/base.py b/skills/pptx/scripts/office/validators/base.py deleted file mode 100644 index db4a06a2..00000000 --- a/skills/pptx/scripts/office/validators/base.py +++ /dev/null @@ -1,847 +0,0 @@ -""" -Base validator with common validation logic for document files. -""" - -import re -from pathlib import Path - -import defusedxml.minidom -import lxml.etree - - -class BaseSchemaValidator: - - IGNORED_VALIDATION_ERRORS = [ - "hyphenationZone", - "purl.org/dc/terms", - ] - - UNIQUE_ID_REQUIREMENTS = { - "comment": ("id", "file"), - "commentrangestart": ("id", "file"), - "commentrangeend": ("id", "file"), - "bookmarkstart": ("id", "file"), - "bookmarkend": ("id", "file"), - "sldid": ("id", "file"), - "sldmasterid": ("id", "global"), - "sldlayoutid": ("id", "global"), - "cm": ("authorid", "file"), - "sheet": ("sheetid", "file"), - "definedname": ("id", "file"), - "cxnsp": ("id", "file"), - "sp": ("id", "file"), - "pic": ("id", "file"), - "grpsp": ("id", "file"), - } - - EXCLUDED_ID_CONTAINERS = { - "sectionlst", - } - - ELEMENT_RELATIONSHIP_TYPES = {} - - SCHEMA_MAPPINGS = { - "word": "ISO-IEC29500-4_2016/wml.xsd", - "ppt": "ISO-IEC29500-4_2016/pml.xsd", - "xl": "ISO-IEC29500-4_2016/sml.xsd", - "[Content_Types].xml": "ecma/fouth-edition/opc-contentTypes.xsd", - "app.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd", - "core.xml": "ecma/fouth-edition/opc-coreProperties.xsd", - "custom.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd", - ".rels": "ecma/fouth-edition/opc-relationships.xsd", - "people.xml": "microsoft/wml-2012.xsd", - "commentsIds.xml": "microsoft/wml-cid-2016.xsd", - "commentsExtensible.xml": "microsoft/wml-cex-2018.xsd", - "commentsExtended.xml": "microsoft/wml-2012.xsd", - "chart": "ISO-IEC29500-4_2016/dml-chart.xsd", - "theme": "ISO-IEC29500-4_2016/dml-main.xsd", - "drawing": "ISO-IEC29500-4_2016/dml-main.xsd", - } - - MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006" - XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace" - - PACKAGE_RELATIONSHIPS_NAMESPACE = ( - "http://schemas.openxmlformats.org/package/2006/relationships" - ) - OFFICE_RELATIONSHIPS_NAMESPACE = ( - "http://schemas.openxmlformats.org/officeDocument/2006/relationships" - ) - CONTENT_TYPES_NAMESPACE = ( - "http://schemas.openxmlformats.org/package/2006/content-types" - ) - - MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"} - - OOXML_NAMESPACES = { - "http://schemas.openxmlformats.org/officeDocument/2006/math", - "http://schemas.openxmlformats.org/officeDocument/2006/relationships", - "http://schemas.openxmlformats.org/schemaLibrary/2006/main", - "http://schemas.openxmlformats.org/drawingml/2006/main", - "http://schemas.openxmlformats.org/drawingml/2006/chart", - "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing", - "http://schemas.openxmlformats.org/drawingml/2006/diagram", - "http://schemas.openxmlformats.org/drawingml/2006/picture", - "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", - "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", - "http://schemas.openxmlformats.org/wordprocessingml/2006/main", - "http://schemas.openxmlformats.org/presentationml/2006/main", - "http://schemas.openxmlformats.org/spreadsheetml/2006/main", - "http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes", - "http://www.w3.org/XML/1998/namespace", - } - - def __init__(self, unpacked_dir, original_file=None, verbose=False): - self.unpacked_dir = Path(unpacked_dir).resolve() - self.original_file = Path(original_file) if original_file else None - self.verbose = verbose - - self.schemas_dir = Path(__file__).parent.parent / "schemas" - - patterns = ["*.xml", "*.rels"] - self.xml_files = [ - f for pattern in patterns for f in self.unpacked_dir.rglob(pattern) - ] - - if not self.xml_files: - print(f"Warning: No XML files found in {self.unpacked_dir}") - - def validate(self): - raise NotImplementedError("Subclasses must implement the validate method") - - def repair(self) -> int: - return self.repair_whitespace_preservation() - - def repair_whitespace_preservation(self) -> int: - repairs = 0 - - for xml_file in self.xml_files: - try: - content = xml_file.read_text(encoding="utf-8") - dom = defusedxml.minidom.parseString(content) - modified = False - - for elem in dom.getElementsByTagName("*"): - if elem.tagName.endswith(":t") and elem.firstChild: - text = elem.firstChild.nodeValue - if text and (text.startswith((' ', '\t')) or text.endswith((' ', '\t'))): - if elem.getAttribute("xml:space") != "preserve": - elem.setAttribute("xml:space", "preserve") - text_preview = repr(text[:30]) + "..." if len(text) > 30 else repr(text) - print(f" Repaired: {xml_file.name}: Added xml:space='preserve' to {elem.tagName}: {text_preview}") - repairs += 1 - modified = True - - if modified: - xml_file.write_bytes(dom.toxml(encoding="UTF-8")) - - except Exception: - pass - - return repairs - - def validate_xml(self): - errors = [] - - for xml_file in self.xml_files: - try: - lxml.etree.parse(str(xml_file)) - except lxml.etree.XMLSyntaxError as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {e.lineno}: {e.msg}" - ) - except Exception as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Unexpected error: {str(e)}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} XML violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All XML files are well-formed") - return True - - def validate_namespaces(self): - errors = [] - - for xml_file in self.xml_files: - try: - root = lxml.etree.parse(str(xml_file)).getroot() - declared = set(root.nsmap.keys()) - {None} - - for attr_val in [ - v for k, v in root.attrib.items() if k.endswith("Ignorable") - ]: - undeclared = set(attr_val.split()) - declared - errors.extend( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Namespace '{ns}' in Ignorable but not declared" - for ns in undeclared - ) - except lxml.etree.XMLSyntaxError: - continue - - if errors: - print(f"FAILED - {len(errors)} namespace issues:") - for error in errors: - print(error) - return False - if self.verbose: - print("PASSED - All namespace prefixes properly declared") - return True - - def validate_unique_ids(self): - errors = [] - global_ids = {} - - for xml_file in self.xml_files: - try: - root = lxml.etree.parse(str(xml_file)).getroot() - file_ids = {} - - mc_elements = root.xpath( - ".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE} - ) - for elem in mc_elements: - elem.getparent().remove(elem) - - for elem in root.iter(): - tag = ( - elem.tag.split("}")[-1].lower() - if "}" in elem.tag - else elem.tag.lower() - ) - - if tag in self.UNIQUE_ID_REQUIREMENTS: - in_excluded_container = any( - ancestor.tag.split("}")[-1].lower() in self.EXCLUDED_ID_CONTAINERS - for ancestor in elem.iterancestors() - ) - if in_excluded_container: - continue - - attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag] - - id_value = None - for attr, value in elem.attrib.items(): - attr_local = ( - attr.split("}")[-1].lower() - if "}" in attr - else attr.lower() - ) - if attr_local == attr_name: - id_value = value - break - - if id_value is not None: - if scope == "global": - if id_value in global_ids: - prev_file, prev_line, prev_tag = global_ids[ - id_value - ] - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: Global ID '{id_value}' in <{tag}> " - f"already used in {prev_file} at line {prev_line} in <{prev_tag}>" - ) - else: - global_ids[id_value] = ( - xml_file.relative_to(self.unpacked_dir), - elem.sourceline, - tag, - ) - elif scope == "file": - key = (tag, attr_name) - if key not in file_ids: - file_ids[key] = {} - - if id_value in file_ids[key]: - prev_line = file_ids[key][id_value] - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: Duplicate {attr_name}='{id_value}' in <{tag}> " - f"(first occurrence at line {prev_line})" - ) - else: - file_ids[key][id_value] = elem.sourceline - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} ID uniqueness violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All required IDs are unique") - return True - - def validate_file_references(self): - errors = [] - - rels_files = list(self.unpacked_dir.rglob("*.rels")) - - if not rels_files: - if self.verbose: - print("PASSED - No .rels files found") - return True - - all_files = [] - for file_path in self.unpacked_dir.rglob("*"): - if ( - file_path.is_file() - and file_path.name != "[Content_Types].xml" - and not file_path.name.endswith(".rels") - ): - all_files.append(file_path.resolve()) - - all_referenced_files = set() - - if self.verbose: - print( - f"Found {len(rels_files)} .rels files and {len(all_files)} target files" - ) - - for rels_file in rels_files: - try: - rels_root = lxml.etree.parse(str(rels_file)).getroot() - - rels_dir = rels_file.parent - - referenced_files = set() - broken_refs = [] - - for rel in rels_root.findall( - ".//ns:Relationship", - namespaces={"ns": self.PACKAGE_RELATIONSHIPS_NAMESPACE}, - ): - target = rel.get("Target") - if target and not target.startswith( - ("http", "mailto:") - ): - if target.startswith("/"): - target_path = self.unpacked_dir / target.lstrip("/") - elif rels_file.name == ".rels": - target_path = self.unpacked_dir / target - else: - base_dir = rels_dir.parent - target_path = base_dir / target - - try: - target_path = target_path.resolve() - if target_path.exists() and target_path.is_file(): - referenced_files.add(target_path) - all_referenced_files.add(target_path) - else: - broken_refs.append((target, rel.sourceline)) - except (OSError, ValueError): - broken_refs.append((target, rel.sourceline)) - - if broken_refs: - rel_path = rels_file.relative_to(self.unpacked_dir) - for broken_ref, line_num in broken_refs: - errors.append( - f" {rel_path}: Line {line_num}: Broken reference to {broken_ref}" - ) - - except Exception as e: - rel_path = rels_file.relative_to(self.unpacked_dir) - errors.append(f" Error parsing {rel_path}: {e}") - - unreferenced_files = set(all_files) - all_referenced_files - - if unreferenced_files: - for unref_file in sorted(unreferenced_files): - unref_rel_path = unref_file.relative_to(self.unpacked_dir) - errors.append(f" Unreferenced file: {unref_rel_path}") - - if errors: - print(f"FAILED - Found {len(errors)} relationship validation errors:") - for error in errors: - print(error) - print( - "CRITICAL: These errors will cause the document to appear corrupt. " - + "Broken references MUST be fixed, " - + "and unreferenced files MUST be referenced or removed." - ) - return False - else: - if self.verbose: - print( - "PASSED - All references are valid and all files are properly referenced" - ) - return True - - def validate_all_relationship_ids(self): - import lxml.etree - - errors = [] - - for xml_file in self.xml_files: - if xml_file.suffix == ".rels": - continue - - rels_dir = xml_file.parent / "_rels" - rels_file = rels_dir / f"{xml_file.name}.rels" - - if not rels_file.exists(): - continue - - try: - rels_root = lxml.etree.parse(str(rels_file)).getroot() - rid_to_type = {} - - for rel in rels_root.findall( - f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" - ): - rid = rel.get("Id") - rel_type = rel.get("Type", "") - if rid: - if rid in rid_to_type: - rels_rel_path = rels_file.relative_to(self.unpacked_dir) - errors.append( - f" {rels_rel_path}: Line {rel.sourceline}: " - f"Duplicate relationship ID '{rid}' (IDs must be unique)" - ) - type_name = ( - rel_type.split("/")[-1] if "/" in rel_type else rel_type - ) - rid_to_type[rid] = type_name - - xml_root = lxml.etree.parse(str(xml_file)).getroot() - - r_ns = self.OFFICE_RELATIONSHIPS_NAMESPACE - rid_attrs_to_check = ["id", "embed", "link"] - for elem in xml_root.iter(): - for attr_name in rid_attrs_to_check: - rid_attr = elem.get(f"{{{r_ns}}}{attr_name}") - if not rid_attr: - continue - xml_rel_path = xml_file.relative_to(self.unpacked_dir) - elem_name = ( - elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag - ) - - if rid_attr not in rid_to_type: - errors.append( - f" {xml_rel_path}: Line {elem.sourceline}: " - f"<{elem_name}> r:{attr_name} references non-existent relationship '{rid_attr}' " - f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})" - ) - elif attr_name == "id" and self.ELEMENT_RELATIONSHIP_TYPES: - expected_type = self._get_expected_relationship_type( - elem_name - ) - if expected_type: - actual_type = rid_to_type[rid_attr] - if expected_type not in actual_type.lower(): - errors.append( - f" {xml_rel_path}: Line {elem.sourceline}: " - f"<{elem_name}> references '{rid_attr}' which points to '{actual_type}' " - f"but should point to a '{expected_type}' relationship" - ) - - except Exception as e: - xml_rel_path = xml_file.relative_to(self.unpacked_dir) - errors.append(f" Error processing {xml_rel_path}: {e}") - - if errors: - print(f"FAILED - Found {len(errors)} relationship ID reference errors:") - for error in errors: - print(error) - print("\nThese ID mismatches will cause the document to appear corrupt!") - return False - else: - if self.verbose: - print("PASSED - All relationship ID references are valid") - return True - - def _get_expected_relationship_type(self, element_name): - elem_lower = element_name.lower() - - if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES: - return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower] - - if elem_lower.endswith("id") and len(elem_lower) > 2: - prefix = elem_lower[:-2] - if prefix.endswith("master"): - return prefix.lower() - elif prefix.endswith("layout"): - return prefix.lower() - else: - if prefix == "sld": - return "slide" - return prefix.lower() - - if elem_lower.endswith("reference") and len(elem_lower) > 9: - prefix = elem_lower[:-9] - return prefix.lower() - - return None - - def validate_content_types(self): - errors = [] - - content_types_file = self.unpacked_dir / "[Content_Types].xml" - if not content_types_file.exists(): - print("FAILED - [Content_Types].xml file not found") - return False - - try: - root = lxml.etree.parse(str(content_types_file)).getroot() - declared_parts = set() - declared_extensions = set() - - for override in root.findall( - f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Override" - ): - part_name = override.get("PartName") - if part_name is not None: - declared_parts.add(part_name.lstrip("/")) - - for default in root.findall( - f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Default" - ): - extension = default.get("Extension") - if extension is not None: - declared_extensions.add(extension.lower()) - - declarable_roots = { - "sld", - "sldLayout", - "sldMaster", - "presentation", - "document", - "workbook", - "worksheet", - "theme", - } - - media_extensions = { - "png": "image/png", - "jpg": "image/jpeg", - "jpeg": "image/jpeg", - "gif": "image/gif", - "bmp": "image/bmp", - "tiff": "image/tiff", - "wmf": "image/x-wmf", - "emf": "image/x-emf", - } - - all_files = list(self.unpacked_dir.rglob("*")) - all_files = [f for f in all_files if f.is_file()] - - for xml_file in self.xml_files: - path_str = str(xml_file.relative_to(self.unpacked_dir)).replace( - "\\", "/" - ) - - if any( - skip in path_str - for skip in [".rels", "[Content_Types]", "docProps/", "_rels/"] - ): - continue - - try: - root_tag = lxml.etree.parse(str(xml_file)).getroot().tag - root_name = root_tag.split("}")[-1] if "}" in root_tag else root_tag - - if root_name in declarable_roots and path_str not in declared_parts: - errors.append( - f" {path_str}: File with <{root_name}> root not declared in [Content_Types].xml" - ) - - except Exception: - continue - - for file_path in all_files: - if file_path.suffix.lower() in {".xml", ".rels"}: - continue - if file_path.name == "[Content_Types].xml": - continue - if "_rels" in file_path.parts or "docProps" in file_path.parts: - continue - - extension = file_path.suffix.lstrip(".").lower() - if extension and extension not in declared_extensions: - if extension in media_extensions: - relative_path = file_path.relative_to(self.unpacked_dir) - errors.append( - f' {relative_path}: File with extension \'{extension}\' not declared in [Content_Types].xml - should add: ' - ) - - except Exception as e: - errors.append(f" Error parsing [Content_Types].xml: {e}") - - if errors: - print(f"FAILED - Found {len(errors)} content type declaration errors:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print( - "PASSED - All content files are properly declared in [Content_Types].xml" - ) - return True - - def validate_file_against_xsd(self, xml_file, verbose=False): - xml_file = Path(xml_file).resolve() - unpacked_dir = self.unpacked_dir.resolve() - - is_valid, current_errors = self._validate_single_file_xsd( - xml_file, unpacked_dir - ) - - if is_valid is None: - return None, set() - elif is_valid: - return True, set() - - original_errors = self._get_original_file_errors(xml_file) - - assert current_errors is not None - new_errors = current_errors - original_errors - - new_errors = { - e for e in new_errors - if not any(pattern in e for pattern in self.IGNORED_VALIDATION_ERRORS) - } - - if new_errors: - if verbose: - relative_path = xml_file.relative_to(unpacked_dir) - print(f"FAILED - {relative_path}: {len(new_errors)} new error(s)") - for error in list(new_errors)[:3]: - truncated = error[:250] + "..." if len(error) > 250 else error - print(f" - {truncated}") - return False, new_errors - else: - if verbose: - print( - f"PASSED - No new errors (original had {len(current_errors)} errors)" - ) - return True, set() - - def validate_against_xsd(self): - new_errors = [] - original_error_count = 0 - valid_count = 0 - skipped_count = 0 - - for xml_file in self.xml_files: - relative_path = str(xml_file.relative_to(self.unpacked_dir)) - is_valid, new_file_errors = self.validate_file_against_xsd( - xml_file, verbose=False - ) - - if is_valid is None: - skipped_count += 1 - continue - elif is_valid and not new_file_errors: - valid_count += 1 - continue - elif is_valid: - original_error_count += 1 - valid_count += 1 - continue - - new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)") - for error in list(new_file_errors)[:3]: - new_errors.append( - f" - {error[:250]}..." if len(error) > 250 else f" - {error}" - ) - - if self.verbose: - print(f"Validated {len(self.xml_files)} files:") - print(f" - Valid: {valid_count}") - print(f" - Skipped (no schema): {skipped_count}") - if original_error_count: - print(f" - With original errors (ignored): {original_error_count}") - print( - f" - With NEW errors: {len(new_errors) > 0 and len([e for e in new_errors if not e.startswith(' ')]) or 0}" - ) - - if new_errors: - print("\nFAILED - Found NEW validation errors:") - for error in new_errors: - print(error) - return False - else: - if self.verbose: - print("\nPASSED - No new XSD validation errors introduced") - return True - - def _get_schema_path(self, xml_file): - if xml_file.name in self.SCHEMA_MAPPINGS: - return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name] - - if xml_file.suffix == ".rels": - return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"] - - if "charts/" in str(xml_file) and xml_file.name.startswith("chart"): - return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"] - - if "theme/" in str(xml_file) and xml_file.name.startswith("theme"): - return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"] - - if xml_file.parent.name in self.MAIN_CONTENT_FOLDERS: - return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.parent.name] - - return None - - def _clean_ignorable_namespaces(self, xml_doc): - xml_string = lxml.etree.tostring(xml_doc, encoding="unicode") - xml_copy = lxml.etree.fromstring(xml_string) - - for elem in xml_copy.iter(): - attrs_to_remove = [] - - for attr in elem.attrib: - if "{" in attr: - ns = attr.split("}")[0][1:] - if ns not in self.OOXML_NAMESPACES: - attrs_to_remove.append(attr) - - for attr in attrs_to_remove: - del elem.attrib[attr] - - self._remove_ignorable_elements(xml_copy) - - return lxml.etree.ElementTree(xml_copy) - - def _remove_ignorable_elements(self, root): - elements_to_remove = [] - - for elem in list(root): - if not hasattr(elem, "tag") or callable(elem.tag): - continue - - tag_str = str(elem.tag) - if tag_str.startswith("{"): - ns = tag_str.split("}")[0][1:] - if ns not in self.OOXML_NAMESPACES: - elements_to_remove.append(elem) - continue - - self._remove_ignorable_elements(elem) - - for elem in elements_to_remove: - root.remove(elem) - - def _preprocess_for_mc_ignorable(self, xml_doc): - root = xml_doc.getroot() - - if f"{{{self.MC_NAMESPACE}}}Ignorable" in root.attrib: - del root.attrib[f"{{{self.MC_NAMESPACE}}}Ignorable"] - - return xml_doc - - def _validate_single_file_xsd(self, xml_file, base_path): - schema_path = self._get_schema_path(xml_file) - if not schema_path: - return None, None - - try: - with open(schema_path, "rb") as xsd_file: - parser = lxml.etree.XMLParser() - xsd_doc = lxml.etree.parse( - xsd_file, parser=parser, base_url=str(schema_path) - ) - schema = lxml.etree.XMLSchema(xsd_doc) - - with open(xml_file, "r") as f: - xml_doc = lxml.etree.parse(f) - - xml_doc, _ = self._remove_template_tags_from_text_nodes(xml_doc) - xml_doc = self._preprocess_for_mc_ignorable(xml_doc) - - relative_path = xml_file.relative_to(base_path) - if ( - relative_path.parts - and relative_path.parts[0] in self.MAIN_CONTENT_FOLDERS - ): - xml_doc = self._clean_ignorable_namespaces(xml_doc) - - if schema.validate(xml_doc): - return True, set() - else: - errors = set() - for error in schema.error_log: - errors.add(error.message) - return False, errors - - except Exception as e: - return False, {str(e)} - - def _get_original_file_errors(self, xml_file): - if self.original_file is None: - return set() - - import tempfile - import zipfile - - xml_file = Path(xml_file).resolve() - unpacked_dir = self.unpacked_dir.resolve() - relative_path = xml_file.relative_to(unpacked_dir) - - with tempfile.TemporaryDirectory() as temp_dir: - temp_path = Path(temp_dir) - - with zipfile.ZipFile(self.original_file, "r") as zip_ref: - zip_ref.extractall(temp_path) - - original_xml_file = temp_path / relative_path - - if not original_xml_file.exists(): - return set() - - is_valid, errors = self._validate_single_file_xsd( - original_xml_file, temp_path - ) - return errors if errors else set() - - def _remove_template_tags_from_text_nodes(self, xml_doc): - warnings = [] - template_pattern = re.compile(r"\{\{[^}]*\}\}") - - xml_string = lxml.etree.tostring(xml_doc, encoding="unicode") - xml_copy = lxml.etree.fromstring(xml_string) - - def process_text_content(text, content_type): - if not text: - return text - matches = list(template_pattern.finditer(text)) - if matches: - for match in matches: - warnings.append( - f"Found template tag in {content_type}: {match.group()}" - ) - return template_pattern.sub("", text) - return text - - for elem in xml_copy.iter(): - if not hasattr(elem, "tag") or callable(elem.tag): - continue - tag_str = str(elem.tag) - if tag_str.endswith("}t") or tag_str == "t": - continue - - elem.text = process_text_content(elem.text, "text content") - elem.tail = process_text_content(elem.tail, "tail content") - - return lxml.etree.ElementTree(xml_copy), warnings - - -if __name__ == "__main__": - raise RuntimeError("This module should not be run directly.") diff --git a/skills/pptx/scripts/office/validators/docx.py b/skills/pptx/scripts/office/validators/docx.py deleted file mode 100644 index fec405e6..00000000 --- a/skills/pptx/scripts/office/validators/docx.py +++ /dev/null @@ -1,446 +0,0 @@ -""" -Validator for Word document XML files against XSD schemas. -""" - -import random -import re -import tempfile -import zipfile - -import defusedxml.minidom -import lxml.etree - -from .base import BaseSchemaValidator - - -class DOCXSchemaValidator(BaseSchemaValidator): - - WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" - W14_NAMESPACE = "http://schemas.microsoft.com/office/word/2010/wordml" - W16CID_NAMESPACE = "http://schemas.microsoft.com/office/word/2016/wordml/cid" - - ELEMENT_RELATIONSHIP_TYPES = {} - - def validate(self): - if not self.validate_xml(): - return False - - all_valid = True - if not self.validate_namespaces(): - all_valid = False - - if not self.validate_unique_ids(): - all_valid = False - - if not self.validate_file_references(): - all_valid = False - - if not self.validate_content_types(): - all_valid = False - - if not self.validate_against_xsd(): - all_valid = False - - if not self.validate_whitespace_preservation(): - all_valid = False - - if not self.validate_deletions(): - all_valid = False - - if not self.validate_insertions(): - all_valid = False - - if not self.validate_all_relationship_ids(): - all_valid = False - - if not self.validate_id_constraints(): - all_valid = False - - if not self.validate_comment_markers(): - all_valid = False - - self.compare_paragraph_counts() - - return all_valid - - def validate_whitespace_preservation(self): - errors = [] - - for xml_file in self.xml_files: - if xml_file.name != "document.xml": - continue - - try: - root = lxml.etree.parse(str(xml_file)).getroot() - - for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"): - if elem.text: - text = elem.text - if re.search(r"^[ \t\n\r]", text) or re.search( - r"[ \t\n\r]$", text - ): - xml_space_attr = f"{{{self.XML_NAMESPACE}}}space" - if ( - xml_space_attr not in elem.attrib - or elem.attrib[xml_space_attr] != "preserve" - ): - text_preview = ( - repr(text)[:50] + "..." - if len(repr(text)) > 50 - else repr(text) - ) - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: w:t element with whitespace missing xml:space='preserve': {text_preview}" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} whitespace preservation violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All whitespace is properly preserved") - return True - - def validate_deletions(self): - errors = [] - - for xml_file in self.xml_files: - if xml_file.name != "document.xml": - continue - - try: - root = lxml.etree.parse(str(xml_file)).getroot() - namespaces = {"w": self.WORD_2006_NAMESPACE} - - for t_elem in root.xpath(".//w:del//w:t", namespaces=namespaces): - if t_elem.text: - text_preview = ( - repr(t_elem.text)[:50] + "..." - if len(repr(t_elem.text)) > 50 - else repr(t_elem.text) - ) - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {t_elem.sourceline}: found within : {text_preview}" - ) - - for instr_elem in root.xpath( - ".//w:del//w:instrText", namespaces=namespaces - ): - text_preview = ( - repr(instr_elem.text or "")[:50] + "..." - if len(repr(instr_elem.text or "")) > 50 - else repr(instr_elem.text or "") - ) - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {instr_elem.sourceline}: found within (use ): {text_preview}" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} deletion validation violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - No w:t elements found within w:del elements") - return True - - def count_paragraphs_in_unpacked(self): - count = 0 - - for xml_file in self.xml_files: - if xml_file.name != "document.xml": - continue - - try: - root = lxml.etree.parse(str(xml_file)).getroot() - paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p") - count = len(paragraphs) - except Exception as e: - print(f"Error counting paragraphs in unpacked document: {e}") - - return count - - def count_paragraphs_in_original(self): - original = self.original_file - if original is None: - return 0 - - count = 0 - - try: - with tempfile.TemporaryDirectory() as temp_dir: - with zipfile.ZipFile(original, "r") as zip_ref: - zip_ref.extractall(temp_dir) - - doc_xml_path = temp_dir + "/word/document.xml" - root = lxml.etree.parse(doc_xml_path).getroot() - - paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p") - count = len(paragraphs) - - except Exception as e: - print(f"Error counting paragraphs in original document: {e}") - - return count - - def validate_insertions(self): - errors = [] - - for xml_file in self.xml_files: - if xml_file.name != "document.xml": - continue - - try: - root = lxml.etree.parse(str(xml_file)).getroot() - namespaces = {"w": self.WORD_2006_NAMESPACE} - - invalid_elements = root.xpath( - ".//w:ins//w:delText[not(ancestor::w:del)]", namespaces=namespaces - ) - - for elem in invalid_elements: - text_preview = ( - repr(elem.text or "")[:50] + "..." - if len(repr(elem.text or "")) > 50 - else repr(elem.text or "") - ) - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: within : {text_preview}" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} insertion validation violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - No w:delText elements within w:ins elements") - return True - - def compare_paragraph_counts(self): - original_count = self.count_paragraphs_in_original() - new_count = self.count_paragraphs_in_unpacked() - - diff = new_count - original_count - diff_str = f"+{diff}" if diff > 0 else str(diff) - print(f"\nParagraphs: {original_count} → {new_count} ({diff_str})") - - def _parse_id_value(self, val: str, base: int = 16) -> int: - return int(val, base) - - def validate_id_constraints(self): - errors = [] - para_id_attr = f"{{{self.W14_NAMESPACE}}}paraId" - durable_id_attr = f"{{{self.W16CID_NAMESPACE}}}durableId" - - for xml_file in self.xml_files: - try: - for elem in lxml.etree.parse(str(xml_file)).iter(): - if val := elem.get(para_id_attr): - if self._parse_id_value(val, base=16) >= 0x80000000: - errors.append( - f" {xml_file.name}:{elem.sourceline}: paraId={val} >= 0x80000000" - ) - - if val := elem.get(durable_id_attr): - if xml_file.name == "numbering.xml": - try: - if self._parse_id_value(val, base=10) >= 0x7FFFFFFF: - errors.append( - f" {xml_file.name}:{elem.sourceline}: " - f"durableId={val} >= 0x7FFFFFFF" - ) - except ValueError: - errors.append( - f" {xml_file.name}:{elem.sourceline}: " - f"durableId={val} must be decimal in numbering.xml" - ) - else: - if self._parse_id_value(val, base=16) >= 0x7FFFFFFF: - errors.append( - f" {xml_file.name}:{elem.sourceline}: " - f"durableId={val} >= 0x7FFFFFFF" - ) - except Exception: - pass - - if errors: - print(f"FAILED - {len(errors)} ID constraint violations:") - for e in errors: - print(e) - elif self.verbose: - print("PASSED - All paraId/durableId values within constraints") - return not errors - - def validate_comment_markers(self): - errors = [] - - document_xml = None - comments_xml = None - for xml_file in self.xml_files: - if xml_file.name == "document.xml" and "word" in str(xml_file): - document_xml = xml_file - elif xml_file.name == "comments.xml": - comments_xml = xml_file - - if not document_xml: - if self.verbose: - print("PASSED - No document.xml found (skipping comment validation)") - return True - - try: - doc_root = lxml.etree.parse(str(document_xml)).getroot() - namespaces = {"w": self.WORD_2006_NAMESPACE} - - range_starts = { - elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id") - for elem in doc_root.xpath( - ".//w:commentRangeStart", namespaces=namespaces - ) - } - range_ends = { - elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id") - for elem in doc_root.xpath( - ".//w:commentRangeEnd", namespaces=namespaces - ) - } - references = { - elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id") - for elem in doc_root.xpath( - ".//w:commentReference", namespaces=namespaces - ) - } - - orphaned_ends = range_ends - range_starts - for comment_id in sorted( - orphaned_ends, key=lambda x: int(x) if x and x.isdigit() else 0 - ): - errors.append( - f' document.xml: commentRangeEnd id="{comment_id}" has no matching commentRangeStart' - ) - - orphaned_starts = range_starts - range_ends - for comment_id in sorted( - orphaned_starts, key=lambda x: int(x) if x and x.isdigit() else 0 - ): - errors.append( - f' document.xml: commentRangeStart id="{comment_id}" has no matching commentRangeEnd' - ) - - comment_ids = set() - if comments_xml and comments_xml.exists(): - comments_root = lxml.etree.parse(str(comments_xml)).getroot() - comment_ids = { - elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id") - for elem in comments_root.xpath( - ".//w:comment", namespaces=namespaces - ) - } - - marker_ids = range_starts | range_ends | references - invalid_refs = marker_ids - comment_ids - for comment_id in sorted( - invalid_refs, key=lambda x: int(x) if x and x.isdigit() else 0 - ): - if comment_id: - errors.append( - f' document.xml: marker id="{comment_id}" references non-existent comment' - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append(f" Error parsing XML: {e}") - - if errors: - print(f"FAILED - {len(errors)} comment marker violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All comment markers properly paired") - return True - - def repair(self) -> int: - repairs = super().repair() - repairs += self.repair_durableId() - return repairs - - def repair_durableId(self) -> int: - repairs = 0 - - for xml_file in self.xml_files: - try: - content = xml_file.read_text(encoding="utf-8") - dom = defusedxml.minidom.parseString(content) - modified = False - - for elem in dom.getElementsByTagName("*"): - if not elem.hasAttribute("w16cid:durableId"): - continue - - durable_id = elem.getAttribute("w16cid:durableId") - needs_repair = False - - if xml_file.name == "numbering.xml": - try: - needs_repair = ( - self._parse_id_value(durable_id, base=10) >= 0x7FFFFFFF - ) - except ValueError: - needs_repair = True - else: - try: - needs_repair = ( - self._parse_id_value(durable_id, base=16) >= 0x7FFFFFFF - ) - except ValueError: - needs_repair = True - - if needs_repair: - value = random.randint(1, 0x7FFFFFFE) - if xml_file.name == "numbering.xml": - new_id = str(value) - else: - new_id = f"{value:08X}" - - elem.setAttribute("w16cid:durableId", new_id) - print( - f" Repaired: {xml_file.name}: durableId {durable_id} → {new_id}" - ) - repairs += 1 - modified = True - - if modified: - xml_file.write_bytes(dom.toxml(encoding="UTF-8")) - - except Exception: - pass - - return repairs - - -if __name__ == "__main__": - raise RuntimeError("This module should not be run directly.") diff --git a/skills/pptx/scripts/office/validators/pptx.py b/skills/pptx/scripts/office/validators/pptx.py deleted file mode 100644 index 09842aa9..00000000 --- a/skills/pptx/scripts/office/validators/pptx.py +++ /dev/null @@ -1,275 +0,0 @@ -""" -Validator for PowerPoint presentation XML files against XSD schemas. -""" - -import re - -from .base import BaseSchemaValidator - - -class PPTXSchemaValidator(BaseSchemaValidator): - - PRESENTATIONML_NAMESPACE = ( - "http://schemas.openxmlformats.org/presentationml/2006/main" - ) - - ELEMENT_RELATIONSHIP_TYPES = { - "sldid": "slide", - "sldmasterid": "slidemaster", - "notesmasterid": "notesmaster", - "sldlayoutid": "slidelayout", - "themeid": "theme", - "tablestyleid": "tablestyles", - } - - def validate(self): - if not self.validate_xml(): - return False - - all_valid = True - if not self.validate_namespaces(): - all_valid = False - - if not self.validate_unique_ids(): - all_valid = False - - if not self.validate_uuid_ids(): - all_valid = False - - if not self.validate_file_references(): - all_valid = False - - if not self.validate_slide_layout_ids(): - all_valid = False - - if not self.validate_content_types(): - all_valid = False - - if not self.validate_against_xsd(): - all_valid = False - - if not self.validate_notes_slide_references(): - all_valid = False - - if not self.validate_all_relationship_ids(): - all_valid = False - - if not self.validate_no_duplicate_slide_layouts(): - all_valid = False - - return all_valid - - def validate_uuid_ids(self): - import lxml.etree - - errors = [] - uuid_pattern = re.compile( - r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$" - ) - - for xml_file in self.xml_files: - try: - root = lxml.etree.parse(str(xml_file)).getroot() - - for elem in root.iter(): - for attr, value in elem.attrib.items(): - attr_name = attr.split("}")[-1].lower() - if attr_name == "id" or attr_name.endswith("id"): - if self._looks_like_uuid(value): - if not uuid_pattern.match(value): - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} UUID ID validation errors:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All UUID-like IDs contain valid hex values") - return True - - def _looks_like_uuid(self, value): - clean_value = value.strip("{}()").replace("-", "") - return len(clean_value) == 32 and all(c.isalnum() for c in clean_value) - - def validate_slide_layout_ids(self): - import lxml.etree - - errors = [] - - slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml")) - - if not slide_masters: - if self.verbose: - print("PASSED - No slide masters found") - return True - - for slide_master in slide_masters: - try: - root = lxml.etree.parse(str(slide_master)).getroot() - - rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels" - - if not rels_file.exists(): - errors.append( - f" {slide_master.relative_to(self.unpacked_dir)}: " - f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}" - ) - continue - - rels_root = lxml.etree.parse(str(rels_file)).getroot() - - valid_layout_rids = set() - for rel in rels_root.findall( - f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" - ): - rel_type = rel.get("Type", "") - if "slideLayout" in rel_type: - valid_layout_rids.add(rel.get("Id")) - - for sld_layout_id in root.findall( - f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId" - ): - r_id = sld_layout_id.get( - f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id" - ) - layout_id = sld_layout_id.get("id") - - if r_id and r_id not in valid_layout_rids: - errors.append( - f" {slide_master.relative_to(self.unpacked_dir)}: " - f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' " - f"references r:id='{r_id}' which is not found in slide layout relationships" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} slide layout ID validation errors:") - for error in errors: - print(error) - print( - "Remove invalid references or add missing slide layouts to the relationships file." - ) - return False - else: - if self.verbose: - print("PASSED - All slide layout IDs reference valid slide layouts") - return True - - def validate_no_duplicate_slide_layouts(self): - import lxml.etree - - errors = [] - slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels")) - - for rels_file in slide_rels_files: - try: - root = lxml.etree.parse(str(rels_file)).getroot() - - layout_rels = [ - rel - for rel in root.findall( - f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" - ) - if "slideLayout" in rel.get("Type", "") - ] - - if len(layout_rels) > 1: - errors.append( - f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references" - ) - - except Exception as e: - errors.append( - f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print("FAILED - Found slides with duplicate slideLayout references:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All slides have exactly one slideLayout reference") - return True - - def validate_notes_slide_references(self): - import lxml.etree - - errors = [] - notes_slide_references = {} - - slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels")) - - if not slide_rels_files: - if self.verbose: - print("PASSED - No slide relationship files found") - return True - - for rels_file in slide_rels_files: - try: - root = lxml.etree.parse(str(rels_file)).getroot() - - for rel in root.findall( - f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" - ): - rel_type = rel.get("Type", "") - if "notesSlide" in rel_type: - target = rel.get("Target", "") - if target: - normalized_target = target.replace("../", "") - - slide_name = rels_file.stem.replace( - ".xml", "" - ) - - if normalized_target not in notes_slide_references: - notes_slide_references[normalized_target] = [] - notes_slide_references[normalized_target].append( - (slide_name, rels_file) - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - for target, references in notes_slide_references.items(): - if len(references) > 1: - slide_names = [ref[0] for ref in references] - errors.append( - f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}" - ) - for slide_name, rels_file in references: - errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}") - - if errors: - print( - f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:" - ) - for error in errors: - print(error) - print("Each slide may optionally have its own slide file.") - return False - else: - if self.verbose: - print("PASSED - All notes slide references are unique") - return True - - -if __name__ == "__main__": - raise RuntimeError("This module should not be run directly.") diff --git a/skills/pptx/scripts/office/validators/redlining.py b/skills/pptx/scripts/office/validators/redlining.py deleted file mode 100644 index 71c81b6b..00000000 --- a/skills/pptx/scripts/office/validators/redlining.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Validator for tracked changes in Word documents. -""" - -import subprocess -import tempfile -import zipfile -from pathlib import Path - - -class RedliningValidator: - - def __init__(self, unpacked_dir, original_docx, verbose=False, author="Claude"): - self.unpacked_dir = Path(unpacked_dir) - self.original_docx = Path(original_docx) - self.verbose = verbose - self.author = author - self.namespaces = { - "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main" - } - - def repair(self) -> int: - return 0 - - def validate(self): - modified_file = self.unpacked_dir / "word" / "document.xml" - if not modified_file.exists(): - print(f"FAILED - Modified document.xml not found at {modified_file}") - return False - - try: - import xml.etree.ElementTree as ET - - tree = ET.parse(modified_file) - root = tree.getroot() - - del_elements = root.findall(".//w:del", self.namespaces) - ins_elements = root.findall(".//w:ins", self.namespaces) - - author_del_elements = [ - elem - for elem in del_elements - if elem.get(f"{{{self.namespaces['w']}}}author") == self.author - ] - author_ins_elements = [ - elem - for elem in ins_elements - if elem.get(f"{{{self.namespaces['w']}}}author") == self.author - ] - - if not author_del_elements and not author_ins_elements: - if self.verbose: - print(f"PASSED - No tracked changes by {self.author} found.") - return True - - except Exception: - pass - - with tempfile.TemporaryDirectory() as temp_dir: - temp_path = Path(temp_dir) - - try: - with zipfile.ZipFile(self.original_docx, "r") as zip_ref: - zip_ref.extractall(temp_path) - except Exception as e: - print(f"FAILED - Error unpacking original docx: {e}") - return False - - original_file = temp_path / "word" / "document.xml" - if not original_file.exists(): - print( - f"FAILED - Original document.xml not found in {self.original_docx}" - ) - return False - - try: - import xml.etree.ElementTree as ET - - modified_tree = ET.parse(modified_file) - modified_root = modified_tree.getroot() - original_tree = ET.parse(original_file) - original_root = original_tree.getroot() - except ET.ParseError as e: - print(f"FAILED - Error parsing XML files: {e}") - return False - - self._remove_author_tracked_changes(original_root) - self._remove_author_tracked_changes(modified_root) - - modified_text = self._extract_text_content(modified_root) - original_text = self._extract_text_content(original_root) - - if modified_text != original_text: - error_message = self._generate_detailed_diff( - original_text, modified_text - ) - print(error_message) - return False - - if self.verbose: - print(f"PASSED - All changes by {self.author} are properly tracked") - return True - - def _generate_detailed_diff(self, original_text, modified_text): - error_parts = [ - f"FAILED - Document text doesn't match after removing {self.author}'s tracked changes", - "", - "Likely causes:", - " 1. Modified text inside another author's or tags", - " 2. Made edits without proper tracked changes", - " 3. Didn't nest inside when deleting another's insertion", - "", - "For pre-redlined documents, use correct patterns:", - " - To reject another's INSERTION: Nest inside their ", - " - To restore another's DELETION: Add new AFTER their ", - "", - ] - - git_diff = self._get_git_word_diff(original_text, modified_text) - if git_diff: - error_parts.extend(["Differences:", "============", git_diff]) - else: - error_parts.append("Unable to generate word diff (git not available)") - - return "\n".join(error_parts) - - def _get_git_word_diff(self, original_text, modified_text): - try: - with tempfile.TemporaryDirectory() as temp_dir: - temp_path = Path(temp_dir) - - original_file = temp_path / "original.txt" - modified_file = temp_path / "modified.txt" - - original_file.write_text(original_text, encoding="utf-8") - modified_file.write_text(modified_text, encoding="utf-8") - - result = subprocess.run( - [ - "git", - "diff", - "--word-diff=plain", - "--word-diff-regex=.", - "-U0", - "--no-index", - str(original_file), - str(modified_file), - ], - capture_output=True, - text=True, - ) - - if result.stdout.strip(): - lines = result.stdout.split("\n") - content_lines = [] - in_content = False - for line in lines: - if line.startswith("@@"): - in_content = True - continue - if in_content and line.strip(): - content_lines.append(line) - - if content_lines: - return "\n".join(content_lines) - - result = subprocess.run( - [ - "git", - "diff", - "--word-diff=plain", - "-U0", - "--no-index", - str(original_file), - str(modified_file), - ], - capture_output=True, - text=True, - ) - - if result.stdout.strip(): - lines = result.stdout.split("\n") - content_lines = [] - in_content = False - for line in lines: - if line.startswith("@@"): - in_content = True - continue - if in_content and line.strip(): - content_lines.append(line) - return "\n".join(content_lines) - - except (subprocess.CalledProcessError, FileNotFoundError, Exception): - pass - - return None - - def _remove_author_tracked_changes(self, root): - ins_tag = f"{{{self.namespaces['w']}}}ins" - del_tag = f"{{{self.namespaces['w']}}}del" - author_attr = f"{{{self.namespaces['w']}}}author" - - for parent in root.iter(): - to_remove = [] - for child in parent: - if child.tag == ins_tag and child.get(author_attr) == self.author: - to_remove.append(child) - for elem in to_remove: - parent.remove(elem) - - deltext_tag = f"{{{self.namespaces['w']}}}delText" - t_tag = f"{{{self.namespaces['w']}}}t" - - for parent in root.iter(): - to_process = [] - for child in parent: - if child.tag == del_tag and child.get(author_attr) == self.author: - to_process.append((child, list(parent).index(child))) - - for del_elem, del_index in reversed(to_process): - for elem in del_elem.iter(): - if elem.tag == deltext_tag: - elem.tag = t_tag - - for child in reversed(list(del_elem)): - parent.insert(del_index, child) - parent.remove(del_elem) - - def _extract_text_content(self, root): - p_tag = f"{{{self.namespaces['w']}}}p" - t_tag = f"{{{self.namespaces['w']}}}t" - - paragraphs = [] - for p_elem in root.findall(f".//{p_tag}"): - text_parts = [] - for t_elem in p_elem.findall(f".//{t_tag}"): - if t_elem.text: - text_parts.append(t_elem.text) - paragraph_text = "".join(text_parts) - if paragraph_text: - paragraphs.append(paragraph_text) - - return "\n".join(paragraphs) - - -if __name__ == "__main__": - raise RuntimeError("This module should not be run directly.") diff --git a/skills/xlsx/scripts/office b/skills/xlsx/scripts/office new file mode 120000 index 00000000..ef6971dd --- /dev/null +++ b/skills/xlsx/scripts/office @@ -0,0 +1 @@ +../../_shared/office \ No newline at end of file diff --git a/skills/xlsx/scripts/office/helpers/__init__.py b/skills/xlsx/scripts/office/helpers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/skills/xlsx/scripts/office/helpers/merge_runs.py b/skills/xlsx/scripts/office/helpers/merge_runs.py deleted file mode 100644 index ad7c25ee..00000000 --- a/skills/xlsx/scripts/office/helpers/merge_runs.py +++ /dev/null @@ -1,199 +0,0 @@ -"""Merge adjacent runs with identical formatting in DOCX. - -Merges adjacent elements that have identical properties. -Works on runs in paragraphs and inside tracked changes (, ). - -Also: -- Removes rsid attributes from runs (revision metadata that doesn't affect rendering) -- Removes proofErr elements (spell/grammar markers that block merging) -""" - -from pathlib import Path - -import defusedxml.minidom - - -def merge_runs(input_dir: str) -> tuple[int, str]: - doc_xml = Path(input_dir) / "word" / "document.xml" - - if not doc_xml.exists(): - return 0, f"Error: {doc_xml} not found" - - try: - dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8")) - root = dom.documentElement - - _remove_elements(root, "proofErr") - _strip_run_rsid_attrs(root) - - containers = {run.parentNode for run in _find_elements(root, "r")} - - merge_count = 0 - for container in containers: - merge_count += _merge_runs_in(container) - - doc_xml.write_bytes(dom.toxml(encoding="UTF-8")) - return merge_count, f"Merged {merge_count} runs" - - except Exception as e: - return 0, f"Error: {e}" - - - - -def _find_elements(root, tag: str) -> list: - results = [] - - def traverse(node): - if node.nodeType == node.ELEMENT_NODE: - name = node.localName or node.tagName - if name == tag or name.endswith(f":{tag}"): - results.append(node) - for child in node.childNodes: - traverse(child) - - traverse(root) - return results - - -def _get_child(parent, tag: str): - for child in parent.childNodes: - if child.nodeType == child.ELEMENT_NODE: - name = child.localName or child.tagName - if name == tag or name.endswith(f":{tag}"): - return child - return None - - -def _get_children(parent, tag: str) -> list: - results = [] - for child in parent.childNodes: - if child.nodeType == child.ELEMENT_NODE: - name = child.localName or child.tagName - if name == tag or name.endswith(f":{tag}"): - results.append(child) - return results - - -def _is_adjacent(elem1, elem2) -> bool: - node = elem1.nextSibling - while node: - if node == elem2: - return True - if node.nodeType == node.ELEMENT_NODE: - return False - if node.nodeType == node.TEXT_NODE and node.data.strip(): - return False - node = node.nextSibling - return False - - - - -def _remove_elements(root, tag: str): - for elem in _find_elements(root, tag): - if elem.parentNode: - elem.parentNode.removeChild(elem) - - -def _strip_run_rsid_attrs(root): - for run in _find_elements(root, "r"): - for attr in list(run.attributes.values()): - if "rsid" in attr.name.lower(): - run.removeAttribute(attr.name) - - - - -def _merge_runs_in(container) -> int: - merge_count = 0 - run = _first_child_run(container) - - while run: - while True: - next_elem = _next_element_sibling(run) - if next_elem and _is_run(next_elem) and _can_merge(run, next_elem): - _merge_run_content(run, next_elem) - container.removeChild(next_elem) - merge_count += 1 - else: - break - - _consolidate_text(run) - run = _next_sibling_run(run) - - return merge_count - - -def _first_child_run(container): - for child in container.childNodes: - if child.nodeType == child.ELEMENT_NODE and _is_run(child): - return child - return None - - -def _next_element_sibling(node): - sibling = node.nextSibling - while sibling: - if sibling.nodeType == sibling.ELEMENT_NODE: - return sibling - sibling = sibling.nextSibling - return None - - -def _next_sibling_run(node): - sibling = node.nextSibling - while sibling: - if sibling.nodeType == sibling.ELEMENT_NODE: - if _is_run(sibling): - return sibling - sibling = sibling.nextSibling - return None - - -def _is_run(node) -> bool: - name = node.localName or node.tagName - return name == "r" or name.endswith(":r") - - -def _can_merge(run1, run2) -> bool: - rpr1 = _get_child(run1, "rPr") - rpr2 = _get_child(run2, "rPr") - - if (rpr1 is None) != (rpr2 is None): - return False - if rpr1 is None: - return True - return rpr1.toxml() == rpr2.toxml() - - -def _merge_run_content(target, source): - for child in list(source.childNodes): - if child.nodeType == child.ELEMENT_NODE: - name = child.localName or child.tagName - if name != "rPr" and not name.endswith(":rPr"): - target.appendChild(child) - - -def _consolidate_text(run): - t_elements = _get_children(run, "t") - - for i in range(len(t_elements) - 1, 0, -1): - curr, prev = t_elements[i], t_elements[i - 1] - - if _is_adjacent(prev, curr): - prev_text = prev.firstChild.data if prev.firstChild else "" - curr_text = curr.firstChild.data if curr.firstChild else "" - merged = prev_text + curr_text - - if prev.firstChild: - prev.firstChild.data = merged - else: - prev.appendChild(run.ownerDocument.createTextNode(merged)) - - if merged.startswith(" ") or merged.endswith(" "): - prev.setAttribute("xml:space", "preserve") - elif prev.hasAttribute("xml:space"): - prev.removeAttribute("xml:space") - - run.removeChild(curr) diff --git a/skills/xlsx/scripts/office/helpers/simplify_redlines.py b/skills/xlsx/scripts/office/helpers/simplify_redlines.py deleted file mode 100644 index db963bb9..00000000 --- a/skills/xlsx/scripts/office/helpers/simplify_redlines.py +++ /dev/null @@ -1,197 +0,0 @@ -"""Simplify tracked changes by merging adjacent w:ins or w:del elements. - -Merges adjacent elements from the same author into a single element. -Same for elements. This makes heavily-redlined documents easier to -work with by reducing the number of tracked change wrappers. - -Rules: -- Only merges w:ins with w:ins, w:del with w:del (same element type) -- Only merges if same author (ignores timestamp differences) -- Only merges if truly adjacent (only whitespace between them) -""" - -import xml.etree.ElementTree as ET -import zipfile -from pathlib import Path - -import defusedxml.minidom - -WORD_NS = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" - - -def simplify_redlines(input_dir: str) -> tuple[int, str]: - doc_xml = Path(input_dir) / "word" / "document.xml" - - if not doc_xml.exists(): - return 0, f"Error: {doc_xml} not found" - - try: - dom = defusedxml.minidom.parseString(doc_xml.read_text(encoding="utf-8")) - root = dom.documentElement - - merge_count = 0 - - containers = _find_elements(root, "p") + _find_elements(root, "tc") - - for container in containers: - merge_count += _merge_tracked_changes_in(container, "ins") - merge_count += _merge_tracked_changes_in(container, "del") - - doc_xml.write_bytes(dom.toxml(encoding="UTF-8")) - return merge_count, f"Simplified {merge_count} tracked changes" - - except Exception as e: - return 0, f"Error: {e}" - - -def _merge_tracked_changes_in(container, tag: str) -> int: - merge_count = 0 - - tracked = [ - child - for child in container.childNodes - if child.nodeType == child.ELEMENT_NODE and _is_element(child, tag) - ] - - if len(tracked) < 2: - return 0 - - i = 0 - while i < len(tracked) - 1: - curr = tracked[i] - next_elem = tracked[i + 1] - - if _can_merge_tracked(curr, next_elem): - _merge_tracked_content(curr, next_elem) - container.removeChild(next_elem) - tracked.pop(i + 1) - merge_count += 1 - else: - i += 1 - - return merge_count - - -def _is_element(node, tag: str) -> bool: - name = node.localName or node.tagName - return name == tag or name.endswith(f":{tag}") - - -def _get_author(elem) -> str: - author = elem.getAttribute("w:author") - if not author: - for attr in elem.attributes.values(): - if attr.localName == "author" or attr.name.endswith(":author"): - return attr.value - return author - - -def _can_merge_tracked(elem1, elem2) -> bool: - if _get_author(elem1) != _get_author(elem2): - return False - - node = elem1.nextSibling - while node and node != elem2: - if node.nodeType == node.ELEMENT_NODE: - return False - if node.nodeType == node.TEXT_NODE and node.data.strip(): - return False - node = node.nextSibling - - return True - - -def _merge_tracked_content(target, source): - while source.firstChild: - child = source.firstChild - source.removeChild(child) - target.appendChild(child) - - -def _find_elements(root, tag: str) -> list: - results = [] - - def traverse(node): - if node.nodeType == node.ELEMENT_NODE: - name = node.localName or node.tagName - if name == tag or name.endswith(f":{tag}"): - results.append(node) - for child in node.childNodes: - traverse(child) - - traverse(root) - return results - - -def get_tracked_change_authors(doc_xml_path: Path) -> dict[str, int]: - if not doc_xml_path.exists(): - return {} - - try: - tree = ET.parse(doc_xml_path) - root = tree.getroot() - except ET.ParseError: - return {} - - namespaces = {"w": WORD_NS} - author_attr = f"{{{WORD_NS}}}author" - - authors: dict[str, int] = {} - for tag in ["ins", "del"]: - for elem in root.findall(f".//w:{tag}", namespaces): - author = elem.get(author_attr) - if author: - authors[author] = authors.get(author, 0) + 1 - - return authors - - -def _get_authors_from_docx(docx_path: Path) -> dict[str, int]: - try: - with zipfile.ZipFile(docx_path, "r") as zf: - if "word/document.xml" not in zf.namelist(): - return {} - with zf.open("word/document.xml") as f: - tree = ET.parse(f) - root = tree.getroot() - - namespaces = {"w": WORD_NS} - author_attr = f"{{{WORD_NS}}}author" - - authors: dict[str, int] = {} - for tag in ["ins", "del"]: - for elem in root.findall(f".//w:{tag}", namespaces): - author = elem.get(author_attr) - if author: - authors[author] = authors.get(author, 0) + 1 - return authors - except (zipfile.BadZipFile, ET.ParseError): - return {} - - -def infer_author(modified_dir: Path, original_docx: Path, default: str = "Claude") -> str: - modified_xml = modified_dir / "word" / "document.xml" - modified_authors = get_tracked_change_authors(modified_xml) - - if not modified_authors: - return default - - original_authors = _get_authors_from_docx(original_docx) - - new_changes: dict[str, int] = {} - for author, count in modified_authors.items(): - original_count = original_authors.get(author, 0) - diff = count - original_count - if diff > 0: - new_changes[author] = diff - - if not new_changes: - return default - - if len(new_changes) == 1: - return next(iter(new_changes)) - - raise ValueError( - f"Multiple authors added new changes: {new_changes}. " - "Cannot infer which author to validate." - ) diff --git a/skills/xlsx/scripts/office/pack.py b/skills/xlsx/scripts/office/pack.py deleted file mode 100755 index db29ed8b..00000000 --- a/skills/xlsx/scripts/office/pack.py +++ /dev/null @@ -1,159 +0,0 @@ -"""Pack a directory into a DOCX, PPTX, or XLSX file. - -Validates with auto-repair, condenses XML formatting, and creates the Office file. - -Usage: - python pack.py [--original ] [--validate true|false] - -Examples: - python pack.py unpacked/ output.docx --original input.docx - python pack.py unpacked/ output.pptx --validate false -""" - -import argparse -import sys -import shutil -import tempfile -import zipfile -from pathlib import Path - -import defusedxml.minidom - -from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator - -def pack( - input_directory: str, - output_file: str, - original_file: str | None = None, - validate: bool = True, - infer_author_func=None, -) -> tuple[None, str]: - input_dir = Path(input_directory) - output_path = Path(output_file) - suffix = output_path.suffix.lower() - - if not input_dir.is_dir(): - return None, f"Error: {input_dir} is not a directory" - - if suffix not in {".docx", ".pptx", ".xlsx"}: - return None, f"Error: {output_file} must be a .docx, .pptx, or .xlsx file" - - if validate and original_file: - original_path = Path(original_file) - if original_path.exists(): - success, output = _run_validation( - input_dir, original_path, suffix, infer_author_func - ) - if output: - print(output) - if not success: - return None, f"Error: Validation failed for {input_dir}" - - with tempfile.TemporaryDirectory() as temp_dir: - temp_content_dir = Path(temp_dir) / "content" - shutil.copytree(input_dir, temp_content_dir) - - for pattern in ["*.xml", "*.rels"]: - for xml_file in temp_content_dir.rglob(pattern): - _condense_xml(xml_file) - - output_path.parent.mkdir(parents=True, exist_ok=True) - with zipfile.ZipFile(output_path, "w", zipfile.ZIP_DEFLATED) as zf: - for f in temp_content_dir.rglob("*"): - if f.is_file(): - zf.write(f, f.relative_to(temp_content_dir)) - - return None, f"Successfully packed {input_dir} to {output_file}" - - -def _run_validation( - unpacked_dir: Path, - original_file: Path, - suffix: str, - infer_author_func=None, -) -> tuple[bool, str | None]: - output_lines = [] - validators = [] - - if suffix == ".docx": - author = "Claude" - if infer_author_func: - try: - author = infer_author_func(unpacked_dir, original_file) - except ValueError as e: - print(f"Warning: {e} Using default author 'Claude'.", file=sys.stderr) - - validators = [ - DOCXSchemaValidator(unpacked_dir, original_file), - RedliningValidator(unpacked_dir, original_file, author=author), - ] - elif suffix == ".pptx": - validators = [PPTXSchemaValidator(unpacked_dir, original_file)] - - if not validators: - return True, None - - total_repairs = sum(v.repair() for v in validators) - if total_repairs: - output_lines.append(f"Auto-repaired {total_repairs} issue(s)") - - success = all(v.validate() for v in validators) - - if success: - output_lines.append("All validations PASSED!") - - return success, "\n".join(output_lines) if output_lines else None - - -def _condense_xml(xml_file: Path) -> None: - try: - with open(xml_file, encoding="utf-8") as f: - dom = defusedxml.minidom.parse(f) - - for element in dom.getElementsByTagName("*"): - if element.tagName.endswith(":t"): - continue - - for child in list(element.childNodes): - if ( - child.nodeType == child.TEXT_NODE - and child.nodeValue - and child.nodeValue.strip() == "" - ) or child.nodeType == child.COMMENT_NODE: - element.removeChild(child) - - xml_file.write_bytes(dom.toxml(encoding="UTF-8")) - except Exception as e: - print(f"ERROR: Failed to parse {xml_file.name}: {e}", file=sys.stderr) - raise - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Pack a directory into a DOCX, PPTX, or XLSX file" - ) - parser.add_argument("input_directory", help="Unpacked Office document directory") - parser.add_argument("output_file", help="Output Office file (.docx/.pptx/.xlsx)") - parser.add_argument( - "--original", - help="Original file for validation comparison", - ) - parser.add_argument( - "--validate", - type=lambda x: x.lower() == "true", - default=True, - metavar="true|false", - help="Run validation with auto-repair (default: true)", - ) - args = parser.parse_args() - - _, message = pack( - args.input_directory, - args.output_file, - original_file=args.original, - validate=args.validate, - ) - print(message) - - if "Error" in message: - sys.exit(1) diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd deleted file mode 100644 index 6454ef9a..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +++ /dev/null @@ -1,1499 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd deleted file mode 100644 index afa4f463..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd deleted file mode 100644 index 64e66b8a..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +++ /dev/null @@ -1,1085 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd deleted file mode 100644 index 687eea82..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd deleted file mode 100644 index 6ac81b06..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-main.xsd +++ /dev/null @@ -1,3081 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd deleted file mode 100644 index 1dbf0514..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd deleted file mode 100644 index f1af17db..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +++ /dev/null @@ -1,185 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd deleted file mode 100644 index 0a185ab6..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +++ /dev/null @@ -1,287 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd deleted file mode 100644 index 14ef4888..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/pml.xsd +++ /dev/null @@ -1,1676 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd deleted file mode 100644 index c20f3bf1..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd deleted file mode 100644 index ac602522..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +++ /dev/null @@ -1,144 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd deleted file mode 100644 index 424b8ba8..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +++ /dev/null @@ -1,174 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd deleted file mode 100644 index 2bddce29..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd deleted file mode 100644 index 8a8c18ba..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd deleted file mode 100644 index 5c42706a..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd deleted file mode 100644 index 853c341c..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd deleted file mode 100644 index da835ee8..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +++ /dev/null @@ -1,195 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd deleted file mode 100644 index 87ad2658..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-math.xsd +++ /dev/null @@ -1,582 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd deleted file mode 100644 index 9e86f1b2..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd deleted file mode 100644 index d0be42e7..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/sml.xsd +++ /dev/null @@ -1,4439 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd deleted file mode 100644 index 8821dd18..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-main.xsd +++ /dev/null @@ -1,570 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd deleted file mode 100644 index ca2575c7..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +++ /dev/null @@ -1,509 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd deleted file mode 100644 index dd079e60..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd deleted file mode 100644 index 3dd6cf62..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd deleted file mode 100644 index f1041e34..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +++ /dev/null @@ -1,96 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd deleted file mode 100644 index 9c5b7a63..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/wml.xsd +++ /dev/null @@ -1,3646 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd b/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd deleted file mode 100644 index 0f13678d..00000000 --- a/skills/xlsx/scripts/office/schemas/ISO-IEC29500-4_2016/xml.xsd +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - See http://www.w3.org/XML/1998/namespace.html and - http://www.w3.org/TR/REC-xml for information about this namespace. - - This schema document describes the XML namespace, in a form - suitable for import by other schema documents. - - Note that local names in this namespace are intended to be defined - only by the World Wide Web Consortium or its subgroups. The - following names are currently defined in this namespace and should - not be used with conflicting semantics by any Working Group, - specification, or document instance: - - base (as an attribute name): denotes an attribute whose value - provides a URI to be used as the base for interpreting any - relative URIs in the scope of the element on which it - appears; its value is inherited. This name is reserved - by virtue of its definition in the XML Base specification. - - lang (as an attribute name): denotes an attribute whose value - is a language code for the natural language of the content of - any element; its value is inherited. This name is reserved - by virtue of its definition in the XML specification. - - space (as an attribute name): denotes an attribute whose - value is a keyword indicating what whitespace processing - discipline is intended for the content of the element; its - value is inherited. This name is reserved by virtue of its - definition in the XML specification. - - Father (in any context at all): denotes Jon Bosak, the chair of - the original XML Working Group. This name is reserved by - the following decision of the W3C XML Plenary and - XML Coordination groups: - - In appreciation for his vision, leadership and dedication - the W3C XML Plenary on this 10th day of February, 2000 - reserves for Jon Bosak in perpetuity the XML name - xml:Father - - - - - This schema defines attributes and an attribute group - suitable for use by - schemas wishing to allow xml:base, xml:lang or xml:space attributes - on elements they define. - - To enable this, such a schema must import this schema - for the XML namespace, e.g. as follows: - <schema . . .> - . . . - <import namespace="http://www.w3.org/XML/1998/namespace" - schemaLocation="http://www.w3.org/2001/03/xml.xsd"/> - - Subsequently, qualified reference to any of the attributes - or the group defined below will have the desired effect, e.g. - - <type . . .> - . . . - <attributeGroup ref="xml:specialAttrs"/> - - will define a type which will schema-validate an instance - element with any of those attributes - - - - In keeping with the XML Schema WG's standard versioning - policy, this schema document will persist at - http://www.w3.org/2001/03/xml.xsd. - At the date of issue it can also be found at - http://www.w3.org/2001/xml.xsd. - The schema document at that URI may however change in the future, - in order to remain compatible with the latest version of XML Schema - itself. In other words, if the XML Schema namespace changes, the version - of this document at - http://www.w3.org/2001/xml.xsd will change - accordingly; the version at - http://www.w3.org/2001/03/xml.xsd will not change. - - - - - - In due course, we should install the relevant ISO 2- and 3-letter - codes as the enumerated possible values . . . - - - - - - - - - - - - - - - See http://www.w3.org/TR/xmlbase/ for - information about this attribute. - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd b/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd deleted file mode 100644 index a6de9d27..00000000 --- a/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-contentTypes.xsd +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd b/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd deleted file mode 100644 index 10e978b6..00000000 --- a/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-coreProperties.xsd +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd b/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd deleted file mode 100644 index 4248bf7a..00000000 --- a/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-digSig.xsd +++ /dev/null @@ -1,49 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd b/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd deleted file mode 100644 index 56497467..00000000 --- a/skills/xlsx/scripts/office/schemas/ecma/fouth-edition/opc-relationships.xsd +++ /dev/null @@ -1,33 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/mce/mc.xsd b/skills/xlsx/scripts/office/schemas/mce/mc.xsd deleted file mode 100644 index ef725457..00000000 --- a/skills/xlsx/scripts/office/schemas/mce/mc.xsd +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd b/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd deleted file mode 100644 index f65f7777..00000000 --- a/skills/xlsx/scripts/office/schemas/microsoft/wml-2010.xsd +++ /dev/null @@ -1,560 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd b/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd deleted file mode 100644 index 6b00755a..00000000 --- a/skills/xlsx/scripts/office/schemas/microsoft/wml-2012.xsd +++ /dev/null @@ -1,67 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd b/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd deleted file mode 100644 index f321d333..00000000 --- a/skills/xlsx/scripts/office/schemas/microsoft/wml-2018.xsd +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd b/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd deleted file mode 100644 index 364c6a9b..00000000 --- a/skills/xlsx/scripts/office/schemas/microsoft/wml-cex-2018.xsd +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd b/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd deleted file mode 100644 index fed9d15b..00000000 --- a/skills/xlsx/scripts/office/schemas/microsoft/wml-cid-2016.xsd +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd b/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd deleted file mode 100644 index 680cf154..00000000 --- a/skills/xlsx/scripts/office/schemas/microsoft/wml-sdtdatahash-2020.xsd +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd b/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd deleted file mode 100644 index 89ada908..00000000 --- a/skills/xlsx/scripts/office/schemas/microsoft/wml-symex-2015.xsd +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/skills/xlsx/scripts/office/soffice.py b/skills/xlsx/scripts/office/soffice.py deleted file mode 100644 index c7f7e328..00000000 --- a/skills/xlsx/scripts/office/soffice.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Helper for running LibreOffice (soffice) in environments where AF_UNIX -sockets may be blocked (e.g., sandboxed VMs). Detects the restriction -at runtime and applies an LD_PRELOAD shim if needed. - -Usage: - from office.soffice import run_soffice, get_soffice_env - - # Option 1 – run soffice directly - result = run_soffice(["--headless", "--convert-to", "pdf", "input.docx"]) - - # Option 2 – get env dict for your own subprocess calls - env = get_soffice_env() - subprocess.run(["soffice", ...], env=env) -""" - -import os -import socket -import subprocess -import tempfile -from pathlib import Path - - -def get_soffice_env() -> dict: - env = os.environ.copy() - env["SAL_USE_VCLPLUGIN"] = "svp" - - if _needs_shim(): - shim = _ensure_shim() - env["LD_PRELOAD"] = str(shim) - - return env - - -def run_soffice(args: list[str], **kwargs) -> subprocess.CompletedProcess: - env = get_soffice_env() - return subprocess.run(["soffice"] + args, env=env, **kwargs) - - - -_SHIM_SO = Path(tempfile.gettempdir()) / "lo_socket_shim.so" - - -def _needs_shim() -> bool: - try: - s = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - s.close() - return False - except OSError: - return True - - -def _ensure_shim() -> Path: - if _SHIM_SO.exists(): - return _SHIM_SO - - src = Path(tempfile.gettempdir()) / "lo_socket_shim.c" - src.write_text(_SHIM_SOURCE) - subprocess.run( - ["gcc", "-shared", "-fPIC", "-o", str(_SHIM_SO), str(src), "-ldl"], - check=True, - capture_output=True, - ) - src.unlink() - return _SHIM_SO - - - -_SHIM_SOURCE = r""" -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include - -static int (*real_socket)(int, int, int); -static int (*real_socketpair)(int, int, int, int[2]); -static int (*real_listen)(int, int); -static int (*real_accept)(int, struct sockaddr *, socklen_t *); -static int (*real_close)(int); -static int (*real_read)(int, void *, size_t); - -/* Per-FD bookkeeping (FDs >= 1024 are passed through unshimmed). */ -static int is_shimmed[1024]; -static int peer_of[1024]; -static int wake_r[1024]; /* accept() blocks reading this */ -static int wake_w[1024]; /* close() writes to this */ -static int listener_fd = -1; /* FD that received listen() */ - -__attribute__((constructor)) -static void init(void) { - real_socket = dlsym(RTLD_NEXT, "socket"); - real_socketpair = dlsym(RTLD_NEXT, "socketpair"); - real_listen = dlsym(RTLD_NEXT, "listen"); - real_accept = dlsym(RTLD_NEXT, "accept"); - real_close = dlsym(RTLD_NEXT, "close"); - real_read = dlsym(RTLD_NEXT, "read"); - for (int i = 0; i < 1024; i++) { - peer_of[i] = -1; - wake_r[i] = -1; - wake_w[i] = -1; - } -} - -/* ---- socket ---------------------------------------------------------- */ -int socket(int domain, int type, int protocol) { - if (domain == AF_UNIX) { - int fd = real_socket(domain, type, protocol); - if (fd >= 0) return fd; - /* socket(AF_UNIX) blocked – fall back to socketpair(). */ - int sv[2]; - if (real_socketpair(domain, type, protocol, sv) == 0) { - if (sv[0] >= 0 && sv[0] < 1024) { - is_shimmed[sv[0]] = 1; - peer_of[sv[0]] = sv[1]; - int wp[2]; - if (pipe(wp) == 0) { - wake_r[sv[0]] = wp[0]; - wake_w[sv[0]] = wp[1]; - } - } - return sv[0]; - } - errno = EPERM; - return -1; - } - return real_socket(domain, type, protocol); -} - -/* ---- listen ---------------------------------------------------------- */ -int listen(int sockfd, int backlog) { - if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) { - listener_fd = sockfd; - return 0; - } - return real_listen(sockfd, backlog); -} - -/* ---- accept ---------------------------------------------------------- */ -int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { - if (sockfd >= 0 && sockfd < 1024 && is_shimmed[sockfd]) { - /* Block until close() writes to the wake pipe. */ - if (wake_r[sockfd] >= 0) { - char buf; - real_read(wake_r[sockfd], &buf, 1); - } - errno = ECONNABORTED; - return -1; - } - return real_accept(sockfd, addr, addrlen); -} - -/* ---- close ----------------------------------------------------------- */ -int close(int fd) { - if (fd >= 0 && fd < 1024 && is_shimmed[fd]) { - int was_listener = (fd == listener_fd); - is_shimmed[fd] = 0; - - if (wake_w[fd] >= 0) { /* unblock accept() */ - char c = 0; - write(wake_w[fd], &c, 1); - real_close(wake_w[fd]); - wake_w[fd] = -1; - } - if (wake_r[fd] >= 0) { real_close(wake_r[fd]); wake_r[fd] = -1; } - if (peer_of[fd] >= 0) { real_close(peer_of[fd]); peer_of[fd] = -1; } - - if (was_listener) - _exit(0); /* conversion done – exit */ - } - return real_close(fd); -} -""" - - - -if __name__ == "__main__": - import sys - result = run_soffice(sys.argv[1:]) - sys.exit(result.returncode) diff --git a/skills/xlsx/scripts/office/unpack.py b/skills/xlsx/scripts/office/unpack.py deleted file mode 100755 index 00152533..00000000 --- a/skills/xlsx/scripts/office/unpack.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Unpack Office files (DOCX, PPTX, XLSX) for editing. - -Extracts the ZIP archive, pretty-prints XML files, and optionally: -- Merges adjacent runs with identical formatting (DOCX only) -- Simplifies adjacent tracked changes from same author (DOCX only) - -Usage: - python unpack.py [options] - -Examples: - python unpack.py document.docx unpacked/ - python unpack.py presentation.pptx unpacked/ - python unpack.py document.docx unpacked/ --merge-runs false -""" - -import argparse -import sys -import zipfile -from pathlib import Path - -import defusedxml.minidom - -from helpers.merge_runs import merge_runs as do_merge_runs -from helpers.simplify_redlines import simplify_redlines as do_simplify_redlines - -SMART_QUOTE_REPLACEMENTS = { - "\u201c": "“", - "\u201d": "”", - "\u2018": "‘", - "\u2019": "’", -} - - -def unpack( - input_file: str, - output_directory: str, - merge_runs: bool = True, - simplify_redlines: bool = True, -) -> tuple[None, str]: - input_path = Path(input_file) - output_path = Path(output_directory) - suffix = input_path.suffix.lower() - - if not input_path.exists(): - return None, f"Error: {input_file} does not exist" - - if suffix not in {".docx", ".pptx", ".xlsx"}: - return None, f"Error: {input_file} must be a .docx, .pptx, or .xlsx file" - - try: - output_path.mkdir(parents=True, exist_ok=True) - - with zipfile.ZipFile(input_path, "r") as zf: - zf.extractall(output_path) - - xml_files = list(output_path.rglob("*.xml")) + list(output_path.rglob("*.rels")) - for xml_file in xml_files: - _pretty_print_xml(xml_file) - - message = f"Unpacked {input_file} ({len(xml_files)} XML files)" - - if suffix == ".docx": - if simplify_redlines: - simplify_count, _ = do_simplify_redlines(str(output_path)) - message += f", simplified {simplify_count} tracked changes" - - if merge_runs: - merge_count, _ = do_merge_runs(str(output_path)) - message += f", merged {merge_count} runs" - - for xml_file in xml_files: - _escape_smart_quotes(xml_file) - - return None, message - - except zipfile.BadZipFile: - return None, f"Error: {input_file} is not a valid Office file" - except Exception as e: - return None, f"Error unpacking: {e}" - - -def _pretty_print_xml(xml_file: Path) -> None: - try: - content = xml_file.read_text(encoding="utf-8") - dom = defusedxml.minidom.parseString(content) - xml_file.write_bytes(dom.toprettyxml(indent=" ", encoding="utf-8")) - except Exception: - pass - - -def _escape_smart_quotes(xml_file: Path) -> None: - try: - content = xml_file.read_text(encoding="utf-8") - for char, entity in SMART_QUOTE_REPLACEMENTS.items(): - content = content.replace(char, entity) - xml_file.write_text(content, encoding="utf-8") - except Exception: - pass - - -if __name__ == "__main__": - parser = argparse.ArgumentParser( - description="Unpack an Office file (DOCX, PPTX, XLSX) for editing" - ) - parser.add_argument("input_file", help="Office file to unpack") - parser.add_argument("output_directory", help="Output directory") - parser.add_argument( - "--merge-runs", - type=lambda x: x.lower() == "true", - default=True, - metavar="true|false", - help="Merge adjacent runs with identical formatting (DOCX only, default: true)", - ) - parser.add_argument( - "--simplify-redlines", - type=lambda x: x.lower() == "true", - default=True, - metavar="true|false", - help="Merge adjacent tracked changes from same author (DOCX only, default: true)", - ) - args = parser.parse_args() - - _, message = unpack( - args.input_file, - args.output_directory, - merge_runs=args.merge_runs, - simplify_redlines=args.simplify_redlines, - ) - print(message) - - if "Error" in message: - sys.exit(1) diff --git a/skills/xlsx/scripts/office/validate.py b/skills/xlsx/scripts/office/validate.py deleted file mode 100755 index 03b01f6e..00000000 --- a/skills/xlsx/scripts/office/validate.py +++ /dev/null @@ -1,111 +0,0 @@ -""" -Command line tool to validate Office document XML files against XSD schemas and tracked changes. - -Usage: - python validate.py [--original ] [--auto-repair] [--author NAME] - -The first argument can be either: -- An unpacked directory containing the Office document XML files -- A packed Office file (.docx/.pptx/.xlsx) which will be unpacked to a temp directory - -Auto-repair fixes: -- paraId/durableId values that exceed OOXML limits -- Missing xml:space="preserve" on w:t elements with whitespace -""" - -import argparse -import sys -import tempfile -import zipfile -from pathlib import Path - -from validators import DOCXSchemaValidator, PPTXSchemaValidator, RedliningValidator - - -def main(): - parser = argparse.ArgumentParser(description="Validate Office document XML files") - parser.add_argument( - "path", - help="Path to unpacked directory or packed Office file (.docx/.pptx/.xlsx)", - ) - parser.add_argument( - "--original", - required=False, - default=None, - help="Path to original file (.docx/.pptx/.xlsx). If omitted, all XSD errors are reported and redlining validation is skipped.", - ) - parser.add_argument( - "-v", - "--verbose", - action="store_true", - help="Enable verbose output", - ) - parser.add_argument( - "--auto-repair", - action="store_true", - help="Automatically repair common issues (hex IDs, whitespace preservation)", - ) - parser.add_argument( - "--author", - default="Claude", - help="Author name for redlining validation (default: Claude)", - ) - args = parser.parse_args() - - path = Path(args.path) - assert path.exists(), f"Error: {path} does not exist" - - original_file = None - if args.original: - original_file = Path(args.original) - assert original_file.is_file(), f"Error: {original_file} is not a file" - assert original_file.suffix.lower() in [".docx", ".pptx", ".xlsx"], ( - f"Error: {original_file} must be a .docx, .pptx, or .xlsx file" - ) - - file_extension = (original_file or path).suffix.lower() - assert file_extension in [".docx", ".pptx", ".xlsx"], ( - f"Error: Cannot determine file type from {path}. Use --original or provide a .docx/.pptx/.xlsx file." - ) - - if path.is_file() and path.suffix.lower() in [".docx", ".pptx", ".xlsx"]: - temp_dir = tempfile.mkdtemp() - with zipfile.ZipFile(path, "r") as zf: - zf.extractall(temp_dir) - unpacked_dir = Path(temp_dir) - else: - assert path.is_dir(), f"Error: {path} is not a directory or Office file" - unpacked_dir = path - - match file_extension: - case ".docx": - validators = [ - DOCXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose), - ] - if original_file: - validators.append( - RedliningValidator(unpacked_dir, original_file, verbose=args.verbose, author=args.author) - ) - case ".pptx": - validators = [ - PPTXSchemaValidator(unpacked_dir, original_file, verbose=args.verbose), - ] - case _: - print(f"Error: Validation not supported for file type {file_extension}") - sys.exit(1) - - if args.auto_repair: - total_repairs = sum(v.repair() for v in validators) - if total_repairs: - print(f"Auto-repaired {total_repairs} issue(s)") - - success = all(v.validate() for v in validators) - - if success: - print("All validations PASSED!") - - sys.exit(0 if success else 1) - - -if __name__ == "__main__": - main() diff --git a/skills/xlsx/scripts/office/validators/__init__.py b/skills/xlsx/scripts/office/validators/__init__.py deleted file mode 100644 index db092ece..00000000 --- a/skills/xlsx/scripts/office/validators/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Validation modules for Word document processing. -""" - -from .base import BaseSchemaValidator -from .docx import DOCXSchemaValidator -from .pptx import PPTXSchemaValidator -from .redlining import RedliningValidator - -__all__ = [ - "BaseSchemaValidator", - "DOCXSchemaValidator", - "PPTXSchemaValidator", - "RedliningValidator", -] diff --git a/skills/xlsx/scripts/office/validators/base.py b/skills/xlsx/scripts/office/validators/base.py deleted file mode 100644 index db4a06a2..00000000 --- a/skills/xlsx/scripts/office/validators/base.py +++ /dev/null @@ -1,847 +0,0 @@ -""" -Base validator with common validation logic for document files. -""" - -import re -from pathlib import Path - -import defusedxml.minidom -import lxml.etree - - -class BaseSchemaValidator: - - IGNORED_VALIDATION_ERRORS = [ - "hyphenationZone", - "purl.org/dc/terms", - ] - - UNIQUE_ID_REQUIREMENTS = { - "comment": ("id", "file"), - "commentrangestart": ("id", "file"), - "commentrangeend": ("id", "file"), - "bookmarkstart": ("id", "file"), - "bookmarkend": ("id", "file"), - "sldid": ("id", "file"), - "sldmasterid": ("id", "global"), - "sldlayoutid": ("id", "global"), - "cm": ("authorid", "file"), - "sheet": ("sheetid", "file"), - "definedname": ("id", "file"), - "cxnsp": ("id", "file"), - "sp": ("id", "file"), - "pic": ("id", "file"), - "grpsp": ("id", "file"), - } - - EXCLUDED_ID_CONTAINERS = { - "sectionlst", - } - - ELEMENT_RELATIONSHIP_TYPES = {} - - SCHEMA_MAPPINGS = { - "word": "ISO-IEC29500-4_2016/wml.xsd", - "ppt": "ISO-IEC29500-4_2016/pml.xsd", - "xl": "ISO-IEC29500-4_2016/sml.xsd", - "[Content_Types].xml": "ecma/fouth-edition/opc-contentTypes.xsd", - "app.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd", - "core.xml": "ecma/fouth-edition/opc-coreProperties.xsd", - "custom.xml": "ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd", - ".rels": "ecma/fouth-edition/opc-relationships.xsd", - "people.xml": "microsoft/wml-2012.xsd", - "commentsIds.xml": "microsoft/wml-cid-2016.xsd", - "commentsExtensible.xml": "microsoft/wml-cex-2018.xsd", - "commentsExtended.xml": "microsoft/wml-2012.xsd", - "chart": "ISO-IEC29500-4_2016/dml-chart.xsd", - "theme": "ISO-IEC29500-4_2016/dml-main.xsd", - "drawing": "ISO-IEC29500-4_2016/dml-main.xsd", - } - - MC_NAMESPACE = "http://schemas.openxmlformats.org/markup-compatibility/2006" - XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace" - - PACKAGE_RELATIONSHIPS_NAMESPACE = ( - "http://schemas.openxmlformats.org/package/2006/relationships" - ) - OFFICE_RELATIONSHIPS_NAMESPACE = ( - "http://schemas.openxmlformats.org/officeDocument/2006/relationships" - ) - CONTENT_TYPES_NAMESPACE = ( - "http://schemas.openxmlformats.org/package/2006/content-types" - ) - - MAIN_CONTENT_FOLDERS = {"word", "ppt", "xl"} - - OOXML_NAMESPACES = { - "http://schemas.openxmlformats.org/officeDocument/2006/math", - "http://schemas.openxmlformats.org/officeDocument/2006/relationships", - "http://schemas.openxmlformats.org/schemaLibrary/2006/main", - "http://schemas.openxmlformats.org/drawingml/2006/main", - "http://schemas.openxmlformats.org/drawingml/2006/chart", - "http://schemas.openxmlformats.org/drawingml/2006/chartDrawing", - "http://schemas.openxmlformats.org/drawingml/2006/diagram", - "http://schemas.openxmlformats.org/drawingml/2006/picture", - "http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing", - "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing", - "http://schemas.openxmlformats.org/wordprocessingml/2006/main", - "http://schemas.openxmlformats.org/presentationml/2006/main", - "http://schemas.openxmlformats.org/spreadsheetml/2006/main", - "http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes", - "http://www.w3.org/XML/1998/namespace", - } - - def __init__(self, unpacked_dir, original_file=None, verbose=False): - self.unpacked_dir = Path(unpacked_dir).resolve() - self.original_file = Path(original_file) if original_file else None - self.verbose = verbose - - self.schemas_dir = Path(__file__).parent.parent / "schemas" - - patterns = ["*.xml", "*.rels"] - self.xml_files = [ - f for pattern in patterns for f in self.unpacked_dir.rglob(pattern) - ] - - if not self.xml_files: - print(f"Warning: No XML files found in {self.unpacked_dir}") - - def validate(self): - raise NotImplementedError("Subclasses must implement the validate method") - - def repair(self) -> int: - return self.repair_whitespace_preservation() - - def repair_whitespace_preservation(self) -> int: - repairs = 0 - - for xml_file in self.xml_files: - try: - content = xml_file.read_text(encoding="utf-8") - dom = defusedxml.minidom.parseString(content) - modified = False - - for elem in dom.getElementsByTagName("*"): - if elem.tagName.endswith(":t") and elem.firstChild: - text = elem.firstChild.nodeValue - if text and (text.startswith((' ', '\t')) or text.endswith((' ', '\t'))): - if elem.getAttribute("xml:space") != "preserve": - elem.setAttribute("xml:space", "preserve") - text_preview = repr(text[:30]) + "..." if len(text) > 30 else repr(text) - print(f" Repaired: {xml_file.name}: Added xml:space='preserve' to {elem.tagName}: {text_preview}") - repairs += 1 - modified = True - - if modified: - xml_file.write_bytes(dom.toxml(encoding="UTF-8")) - - except Exception: - pass - - return repairs - - def validate_xml(self): - errors = [] - - for xml_file in self.xml_files: - try: - lxml.etree.parse(str(xml_file)) - except lxml.etree.XMLSyntaxError as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {e.lineno}: {e.msg}" - ) - except Exception as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Unexpected error: {str(e)}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} XML violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All XML files are well-formed") - return True - - def validate_namespaces(self): - errors = [] - - for xml_file in self.xml_files: - try: - root = lxml.etree.parse(str(xml_file)).getroot() - declared = set(root.nsmap.keys()) - {None} - - for attr_val in [ - v for k, v in root.attrib.items() if k.endswith("Ignorable") - ]: - undeclared = set(attr_val.split()) - declared - errors.extend( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Namespace '{ns}' in Ignorable but not declared" - for ns in undeclared - ) - except lxml.etree.XMLSyntaxError: - continue - - if errors: - print(f"FAILED - {len(errors)} namespace issues:") - for error in errors: - print(error) - return False - if self.verbose: - print("PASSED - All namespace prefixes properly declared") - return True - - def validate_unique_ids(self): - errors = [] - global_ids = {} - - for xml_file in self.xml_files: - try: - root = lxml.etree.parse(str(xml_file)).getroot() - file_ids = {} - - mc_elements = root.xpath( - ".//mc:AlternateContent", namespaces={"mc": self.MC_NAMESPACE} - ) - for elem in mc_elements: - elem.getparent().remove(elem) - - for elem in root.iter(): - tag = ( - elem.tag.split("}")[-1].lower() - if "}" in elem.tag - else elem.tag.lower() - ) - - if tag in self.UNIQUE_ID_REQUIREMENTS: - in_excluded_container = any( - ancestor.tag.split("}")[-1].lower() in self.EXCLUDED_ID_CONTAINERS - for ancestor in elem.iterancestors() - ) - if in_excluded_container: - continue - - attr_name, scope = self.UNIQUE_ID_REQUIREMENTS[tag] - - id_value = None - for attr, value in elem.attrib.items(): - attr_local = ( - attr.split("}")[-1].lower() - if "}" in attr - else attr.lower() - ) - if attr_local == attr_name: - id_value = value - break - - if id_value is not None: - if scope == "global": - if id_value in global_ids: - prev_file, prev_line, prev_tag = global_ids[ - id_value - ] - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: Global ID '{id_value}' in <{tag}> " - f"already used in {prev_file} at line {prev_line} in <{prev_tag}>" - ) - else: - global_ids[id_value] = ( - xml_file.relative_to(self.unpacked_dir), - elem.sourceline, - tag, - ) - elif scope == "file": - key = (tag, attr_name) - if key not in file_ids: - file_ids[key] = {} - - if id_value in file_ids[key]: - prev_line = file_ids[key][id_value] - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: Duplicate {attr_name}='{id_value}' in <{tag}> " - f"(first occurrence at line {prev_line})" - ) - else: - file_ids[key][id_value] = elem.sourceline - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} ID uniqueness violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All required IDs are unique") - return True - - def validate_file_references(self): - errors = [] - - rels_files = list(self.unpacked_dir.rglob("*.rels")) - - if not rels_files: - if self.verbose: - print("PASSED - No .rels files found") - return True - - all_files = [] - for file_path in self.unpacked_dir.rglob("*"): - if ( - file_path.is_file() - and file_path.name != "[Content_Types].xml" - and not file_path.name.endswith(".rels") - ): - all_files.append(file_path.resolve()) - - all_referenced_files = set() - - if self.verbose: - print( - f"Found {len(rels_files)} .rels files and {len(all_files)} target files" - ) - - for rels_file in rels_files: - try: - rels_root = lxml.etree.parse(str(rels_file)).getroot() - - rels_dir = rels_file.parent - - referenced_files = set() - broken_refs = [] - - for rel in rels_root.findall( - ".//ns:Relationship", - namespaces={"ns": self.PACKAGE_RELATIONSHIPS_NAMESPACE}, - ): - target = rel.get("Target") - if target and not target.startswith( - ("http", "mailto:") - ): - if target.startswith("/"): - target_path = self.unpacked_dir / target.lstrip("/") - elif rels_file.name == ".rels": - target_path = self.unpacked_dir / target - else: - base_dir = rels_dir.parent - target_path = base_dir / target - - try: - target_path = target_path.resolve() - if target_path.exists() and target_path.is_file(): - referenced_files.add(target_path) - all_referenced_files.add(target_path) - else: - broken_refs.append((target, rel.sourceline)) - except (OSError, ValueError): - broken_refs.append((target, rel.sourceline)) - - if broken_refs: - rel_path = rels_file.relative_to(self.unpacked_dir) - for broken_ref, line_num in broken_refs: - errors.append( - f" {rel_path}: Line {line_num}: Broken reference to {broken_ref}" - ) - - except Exception as e: - rel_path = rels_file.relative_to(self.unpacked_dir) - errors.append(f" Error parsing {rel_path}: {e}") - - unreferenced_files = set(all_files) - all_referenced_files - - if unreferenced_files: - for unref_file in sorted(unreferenced_files): - unref_rel_path = unref_file.relative_to(self.unpacked_dir) - errors.append(f" Unreferenced file: {unref_rel_path}") - - if errors: - print(f"FAILED - Found {len(errors)} relationship validation errors:") - for error in errors: - print(error) - print( - "CRITICAL: These errors will cause the document to appear corrupt. " - + "Broken references MUST be fixed, " - + "and unreferenced files MUST be referenced or removed." - ) - return False - else: - if self.verbose: - print( - "PASSED - All references are valid and all files are properly referenced" - ) - return True - - def validate_all_relationship_ids(self): - import lxml.etree - - errors = [] - - for xml_file in self.xml_files: - if xml_file.suffix == ".rels": - continue - - rels_dir = xml_file.parent / "_rels" - rels_file = rels_dir / f"{xml_file.name}.rels" - - if not rels_file.exists(): - continue - - try: - rels_root = lxml.etree.parse(str(rels_file)).getroot() - rid_to_type = {} - - for rel in rels_root.findall( - f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" - ): - rid = rel.get("Id") - rel_type = rel.get("Type", "") - if rid: - if rid in rid_to_type: - rels_rel_path = rels_file.relative_to(self.unpacked_dir) - errors.append( - f" {rels_rel_path}: Line {rel.sourceline}: " - f"Duplicate relationship ID '{rid}' (IDs must be unique)" - ) - type_name = ( - rel_type.split("/")[-1] if "/" in rel_type else rel_type - ) - rid_to_type[rid] = type_name - - xml_root = lxml.etree.parse(str(xml_file)).getroot() - - r_ns = self.OFFICE_RELATIONSHIPS_NAMESPACE - rid_attrs_to_check = ["id", "embed", "link"] - for elem in xml_root.iter(): - for attr_name in rid_attrs_to_check: - rid_attr = elem.get(f"{{{r_ns}}}{attr_name}") - if not rid_attr: - continue - xml_rel_path = xml_file.relative_to(self.unpacked_dir) - elem_name = ( - elem.tag.split("}")[-1] if "}" in elem.tag else elem.tag - ) - - if rid_attr not in rid_to_type: - errors.append( - f" {xml_rel_path}: Line {elem.sourceline}: " - f"<{elem_name}> r:{attr_name} references non-existent relationship '{rid_attr}' " - f"(valid IDs: {', '.join(sorted(rid_to_type.keys())[:5])}{'...' if len(rid_to_type) > 5 else ''})" - ) - elif attr_name == "id" and self.ELEMENT_RELATIONSHIP_TYPES: - expected_type = self._get_expected_relationship_type( - elem_name - ) - if expected_type: - actual_type = rid_to_type[rid_attr] - if expected_type not in actual_type.lower(): - errors.append( - f" {xml_rel_path}: Line {elem.sourceline}: " - f"<{elem_name}> references '{rid_attr}' which points to '{actual_type}' " - f"but should point to a '{expected_type}' relationship" - ) - - except Exception as e: - xml_rel_path = xml_file.relative_to(self.unpacked_dir) - errors.append(f" Error processing {xml_rel_path}: {e}") - - if errors: - print(f"FAILED - Found {len(errors)} relationship ID reference errors:") - for error in errors: - print(error) - print("\nThese ID mismatches will cause the document to appear corrupt!") - return False - else: - if self.verbose: - print("PASSED - All relationship ID references are valid") - return True - - def _get_expected_relationship_type(self, element_name): - elem_lower = element_name.lower() - - if elem_lower in self.ELEMENT_RELATIONSHIP_TYPES: - return self.ELEMENT_RELATIONSHIP_TYPES[elem_lower] - - if elem_lower.endswith("id") and len(elem_lower) > 2: - prefix = elem_lower[:-2] - if prefix.endswith("master"): - return prefix.lower() - elif prefix.endswith("layout"): - return prefix.lower() - else: - if prefix == "sld": - return "slide" - return prefix.lower() - - if elem_lower.endswith("reference") and len(elem_lower) > 9: - prefix = elem_lower[:-9] - return prefix.lower() - - return None - - def validate_content_types(self): - errors = [] - - content_types_file = self.unpacked_dir / "[Content_Types].xml" - if not content_types_file.exists(): - print("FAILED - [Content_Types].xml file not found") - return False - - try: - root = lxml.etree.parse(str(content_types_file)).getroot() - declared_parts = set() - declared_extensions = set() - - for override in root.findall( - f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Override" - ): - part_name = override.get("PartName") - if part_name is not None: - declared_parts.add(part_name.lstrip("/")) - - for default in root.findall( - f".//{{{self.CONTENT_TYPES_NAMESPACE}}}Default" - ): - extension = default.get("Extension") - if extension is not None: - declared_extensions.add(extension.lower()) - - declarable_roots = { - "sld", - "sldLayout", - "sldMaster", - "presentation", - "document", - "workbook", - "worksheet", - "theme", - } - - media_extensions = { - "png": "image/png", - "jpg": "image/jpeg", - "jpeg": "image/jpeg", - "gif": "image/gif", - "bmp": "image/bmp", - "tiff": "image/tiff", - "wmf": "image/x-wmf", - "emf": "image/x-emf", - } - - all_files = list(self.unpacked_dir.rglob("*")) - all_files = [f for f in all_files if f.is_file()] - - for xml_file in self.xml_files: - path_str = str(xml_file.relative_to(self.unpacked_dir)).replace( - "\\", "/" - ) - - if any( - skip in path_str - for skip in [".rels", "[Content_Types]", "docProps/", "_rels/"] - ): - continue - - try: - root_tag = lxml.etree.parse(str(xml_file)).getroot().tag - root_name = root_tag.split("}")[-1] if "}" in root_tag else root_tag - - if root_name in declarable_roots and path_str not in declared_parts: - errors.append( - f" {path_str}: File with <{root_name}> root not declared in [Content_Types].xml" - ) - - except Exception: - continue - - for file_path in all_files: - if file_path.suffix.lower() in {".xml", ".rels"}: - continue - if file_path.name == "[Content_Types].xml": - continue - if "_rels" in file_path.parts or "docProps" in file_path.parts: - continue - - extension = file_path.suffix.lstrip(".").lower() - if extension and extension not in declared_extensions: - if extension in media_extensions: - relative_path = file_path.relative_to(self.unpacked_dir) - errors.append( - f' {relative_path}: File with extension \'{extension}\' not declared in [Content_Types].xml - should add: ' - ) - - except Exception as e: - errors.append(f" Error parsing [Content_Types].xml: {e}") - - if errors: - print(f"FAILED - Found {len(errors)} content type declaration errors:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print( - "PASSED - All content files are properly declared in [Content_Types].xml" - ) - return True - - def validate_file_against_xsd(self, xml_file, verbose=False): - xml_file = Path(xml_file).resolve() - unpacked_dir = self.unpacked_dir.resolve() - - is_valid, current_errors = self._validate_single_file_xsd( - xml_file, unpacked_dir - ) - - if is_valid is None: - return None, set() - elif is_valid: - return True, set() - - original_errors = self._get_original_file_errors(xml_file) - - assert current_errors is not None - new_errors = current_errors - original_errors - - new_errors = { - e for e in new_errors - if not any(pattern in e for pattern in self.IGNORED_VALIDATION_ERRORS) - } - - if new_errors: - if verbose: - relative_path = xml_file.relative_to(unpacked_dir) - print(f"FAILED - {relative_path}: {len(new_errors)} new error(s)") - for error in list(new_errors)[:3]: - truncated = error[:250] + "..." if len(error) > 250 else error - print(f" - {truncated}") - return False, new_errors - else: - if verbose: - print( - f"PASSED - No new errors (original had {len(current_errors)} errors)" - ) - return True, set() - - def validate_against_xsd(self): - new_errors = [] - original_error_count = 0 - valid_count = 0 - skipped_count = 0 - - for xml_file in self.xml_files: - relative_path = str(xml_file.relative_to(self.unpacked_dir)) - is_valid, new_file_errors = self.validate_file_against_xsd( - xml_file, verbose=False - ) - - if is_valid is None: - skipped_count += 1 - continue - elif is_valid and not new_file_errors: - valid_count += 1 - continue - elif is_valid: - original_error_count += 1 - valid_count += 1 - continue - - new_errors.append(f" {relative_path}: {len(new_file_errors)} new error(s)") - for error in list(new_file_errors)[:3]: - new_errors.append( - f" - {error[:250]}..." if len(error) > 250 else f" - {error}" - ) - - if self.verbose: - print(f"Validated {len(self.xml_files)} files:") - print(f" - Valid: {valid_count}") - print(f" - Skipped (no schema): {skipped_count}") - if original_error_count: - print(f" - With original errors (ignored): {original_error_count}") - print( - f" - With NEW errors: {len(new_errors) > 0 and len([e for e in new_errors if not e.startswith(' ')]) or 0}" - ) - - if new_errors: - print("\nFAILED - Found NEW validation errors:") - for error in new_errors: - print(error) - return False - else: - if self.verbose: - print("\nPASSED - No new XSD validation errors introduced") - return True - - def _get_schema_path(self, xml_file): - if xml_file.name in self.SCHEMA_MAPPINGS: - return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.name] - - if xml_file.suffix == ".rels": - return self.schemas_dir / self.SCHEMA_MAPPINGS[".rels"] - - if "charts/" in str(xml_file) and xml_file.name.startswith("chart"): - return self.schemas_dir / self.SCHEMA_MAPPINGS["chart"] - - if "theme/" in str(xml_file) and xml_file.name.startswith("theme"): - return self.schemas_dir / self.SCHEMA_MAPPINGS["theme"] - - if xml_file.parent.name in self.MAIN_CONTENT_FOLDERS: - return self.schemas_dir / self.SCHEMA_MAPPINGS[xml_file.parent.name] - - return None - - def _clean_ignorable_namespaces(self, xml_doc): - xml_string = lxml.etree.tostring(xml_doc, encoding="unicode") - xml_copy = lxml.etree.fromstring(xml_string) - - for elem in xml_copy.iter(): - attrs_to_remove = [] - - for attr in elem.attrib: - if "{" in attr: - ns = attr.split("}")[0][1:] - if ns not in self.OOXML_NAMESPACES: - attrs_to_remove.append(attr) - - for attr in attrs_to_remove: - del elem.attrib[attr] - - self._remove_ignorable_elements(xml_copy) - - return lxml.etree.ElementTree(xml_copy) - - def _remove_ignorable_elements(self, root): - elements_to_remove = [] - - for elem in list(root): - if not hasattr(elem, "tag") or callable(elem.tag): - continue - - tag_str = str(elem.tag) - if tag_str.startswith("{"): - ns = tag_str.split("}")[0][1:] - if ns not in self.OOXML_NAMESPACES: - elements_to_remove.append(elem) - continue - - self._remove_ignorable_elements(elem) - - for elem in elements_to_remove: - root.remove(elem) - - def _preprocess_for_mc_ignorable(self, xml_doc): - root = xml_doc.getroot() - - if f"{{{self.MC_NAMESPACE}}}Ignorable" in root.attrib: - del root.attrib[f"{{{self.MC_NAMESPACE}}}Ignorable"] - - return xml_doc - - def _validate_single_file_xsd(self, xml_file, base_path): - schema_path = self._get_schema_path(xml_file) - if not schema_path: - return None, None - - try: - with open(schema_path, "rb") as xsd_file: - parser = lxml.etree.XMLParser() - xsd_doc = lxml.etree.parse( - xsd_file, parser=parser, base_url=str(schema_path) - ) - schema = lxml.etree.XMLSchema(xsd_doc) - - with open(xml_file, "r") as f: - xml_doc = lxml.etree.parse(f) - - xml_doc, _ = self._remove_template_tags_from_text_nodes(xml_doc) - xml_doc = self._preprocess_for_mc_ignorable(xml_doc) - - relative_path = xml_file.relative_to(base_path) - if ( - relative_path.parts - and relative_path.parts[0] in self.MAIN_CONTENT_FOLDERS - ): - xml_doc = self._clean_ignorable_namespaces(xml_doc) - - if schema.validate(xml_doc): - return True, set() - else: - errors = set() - for error in schema.error_log: - errors.add(error.message) - return False, errors - - except Exception as e: - return False, {str(e)} - - def _get_original_file_errors(self, xml_file): - if self.original_file is None: - return set() - - import tempfile - import zipfile - - xml_file = Path(xml_file).resolve() - unpacked_dir = self.unpacked_dir.resolve() - relative_path = xml_file.relative_to(unpacked_dir) - - with tempfile.TemporaryDirectory() as temp_dir: - temp_path = Path(temp_dir) - - with zipfile.ZipFile(self.original_file, "r") as zip_ref: - zip_ref.extractall(temp_path) - - original_xml_file = temp_path / relative_path - - if not original_xml_file.exists(): - return set() - - is_valid, errors = self._validate_single_file_xsd( - original_xml_file, temp_path - ) - return errors if errors else set() - - def _remove_template_tags_from_text_nodes(self, xml_doc): - warnings = [] - template_pattern = re.compile(r"\{\{[^}]*\}\}") - - xml_string = lxml.etree.tostring(xml_doc, encoding="unicode") - xml_copy = lxml.etree.fromstring(xml_string) - - def process_text_content(text, content_type): - if not text: - return text - matches = list(template_pattern.finditer(text)) - if matches: - for match in matches: - warnings.append( - f"Found template tag in {content_type}: {match.group()}" - ) - return template_pattern.sub("", text) - return text - - for elem in xml_copy.iter(): - if not hasattr(elem, "tag") or callable(elem.tag): - continue - tag_str = str(elem.tag) - if tag_str.endswith("}t") or tag_str == "t": - continue - - elem.text = process_text_content(elem.text, "text content") - elem.tail = process_text_content(elem.tail, "tail content") - - return lxml.etree.ElementTree(xml_copy), warnings - - -if __name__ == "__main__": - raise RuntimeError("This module should not be run directly.") diff --git a/skills/xlsx/scripts/office/validators/docx.py b/skills/xlsx/scripts/office/validators/docx.py deleted file mode 100644 index fec405e6..00000000 --- a/skills/xlsx/scripts/office/validators/docx.py +++ /dev/null @@ -1,446 +0,0 @@ -""" -Validator for Word document XML files against XSD schemas. -""" - -import random -import re -import tempfile -import zipfile - -import defusedxml.minidom -import lxml.etree - -from .base import BaseSchemaValidator - - -class DOCXSchemaValidator(BaseSchemaValidator): - - WORD_2006_NAMESPACE = "http://schemas.openxmlformats.org/wordprocessingml/2006/main" - W14_NAMESPACE = "http://schemas.microsoft.com/office/word/2010/wordml" - W16CID_NAMESPACE = "http://schemas.microsoft.com/office/word/2016/wordml/cid" - - ELEMENT_RELATIONSHIP_TYPES = {} - - def validate(self): - if not self.validate_xml(): - return False - - all_valid = True - if not self.validate_namespaces(): - all_valid = False - - if not self.validate_unique_ids(): - all_valid = False - - if not self.validate_file_references(): - all_valid = False - - if not self.validate_content_types(): - all_valid = False - - if not self.validate_against_xsd(): - all_valid = False - - if not self.validate_whitespace_preservation(): - all_valid = False - - if not self.validate_deletions(): - all_valid = False - - if not self.validate_insertions(): - all_valid = False - - if not self.validate_all_relationship_ids(): - all_valid = False - - if not self.validate_id_constraints(): - all_valid = False - - if not self.validate_comment_markers(): - all_valid = False - - self.compare_paragraph_counts() - - return all_valid - - def validate_whitespace_preservation(self): - errors = [] - - for xml_file in self.xml_files: - if xml_file.name != "document.xml": - continue - - try: - root = lxml.etree.parse(str(xml_file)).getroot() - - for elem in root.iter(f"{{{self.WORD_2006_NAMESPACE}}}t"): - if elem.text: - text = elem.text - if re.search(r"^[ \t\n\r]", text) or re.search( - r"[ \t\n\r]$", text - ): - xml_space_attr = f"{{{self.XML_NAMESPACE}}}space" - if ( - xml_space_attr not in elem.attrib - or elem.attrib[xml_space_attr] != "preserve" - ): - text_preview = ( - repr(text)[:50] + "..." - if len(repr(text)) > 50 - else repr(text) - ) - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: w:t element with whitespace missing xml:space='preserve': {text_preview}" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} whitespace preservation violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All whitespace is properly preserved") - return True - - def validate_deletions(self): - errors = [] - - for xml_file in self.xml_files: - if xml_file.name != "document.xml": - continue - - try: - root = lxml.etree.parse(str(xml_file)).getroot() - namespaces = {"w": self.WORD_2006_NAMESPACE} - - for t_elem in root.xpath(".//w:del//w:t", namespaces=namespaces): - if t_elem.text: - text_preview = ( - repr(t_elem.text)[:50] + "..." - if len(repr(t_elem.text)) > 50 - else repr(t_elem.text) - ) - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {t_elem.sourceline}: found within : {text_preview}" - ) - - for instr_elem in root.xpath( - ".//w:del//w:instrText", namespaces=namespaces - ): - text_preview = ( - repr(instr_elem.text or "")[:50] + "..." - if len(repr(instr_elem.text or "")) > 50 - else repr(instr_elem.text or "") - ) - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {instr_elem.sourceline}: found within (use ): {text_preview}" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} deletion validation violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - No w:t elements found within w:del elements") - return True - - def count_paragraphs_in_unpacked(self): - count = 0 - - for xml_file in self.xml_files: - if xml_file.name != "document.xml": - continue - - try: - root = lxml.etree.parse(str(xml_file)).getroot() - paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p") - count = len(paragraphs) - except Exception as e: - print(f"Error counting paragraphs in unpacked document: {e}") - - return count - - def count_paragraphs_in_original(self): - original = self.original_file - if original is None: - return 0 - - count = 0 - - try: - with tempfile.TemporaryDirectory() as temp_dir: - with zipfile.ZipFile(original, "r") as zip_ref: - zip_ref.extractall(temp_dir) - - doc_xml_path = temp_dir + "/word/document.xml" - root = lxml.etree.parse(doc_xml_path).getroot() - - paragraphs = root.findall(f".//{{{self.WORD_2006_NAMESPACE}}}p") - count = len(paragraphs) - - except Exception as e: - print(f"Error counting paragraphs in original document: {e}") - - return count - - def validate_insertions(self): - errors = [] - - for xml_file in self.xml_files: - if xml_file.name != "document.xml": - continue - - try: - root = lxml.etree.parse(str(xml_file)).getroot() - namespaces = {"w": self.WORD_2006_NAMESPACE} - - invalid_elements = root.xpath( - ".//w:ins//w:delText[not(ancestor::w:del)]", namespaces=namespaces - ) - - for elem in invalid_elements: - text_preview = ( - repr(elem.text or "")[:50] + "..." - if len(repr(elem.text or "")) > 50 - else repr(elem.text or "") - ) - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: within : {text_preview}" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} insertion validation violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - No w:delText elements within w:ins elements") - return True - - def compare_paragraph_counts(self): - original_count = self.count_paragraphs_in_original() - new_count = self.count_paragraphs_in_unpacked() - - diff = new_count - original_count - diff_str = f"+{diff}" if diff > 0 else str(diff) - print(f"\nParagraphs: {original_count} → {new_count} ({diff_str})") - - def _parse_id_value(self, val: str, base: int = 16) -> int: - return int(val, base) - - def validate_id_constraints(self): - errors = [] - para_id_attr = f"{{{self.W14_NAMESPACE}}}paraId" - durable_id_attr = f"{{{self.W16CID_NAMESPACE}}}durableId" - - for xml_file in self.xml_files: - try: - for elem in lxml.etree.parse(str(xml_file)).iter(): - if val := elem.get(para_id_attr): - if self._parse_id_value(val, base=16) >= 0x80000000: - errors.append( - f" {xml_file.name}:{elem.sourceline}: paraId={val} >= 0x80000000" - ) - - if val := elem.get(durable_id_attr): - if xml_file.name == "numbering.xml": - try: - if self._parse_id_value(val, base=10) >= 0x7FFFFFFF: - errors.append( - f" {xml_file.name}:{elem.sourceline}: " - f"durableId={val} >= 0x7FFFFFFF" - ) - except ValueError: - errors.append( - f" {xml_file.name}:{elem.sourceline}: " - f"durableId={val} must be decimal in numbering.xml" - ) - else: - if self._parse_id_value(val, base=16) >= 0x7FFFFFFF: - errors.append( - f" {xml_file.name}:{elem.sourceline}: " - f"durableId={val} >= 0x7FFFFFFF" - ) - except Exception: - pass - - if errors: - print(f"FAILED - {len(errors)} ID constraint violations:") - for e in errors: - print(e) - elif self.verbose: - print("PASSED - All paraId/durableId values within constraints") - return not errors - - def validate_comment_markers(self): - errors = [] - - document_xml = None - comments_xml = None - for xml_file in self.xml_files: - if xml_file.name == "document.xml" and "word" in str(xml_file): - document_xml = xml_file - elif xml_file.name == "comments.xml": - comments_xml = xml_file - - if not document_xml: - if self.verbose: - print("PASSED - No document.xml found (skipping comment validation)") - return True - - try: - doc_root = lxml.etree.parse(str(document_xml)).getroot() - namespaces = {"w": self.WORD_2006_NAMESPACE} - - range_starts = { - elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id") - for elem in doc_root.xpath( - ".//w:commentRangeStart", namespaces=namespaces - ) - } - range_ends = { - elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id") - for elem in doc_root.xpath( - ".//w:commentRangeEnd", namespaces=namespaces - ) - } - references = { - elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id") - for elem in doc_root.xpath( - ".//w:commentReference", namespaces=namespaces - ) - } - - orphaned_ends = range_ends - range_starts - for comment_id in sorted( - orphaned_ends, key=lambda x: int(x) if x and x.isdigit() else 0 - ): - errors.append( - f' document.xml: commentRangeEnd id="{comment_id}" has no matching commentRangeStart' - ) - - orphaned_starts = range_starts - range_ends - for comment_id in sorted( - orphaned_starts, key=lambda x: int(x) if x and x.isdigit() else 0 - ): - errors.append( - f' document.xml: commentRangeStart id="{comment_id}" has no matching commentRangeEnd' - ) - - comment_ids = set() - if comments_xml and comments_xml.exists(): - comments_root = lxml.etree.parse(str(comments_xml)).getroot() - comment_ids = { - elem.get(f"{{{self.WORD_2006_NAMESPACE}}}id") - for elem in comments_root.xpath( - ".//w:comment", namespaces=namespaces - ) - } - - marker_ids = range_starts | range_ends | references - invalid_refs = marker_ids - comment_ids - for comment_id in sorted( - invalid_refs, key=lambda x: int(x) if x and x.isdigit() else 0 - ): - if comment_id: - errors.append( - f' document.xml: marker id="{comment_id}" references non-existent comment' - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append(f" Error parsing XML: {e}") - - if errors: - print(f"FAILED - {len(errors)} comment marker violations:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All comment markers properly paired") - return True - - def repair(self) -> int: - repairs = super().repair() - repairs += self.repair_durableId() - return repairs - - def repair_durableId(self) -> int: - repairs = 0 - - for xml_file in self.xml_files: - try: - content = xml_file.read_text(encoding="utf-8") - dom = defusedxml.minidom.parseString(content) - modified = False - - for elem in dom.getElementsByTagName("*"): - if not elem.hasAttribute("w16cid:durableId"): - continue - - durable_id = elem.getAttribute("w16cid:durableId") - needs_repair = False - - if xml_file.name == "numbering.xml": - try: - needs_repair = ( - self._parse_id_value(durable_id, base=10) >= 0x7FFFFFFF - ) - except ValueError: - needs_repair = True - else: - try: - needs_repair = ( - self._parse_id_value(durable_id, base=16) >= 0x7FFFFFFF - ) - except ValueError: - needs_repair = True - - if needs_repair: - value = random.randint(1, 0x7FFFFFFE) - if xml_file.name == "numbering.xml": - new_id = str(value) - else: - new_id = f"{value:08X}" - - elem.setAttribute("w16cid:durableId", new_id) - print( - f" Repaired: {xml_file.name}: durableId {durable_id} → {new_id}" - ) - repairs += 1 - modified = True - - if modified: - xml_file.write_bytes(dom.toxml(encoding="UTF-8")) - - except Exception: - pass - - return repairs - - -if __name__ == "__main__": - raise RuntimeError("This module should not be run directly.") diff --git a/skills/xlsx/scripts/office/validators/pptx.py b/skills/xlsx/scripts/office/validators/pptx.py deleted file mode 100644 index 09842aa9..00000000 --- a/skills/xlsx/scripts/office/validators/pptx.py +++ /dev/null @@ -1,275 +0,0 @@ -""" -Validator for PowerPoint presentation XML files against XSD schemas. -""" - -import re - -from .base import BaseSchemaValidator - - -class PPTXSchemaValidator(BaseSchemaValidator): - - PRESENTATIONML_NAMESPACE = ( - "http://schemas.openxmlformats.org/presentationml/2006/main" - ) - - ELEMENT_RELATIONSHIP_TYPES = { - "sldid": "slide", - "sldmasterid": "slidemaster", - "notesmasterid": "notesmaster", - "sldlayoutid": "slidelayout", - "themeid": "theme", - "tablestyleid": "tablestyles", - } - - def validate(self): - if not self.validate_xml(): - return False - - all_valid = True - if not self.validate_namespaces(): - all_valid = False - - if not self.validate_unique_ids(): - all_valid = False - - if not self.validate_uuid_ids(): - all_valid = False - - if not self.validate_file_references(): - all_valid = False - - if not self.validate_slide_layout_ids(): - all_valid = False - - if not self.validate_content_types(): - all_valid = False - - if not self.validate_against_xsd(): - all_valid = False - - if not self.validate_notes_slide_references(): - all_valid = False - - if not self.validate_all_relationship_ids(): - all_valid = False - - if not self.validate_no_duplicate_slide_layouts(): - all_valid = False - - return all_valid - - def validate_uuid_ids(self): - import lxml.etree - - errors = [] - uuid_pattern = re.compile( - r"^[\{\(]?[0-9A-Fa-f]{8}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{4}-?[0-9A-Fa-f]{12}[\}\)]?$" - ) - - for xml_file in self.xml_files: - try: - root = lxml.etree.parse(str(xml_file)).getroot() - - for elem in root.iter(): - for attr, value in elem.attrib.items(): - attr_name = attr.split("}")[-1].lower() - if attr_name == "id" or attr_name.endswith("id"): - if self._looks_like_uuid(value): - if not uuid_pattern.match(value): - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: " - f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} UUID ID validation errors:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All UUID-like IDs contain valid hex values") - return True - - def _looks_like_uuid(self, value): - clean_value = value.strip("{}()").replace("-", "") - return len(clean_value) == 32 and all(c.isalnum() for c in clean_value) - - def validate_slide_layout_ids(self): - import lxml.etree - - errors = [] - - slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml")) - - if not slide_masters: - if self.verbose: - print("PASSED - No slide masters found") - return True - - for slide_master in slide_masters: - try: - root = lxml.etree.parse(str(slide_master)).getroot() - - rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels" - - if not rels_file.exists(): - errors.append( - f" {slide_master.relative_to(self.unpacked_dir)}: " - f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}" - ) - continue - - rels_root = lxml.etree.parse(str(rels_file)).getroot() - - valid_layout_rids = set() - for rel in rels_root.findall( - f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" - ): - rel_type = rel.get("Type", "") - if "slideLayout" in rel_type: - valid_layout_rids.add(rel.get("Id")) - - for sld_layout_id in root.findall( - f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId" - ): - r_id = sld_layout_id.get( - f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id" - ) - layout_id = sld_layout_id.get("id") - - if r_id and r_id not in valid_layout_rids: - errors.append( - f" {slide_master.relative_to(self.unpacked_dir)}: " - f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' " - f"references r:id='{r_id}' which is not found in slide layout relationships" - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print(f"FAILED - Found {len(errors)} slide layout ID validation errors:") - for error in errors: - print(error) - print( - "Remove invalid references or add missing slide layouts to the relationships file." - ) - return False - else: - if self.verbose: - print("PASSED - All slide layout IDs reference valid slide layouts") - return True - - def validate_no_duplicate_slide_layouts(self): - import lxml.etree - - errors = [] - slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels")) - - for rels_file in slide_rels_files: - try: - root = lxml.etree.parse(str(rels_file)).getroot() - - layout_rels = [ - rel - for rel in root.findall( - f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" - ) - if "slideLayout" in rel.get("Type", "") - ] - - if len(layout_rels) > 1: - errors.append( - f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references" - ) - - except Exception as e: - errors.append( - f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - if errors: - print("FAILED - Found slides with duplicate slideLayout references:") - for error in errors: - print(error) - return False - else: - if self.verbose: - print("PASSED - All slides have exactly one slideLayout reference") - return True - - def validate_notes_slide_references(self): - import lxml.etree - - errors = [] - notes_slide_references = {} - - slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels")) - - if not slide_rels_files: - if self.verbose: - print("PASSED - No slide relationship files found") - return True - - for rels_file in slide_rels_files: - try: - root = lxml.etree.parse(str(rels_file)).getroot() - - for rel in root.findall( - f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship" - ): - rel_type = rel.get("Type", "") - if "notesSlide" in rel_type: - target = rel.get("Target", "") - if target: - normalized_target = target.replace("../", "") - - slide_name = rels_file.stem.replace( - ".xml", "" - ) - - if normalized_target not in notes_slide_references: - notes_slide_references[normalized_target] = [] - notes_slide_references[normalized_target].append( - (slide_name, rels_file) - ) - - except (lxml.etree.XMLSyntaxError, Exception) as e: - errors.append( - f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}" - ) - - for target, references in notes_slide_references.items(): - if len(references) > 1: - slide_names = [ref[0] for ref in references] - errors.append( - f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}" - ) - for slide_name, rels_file in references: - errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}") - - if errors: - print( - f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:" - ) - for error in errors: - print(error) - print("Each slide may optionally have its own slide file.") - return False - else: - if self.verbose: - print("PASSED - All notes slide references are unique") - return True - - -if __name__ == "__main__": - raise RuntimeError("This module should not be run directly.") diff --git a/skills/xlsx/scripts/office/validators/redlining.py b/skills/xlsx/scripts/office/validators/redlining.py deleted file mode 100644 index 71c81b6b..00000000 --- a/skills/xlsx/scripts/office/validators/redlining.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Validator for tracked changes in Word documents. -""" - -import subprocess -import tempfile -import zipfile -from pathlib import Path - - -class RedliningValidator: - - def __init__(self, unpacked_dir, original_docx, verbose=False, author="Claude"): - self.unpacked_dir = Path(unpacked_dir) - self.original_docx = Path(original_docx) - self.verbose = verbose - self.author = author - self.namespaces = { - "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main" - } - - def repair(self) -> int: - return 0 - - def validate(self): - modified_file = self.unpacked_dir / "word" / "document.xml" - if not modified_file.exists(): - print(f"FAILED - Modified document.xml not found at {modified_file}") - return False - - try: - import xml.etree.ElementTree as ET - - tree = ET.parse(modified_file) - root = tree.getroot() - - del_elements = root.findall(".//w:del", self.namespaces) - ins_elements = root.findall(".//w:ins", self.namespaces) - - author_del_elements = [ - elem - for elem in del_elements - if elem.get(f"{{{self.namespaces['w']}}}author") == self.author - ] - author_ins_elements = [ - elem - for elem in ins_elements - if elem.get(f"{{{self.namespaces['w']}}}author") == self.author - ] - - if not author_del_elements and not author_ins_elements: - if self.verbose: - print(f"PASSED - No tracked changes by {self.author} found.") - return True - - except Exception: - pass - - with tempfile.TemporaryDirectory() as temp_dir: - temp_path = Path(temp_dir) - - try: - with zipfile.ZipFile(self.original_docx, "r") as zip_ref: - zip_ref.extractall(temp_path) - except Exception as e: - print(f"FAILED - Error unpacking original docx: {e}") - return False - - original_file = temp_path / "word" / "document.xml" - if not original_file.exists(): - print( - f"FAILED - Original document.xml not found in {self.original_docx}" - ) - return False - - try: - import xml.etree.ElementTree as ET - - modified_tree = ET.parse(modified_file) - modified_root = modified_tree.getroot() - original_tree = ET.parse(original_file) - original_root = original_tree.getroot() - except ET.ParseError as e: - print(f"FAILED - Error parsing XML files: {e}") - return False - - self._remove_author_tracked_changes(original_root) - self._remove_author_tracked_changes(modified_root) - - modified_text = self._extract_text_content(modified_root) - original_text = self._extract_text_content(original_root) - - if modified_text != original_text: - error_message = self._generate_detailed_diff( - original_text, modified_text - ) - print(error_message) - return False - - if self.verbose: - print(f"PASSED - All changes by {self.author} are properly tracked") - return True - - def _generate_detailed_diff(self, original_text, modified_text): - error_parts = [ - f"FAILED - Document text doesn't match after removing {self.author}'s tracked changes", - "", - "Likely causes:", - " 1. Modified text inside another author's or tags", - " 2. Made edits without proper tracked changes", - " 3. Didn't nest inside when deleting another's insertion", - "", - "For pre-redlined documents, use correct patterns:", - " - To reject another's INSERTION: Nest inside their ", - " - To restore another's DELETION: Add new AFTER their ", - "", - ] - - git_diff = self._get_git_word_diff(original_text, modified_text) - if git_diff: - error_parts.extend(["Differences:", "============", git_diff]) - else: - error_parts.append("Unable to generate word diff (git not available)") - - return "\n".join(error_parts) - - def _get_git_word_diff(self, original_text, modified_text): - try: - with tempfile.TemporaryDirectory() as temp_dir: - temp_path = Path(temp_dir) - - original_file = temp_path / "original.txt" - modified_file = temp_path / "modified.txt" - - original_file.write_text(original_text, encoding="utf-8") - modified_file.write_text(modified_text, encoding="utf-8") - - result = subprocess.run( - [ - "git", - "diff", - "--word-diff=plain", - "--word-diff-regex=.", - "-U0", - "--no-index", - str(original_file), - str(modified_file), - ], - capture_output=True, - text=True, - ) - - if result.stdout.strip(): - lines = result.stdout.split("\n") - content_lines = [] - in_content = False - for line in lines: - if line.startswith("@@"): - in_content = True - continue - if in_content and line.strip(): - content_lines.append(line) - - if content_lines: - return "\n".join(content_lines) - - result = subprocess.run( - [ - "git", - "diff", - "--word-diff=plain", - "-U0", - "--no-index", - str(original_file), - str(modified_file), - ], - capture_output=True, - text=True, - ) - - if result.stdout.strip(): - lines = result.stdout.split("\n") - content_lines = [] - in_content = False - for line in lines: - if line.startswith("@@"): - in_content = True - continue - if in_content and line.strip(): - content_lines.append(line) - return "\n".join(content_lines) - - except (subprocess.CalledProcessError, FileNotFoundError, Exception): - pass - - return None - - def _remove_author_tracked_changes(self, root): - ins_tag = f"{{{self.namespaces['w']}}}ins" - del_tag = f"{{{self.namespaces['w']}}}del" - author_attr = f"{{{self.namespaces['w']}}}author" - - for parent in root.iter(): - to_remove = [] - for child in parent: - if child.tag == ins_tag and child.get(author_attr) == self.author: - to_remove.append(child) - for elem in to_remove: - parent.remove(elem) - - deltext_tag = f"{{{self.namespaces['w']}}}delText" - t_tag = f"{{{self.namespaces['w']}}}t" - - for parent in root.iter(): - to_process = [] - for child in parent: - if child.tag == del_tag and child.get(author_attr) == self.author: - to_process.append((child, list(parent).index(child))) - - for del_elem, del_index in reversed(to_process): - for elem in del_elem.iter(): - if elem.tag == deltext_tag: - elem.tag = t_tag - - for child in reversed(list(del_elem)): - parent.insert(del_index, child) - parent.remove(del_elem) - - def _extract_text_content(self, root): - p_tag = f"{{{self.namespaces['w']}}}p" - t_tag = f"{{{self.namespaces['w']}}}t" - - paragraphs = [] - for p_elem in root.findall(f".//{p_tag}"): - text_parts = [] - for t_elem in p_elem.findall(f".//{t_tag}"): - if t_elem.text: - text_parts.append(t_elem.text) - paragraph_text = "".join(text_parts) - if paragraph_text: - paragraphs.append(paragraph_text) - - return "\n".join(paragraphs) - - -if __name__ == "__main__": - raise RuntimeError("This module should not be run directly.") From 7fdd0dbd3d742778ccaff25344b615f11a785b5c Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Sun, 15 Feb 2026 01:32:24 +0800 Subject: [PATCH 2/3] chore: add __pycache__ to gitignore and remove committed cache Co-Authored-By: Claude Opus 4.6 --- .gitignore | 3 +++ .../__pycache__/__init__.cpython-314.pyc | Bin 523 -> 0 bytes .../validators/__pycache__/base.cpython-314.pyc | Bin 41902 -> 0 bytes .../validators/__pycache__/docx.cpython-314.pyc | Bin 22029 -> 0 bytes .../validators/__pycache__/pptx.cpython-314.pyc | Bin 13284 -> 0 bytes .../__pycache__/redlining.cpython-314.pyc | Bin 11816 -> 0 bytes 6 files changed, 3 insertions(+) delete mode 100644 skills/_shared/office/validators/__pycache__/__init__.cpython-314.pyc delete mode 100644 skills/_shared/office/validators/__pycache__/base.cpython-314.pyc delete mode 100644 skills/_shared/office/validators/__pycache__/docx.cpython-314.pyc delete mode 100644 skills/_shared/office/validators/__pycache__/pptx.cpython-314.pyc delete mode 100644 skills/_shared/office/validators/__pycache__/redlining.cpython-314.pyc diff --git a/.gitignore b/.gitignore index bc6af42b..275c39b1 100644 --- a/.gitignore +++ b/.gitignore @@ -26,5 +26,8 @@ release *.ipa monorepo.md +# python +__pycache__ + # test coverage coverage diff --git a/skills/_shared/office/validators/__pycache__/__init__.cpython-314.pyc b/skills/_shared/office/validators/__pycache__/__init__.cpython-314.pyc deleted file mode 100644 index a05a40995982cdc2a742c9975e82387e1d055cf5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 523 zcmZuuOHRWu5VceHLlGds0U8N`6rt7RH5?+VK#X=`xe$abKxLY#nWu;4&M zV#Ni3ianD+S#@MRJ&)$S@r<{YmzoIT_2o$nFhcLeWfsmKneG93Kt8hQ0{gg!yN-2Q z1$TOtF0rKKm0q=5v$c|wUcK9}jTY)4zt+cg!LP0(uYSMXJmpe^+zOSru?jO88F#3( zd!}^ghAPP7D6wv;RS+2?l2NDW;e2h6o9HMQM=}4CD_x`y`upc|(u2YAob;+6g;FHo z`R{S)5narvD+Q1OssLUyuQLXZVJs(?(7G(j31lW^X-?AAPD(+(pl7zFwoq~@8GA-k zutIrEPr$&?E5YwFp4^XlGNOS>!Yr^#(_5vlP0C?fYO*xa?KqQG1e_YK+9}VZqUKsi zX(%&e4!x)v4n+{ro8LVuux1R#vW#`oyS%aUyVYI20^Q+5k!b;ZR4~S$j)Tc}1L4g# Rv{4@Ki&Ph#$GTpEnjdg$me&9P diff --git a/skills/_shared/office/validators/__pycache__/base.cpython-314.pyc b/skills/_shared/office/validators/__pycache__/base.cpython-314.pyc deleted file mode 100644 index 0d8c5eaf9fd19e7d32be0d8e9f2be07fad6dcc6a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41902 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�h`E~f&th6l^|O4$ybek#31y+aH4$F4oB7i%>^9|fo*-) z9evRJiI|Ha@CMCQVROwja}BdcvUk(84_Mc2?WoFdKy}laD_SHYMEINCfvUDpS$oLZ z5w`XOtvw-Y-%`&_hwGK6mWFPYlz*cdp)xK%1ir7lRwu+#wV)cBua%Zq|=Y}R!*>S_R=Z+DvaQF+! zc3kR6O7`i1WiP?X8Ox~vaUErs2QCe)8Mh=`89VdW|E(i$90}F!xnb)>lJ<;`G7PSa zfXRM4kIT$i{#*nW+CC4YZUt~%w)hjzVm~j?vkSK~#TfT;a5QjbRBX$zqc!M&oz9SB zcZlHg_si--d3#>#UwQs}`QOg}Ug71;mCV=rm-+*Fd#GiGzCN_9C;K($9s&wOg$Kfg zM}mb%a9nYv6i_L^ay_)GY`I@Zq~~!FSq>B)<;l~vyq`UTh<*tm+iQ% zP0MuLgk4bB$O$_egU&`^5C^}L9&&buoqa(kG|3Kzori68+liYHg_LO(m1Q%Fsy$ruzx0CpM~mOegOts z+i}&k+}1nT^{ljyqjNU-xrsA_m6J)!iDRh&dTaMf_-bGKm}M0J9! zlRm3HO}7*3Gjuznet~Xh)dw{HLN{K6UoWUf=r#gPE8HB%G)%?rPChDnzv5vvxTAqA ze$zDa5T=}D0dR}j+pj;G1xDTZ@x3}OuNZo7nT`)^cI;(jI{x)u1!u{}BTUPPDeFsv zUmOhByRH;pu?7ly!=}EVsSk(s=8a>R~~LZEbJ=A?MEHCN49By zoR>mwQCB%0T}^S2Tc#$ra)0WP9qOMn?9aikzcpyc&17zV7u|lgO-Jqy<8X>Dpwhx0 z(5c9uLT>{G<8ZSkV6zR^YXbE;_$P>?k}e{R_^rSD4BT=3ONk%PkQwmD0awhLxmHRp zet&En0)|rKVI|)&tE8QE76g-Q>YYw!B38nIG)anTA?98yGxB0MRs1N%mT;UBEEczL z0q{vD0yIatDb+FemZK|xodA32$Mp;9Ci-v(gt>YB32>U?6@v__shki^^neUR3ww%I zkv;*8pc*VOI`d%T7dGS~;bJE0y@S_9A&65H6F%EnP#k0zhePl#(wD2sfs z5j8U<3Z(oa#vQXtsP^jdiInuAQDv!yxW5Fc#sF9?bBjz9gFd#%G=dX+L`T2;&M|6=HuwB8Ki{($bnQMij*7o?2Ea{2TM4(`a2e6A|# zt1qspah%H0$0UO4Y7_fdj8KXs7y}*6Cp!!Dy*ker>70N^WOYJa*l7>ECnX3eIq@a< z&UwMd_}^%L|AC<+JPeY!yAJpFurnTGy*$1#HzMk6Q16KRFEP}}pY~Tor3hk(#X{(D z=!6v`njsqIfZ7GiBWjj&jy*_2En_c5?z0UY$<9AS4Sr!^j?8^b`e<)|k{+u6CfhY*AvSnkwZ4+Q+#WC$eF(Ga2|sTY6om_R z1q*g<tJahZmWvt2<{J5 z<>c0;z}>QFU?~uZbrT4#xc7w5iKy|X!5^tmBS7+nMfIPssOnD8G-MYPn`OjHdIoGz z@m#pH{7QF_N)BAbLzi2JeNWloA0yxAuwR3bjOALJ^oa=VzyR`#lOY=mrI$$888Mb7 zDVAmc;%P!GtV%?@l0vE7561(U1O7~{mvmu=Dg}$VHeshhHcv}rZ43D_OOfLyZ26QV z@&&HLYXIJ-kXJ$;GA0@$YHZJsPjwr%k~NgT(5xBM(wig#2IQIum~;XcePWSH7r#sG zIr-frqL!9W7R50m^)^aiOeAy(gwf>IY%Np5yL7j9>$?ON1Fg7)z-GABTf-)#V@@ci z%#5Do==(|SaSR#wF0o#TVZpGa#rCOy$36e2O2#62Ss`0)Cy`|WChtzeP8HBwYU2g9 zS7t)uxr&%OeSURo>D}>il_b?PQ)<-&1UrG1a?7J40l`)=1p9G)%jQZ#n`Omw;jJ=) zody=fCScZaebp-QyyWxv!3egnk7!UAoW&HvKI&aUH$7p5nhq^8`U*OnDxM*2TQpK2 z_5uo~`SfE1$f_3xq^joTUE+t>s`09fULB*)$VzlDM#)`O(_~Q@#~u+XTMjkUdrSiV zT*M-{T#FTHGUP2D+odouLYQz8c5z!;GB0s_r03y)KnP*V7awgS1NlY~7CTD~LD-mx z$qiE785%ZD&xr2F516vUo`}fUfB}Sz+N7$9WF2;)7U*kqtcbB6>4ZC@b;O?{_rU1> zK}x_(ECHkFF~;OY%8_&zeYA=s08%0wV2jn>GnT-)MXMgm0VIU{JQ4r<$e2_%VCE^} zbm4qqBqw*t7|F|DN|(*+--O*IeT;}oXWd)}LiU48gD~(DuIdX`^+hastL31+)dy5C zOjDf^G?nAqD)DvK$9f3VZ108%IXJpedia?PpvY346P4h-^u&O;=buLhK z?uVM-@H3&Fvv99&3!j<`o|+5HFN9CM@ZQlYNAWP&b2hZ^T)65&0RKOKAy5gmmlxUL zPCDC{!%#sg(g*paTfSsamV{#jat>`;2A~r6>yL7=k$O(Wn9Cn$<{h+V{J0?n?iM{Y z@D(D5VZmDX5>ZV&NWMUAFo=?`-FP6HiN||NC+Sj-rJO?|RZY~Q!Dv^eM$?HXGIDt9>XtFTKJPg{TVI#+2W#aw0>#N7x$1JcvH~c zv}tT&`gydKKqtK0f)#BWyWZLJ_MYn%{nxGi0aO1i#Mt}Ay(?!njpYxA-FdyD`?|F| zVCueQw7sfXIrvIC!mU+(tNxApaLxW;&HgJp!!-lfY6c!3*ZLLT%C0Y8*ff@{<$cTX zh9g|v6|C;MQW>s3bglZ(6XH_&!bWG%=v-|Lmv#h8J2s6S569W{&fd58hTD$>+mBqY z7`|>D4w!~zHNV6qjl?4?fcm@tCu_44_$gjW3!kDeh3`M`s-3Yk@$C{R0mY?L2@8!Y z;j%Nt3L=+;a6-U453xgw)5v6{9sAQ`?(*%sZ?!NzrDp9QGgwosz+qxH&H{yO{sPXuEYNCssN68Y z=X0?*oRZ>U9IJ#_AXiSY?+DsTy5g}h<#o|i9M8f>0zJZEgcJUm9B9&TfALFHlN%ZW zGw7#h3vi-(|2&xif!ajcX<>d=^oxgh{*VGKQy|NcBlz-CeE)WYahuTs>=%=v)G@{y zv;9uV25HpTD_SER#W$R=#1QuxO{F6-*lv!dktdcdJ6{7Er03Zwny6VmS|g?{`~{Wb zci{L4_-CIs2A3@|mWBAZtY>^KA%EI96~Ag6ZB$%l9v$HwT5*VX2E+urhtFJmCY)Ur z%&uC?-Pj$-t_o%ME~Ux)m{mb*6*eBNtxNh_HamUW;j2fMdT@4S=?Jl-##fEY+K4r0 zrR=Niu)1-%;8MYA_bbk|%{6*lM6gYa?Z>m?|ZaKR&aOGIslECE>L+%SA;9d2$QS zVsg{erV$)Xof`MiM2hP1d7BxUnPTubC8v2Uj3bUHIG!TkpWzue1|})~5har)O=mjl z7MqVVEPWXKr3oxfOzL-WVzOx|P-4*r#p(CR1tUbWj|u=G&*6kp!7s3VZTspWC)8#<>WDQv>}U=;nm4V@5nF|5;B;+r)7C<} z#3oDFR2wvrfnU=$7*x%6-Ywv=D>1(zaSeQNU`2bwLHL|{HPgSSWLShe}D;_Grx!2 zg*VBewI2`IP?)5#LF2)Picv?4i?5wNe+q>rIy^guFZhkY1hq^EqD_k3|Ckv68w~)0 zdoTdZIdqI7WOglSZ>5`;wW5?9Fqf>>28<2sW#6m)cI~EdPXu4mGhQ^VoLaSshKK^t zI%p5r_d-kLmN{oBL+le@w4D@iG&y#X?YPD1U{vdG@s=(-5M-J zu?}okjj0yY3z{+cgC+Q0rrd!_%cBx5Ee{Ue6XFuql5`#GO~4f8MF}W(ti1S8+vCe4 zBgXNBITSRKl^0!^STOsjq-2$JNn>IXodwCxsALFcE1^MIXwxL=VpACmB9jhDHcPtV z2&>m{JXJ}%IvzqUQ#z%G9Ri@i;w6nJ+BkmrDf>{3O9~smgD@3=Fr~xlYH9qb|d|yaE186>&J&X{C`QIwtPH0 zpVISYU{fD;7q3zId_j|{MZKVgrZPh8%_xoC6Eu}mhoWlX6zmD6kflSI#}{qKEeJDF zl`z^@tp>|%6P_lbriHEixdiUn0Jaz^%7_#<%NM1GBRCj7J02~UIAnZl4LXe2^9A9= zY6(u8eMEbVK6dRBo~IR-;kgauJ5J6NIXh7qaa-8Hpw_XO`7^>#@ep6!#I;c#0TE)JCMWKB^iM7}$G(a*t!X zv^Ge4yo7Y)66t*}MN-_*edJ#EKhtXe6~x18pHmRdsSW1Tt{n{JY+o{dXfB9Y9RU;6 z&x#DCH#4)BpM!{;S+$v2wRR97DlyaIH%vp|qf>4c6kV>lR1+?!2^Q4g%g^7P*r*Nd z=mW%CSPO)=Fl#v#Uu8oeIojHpQ2vhPl>0_5tNLCBmzA?TwW71b%~xzB8D!b0amBvv29ZEqj@WQLN{O?Cnc~ zw=51A)x-Y)+0`o!+bV-L;zMjT^!e2T!G;5ywgYU#xh+`Gwy`r*(7l{`D|U$RY}j0R z&0LAi?N>)$d20D{I3E6sd^`4GQzDMiu%mv{QNP?vAC`lK_LYUp7cO0(-CYe=)Nm`; zzH)l`0x;_P+fhffT_CgS!F^P<9M$~Sdo?iyu+oS%@70!-=U>^eQnkGIx~YUIew9A( zJ%gV9y6u32`*v4V-wxfo`F8l<-R?S&ru#wm{yhBnQG=1(wsyEb-eEkDYIqMSV-D`U zG~C+zg-7+3w$dRIHeSrE|?O)r$CV8I9iYmB|<4fkw7&RlNp*xIb8)t&akqN$71%#)<_!`Aj_)+mDEqpZUDo@oJJ(ViF1M^m4f z_V!WCG$@A)Fa-i}P54_%nHJS8+B>BFBK$5nM0nW>4fF;{GApMB=3}cq6#{hUbTm`m z=p(#^3MkP2oj~h)Ok+n}#^XzQAKpJ=DSwn&k`>d^_ur5tmU5#xY;**Tj%&v9n^20` zzI6ES^|>GDbAIuObar4yB%D_Vvy^KmNC`UqR(f_gy(*Yq6;7|&Os`p+x|wHRIsQrp ztczskvD4Jx`&)v=E$buU%-x%ryEl$SEV*GzNzhWVx_i@7PYZV&7VhGj>#p6PD7UO- zVQX#BS{t_3U$fTV%q{rx!hI{3QF|@D=5`L}DhoUJ1f7s8ilH*(!rq{L=^(636qQ}v z@s{~|CA;$;zQ}Fs?rnts2fHia{;G4^H{wk(1&cuSuF9#0+Dlco$*Hqof!r6eL2V=_tn)_m#{_-h3))~!m^ ze*qMvm2*=7a+DU)Er4$-6hEQfOlMC4*=UqNT4|{OXZbQXOWg^15rr;}R+{qiR2Kmz zna>9-3r;!tl3GBHh0zEl(q;fm@^bh1m`^^(Ev0=12qZZ56h0;4#N`>lJzA)}>Xi8V z_?ca|ZcZjMvG4`Sb-@xY8+$}}nU@{7B=XWv=ppEhBwum(Xav0jvEQ(me&TZ{KKGfE z7oM#-apA-TfdD{;6K@%i1htaJ?A=>%I@Cm$|%{c_*WI-Un7btUcfR`goR*0rAq0>@6v#RLW@j$A$rt^&4p4a414S-$XF)P zA2f<)F-O$Ypf!}Zbl6lsLnpIDWs`qIhQQn2!;QYUH8;sV3wEA<>dpxB3trg1SU3gX zY?dCG5)T=HO&6_#MRm**wMbbIgH9jT!-1}GVGj7a&nLlX(g&?=61d_AZD3#T!eOrH zF-2e{hO5b`D~TC#U0J1sx6?BIS=8X;m$(nDxnMh&jW=@&0)@5V!q#9RtOvt(Q7ET< z=>U`!KX8;0?`ePe$i*X(f~Ix*dd9}7E7|WAe!uVsjw|Ls9$9J1&LM8J9Lg1W(9En2 z<^uX&*M@Rou6a9lx|RkQJi~y=MfC-LROMeZQB|d5cK8L$?$WPnGo&<7*opaBFHtf- ziYgj18>1ooTjcy_diEUoX2^Mselp1OGvs64qop(j(ow87B*0r93V)iMuaPrD;pfP= zLe3>PJ{n80?T3B)M8W5CcplyTn|BB``OlH;uR%Jk*-JRl|5}3oe_(!mVsXJg1x98v_A;94qtlJC-<`DX2!yN%x#SR5(@A7S0CvGW*iK&v zXlrZ~MO5P z7I;P&pZyJDdZ%GGPVhX~{p38w+8>@iMZ9N1H9tvIAhzR^ti`vqP}`GpormSY2~W@b z1UtAYW$T}M@Co83o?P9MZ^x7K#R1sK*aDZ_uX~;xJ83wue(e#5$b+sYM^~z}Zweo% zoTO97z6ae;P9muUzIYGB${`T2_n_}nr?jOb59)lPm`26(ogt_^#G2s9$kKdyC_6>4=Mb0sL_9lGM^ewx_ zqIJo86r_%VFqmtceBjn%Sl0pa^^x-uIVazePSQ>=+qpZxYi9ALN`F8-uoK7qajn zCKp@hi8B_+Ar{W}O}zQ|ce$51NEU{Ih`#W)PE(u`&^m6LI78NQC!VaFjOg=M;LBYp zis-YKzYx)zm&q5%t(06-Afp721DR!sAxuki5qfh( z#i#a<=u49UZQe&(OCCyXyH}R3>sNhLq|%M3?x(19+hKK2xBY%ffiCwp2j`<*`MSK1 zb`O0x9~)=-FO*crvSZ~7=Mh)Kij^xX91{H)-{ z3*jSj7;nM`%{oOj@lddxo{fD-g$_cAMcc-T)*%+AQ}|1~z<=?hzc_-aQr%IjRoc6H zPL=j|T;AVv`5$nZ_fuXpsoL&v?r!pyRJyMJ_MXV*M#Ny*IZ ze!oBI9LYu)Xm7jydHg#+{m$?G`2IfM$M0;-&Qeq0KKn0=zAtuC)IZ`6!O0VW`ymLN zrW{m|I!rm}0Xj&Nrz9vLPias}p0c2fJmoeQn)n$O+s4tV#I7_p?u zMEA%e(H`pl2k_HPx#`o?Au32ipCyNNL1{iU3vCI?9Fm~iAq^_(si4v!Gg3j7Lk@n1 zLjk_pp#(qEp#opy$N)dfp$0$OVQ|t-v?J30eUt2LVD@OR zZgO_Ie)i~5-=vr1hdhl9Bs4u0YI~ks+Y`C!Cw-pD#DtTnkbn=5b2s-t^@3R?3NFM$$iS%i=Y!$0?211-pgM6@SgR zr@aBc8#YTyjzW20-GHvtZUlMp765p1|s}!*6RY#eT;Sck3+^e$YYc6 zvVeE$D4&5O6CkK+cf}oQRKWePGV- zWoxJBrh>jncYVM;TkChvP0iK^PWYy#0`;!Iaj4oOP@hEWXQ5~~2~O4d=XsUTBi@{b zB-ROU3!q^EB@mU6rm~ zCUeTh^g^2CbN#_H1ZygNI=_4ZN{PF%d?A#4A|`Umn-gOvmdfC9_SBrRG&$pA{S^uh zLg**1^$iaCDc>gbD-L+Z3OBrhpF3b*94o@wp+9NfI~6r4u?_{!Z~X8bdXJ(eEJBGB zRv`p!OdR0C^YFH|zlK!-8U5ZtQE>~WGBbS zNvG7PO|CsWkaSdkHmtsk2{ETLLr86j^yE{lU$de9oMb5ueHq0( zZAVrm%M?Nr)k3IAqM$sTSc6|R{)8DAPdfwSPn!YFxC+KumTawsa%A~cJ5_y>p$=Hx zB*k+iO^?vaFH9x3h2uy4l8ggWfZNqdli)0XfXCw;-=O!9PQrhNoJGf=CY z6xz8(D0GXRd6Z#i`dD_BwKJoSwEf)!!+pI>Ez=L@riTfYrxuiY%rme1W)laty+2et z*1dl}$=5wKm72vDU;x6yxj8it0fG{|o^e|Np8>6L5w3`rA$H+qQ#dMeZ;5 z0WU9wGoM!;gSgq*ATI;F!ScBW_Bwi9$O^bdyGQ!=k9GI-@v@-zRFIdjUS1LKy4lI& zd=_wBDG3zrV3744;iXgF8D7SE{VcC=`+?{1@R@Ps@w(R)oaHrhGXSDacs(wUkLA^Y z**SL7I|ZZ6XG4Me=VyZMQ)IOGjJ{KoUOz!Raz6{egjc@qWsl4Tyf!t9=#p1>aaaSq zO6V$7O!|Qn#p1r|0a83PfTlk=dHKjx|qGHt@}(@uqG?Y(%&`~ zoeiA{MU7j+#w{O6m-f7$6)|p)8oR>Au848hqU?^DxiD~kU`evn9NpRz-rB=e_g?Mc zD)&Uq1B<;e$Uc8|{@wYQwd}(D`T29Q+eMZOJKo!I@z8Q_v}nhTq8&eWeEianUW%D* zOU)0ZlCHuB6nJY2s$tu5d30NEcv~;m*mv~+S37cU^bS+^!xP^-vC4GE%C=l?ztq0; z)thBqSF3N84L?v(Ra-x3UY5OY|IXJ|^s8IiA4sVR+mhisgG+(`ym#4qtGxZ!w%R4n z`-N*ts-$*JO_gl9oOLNHR#~-F5VduNZJjHgXxH9w*WQS2jH}wWMgdb@6RTJm%>aBx_Q-VvppZR9aH{poFpv(s#9#Y0JaiG^Nqs*5oYi{cg{a z;yXhZJGq8&PXFSn#`(F)x+uS`%Ukq*Q*&2J6*B(=Ae}b%oj2ckbKy;h=kohjb^V{^ z8ozi@MCF;z4xSl|8fwFa+7ITr+#L@nX_h{w%RifSCM%X-aIPR~stuc z(=M){`>N@B3^zR8{PXTV-?`c8=K;{^PQyYzX5^F(+iun3?5z#`9>*CAVnPvqD(BtCAQ@|UQQ?VYi41F(wQ_{~x#)i50 z25e*mIP zvp)b)ZoWS@LzGMD5G9M84o{y9W!2T!pR|Lx1s}mHG_OvIgIHvFSma_@20i3LLdABb z15$Q@v>IU^B1Yc~MnM@}b|#dUS|Ez7*>MC|RH&0=6l2mCz*Nlzyou5Vgv?zwJHz5I zjfu50|xbmX1aBo#N69ubzK($$ItWbFbbi9$V~>87*huIQz!CZ#+;?OxbsvmlPjn zUdfDh`c=Ym#OD7^F+W>f( zDgbzytQWG*XT^${iv`h=mT*bSvM0Lzh4A(lA|-n`W&nWKAYC3Sua1_tgv(pFvhCdV zaqdMoH+}>lm&HT>Rw^|W0@$+dqt^;3K!Vkms$+(&E9x~VRaX03N>Zj@9DJar%+_xQ zFAiSbduea9d27w+bG=D5@CjkpGfu7;KgQ zvQ-Y@b*NDRnL+NOx_^`~9fvoO*Y%&=1T)ep(mIe;{Y6OYB!GK#(7z21P>nPxG4abL zkR>F}Das7|^w7L;dJ;*8D2W591(801bWX&44%xUIP?9KPv{E2(ko)C3CH8TmDc3VO|*`6$hmnBMQm`3>*Lg^DF8uK_+0+JOer9l}CLu z9+0gQx*DgYBDJ(B;{33j)S4ufygFGsi-a+N>m<(R5*C*?lN?EDe!%6MaS4s&8`2TV z&2oGP>$nIWkW5|9f}HTZ-%iK(%Cko4r!u}VOUZ?a^Ak&bxc3Lc+XlJDq3cat_1=hO zY*Br?py=G-x3urd2~W6|PwDa&G+#VWQU=QdipKJR+bh^}J?FZV%O8&FMsDavfKS6W zj{+zb{o8J4w?^`bCUbX#{1apL?yd4qw#p&AZfBu9n(VAWauUE=goup}q~r8aNM#27 zcp3crC9rcKecX-7gi!KXA}S->X%{^wnUlK27{7D^=ZUy6l@LlkOF%m4paqF>n*1oS z7G>tid`?le4S1aV30ioNH%c9nR4xYgg+|CZmWGc@$H67#ykp{C6v{{{F9B66#zefD zd=FA^DxG{B_PH!&pDQOsMCeosyo6u!SbrqA-?vf%W<3V8VWxZ%+|yR_Hynz~4L46U z4&?~xCJ+r0CsGepQkLtG6M5x&>H$*GohmHDK|-n2W0a@akUp+MPnBD;xWL$b;5~(w z%(A{?z8Uva3N5i|`79xt$o_a$&^zr%DIl*OhtMqRX6Ji-tQS>p^SmhmqNkTh<>Kqt{3uPk-%7~L7l~6sq9}>l@Cz3v_3YDTf3TaWb3PcBx zAwhKGSRSEIxZ6ZA+$JPSsO8y7l#t{5P($E%-~oxsHAZtQ!nqX-s#s?3jm)CYat(Ko zY^}*O8H4og98)x>=0;9UG^cJgr*66GcAojH>s{BygG+&EUejt`)AGrfp(tvo3>zvh zo?10DJd{%T%^+7}s<`T|o7S#H%{mgdq=^_?qQ=g!u`^=a339z>A#aQFhdGqKeyxy@ zw}&RE0V;n4Rq5+*<+3z{$2l=%8l zU<7*qO-oM~^?jzNTKBfHlk{i#I`;dKNPVK*U|zXkuP0xV$v|1wpFf2;rtMOPri`Uv``NJ5ho zMqQDP*rgGih+sw_cIyNuA|VB$nnt;}S$PimIw6)KmHdws|1m8k$~cis!+JNKjjOrnaThkgyvn1oXf zw;Y0s*?wqW$VMa`z@aB3C@AN6Wo9;5;QUC7O4PxjwN)Z zh*Y3#5UHe!K1u*_}4Rf$FG#3CV}fH$&`L zh`0l6a{?@wZBi@>LdY5VWZ?(GIDShyOfMM50Qwhm%0YND&G`ick;& za)t2=FP(qsg6o`XsWei6;*bIqhZNXRs!)E}cFA_R{$l+S8?m5Jqo*`~Uxn5s{tmJ~&E)cF=9PZQfsz`80SSk8IEN`>@d11d z7-`Z@De~E1$srfgBzY6yU5ku=j2AF5v)Y=wgv66}<`nv^-`xb+XN6*+l2rM_hKfj>5t;$-=KKE6wxNgb2 zJg^Kxx2pwL)z?e7?BQFg5f*t|GRv@wiG$HcXhz+~qrQX2c|I&@`zygdYafmF*8AyK zzzzz0+95^1UJ7F@`weolyc7rpFFOLJ)VzZA2Itrrn}iG`F9rQP*Z?Zx=EZ!L%QZRW z4g@^D$)L+cECEYT$ML+z<#Nve{dWhwE*H`K^@Qw>~k4)g&w^^v#&!6o0O~sHUMEZ z2wngStE{Yyw_J;Ik^owJqJiU_moH1(`1OfM`{CS&>ThfHi%tLTtDMS`oEmsR9Ht;G zeGl3kN6ij2>`A;%eZVH5!F&r?UU-kwvcbW7Fk^#FN2L=y5VJNfpi&u#C~!d#mrl%( zPJ}Rl0(9b%LN)=zq%*ZcNk2MLYX#Jb3f(ly-=tw*JMCA9cnL)PV1`De6DK$ohjmGH zu|wAOV_3!wBt4y0ydVdmNE(C;qosg0;R4y<;EfX1at>80$$3KiMbS!X`x9nyUn8c0 zY3g=lkc$t%kC{Q#`ow;!Q~IeU{RC72ik;JSn(eyN-&>;HkgH4K)gi`Y7~}%u^qHcNM*}miy&1<`4&o+2RtG5Tb?=AePqfz z-~l3vG;>NIz>>H=l&9?>uz-uF?+j%#U7bwBskVl=dz3XZH+{ql=3U3oXzw?_|NZa3 z7Sh-mu~acfYk%B@N|@=nK#)1&1^wovZyE$(AXr~l%A*X?$}UvM4D^CDUNCM?)qjRs zMfE2d)x}#_FV>8l!yMcxO6YErWdv+`KI=eZbBgtw?m*-Ao`K$EOkAX23FDC>K_if3 zE5z|qG`agKzC{Wd*QBBvl*fBcb6%oDNYRE8E}|XY+f?xrssqz%u*G;95z97hqTxbB zTn#{Y19G12hAaV;CJXYlICjxtNn6t$WYB%QaYfF%l+OP%ma`kq9e^%q+ZHnwo$HO7 z>%!(buBd+L6lZK((8SEe=YmmdbJ*I<6}K$E!WDKeXhkcyHHkSZ?>1Pq4W1v2nrp)5 znh*9b7rcKcVs4L`cZJQnqUPOT*ajNX(1GkLLu_9}Xa)*t!^q8u1w`D!b5mzH@2sYEk=|L~SVr8@Om$Yq+d+MS8WA zD{GCE^<6JqE!z*8)5{~5Mq<@9O9wwZbmh>}-j&9c@sC{}xwwuIz$Bw|ORS}Bxo@Qy zxCh$+at{@iV2)NkPOs%tOnKDW6t;pUbma(VZHidCufBZCx;MsDiq>);TCP}@w9Bup z7(TXsWaZl88r#NLWAn0n`7jh((MQM4;%X^N4}DjTJV{rACmHzmRnn^Sq)()Q2p(zo~*|aXKWnT;>6Y+jJIx$0?WNMDdhk1L4HCpllQsK07@F7r$9 zYyzZ_t|TdzlsF|}Gec_W5bRGPqml^c!7&C)f|CVWztIV45^^MD+K6+AZ2^`@UWRI0 zk^LG&WVykmLux5K6IB?(d6f>a3CPYb2mC~(GXeusKSWmP&M?4L4pq|B??vUDq=A}~ z^q0asD4ohNQ6-yfI2iJ$tp(0Cby8Y)LNZhyA5p&wN=jclpq)Q??HNz3UGrzCUF}yM zIJb@k&rDI9W#YwCM|N`j`vLv>HGtaiY~SD-69_3Nw;&HKwSH0+Yb!p`+$x zDM&}>KwUg1@hg2#2?2KAoZd3Yc@RPyw{zr`bCU~#lh@hV5H1%=L%4;9be>DH`8$Wsj zWSfoezs}X~kki(Z-R9-_#a|BY6!S*2qk)_mnSyH_(N}6v|`bchivj z#g9PXw=_VP_HSWcLPbIre#1?-gY=57J*NU$aWn?B_LdL|80I8Q($A^pD#@7gXJ*X#;qRBHrvjmo1xSxj4 zIQx#W)by$ck$o5HPfSfUuCCXCGA8))HYh{EpPwv{;LIle`vL7FlOEI zt|pdaI%_#&xmb5Ar-4M=%|O;uL+Q+LX+Gte)%dFJMX;4!lXYaYt}}yyD{jM)svQxd zJ!;$?HtvoXd*NbSp#=p7tzl~`XKGs*1pd@gabfiQXv|QvQV1$S%T~CxX3^i5%gxz$ zWe{5Ih1i_Dv#K+ybKA~&qQ%?8#oIYc>vE7Yb==Z*-jzTiU~aQaYZT;J(}@LEFNf9@ zSooX;8lk|#)B@vX(a9G6fdyt$mMu5UEej*Ji_2p=OSo(=Trjg{FRIrRRE{xzCC$EK zU#a2xeVpOd-{S4FyGls0mQ7i=-Yjli*h_L+!xj4;&)Ko^^2#u`XMN5G*;G;Sw<;Ef z{=2s5q9)2Tg_$O>sJ_9p+-Axzt1qdeOhcGy_(AUSM5J+dgz1SgLt$np!i+3tL90sY z;#pz^mCJS*(~@SGq>_A?B&!}KsU-iL3}WGy-JUah&aoHEqoww6shulnUuotnyKWh} zVUkMnVUlvpFs4>LbfmcYre*s|;Y~}=qB>@^Ee+kYwt^j1X;sX?gex7;FSI;=sGtf; z&xB$H#c?zD9V^uiOM04NSSuUKUfXuH&u*mm;S22Fw!XfRihlj8v^fNBQ-%1^#!Wuq;lh3^v+Ak z<8d_?HU<7w_ffHh&6$8_-0cTD@2`LuMSyaC`hac>es6Tb`x;Rd1*{3!-Hf z<>ll%BB-JC!{Nj0;!l?-*iESUcxC)WfX_&1D?w9<4XkA1s!A4aAX0o`I^Y-2NjhHv ziJ&eTQpr3=cKN*m3s3g=cua;tBusv$y~zmT0AjX!8|*bkthXPlL9tecf4 zoE7jcm61oX9Z(=~$h?qQIS&450LYtu?ZXS>4ff4o%7ufq!8zc2U#*G$fgQ6@abyBvWf$-Cz zO|C|P0VLn+fHe&2uj7dtfOY^2k5r7fr1}fmp|ViJ*97&~rm1eFq$+>BETV9`l%h)0 z5f(Hgp9>zXZ3C`P<(-+F^#BI%=jrN@?quV3_%NDXq+0C1fil^Dh2CF*H}3mqP?ho0 zhPHzt4Z}DHt@C=|YGJDOjA37ev?2M6;v;fD}-wa0rDt`d3x6Z6Vx z-;B>QJI!YzF92LUD&=?sXsY_+-@ORzo*#D~8^v!)Xn^VLCLes53&Jl*BmRmjvxsi% zZKxZ_uzLneRpJw@MB?@$8UXRhEOtzod6I?iPq>cE!$+@pd2kkSI?C276JqX8v zyAsq<$i?tdELb%SqjuB>>;XaW>ZBfw*UWgm9>Ci1gFsFcdb|Sddf!&G`}jG zUlq-74(B&7ABg04E=prX%%bX!*%~!Bht18)`)`?dfa(j?Ts7gM8m@L{v}o6jqFtXA zFn3BSq9xnHCEK{BzDP;`;$W<-YH|3E(R%i+GjA)XLHU_CZ-crg`#)tby|!E(VLH#rQ#E6^E2@_A zzVq_={ySA*c6afObN$fwXn`$UVB@MgR*tR~^xZD7#EPqz+CJ>O(z#T>V!rz7)d_B& zlRI#Ta~|f*E>Odn@Cy_s=3?XDXwE4?&-R|`ZXZoq><{TxC1F_`kd-Yx4dk$hsx?`xNRQcq*JF~2`sc1pLXG$@goz-8g{IrO~TWAayllYR%{zmzy4K(;yC>#A7q{cQQuX4HY4l&q3sa-Bu z!gKJ^I2RArQLoLprxI_pF1W?P27>r~l$lwVE6x{x9SUH-feW>;Z@6!yZ*<({=o{`H zAJ{v(f6u^}Yy73LzWpp7666ymEFO&Pi|9?Fhi0Ix9zC*2u{kWVkp%9rWCP>@5&{D) z-y~5j`;NdS4?yE`Qmne*qb*YqB^Y|ee*#|ouUL|Z7kBN7Z!5$BEP7a5APC;?z5yHO zPT@gLW{epIPmjcm`#725PDvRjE50YIlAG?0(>3yfdlPgc_($n%dEPxH54^f9@Y_Kv z{J2%6?S8Cw0%+!3HEt^-mTqrVdu~(r3Y#n+pqlX}t z*C$D7U4oLbKf)Jk{O(R2`Rq>oN)2|OMPN>v!u~f*q;bGk^nB9^&Hu0PWdVAqJ>yj! z@#b~0x8Wy{94asj9;|bk1`djrfz>RX{W+EKIi>xa%Kk^n^f{IDzo?1_idPl1kt?iy jKw)6*S&@e(Guf4P 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 diff --git a/skills/_shared/office/validators/__pycache__/redlining.cpython-314.pyc b/skills/_shared/office/validators/__pycache__/redlining.cpython-314.pyc deleted file mode 100644 index 295144e3cfab1cd818949bcb1e33c9a65cdcc734..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11816 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

4;A5$y?yZQ)^yE@4eLn^e^`d^1^CZ) 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 Date: Sun, 15 Feb 2026 01:32:32 +0800 Subject: [PATCH 3/3] refactor(skills): merge check_fillable_fields into extract_form_field_info The 12-line check_fillable_fields.py was a subset of extract_form_field_info.py. Add a --check flag to the latter and remove the standalone script. Update forms.md reference accordingly. Co-Authored-By: Claude Opus 4.6 --- skills/pdf/forms.md | 2 +- skills/pdf/scripts/check_fillable_fields.py | 11 ----------- skills/pdf/scripts/extract_form_field_info.py | 15 +++++++++++++-- 3 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 skills/pdf/scripts/check_fillable_fields.py diff --git a/skills/pdf/forms.md b/skills/pdf/forms.md index 6e7e1e0d..2dece27c 100644 --- a/skills/pdf/forms.md +++ b/skills/pdf/forms.md @@ -1,7 +1,7 @@ **CRITICAL: You MUST complete these steps in order. Do not skip ahead to writing code.** If you need to fill out a PDF form, first check to see if the PDF has fillable form fields. Run this script from this file's directory: - `python scripts/check_fillable_fields `, and depending on the result go to either the "Fillable fields" or "Non-fillable fields" and follow those instructions. + `python scripts/extract_form_field_info.py --check `, and depending on the result go to either the "Fillable fields" or "Non-fillable fields" and follow those instructions. # Fillable fields If the PDF has fillable form fields: diff --git a/skills/pdf/scripts/check_fillable_fields.py b/skills/pdf/scripts/check_fillable_fields.py deleted file mode 100644 index 36dfb951..00000000 --- a/skills/pdf/scripts/check_fillable_fields.py +++ /dev/null @@ -1,11 +0,0 @@ -import sys -from pypdf import PdfReader - - - - -reader = PdfReader(sys.argv[1]) -if (reader.get_fields()): - print("This PDF has fillable form fields") -else: - print("This PDF does not have fillable form fields; you will need to visually determine where to enter data") diff --git a/skills/pdf/scripts/extract_form_field_info.py b/skills/pdf/scripts/extract_form_field_info.py index 64cd4703..1a07842d 100644 --- a/skills/pdf/scripts/extract_form_field_info.py +++ b/skills/pdf/scripts/extract_form_field_info.py @@ -116,7 +116,18 @@ def write_field_info(pdf_path: str, json_output_path: str): if __name__ == "__main__": - if len(sys.argv) != 3: + if len(sys.argv) >= 2 and sys.argv[1] == "--check": + if len(sys.argv) != 3: + print("Usage: extract_form_field_info.py --check ") + sys.exit(1) + reader = PdfReader(sys.argv[2]) + if reader.get_fields(): + print("This PDF has fillable form fields") + else: + print("This PDF does not have fillable form fields; you will need to visually determine where to enter data") + elif len(sys.argv) == 3: + write_field_info(sys.argv[1], sys.argv[2]) + else: print("Usage: extract_form_field_info.py [input pdf] [output json]") + print(" extract_form_field_info.py --check ") sys.exit(1) - write_field_info(sys.argv[1], sys.argv[2])