@farazirfan/costar-server-executor 1.7.37 → 1.7.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/dist/agent/agent.d.ts +90 -0
  2. package/dist/agent/agent.d.ts.map +1 -1
  3. package/dist/agent/agent.js +606 -0
  4. package/dist/agent/agent.js.map +1 -1
  5. package/dist/agent/pi-embedded-runner/run.d.ts.map +1 -1
  6. package/dist/agent/pi-embedded-runner/run.js +2 -1
  7. package/dist/agent/pi-embedded-runner/run.js.map +1 -1
  8. package/dist/agent/pi-embedded-runner/system-prompt.d.ts.map +1 -1
  9. package/dist/agent/pi-embedded-runner/system-prompt.js +16 -37
  10. package/dist/agent/pi-embedded-runner/system-prompt.js.map +1 -1
  11. package/dist/agent/pi-embedded-runner/tools.d.ts +4 -1
  12. package/dist/agent/pi-embedded-runner/tools.d.ts.map +1 -1
  13. package/dist/agent/pi-embedded-runner/tools.js +3 -1
  14. package/dist/agent/pi-embedded-runner/tools.js.map +1 -1
  15. package/dist/agent/pi-embedded-runner/types.d.ts +4 -0
  16. package/dist/agent/pi-embedded-runner/types.d.ts.map +1 -1
  17. package/dist/cli/env-loader.d.ts.map +1 -1
  18. package/dist/cli/env-loader.js +1 -0
  19. package/dist/cli/env-loader.js.map +1 -1
  20. package/dist/cli/setup.js +2 -2
  21. package/dist/cli/setup.js.map +1 -1
  22. package/dist/cron/normalize.d.ts +31 -0
  23. package/dist/cron/normalize.d.ts.map +1 -0
  24. package/dist/cron/normalize.js +211 -0
  25. package/dist/cron/normalize.js.map +1 -0
  26. package/dist/cron/scheduler.d.ts +33 -3
  27. package/dist/cron/scheduler.d.ts.map +1 -1
  28. package/dist/cron/scheduler.js +253 -48
  29. package/dist/cron/scheduler.js.map +1 -1
  30. package/dist/heartbeat/runner.d.ts +27 -12
  31. package/dist/heartbeat/runner.d.ts.map +1 -1
  32. package/dist/heartbeat/runner.js +82 -104
  33. package/dist/heartbeat/runner.js.map +1 -1
  34. package/dist/infra/heartbeat-events-filter.d.ts +29 -0
  35. package/dist/infra/heartbeat-events-filter.d.ts.map +1 -0
  36. package/dist/infra/heartbeat-events-filter.js +80 -0
  37. package/dist/infra/heartbeat-events-filter.js.map +1 -0
  38. package/dist/infra/index.d.ts +9 -0
  39. package/dist/infra/index.d.ts.map +1 -0
  40. package/dist/infra/index.js +9 -0
  41. package/dist/infra/index.js.map +1 -0
  42. package/dist/infra/system-events.d.ts +58 -2
  43. package/dist/infra/system-events.d.ts.map +1 -1
  44. package/dist/infra/system-events.js +80 -14
  45. package/dist/infra/system-events.js.map +1 -1
  46. package/dist/server.d.ts.map +1 -1
  47. package/dist/server.js +6 -1
  48. package/dist/server.js.map +1 -1
  49. package/dist/services/platform-keys.d.ts +19 -0
  50. package/dist/services/platform-keys.d.ts.map +1 -0
  51. package/dist/services/platform-keys.js +74 -0
  52. package/dist/services/platform-keys.js.map +1 -0
  53. package/dist/subagent/registry.d.ts +96 -0
  54. package/dist/subagent/registry.d.ts.map +1 -0
  55. package/dist/subagent/registry.js +180 -0
  56. package/dist/subagent/registry.js.map +1 -0
  57. package/dist/tools/complete-turn.d.ts +2 -2
  58. package/dist/tools/complete-turn.js +10 -10
  59. package/dist/tools/complete-turn.js.map +1 -1
  60. package/dist/tools/contacts.d.ts +13 -0
  61. package/dist/tools/contacts.d.ts.map +1 -0
  62. package/dist/tools/contacts.js +80 -0
  63. package/dist/tools/contacts.js.map +1 -0
  64. package/dist/tools/cron.d.ts +17 -2
  65. package/dist/tools/cron.d.ts.map +1 -1
  66. package/dist/tools/cron.js +117 -35
  67. package/dist/tools/cron.js.map +1 -1
  68. package/dist/tools/google-maps.d.ts +6 -6
  69. package/dist/tools/google-maps.d.ts.map +1 -1
  70. package/dist/tools/google-maps.js +207 -262
  71. package/dist/tools/google-maps.js.map +1 -1
  72. package/dist/tools/index.d.ts +17 -7
  73. package/dist/tools/index.d.ts.map +1 -1
  74. package/dist/tools/index.js +40 -9
  75. package/dist/tools/index.js.map +1 -1
  76. package/dist/tools/phone-call.d.ts +11 -0
  77. package/dist/tools/phone-call.d.ts.map +1 -0
  78. package/dist/tools/phone-call.js +151 -0
  79. package/dist/tools/phone-call.js.map +1 -0
  80. package/dist/tools/sessions-spawn.d.ts +33 -0
  81. package/dist/tools/sessions-spawn.d.ts.map +1 -0
  82. package/dist/tools/sessions-spawn.js +164 -0
  83. package/dist/tools/sessions-spawn.js.map +1 -0
  84. package/dist/tools/spotify.d.ts +12 -0
  85. package/dist/tools/spotify.d.ts.map +1 -0
  86. package/dist/tools/spotify.js +251 -0
  87. package/dist/tools/spotify.js.map +1 -0
  88. package/dist/tools/subagents.d.ts +23 -0
  89. package/dist/tools/subagents.d.ts.map +1 -0
  90. package/dist/tools/subagents.js +209 -0
  91. package/dist/tools/subagents.js.map +1 -0
  92. package/dist/tools/whatsapp.d.ts +13 -0
  93. package/dist/tools/whatsapp.d.ts.map +1 -0
  94. package/dist/tools/whatsapp.js +215 -0
  95. package/dist/tools/whatsapp.js.map +1 -0
  96. package/dist/tools/youtube.d.ts +12 -0
  97. package/dist/tools/youtube.d.ts.map +1 -0
  98. package/dist/tools/youtube.js +218 -0
  99. package/dist/tools/youtube.js.map +1 -0
  100. package/dist/utils/asterizk-auth.d.ts +43 -0
  101. package/dist/utils/asterizk-auth.d.ts.map +1 -0
  102. package/dist/utils/asterizk-auth.js +125 -0
  103. package/dist/utils/asterizk-auth.js.map +1 -0
  104. package/dist/web-server.d.ts.map +1 -1
  105. package/dist/web-server.js +132 -0
  106. package/dist/web-server.js.map +1 -1
  107. package/dist/workspace/index.d.ts +3 -4
  108. package/dist/workspace/index.d.ts.map +1 -1
  109. package/dist/workspace/index.js +3 -4
  110. package/dist/workspace/index.js.map +1 -1
  111. package/dist/workspace/templates.d.ts +8 -7
  112. package/dist/workspace/templates.d.ts.map +1 -1
  113. package/dist/workspace/templates.js +18 -127
  114. package/dist/workspace/templates.js.map +1 -1
  115. package/dist/workspace/workspace.d.ts +2 -4
  116. package/dist/workspace/workspace.d.ts.map +1 -1
  117. package/dist/workspace/workspace.js +7 -16
  118. package/dist/workspace/workspace.js.map +1 -1
  119. package/package.json +1 -1
  120. package/public/index.html +231 -0
  121. package/skills/docx/SKILL.md +468 -0
  122. package/skills/docx/scripts/__init__.py +1 -0
  123. package/skills/docx/scripts/accept_changes.py +181 -0
  124. package/skills/docx/scripts/comment.py +347 -0
  125. package/skills/docx/scripts/helpers/__init__.py +0 -0
  126. package/skills/docx/scripts/helpers/merge_runs.py +231 -0
  127. package/skills/docx/scripts/helpers/simplify_redlines.py +240 -0
  128. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  129. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  130. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  131. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  132. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  133. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  134. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  135. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  136. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  137. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  138. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  139. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  140. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  141. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  142. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  143. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  144. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  145. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  146. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  147. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  148. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  149. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  150. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  151. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  152. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  153. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  154. package/skills/docx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  155. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  156. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  157. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  158. package/skills/docx/scripts/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  159. package/skills/docx/scripts/ooxml/schemas/mce/mc.xsd +75 -0
  160. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  161. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  162. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  163. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  164. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  165. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  166. package/skills/docx/scripts/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  167. package/skills/docx/scripts/ooxml/scripts/pack.py +159 -0
  168. package/skills/docx/scripts/ooxml/scripts/unpack.py +29 -0
  169. package/skills/docx/scripts/ooxml/scripts/validate.py +106 -0
  170. package/skills/docx/scripts/ooxml/scripts/validation/__init__.py +15 -0
  171. package/skills/docx/scripts/ooxml/scripts/validation/base.py +1023 -0
  172. package/skills/docx/scripts/ooxml/scripts/validation/docx.py +519 -0
  173. package/skills/docx/scripts/ooxml/scripts/validation/pptx.py +315 -0
  174. package/skills/docx/scripts/ooxml/scripts/validation/redlining.py +284 -0
  175. package/skills/docx/scripts/pack.py +166 -0
  176. package/skills/docx/scripts/templates/comments.xml +3 -0
  177. package/skills/docx/scripts/templates/commentsExtended.xml +3 -0
  178. package/skills/docx/scripts/templates/commentsExtensible.xml +3 -0
  179. package/skills/docx/scripts/templates/commentsIds.xml +3 -0
  180. package/skills/docx/scripts/templates/people.xml +3 -0
  181. package/skills/docx/scripts/unpack.py +134 -0
  182. package/skills/longform-video-generation/SKILL.md +298 -0
  183. package/skills/longform-video-generation/references/advanced_techniques.md +474 -0
  184. package/skills/longform-video-generation/references/google_api_guide.md +288 -0
  185. package/skills/longform-video-generation/scripts/video_generator.py +579 -0
  186. package/skills/pdf/FORMS.md +305 -0
  187. package/skills/pdf/REFERENCE.md +612 -0
  188. package/skills/pdf/SKILL.md +293 -0
  189. package/skills/pdf/scripts/check_bounding_boxes.py +70 -0
  190. package/skills/pdf/scripts/check_fillable_fields.py +12 -0
  191. package/skills/pdf/scripts/convert_pdf_to_images.py +35 -0
  192. package/skills/pdf/scripts/create_validation_image.py +41 -0
  193. package/skills/pdf/scripts/extract_form_field_info.py +152 -0
  194. package/skills/pdf/scripts/extract_form_structure.py +124 -0
  195. package/skills/pdf/scripts/fill_fillable_fields.py +116 -0
  196. package/skills/pdf/scripts/fill_pdf_form_with_annotations.py +136 -0
  197. package/skills/pptx/SKILL.md +171 -0
  198. package/skills/pptx/editing.md +205 -0
  199. package/skills/pptx/pptxgenjs.md +377 -0
  200. package/skills/pptx/scripts/add_slide.py +225 -0
  201. package/skills/pptx/scripts/clean.py +309 -0
  202. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +1499 -0
  203. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +146 -0
  204. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +1085 -0
  205. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +11 -0
  206. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +3081 -0
  207. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +23 -0
  208. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +185 -0
  209. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +287 -0
  210. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +1676 -0
  211. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +28 -0
  212. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +144 -0
  213. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +174 -0
  214. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +25 -0
  215. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +18 -0
  216. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +59 -0
  217. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +56 -0
  218. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +195 -0
  219. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +582 -0
  220. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +25 -0
  221. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +4439 -0
  222. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +570 -0
  223. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +509 -0
  224. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +12 -0
  225. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +108 -0
  226. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +96 -0
  227. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +3646 -0
  228. package/skills/pptx/scripts/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +116 -0
  229. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +42 -0
  230. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +50 -0
  231. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +49 -0
  232. package/skills/pptx/scripts/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +33 -0
  233. package/skills/pptx/scripts/ooxml/schemas/mce/mc.xsd +75 -0
  234. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2010.xsd +560 -0
  235. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2012.xsd +67 -0
  236. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-2018.xsd +14 -0
  237. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-cex-2018.xsd +20 -0
  238. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-cid-2016.xsd +13 -0
  239. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +4 -0
  240. package/skills/pptx/scripts/ooxml/schemas/microsoft/wml-symex-2015.xsd +8 -0
  241. package/skills/pptx/scripts/ooxml/scripts/pack.py +159 -0
  242. package/skills/pptx/scripts/ooxml/scripts/unpack.py +29 -0
  243. package/skills/pptx/scripts/ooxml/scripts/validate.py +106 -0
  244. package/skills/pptx/scripts/ooxml/scripts/validation/__init__.py +15 -0
  245. package/skills/pptx/scripts/ooxml/scripts/validation/base.py +1023 -0
  246. package/skills/pptx/scripts/ooxml/scripts/validation/docx.py +519 -0
  247. package/skills/pptx/scripts/ooxml/scripts/validation/pptx.py +315 -0
  248. package/skills/pptx/scripts/ooxml/scripts/validation/redlining.py +284 -0
  249. package/skills/pptx/scripts/pack.py +168 -0
  250. package/skills/pptx/scripts/thumbnail.py +318 -0
  251. package/skills/pptx/scripts/unpack.py +86 -0
  252. package/skills/xlsx/SKILL.md +291 -0
  253. package/skills/xlsx/recalc.py +247 -0
@@ -0,0 +1,315 @@
1
+ """
2
+ Validator for PowerPoint presentation XML files against XSD schemas.
3
+ """
4
+
5
+ import re
6
+
7
+ from .base import BaseSchemaValidator
8
+
9
+
10
+ class PPTXSchemaValidator(BaseSchemaValidator):
11
+ """Validator for PowerPoint presentation XML files against XSD schemas."""
12
+
13
+ # PowerPoint presentation namespace
14
+ PRESENTATIONML_NAMESPACE = (
15
+ "http://schemas.openxmlformats.org/presentationml/2006/main"
16
+ )
17
+
18
+ # PowerPoint-specific element to relationship type mappings
19
+ ELEMENT_RELATIONSHIP_TYPES = {
20
+ "sldid": "slide",
21
+ "sldmasterid": "slidemaster",
22
+ "notesmasterid": "notesmaster",
23
+ "sldlayoutid": "slidelayout",
24
+ "themeid": "theme",
25
+ "tablestyleid": "tablestyles",
26
+ }
27
+
28
+ def validate(self):
29
+ """Run all validation checks and return True if all pass."""
30
+ # Test 0: XML well-formedness
31
+ if not self.validate_xml():
32
+ return False
33
+
34
+ # Test 1: Namespace declarations
35
+ all_valid = True
36
+ if not self.validate_namespaces():
37
+ all_valid = False
38
+
39
+ # Test 2: Unique IDs
40
+ if not self.validate_unique_ids():
41
+ all_valid = False
42
+
43
+ # Test 3: UUID ID validation
44
+ if not self.validate_uuid_ids():
45
+ all_valid = False
46
+
47
+ # Test 4: Relationship and file reference validation
48
+ if not self.validate_file_references():
49
+ all_valid = False
50
+
51
+ # Test 5: Slide layout ID validation
52
+ if not self.validate_slide_layout_ids():
53
+ all_valid = False
54
+
55
+ # Test 6: Content type declarations
56
+ if not self.validate_content_types():
57
+ all_valid = False
58
+
59
+ # Test 7: XSD schema validation
60
+ if not self.validate_against_xsd():
61
+ all_valid = False
62
+
63
+ # Test 8: Notes slide reference validation
64
+ if not self.validate_notes_slide_references():
65
+ all_valid = False
66
+
67
+ # Test 9: Relationship ID reference validation
68
+ if not self.validate_all_relationship_ids():
69
+ all_valid = False
70
+
71
+ # Test 10: Duplicate slide layout references validation
72
+ if not self.validate_no_duplicate_slide_layouts():
73
+ all_valid = False
74
+
75
+ return all_valid
76
+
77
+ def validate_uuid_ids(self):
78
+ """Validate that ID attributes that look like UUIDs contain only hex values."""
79
+ import lxml.etree
80
+
81
+ errors = []
82
+ # UUID pattern: 8-4-4-4-12 hex digits with optional braces/hyphens
83
+ uuid_pattern = re.compile(
84
+ 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}[\}\)]?$"
85
+ )
86
+
87
+ for xml_file in self.xml_files:
88
+ try:
89
+ root = lxml.etree.parse(str(xml_file)).getroot()
90
+
91
+ # Check all elements for ID attributes
92
+ for elem in root.iter():
93
+ for attr, value in elem.attrib.items():
94
+ # Check if this is an ID attribute
95
+ attr_name = attr.split("}")[-1].lower()
96
+ if attr_name == "id" or attr_name.endswith("id"):
97
+ # Check if value looks like a UUID (has the right length and pattern structure)
98
+ if self._looks_like_uuid(value):
99
+ # Validate that it contains only hex characters in the right positions
100
+ if not uuid_pattern.match(value):
101
+ errors.append(
102
+ f" {xml_file.relative_to(self.unpacked_dir)}: "
103
+ f"Line {elem.sourceline}: ID '{value}' appears to be a UUID but contains invalid hex characters"
104
+ )
105
+
106
+ except (lxml.etree.XMLSyntaxError, Exception) as e:
107
+ errors.append(
108
+ f" {xml_file.relative_to(self.unpacked_dir)}: Error: {e}"
109
+ )
110
+
111
+ if errors:
112
+ print(f"FAILED - Found {len(errors)} UUID ID validation errors:")
113
+ for error in errors:
114
+ print(error)
115
+ return False
116
+ else:
117
+ if self.verbose:
118
+ print("PASSED - All UUID-like IDs contain valid hex values")
119
+ return True
120
+
121
+ def _looks_like_uuid(self, value):
122
+ """Check if a value has the general structure of a UUID."""
123
+ # Remove common UUID delimiters
124
+ clean_value = value.strip("{}()").replace("-", "")
125
+ # Check if it's 32 hex-like characters (could include invalid hex chars)
126
+ return len(clean_value) == 32 and all(c.isalnum() for c in clean_value)
127
+
128
+ def validate_slide_layout_ids(self):
129
+ """Validate that sldLayoutId elements in slide masters reference valid slide layouts."""
130
+ import lxml.etree
131
+
132
+ errors = []
133
+
134
+ # Find all slide master files
135
+ slide_masters = list(self.unpacked_dir.glob("ppt/slideMasters/*.xml"))
136
+
137
+ if not slide_masters:
138
+ if self.verbose:
139
+ print("PASSED - No slide masters found")
140
+ return True
141
+
142
+ for slide_master in slide_masters:
143
+ try:
144
+ # Parse the slide master file
145
+ root = lxml.etree.parse(str(slide_master)).getroot()
146
+
147
+ # Find the corresponding _rels file for this slide master
148
+ rels_file = slide_master.parent / "_rels" / f"{slide_master.name}.rels"
149
+
150
+ if not rels_file.exists():
151
+ errors.append(
152
+ f" {slide_master.relative_to(self.unpacked_dir)}: "
153
+ f"Missing relationships file: {rels_file.relative_to(self.unpacked_dir)}"
154
+ )
155
+ continue
156
+
157
+ # Parse the relationships file
158
+ rels_root = lxml.etree.parse(str(rels_file)).getroot()
159
+
160
+ # Build a set of valid relationship IDs that point to slide layouts
161
+ valid_layout_rids = set()
162
+ for rel in rels_root.findall(
163
+ f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
164
+ ):
165
+ rel_type = rel.get("Type", "")
166
+ if "slideLayout" in rel_type:
167
+ valid_layout_rids.add(rel.get("Id"))
168
+
169
+ # Find all sldLayoutId elements in the slide master
170
+ for sld_layout_id in root.findall(
171
+ f".//{{{self.PRESENTATIONML_NAMESPACE}}}sldLayoutId"
172
+ ):
173
+ r_id = sld_layout_id.get(
174
+ f"{{{self.OFFICE_RELATIONSHIPS_NAMESPACE}}}id"
175
+ )
176
+ layout_id = sld_layout_id.get("id")
177
+
178
+ if r_id and r_id not in valid_layout_rids:
179
+ errors.append(
180
+ f" {slide_master.relative_to(self.unpacked_dir)}: "
181
+ f"Line {sld_layout_id.sourceline}: sldLayoutId with id='{layout_id}' "
182
+ f"references r:id='{r_id}' which is not found in slide layout relationships"
183
+ )
184
+
185
+ except (lxml.etree.XMLSyntaxError, Exception) as e:
186
+ errors.append(
187
+ f" {slide_master.relative_to(self.unpacked_dir)}: Error: {e}"
188
+ )
189
+
190
+ if errors:
191
+ print(f"FAILED - Found {len(errors)} slide layout ID validation errors:")
192
+ for error in errors:
193
+ print(error)
194
+ print(
195
+ "Remove invalid references or add missing slide layouts to the relationships file."
196
+ )
197
+ return False
198
+ else:
199
+ if self.verbose:
200
+ print("PASSED - All slide layout IDs reference valid slide layouts")
201
+ return True
202
+
203
+ def validate_no_duplicate_slide_layouts(self):
204
+ """Validate that each slide has exactly one slideLayout reference."""
205
+ import lxml.etree
206
+
207
+ errors = []
208
+ slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
209
+
210
+ for rels_file in slide_rels_files:
211
+ try:
212
+ root = lxml.etree.parse(str(rels_file)).getroot()
213
+
214
+ # Find all slideLayout relationships
215
+ layout_rels = [
216
+ rel
217
+ for rel in root.findall(
218
+ f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
219
+ )
220
+ if "slideLayout" in rel.get("Type", "")
221
+ ]
222
+
223
+ if len(layout_rels) > 1:
224
+ errors.append(
225
+ f" {rels_file.relative_to(self.unpacked_dir)}: has {len(layout_rels)} slideLayout references"
226
+ )
227
+
228
+ except Exception as e:
229
+ errors.append(
230
+ f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
231
+ )
232
+
233
+ if errors:
234
+ print("FAILED - Found slides with duplicate slideLayout references:")
235
+ for error in errors:
236
+ print(error)
237
+ return False
238
+ else:
239
+ if self.verbose:
240
+ print("PASSED - All slides have exactly one slideLayout reference")
241
+ return True
242
+
243
+ def validate_notes_slide_references(self):
244
+ """Validate that each notesSlide file is referenced by only one slide."""
245
+ import lxml.etree
246
+
247
+ errors = []
248
+ notes_slide_references = {} # Track which slides reference each notesSlide
249
+
250
+ # Find all slide relationship files
251
+ slide_rels_files = list(self.unpacked_dir.glob("ppt/slides/_rels/*.xml.rels"))
252
+
253
+ if not slide_rels_files:
254
+ if self.verbose:
255
+ print("PASSED - No slide relationship files found")
256
+ return True
257
+
258
+ for rels_file in slide_rels_files:
259
+ try:
260
+ # Parse the relationships file
261
+ root = lxml.etree.parse(str(rels_file)).getroot()
262
+
263
+ # Find all notesSlide relationships
264
+ for rel in root.findall(
265
+ f".//{{{self.PACKAGE_RELATIONSHIPS_NAMESPACE}}}Relationship"
266
+ ):
267
+ rel_type = rel.get("Type", "")
268
+ if "notesSlide" in rel_type:
269
+ target = rel.get("Target", "")
270
+ if target:
271
+ # Normalize the target path to handle relative paths
272
+ normalized_target = target.replace("../", "")
273
+
274
+ # Track which slide references this notesSlide
275
+ slide_name = rels_file.stem.replace(
276
+ ".xml", ""
277
+ ) # e.g., "slide1"
278
+
279
+ if normalized_target not in notes_slide_references:
280
+ notes_slide_references[normalized_target] = []
281
+ notes_slide_references[normalized_target].append(
282
+ (slide_name, rels_file)
283
+ )
284
+
285
+ except (lxml.etree.XMLSyntaxError, Exception) as e:
286
+ errors.append(
287
+ f" {rels_file.relative_to(self.unpacked_dir)}: Error: {e}"
288
+ )
289
+
290
+ # Check for duplicate references
291
+ for target, references in notes_slide_references.items():
292
+ if len(references) > 1:
293
+ slide_names = [ref[0] for ref in references]
294
+ errors.append(
295
+ f" Notes slide '{target}' is referenced by multiple slides: {', '.join(slide_names)}"
296
+ )
297
+ for slide_name, rels_file in references:
298
+ errors.append(f" - {rels_file.relative_to(self.unpacked_dir)}")
299
+
300
+ if errors:
301
+ print(
302
+ f"FAILED - Found {len([e for e in errors if not e.startswith(' ')])} notes slide reference validation errors:"
303
+ )
304
+ for error in errors:
305
+ print(error)
306
+ print("Each slide may optionally have its own slide file.")
307
+ return False
308
+ else:
309
+ if self.verbose:
310
+ print("PASSED - All notes slide references are unique")
311
+ return True
312
+
313
+
314
+ if __name__ == "__main__":
315
+ raise RuntimeError("This module should not be run directly.")
@@ -0,0 +1,284 @@
1
+ """
2
+ Validator for tracked changes in Word documents.
3
+ """
4
+
5
+ import subprocess
6
+ import tempfile
7
+ import zipfile
8
+ from pathlib import Path
9
+
10
+
11
+ class RedliningValidator:
12
+ """Validator for tracked changes in Word documents."""
13
+
14
+ def __init__(self, unpacked_dir, original_docx, verbose=False, author="Claude"):
15
+ self.unpacked_dir = Path(unpacked_dir)
16
+ self.original_docx = Path(original_docx)
17
+ self.verbose = verbose
18
+ self.author = author
19
+ self.namespaces = {
20
+ "w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
21
+ }
22
+
23
+ def repair(self) -> int:
24
+ """No auto-repairs for redlining validation. Returns 0."""
25
+ return 0
26
+
27
+ def validate(self):
28
+ """Main validation method that returns True if valid, False otherwise."""
29
+ # Verify unpacked directory exists and has correct structure
30
+ modified_file = self.unpacked_dir / "word" / "document.xml"
31
+ if not modified_file.exists():
32
+ print(f"FAILED - Modified document.xml not found at {modified_file}")
33
+ return False
34
+
35
+ # First, check if there are any tracked changes by the author to validate
36
+ try:
37
+ import xml.etree.ElementTree as ET
38
+
39
+ tree = ET.parse(modified_file)
40
+ root = tree.getroot()
41
+
42
+ # Check for w:del or w:ins tags by the specified author
43
+ del_elements = root.findall(".//w:del", self.namespaces)
44
+ ins_elements = root.findall(".//w:ins", self.namespaces)
45
+
46
+ # Filter to only include changes by the specified author
47
+ author_del_elements = [
48
+ elem
49
+ for elem in del_elements
50
+ if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
51
+ ]
52
+ author_ins_elements = [
53
+ elem
54
+ for elem in ins_elements
55
+ if elem.get(f"{{{self.namespaces['w']}}}author") == self.author
56
+ ]
57
+
58
+ # Redlining validation is only needed if tracked changes by the author have been used.
59
+ if not author_del_elements and not author_ins_elements:
60
+ if self.verbose:
61
+ print(f"PASSED - No tracked changes by {self.author} found.")
62
+ return True
63
+
64
+ except Exception:
65
+ # If we can't parse the XML, continue with full validation
66
+ pass
67
+
68
+ # Create temporary directory for unpacking original docx
69
+ with tempfile.TemporaryDirectory() as temp_dir:
70
+ temp_path = Path(temp_dir)
71
+
72
+ # Unpack original docx
73
+ try:
74
+ with zipfile.ZipFile(self.original_docx, "r") as zip_ref:
75
+ zip_ref.extractall(temp_path)
76
+ except Exception as e:
77
+ print(f"FAILED - Error unpacking original docx: {e}")
78
+ return False
79
+
80
+ original_file = temp_path / "word" / "document.xml"
81
+ if not original_file.exists():
82
+ print(
83
+ f"FAILED - Original document.xml not found in {self.original_docx}"
84
+ )
85
+ return False
86
+
87
+ # Parse both XML files using xml.etree.ElementTree for redlining validation
88
+ try:
89
+ import xml.etree.ElementTree as ET
90
+
91
+ modified_tree = ET.parse(modified_file)
92
+ modified_root = modified_tree.getroot()
93
+ original_tree = ET.parse(original_file)
94
+ original_root = original_tree.getroot()
95
+ except ET.ParseError as e:
96
+ print(f"FAILED - Error parsing XML files: {e}")
97
+ return False
98
+
99
+ # Remove the author's tracked changes from both documents
100
+ self._remove_author_tracked_changes(original_root)
101
+ self._remove_author_tracked_changes(modified_root)
102
+
103
+ # Extract and compare text content
104
+ modified_text = self._extract_text_content(modified_root)
105
+ original_text = self._extract_text_content(original_root)
106
+
107
+ if modified_text != original_text:
108
+ # Show detailed character-level differences for each paragraph
109
+ error_message = self._generate_detailed_diff(
110
+ original_text, modified_text
111
+ )
112
+ print(error_message)
113
+ return False
114
+
115
+ if self.verbose:
116
+ print(f"PASSED - All changes by {self.author} are properly tracked")
117
+ return True
118
+
119
+ def _generate_detailed_diff(self, original_text, modified_text):
120
+ """Generate detailed word-level differences using git word diff."""
121
+ error_parts = [
122
+ f"FAILED - Document text doesn't match after removing {self.author}'s tracked changes",
123
+ "",
124
+ "Likely causes:",
125
+ " 1. Modified text inside another author's <w:ins> or <w:del> tags",
126
+ " 2. Made edits without proper tracked changes",
127
+ " 3. Didn't nest <w:del> inside <w:ins> when deleting another's insertion",
128
+ "",
129
+ "For pre-redlined documents, use correct patterns:",
130
+ " - To reject another's INSERTION: Nest <w:del> inside their <w:ins>",
131
+ " - To restore another's DELETION: Add new <w:ins> AFTER their <w:del>",
132
+ "",
133
+ ]
134
+
135
+ # Show git word diff
136
+ git_diff = self._get_git_word_diff(original_text, modified_text)
137
+ if git_diff:
138
+ error_parts.extend(["Differences:", "============", git_diff])
139
+ else:
140
+ error_parts.append("Unable to generate word diff (git not available)")
141
+
142
+ return "\n".join(error_parts)
143
+
144
+ def _get_git_word_diff(self, original_text, modified_text):
145
+ """Generate word diff using git with character-level precision."""
146
+ try:
147
+ with tempfile.TemporaryDirectory() as temp_dir:
148
+ temp_path = Path(temp_dir)
149
+
150
+ # Create two files
151
+ original_file = temp_path / "original.txt"
152
+ modified_file = temp_path / "modified.txt"
153
+
154
+ original_file.write_text(original_text, encoding="utf-8")
155
+ modified_file.write_text(modified_text, encoding="utf-8")
156
+
157
+ # Try character-level diff first for precise differences
158
+ result = subprocess.run(
159
+ [
160
+ "git",
161
+ "diff",
162
+ "--word-diff=plain",
163
+ "--word-diff-regex=.", # Character-by-character diff
164
+ "-U0", # Zero lines of context - show only changed lines
165
+ "--no-index",
166
+ str(original_file),
167
+ str(modified_file),
168
+ ],
169
+ capture_output=True,
170
+ text=True,
171
+ )
172
+
173
+ if result.stdout.strip():
174
+ # Clean up the output - remove git diff header lines
175
+ lines = result.stdout.split("\n")
176
+ # Skip the header lines (diff --git, index, +++, ---, @@)
177
+ content_lines = []
178
+ in_content = False
179
+ for line in lines:
180
+ if line.startswith("@@"):
181
+ in_content = True
182
+ continue
183
+ if in_content and line.strip():
184
+ content_lines.append(line)
185
+
186
+ if content_lines:
187
+ return "\n".join(content_lines)
188
+
189
+ # Fallback to word-level diff if character-level is too verbose
190
+ result = subprocess.run(
191
+ [
192
+ "git",
193
+ "diff",
194
+ "--word-diff=plain",
195
+ "-U0", # Zero lines of context
196
+ "--no-index",
197
+ str(original_file),
198
+ str(modified_file),
199
+ ],
200
+ capture_output=True,
201
+ text=True,
202
+ )
203
+
204
+ if result.stdout.strip():
205
+ lines = result.stdout.split("\n")
206
+ content_lines = []
207
+ in_content = False
208
+ for line in lines:
209
+ if line.startswith("@@"):
210
+ in_content = True
211
+ continue
212
+ if in_content and line.strip():
213
+ content_lines.append(line)
214
+ return "\n".join(content_lines)
215
+
216
+ except (subprocess.CalledProcessError, FileNotFoundError, Exception):
217
+ # Git not available or other error, return None to use fallback
218
+ pass
219
+
220
+ return None
221
+
222
+ def _remove_author_tracked_changes(self, root):
223
+ """Remove tracked changes authored by the specified author from the XML root."""
224
+ ins_tag = f"{{{self.namespaces['w']}}}ins"
225
+ del_tag = f"{{{self.namespaces['w']}}}del"
226
+ author_attr = f"{{{self.namespaces['w']}}}author"
227
+
228
+ # Remove w:ins elements
229
+ for parent in root.iter():
230
+ to_remove = []
231
+ for child in parent:
232
+ if child.tag == ins_tag and child.get(author_attr) == self.author:
233
+ to_remove.append(child)
234
+ for elem in to_remove:
235
+ parent.remove(elem)
236
+
237
+ # Unwrap content in w:del elements where author matches
238
+ deltext_tag = f"{{{self.namespaces['w']}}}delText"
239
+ t_tag = f"{{{self.namespaces['w']}}}t"
240
+
241
+ for parent in root.iter():
242
+ to_process = []
243
+ for child in parent:
244
+ if child.tag == del_tag and child.get(author_attr) == self.author:
245
+ to_process.append((child, list(parent).index(child)))
246
+
247
+ # Process in reverse order to maintain indices
248
+ for del_elem, del_index in reversed(to_process):
249
+ # Convert w:delText to w:t before moving
250
+ for elem in del_elem.iter():
251
+ if elem.tag == deltext_tag:
252
+ elem.tag = t_tag
253
+
254
+ # Move all children of w:del to its parent before removing w:del
255
+ for child in reversed(list(del_elem)):
256
+ parent.insert(del_index, child)
257
+ parent.remove(del_elem)
258
+
259
+ def _extract_text_content(self, root):
260
+ """Extract text content from Word XML, preserving paragraph structure.
261
+
262
+ Empty paragraphs are skipped to avoid false positives when tracked
263
+ insertions add only structural elements without text content.
264
+ """
265
+ p_tag = f"{{{self.namespaces['w']}}}p"
266
+ t_tag = f"{{{self.namespaces['w']}}}t"
267
+
268
+ paragraphs = []
269
+ for p_elem in root.findall(f".//{p_tag}"):
270
+ # Get all text elements within this paragraph
271
+ text_parts = []
272
+ for t_elem in p_elem.findall(f".//{t_tag}"):
273
+ if t_elem.text:
274
+ text_parts.append(t_elem.text)
275
+ paragraph_text = "".join(text_parts)
276
+ # Skip empty paragraphs - they don't affect content validation
277
+ if paragraph_text:
278
+ paragraphs.append(paragraph_text)
279
+
280
+ return "\n".join(paragraphs)
281
+
282
+
283
+ if __name__ == "__main__":
284
+ raise RuntimeError("This module should not be run directly.")