@amaster.ai/pi-lark 0.1.4 → 0.1.6

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 (416) hide show
  1. package/package.json +3 -3
  2. package/skills/lark-approval/SKILL.md +99 -0
  3. package/skills/lark-approval/references/lark-approval-approvals-get.md +128 -0
  4. package/skills/lark-approval/references/lark-approval-approvals-search.md +103 -0
  5. package/skills/lark-approval/references/lark-approval-initiate.md +221 -0
  6. package/skills/lark-approval/references/lark-approval-instance-form-control-parameters.md +606 -0
  7. package/skills/lark-approval/references/lark-approval-instance-value-sourcing.md +108 -0
  8. package/skills/lark-approval/references/lark-approval-instances-cancel.md +78 -0
  9. package/skills/lark-approval/references/lark-approval-instances-cc.md +105 -0
  10. package/skills/lark-approval/references/lark-approval-instances-get.md +145 -0
  11. package/skills/lark-approval/references/lark-approval-instances-initiated.md +128 -0
  12. package/skills/lark-approval/references/lark-approval-tasks-add-sign.md +120 -0
  13. package/skills/lark-approval/references/lark-approval-tasks-approve.md +81 -0
  14. package/skills/lark-approval/references/lark-approval-tasks-query.md +85 -0
  15. package/skills/lark-approval/references/lark-approval-tasks-reject.md +73 -0
  16. package/skills/lark-approval/references/lark-approval-tasks-remind.md +82 -0
  17. package/skills/lark-approval/references/lark-approval-tasks-rollback.md +89 -0
  18. package/skills/lark-approval/references/lark-approval-tasks-transfer.md +91 -0
  19. package/skills/lark-apps/SKILL.md +110 -0
  20. package/skills/lark-apps/references/lark-apps-access-scope-get.md +28 -0
  21. package/skills/lark-apps/references/lark-apps-access-scope-set.md +40 -0
  22. package/skills/lark-apps/references/lark-apps-automation.md +164 -0
  23. package/skills/lark-apps/references/lark-apps-cloud-dev.md +120 -0
  24. package/skills/lark-apps/references/lark-apps-create.md +40 -0
  25. package/skills/lark-apps/references/lark-apps-db-execute.md +228 -0
  26. package/skills/lark-apps/references/lark-apps-db.md +162 -0
  27. package/skills/lark-apps/references/lark-apps-env-pull.md +37 -0
  28. package/skills/lark-apps/references/lark-apps-env.md +48 -0
  29. package/skills/lark-apps/references/lark-apps-file.md +96 -0
  30. package/skills/lark-apps/references/lark-apps-get.md +43 -0
  31. package/skills/lark-apps/references/lark-apps-git-credential.md +37 -0
  32. package/skills/lark-apps/references/lark-apps-html-publish.md +62 -0
  33. package/skills/lark-apps/references/lark-apps-init.md +36 -0
  34. package/skills/lark-apps/references/lark-apps-list.md +37 -0
  35. package/skills/lark-apps/references/lark-apps-local-dev.md +78 -0
  36. package/skills/lark-apps/references/lark-apps-observability.md +48 -0
  37. package/skills/lark-apps/references/lark-apps-openapi-key.md +79 -0
  38. package/skills/lark-apps/references/lark-apps-plugin-install.md +36 -0
  39. package/skills/lark-apps/references/lark-apps-plugin-list.md +23 -0
  40. package/skills/lark-apps/references/lark-apps-plugin-uninstall.md +25 -0
  41. package/skills/lark-apps/references/lark-apps-release-create.md +32 -0
  42. package/skills/lark-apps/references/lark-apps-release-get.md +28 -0
  43. package/skills/lark-apps/references/lark-apps-release-list.md +31 -0
  44. package/skills/lark-apps/references/lark-apps-role.md +133 -0
  45. package/skills/lark-apps/references/lark-apps-session-messages-list.md +53 -0
  46. package/skills/lark-apps/references/lark-apps-update.md +30 -0
  47. package/skills/lark-attendance/SKILL.md +57 -0
  48. package/skills/lark-base/SKILL.md +161 -0
  49. package/skills/lark-base/references/dashboard-block-data-config.md +376 -0
  50. package/skills/lark-base/references/formula-field-guide.md +737 -0
  51. package/skills/lark-base/references/lark-base-cell-value.md +158 -0
  52. package/skills/lark-base/references/lark-base-dashboard-block-get-data.md +717 -0
  53. package/skills/lark-base/references/lark-base-dashboard.md +247 -0
  54. package/skills/lark-base/references/lark-base-data-analysis-sop.md +210 -0
  55. package/skills/lark-base/references/lark-base-data-query-guide.md +61 -0
  56. package/skills/lark-base/references/lark-base-data-query.md +454 -0
  57. package/skills/lark-base/references/lark-base-field-create.md +105 -0
  58. package/skills/lark-base/references/lark-base-field-json.md +527 -0
  59. package/skills/lark-base/references/lark-base-field-update.md +173 -0
  60. package/skills/lark-base/references/lark-base-form-detail.md +71 -0
  61. package/skills/lark-base/references/lark-base-form-questions-create.md +118 -0
  62. package/skills/lark-base/references/lark-base-form-questions-update.md +92 -0
  63. package/skills/lark-base/references/lark-base-form-submit.md +170 -0
  64. package/skills/lark-base/references/lark-base-record-batch-create.md +57 -0
  65. package/skills/lark-base/references/lark-base-record-batch-update.md +52 -0
  66. package/skills/lark-base/references/lark-base-record-history-list.md +43 -0
  67. package/skills/lark-base/references/lark-base-record-upsert.md +63 -0
  68. package/skills/lark-base/references/lark-base-role-guide.md +65 -0
  69. package/skills/lark-base/references/lark-base-view-set-filter.md +191 -0
  70. package/skills/lark-base/references/lark-base-workflow-guide.md +830 -0
  71. package/skills/lark-base/references/lark-base-workflow-schema.md +1071 -0
  72. package/skills/lark-base/references/lookup-field-guide.md +512 -0
  73. package/skills/lark-base/references/role-config.md +549 -0
  74. package/skills/lark-calendar/SKILL.md +198 -0
  75. package/skills/lark-calendar/references/lark-calendar-create.md +83 -0
  76. package/skills/lark-calendar/references/lark-calendar-meeting.md +40 -0
  77. package/skills/lark-calendar/references/lark-calendar-recurring.md +90 -0
  78. package/skills/lark-calendar/references/lark-calendar-room-find.md +109 -0
  79. package/skills/lark-calendar/references/lark-calendar-rsvp.md +38 -0
  80. package/skills/lark-calendar/references/lark-calendar-schedule-clear-time.md +59 -0
  81. package/skills/lark-calendar/references/lark-calendar-schedule-fuzzy-time.md +88 -0
  82. package/skills/lark-calendar/references/lark-calendar-schedule-meeting.md +122 -0
  83. package/skills/lark-calendar/references/lark-calendar-suggestion.md +121 -0
  84. package/skills/lark-calendar/references/lark-calendar-update.md +100 -0
  85. package/skills/lark-contact/SKILL.md +55 -0
  86. package/skills/lark-contact/references/lark-contact-get-user.md +19 -0
  87. package/skills/lark-contact/references/lark-contact-search-user.md +123 -0
  88. package/skills/lark-doc/SKILL.md +84 -0
  89. package/skills/lark-doc/references/lark-doc-create.md +80 -0
  90. package/skills/lark-doc/references/lark-doc-fetch.md +140 -0
  91. package/skills/lark-doc/references/lark-doc-history.md +107 -0
  92. package/skills/lark-doc/references/lark-doc-md.md +76 -0
  93. package/skills/lark-doc/references/lark-doc-media-download.md +50 -0
  94. package/skills/lark-doc/references/lark-doc-media-insert.md +114 -0
  95. package/skills/lark-doc/references/lark-doc-media-preview.md +41 -0
  96. package/skills/lark-doc/references/lark-doc-mindnote.md +128 -0
  97. package/skills/lark-doc/references/lark-doc-resource-cover.md +70 -0
  98. package/skills/lark-doc/references/lark-doc-update.md +260 -0
  99. package/skills/lark-doc/references/lark-doc-whiteboard.md +158 -0
  100. package/skills/lark-doc/references/lark-doc-word-stat.md +93 -0
  101. package/skills/lark-doc/references/lark-doc-xml-extended-blocks.md +35 -0
  102. package/skills/lark-doc/references/lark-doc-xml.md +182 -0
  103. package/skills/lark-doc/references/style/lark-doc-create-workflow.md +47 -0
  104. package/skills/lark-doc/references/style/lark-doc-style.md +68 -0
  105. package/skills/lark-doc/references/style/lark-doc-update-workflow.md +48 -0
  106. package/skills/lark-doc/scripts/doc_word_stat.py +1243 -0
  107. package/skills/lark-drive/SKILL.md +232 -0
  108. package/skills/lark-drive/references/lark-drive-add-comment.md +193 -0
  109. package/skills/lark-drive/references/lark-drive-apply-permission.md +77 -0
  110. package/skills/lark-drive/references/lark-drive-comment-location.md +205 -0
  111. package/skills/lark-drive/references/lark-drive-comments-guide.md +80 -0
  112. package/skills/lark-drive/references/lark-drive-cover.md +79 -0
  113. package/skills/lark-drive/references/lark-drive-create-folder.md +73 -0
  114. package/skills/lark-drive/references/lark-drive-create-shortcut.md +103 -0
  115. package/skills/lark-drive/references/lark-drive-delete.md +103 -0
  116. package/skills/lark-drive/references/lark-drive-download.md +31 -0
  117. package/skills/lark-drive/references/lark-drive-export-download.md +50 -0
  118. package/skills/lark-drive/references/lark-drive-export.md +174 -0
  119. package/skills/lark-drive/references/lark-drive-files-list.md +183 -0
  120. package/skills/lark-drive/references/lark-drive-import.md +178 -0
  121. package/skills/lark-drive/references/lark-drive-inspect.md +52 -0
  122. package/skills/lark-drive/references/lark-drive-list-comments.md +125 -0
  123. package/skills/lark-drive/references/lark-drive-member-add.md +66 -0
  124. package/skills/lark-drive/references/lark-drive-move.md +122 -0
  125. package/skills/lark-drive/references/lark-drive-permission-guide.md +53 -0
  126. package/skills/lark-drive/references/lark-drive-preview.md +87 -0
  127. package/skills/lark-drive/references/lark-drive-pull.md +137 -0
  128. package/skills/lark-drive/references/lark-drive-push.md +189 -0
  129. package/skills/lark-drive/references/lark-drive-reactions.md +113 -0
  130. package/skills/lark-drive/references/lark-drive-search.md +273 -0
  131. package/skills/lark-drive/references/lark-drive-secure-label.md +52 -0
  132. package/skills/lark-drive/references/lark-drive-status.md +135 -0
  133. package/skills/lark-drive/references/lark-drive-task-result.md +355 -0
  134. package/skills/lark-drive/references/lark-drive-upload.md +101 -0
  135. package/skills/lark-drive/references/lark-drive-version-delete.md +38 -0
  136. package/skills/lark-drive/references/lark-drive-version-get.md +71 -0
  137. package/skills/lark-drive/references/lark-drive-version-history.md +73 -0
  138. package/skills/lark-drive/references/lark-drive-version-revert.md +35 -0
  139. package/skills/lark-drive/references/lark-drive-workflow-knowledge-organize-analysis.md +249 -0
  140. package/skills/lark-drive/references/lark-drive-workflow-knowledge-organize-discovery.md +253 -0
  141. package/skills/lark-drive/references/lark-drive-workflow-knowledge-organize-execution.md +200 -0
  142. package/skills/lark-drive/references/lark-drive-workflow-knowledge-organize-planning.md +336 -0
  143. package/skills/lark-drive/references/lark-drive-workflow-knowledge-organize-rollback.md +308 -0
  144. package/skills/lark-drive/references/lark-drive-workflow-knowledge-organize.md +232 -0
  145. package/skills/lark-drive/references/lark-drive-workflow-permission-governance-commands.md +168 -0
  146. package/skills/lark-drive/references/lark-drive-workflow-permission-governance-outputs.md +424 -0
  147. package/skills/lark-drive/references/lark-drive-workflow-permission-governance.md +207 -0
  148. package/skills/lark-drive/references/lark-drive-workflow.md +131 -0
  149. package/skills/lark-event/SKILL.md +155 -0
  150. package/skills/lark-event/references/lark-event-approval.md +170 -0
  151. package/skills/lark-event/references/lark-event-im.md +87 -0
  152. package/skills/lark-event/references/lark-event-minutes.md +54 -0
  153. package/skills/lark-event/references/lark-event-task.md +78 -0
  154. package/skills/lark-event/references/lark-event-vc.md +106 -0
  155. package/skills/lark-event/references/lark-event-whiteboard.md +67 -0
  156. package/skills/lark-im/SKILL.md +249 -0
  157. package/skills/lark-im/references/card/card-2.0-schema.md +107 -0
  158. package/skills/lark-im/references/card/components/button.md +63 -0
  159. package/skills/lark-im/references/card/components/chart.md +57 -0
  160. package/skills/lark-im/references/card/components/checker.md +38 -0
  161. package/skills/lark-im/references/card/components/collapsible_panel.md +46 -0
  162. package/skills/lark-im/references/card/components/column_set.md +53 -0
  163. package/skills/lark-im/references/card/components/date_picker.md +34 -0
  164. package/skills/lark-im/references/card/components/div.md +36 -0
  165. package/skills/lark-im/references/card/components/form.md +51 -0
  166. package/skills/lark-im/references/card/components/header.md +34 -0
  167. package/skills/lark-im/references/card/components/hr.md +17 -0
  168. package/skills/lark-im/references/card/components/img.md +34 -0
  169. package/skills/lark-im/references/card/components/img_combination.md +30 -0
  170. package/skills/lark-im/references/card/components/input.md +43 -0
  171. package/skills/lark-im/references/card/components/interactive_container.md +46 -0
  172. package/skills/lark-im/references/card/components/markdown.md +56 -0
  173. package/skills/lark-im/references/card/components/multi_select_person.md +40 -0
  174. package/skills/lark-im/references/card/components/multi_select_static.md +40 -0
  175. package/skills/lark-im/references/card/components/overflow.md +36 -0
  176. package/skills/lark-im/references/card/components/person.md +30 -0
  177. package/skills/lark-im/references/card/components/person_list.md +31 -0
  178. package/skills/lark-im/references/card/components/picker_datetime.md +34 -0
  179. package/skills/lark-im/references/card/components/picker_time.md +34 -0
  180. package/skills/lark-im/references/card/components/recycling_container.md +35 -0
  181. package/skills/lark-im/references/card/components/select_img.md +42 -0
  182. package/skills/lark-im/references/card/components/select_person.md +39 -0
  183. package/skills/lark-im/references/card/components/select_static.md +43 -0
  184. package/skills/lark-im/references/card/components/table.md +53 -0
  185. package/skills/lark-im/references/card/lark-im-card-create.md +180 -0
  186. package/skills/lark-im/references/card/lark-im-card-style.md +281 -0
  187. package/skills/lark-im/references/card/resource/colors.md +34 -0
  188. package/skills/lark-im/references/card/resource/icons.md +38 -0
  189. package/skills/lark-im/references/lark-im-card-action-reply.md +175 -0
  190. package/skills/lark-im/references/lark-im-chat-create.md +162 -0
  191. package/skills/lark-im/references/lark-im-chat-identity.md +55 -0
  192. package/skills/lark-im/references/lark-im-chat-list.md +166 -0
  193. package/skills/lark-im/references/lark-im-chat-members-list.md +83 -0
  194. package/skills/lark-im/references/lark-im-chat-messages-list.md +157 -0
  195. package/skills/lark-im/references/lark-im-chat-search.md +142 -0
  196. package/skills/lark-im/references/lark-im-chat-update.md +84 -0
  197. package/skills/lark-im/references/lark-im-feed-group-list-item.md +68 -0
  198. package/skills/lark-im/references/lark-im-feed-group-list.md +65 -0
  199. package/skills/lark-im/references/lark-im-feed-group-query-item.md +44 -0
  200. package/skills/lark-im/references/lark-im-feed-groups.md +452 -0
  201. package/skills/lark-im/references/lark-im-feed-shortcut-create.md +97 -0
  202. package/skills/lark-im/references/lark-im-feed-shortcut-list.md +103 -0
  203. package/skills/lark-im/references/lark-im-feed-shortcut-remove.md +48 -0
  204. package/skills/lark-im/references/lark-im-flag-cancel.md +67 -0
  205. package/skills/lark-im/references/lark-im-flag-create.md +67 -0
  206. package/skills/lark-im/references/lark-im-flag-list.md +100 -0
  207. package/skills/lark-im/references/lark-im-message-enrichment.md +54 -0
  208. package/skills/lark-im/references/lark-im-messages-mget.md +99 -0
  209. package/skills/lark-im/references/lark-im-messages-reply.md +277 -0
  210. package/skills/lark-im/references/lark-im-messages-resources-download.md +94 -0
  211. package/skills/lark-im/references/lark-im-messages-search.md +234 -0
  212. package/skills/lark-im/references/lark-im-messages-send.md +279 -0
  213. package/skills/lark-im/references/lark-im-reactions.md +299 -0
  214. package/skills/lark-im/references/lark-im-threads-messages-list.md +115 -0
  215. package/skills/lark-mail/SKILL.md +290 -0
  216. package/skills/lark-mail/assets/templates/job-application--resume.html +33 -0
  217. package/skills/lark-mail/assets/templates/newsletter--weekly-brief.html +50 -0
  218. package/skills/lark-mail/assets/templates/research--market-report.html +256 -0
  219. package/skills/lark-mail/assets/templates/weekly--personal-report.html +43 -0
  220. package/skills/lark-mail/assets/templates/weekly--team-report.html +9 -0
  221. package/skills/lark-mail/references/lark-mail-calendar-invite.md +36 -0
  222. package/skills/lark-mail/references/lark-mail-decline-receipt.md +115 -0
  223. package/skills/lark-mail/references/lark-mail-draft-create.md +127 -0
  224. package/skills/lark-mail/references/lark-mail-draft-edit.md +404 -0
  225. package/skills/lark-mail/references/lark-mail-forward.md +239 -0
  226. package/skills/lark-mail/references/lark-mail-html.md +333 -0
  227. package/skills/lark-mail/references/lark-mail-lint-html.md +243 -0
  228. package/skills/lark-mail/references/lark-mail-message-modify.md +48 -0
  229. package/skills/lark-mail/references/lark-mail-message-trash.md +41 -0
  230. package/skills/lark-mail/references/lark-mail-message.md +233 -0
  231. package/skills/lark-mail/references/lark-mail-messages.md +108 -0
  232. package/skills/lark-mail/references/lark-mail-recall.md +66 -0
  233. package/skills/lark-mail/references/lark-mail-recipient-search.md +59 -0
  234. package/skills/lark-mail/references/lark-mail-reply-all.md +213 -0
  235. package/skills/lark-mail/references/lark-mail-reply.md +249 -0
  236. package/skills/lark-mail/references/lark-mail-rules.md +31 -0
  237. package/skills/lark-mail/references/lark-mail-send-as.md +44 -0
  238. package/skills/lark-mail/references/lark-mail-send-receipt.md +120 -0
  239. package/skills/lark-mail/references/lark-mail-send-status.md +46 -0
  240. package/skills/lark-mail/references/lark-mail-send.md +222 -0
  241. package/skills/lark-mail/references/lark-mail-share-to-chat.md +87 -0
  242. package/skills/lark-mail/references/lark-mail-signature.md +98 -0
  243. package/skills/lark-mail/references/lark-mail-template-create.md +129 -0
  244. package/skills/lark-mail/references/lark-mail-template-update.md +150 -0
  245. package/skills/lark-mail/references/lark-mail-template.md +54 -0
  246. package/skills/lark-mail/references/lark-mail-thread.md +111 -0
  247. package/skills/lark-mail/references/lark-mail-triage.md +131 -0
  248. package/skills/lark-mail/references/lark-mail-watch.md +94 -0
  249. package/skills/lark-markdown/SKILL.md +70 -0
  250. package/skills/lark-markdown/references/lark-markdown-create.md +114 -0
  251. package/skills/lark-markdown/references/lark-markdown-diff.md +156 -0
  252. package/skills/lark-markdown/references/lark-markdown-fetch.md +79 -0
  253. package/skills/lark-markdown/references/lark-markdown-overwrite.md +85 -0
  254. package/skills/lark-markdown/references/lark-markdown-patch.md +160 -0
  255. package/skills/lark-minutes/SKILL.md +207 -0
  256. package/skills/lark-minutes/references/lark-minutes-detail.md +62 -0
  257. package/skills/lark-minutes/references/lark-minutes-download.md +135 -0
  258. package/skills/lark-minutes/references/lark-minutes-search.md +202 -0
  259. package/skills/lark-minutes/references/lark-minutes-speaker-replace.md +107 -0
  260. package/skills/lark-minutes/references/lark-minutes-summary.md +120 -0
  261. package/skills/lark-minutes/references/lark-minutes-todo.md +136 -0
  262. package/skills/lark-minutes/references/lark-minutes-update.md +39 -0
  263. package/skills/lark-minutes/references/lark-minutes-upload.md +104 -0
  264. package/skills/lark-note/SKILL.md +94 -0
  265. package/skills/lark-note/references/lark-note-detail.md +26 -0
  266. package/skills/lark-note/references/lark-note-transcript.md +23 -0
  267. package/skills/lark-okr/SKILL.md +122 -0
  268. package/skills/lark-okr/references/lark-okr-alignments.md +180 -0
  269. package/skills/lark-okr/references/lark-okr-batch-create.md +106 -0
  270. package/skills/lark-okr/references/lark-okr-contentblock.md +427 -0
  271. package/skills/lark-okr/references/lark-okr-cycle-detail.md +91 -0
  272. package/skills/lark-okr/references/lark-okr-cycle-list.md +93 -0
  273. package/skills/lark-okr/references/lark-okr-entities.md +329 -0
  274. package/skills/lark-okr/references/lark-okr-image-upload.md +116 -0
  275. package/skills/lark-okr/references/lark-okr-indicator-update.md +80 -0
  276. package/skills/lark-okr/references/lark-okr-indicators.md +223 -0
  277. package/skills/lark-okr/references/lark-okr-patch.md +104 -0
  278. package/skills/lark-okr/references/lark-okr-progress-create.md +85 -0
  279. package/skills/lark-okr/references/lark-okr-progress-delete.md +47 -0
  280. package/skills/lark-okr/references/lark-okr-progress-get.md +93 -0
  281. package/skills/lark-okr/references/lark-okr-progress-list.md +80 -0
  282. package/skills/lark-okr/references/lark-okr-progress-update.md +85 -0
  283. package/skills/lark-okr/references/lark-okr-reorder.md +81 -0
  284. package/skills/lark-okr/references/lark-okr-weight.md +96 -0
  285. package/skills/lark-openapi-explorer/SKILL.md +153 -0
  286. package/skills/lark-shared/SKILL.md +211 -0
  287. package/skills/lark-shared/references/lark-wiki-token-routing.md +42 -0
  288. package/skills/lark-sheets/SKILL.md +234 -0
  289. package/skills/lark-sheets/references/lark-sheets-batch-update.md +200 -0
  290. package/skills/lark-sheets/references/lark-sheets-changeset.md +105 -0
  291. package/skills/lark-sheets/references/lark-sheets-chart.md +332 -0
  292. package/skills/lark-sheets/references/lark-sheets-conditional-format.md +181 -0
  293. package/skills/lark-sheets/references/lark-sheets-filter-view.md +137 -0
  294. package/skills/lark-sheets/references/lark-sheets-filter.md +130 -0
  295. package/skills/lark-sheets/references/lark-sheets-float-image.md +159 -0
  296. package/skills/lark-sheets/references/lark-sheets-formula-translation.md +276 -0
  297. package/skills/lark-sheets/references/lark-sheets-formula-verify.md +77 -0
  298. package/skills/lark-sheets/references/lark-sheets-history.md +93 -0
  299. package/skills/lark-sheets/references/lark-sheets-pivot-table.md +171 -0
  300. package/skills/lark-sheets/references/lark-sheets-range-operations.md +297 -0
  301. package/skills/lark-sheets/references/lark-sheets-read-data.md +235 -0
  302. package/skills/lark-sheets/references/lark-sheets-search-replace.md +111 -0
  303. package/skills/lark-sheets/references/lark-sheets-sheet-structure.md +212 -0
  304. package/skills/lark-sheets/references/lark-sheets-sparkline.md +149 -0
  305. package/skills/lark-sheets/references/lark-sheets-visual-standards.md +205 -0
  306. package/skills/lark-sheets/references/lark-sheets-workbook.md +420 -0
  307. package/skills/lark-sheets/references/lark-sheets-write-cells.md +575 -0
  308. package/skills/lark-sheets/scripts/sheets_df.py +32 -0
  309. package/skills/lark-skill-maker/SKILL.md +85 -0
  310. package/skills/lark-slides/SKILL.md +285 -0
  311. package/skills/lark-slides/references/asset-planning.md +135 -0
  312. package/skills/lark-slides/references/examples.md +91 -0
  313. package/skills/lark-slides/references/iconpark-index.json +41901 -0
  314. package/skills/lark-slides/references/iconpark.md +46 -0
  315. package/skills/lark-slides/references/lark-slides-create.md +156 -0
  316. package/skills/lark-slides/references/lark-slides-edit-workflows.md +144 -0
  317. package/skills/lark-slides/references/lark-slides-media-upload.md +127 -0
  318. package/skills/lark-slides/references/lark-slides-pptx-template-workflows.md +89 -0
  319. package/skills/lark-slides/references/lark-slides-replace-pages.md +95 -0
  320. package/skills/lark-slides/references/lark-slides-replace-slide.md +240 -0
  321. package/skills/lark-slides/references/lark-slides-screenshot.md +97 -0
  322. package/skills/lark-slides/references/lark-slides-whiteboard.md +331 -0
  323. package/skills/lark-slides/references/lark-slides-xml-get.md +100 -0
  324. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md +125 -0
  325. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md +110 -0
  326. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-replace.md +189 -0
  327. package/skills/lark-slides/references/lark-slides-xml-presentations-get.md +99 -0
  328. package/skills/lark-slides/references/planning-layer.md +246 -0
  329. package/skills/lark-slides/references/slide-templates.md +201 -0
  330. package/skills/lark-slides/references/slides_chart_demo.xml +1 -0
  331. package/skills/lark-slides/references/slides_demo.xml +226 -0
  332. package/skills/lark-slides/references/slides_xml_schema_definition.xml +3054 -0
  333. package/skills/lark-slides/references/troubleshooting.md +45 -0
  334. package/skills/lark-slides/references/validation-checklist.md +119 -0
  335. package/skills/lark-slides/references/visual-planning.md +255 -0
  336. package/skills/lark-slides/references/xml-format-guide.md +433 -0
  337. package/skills/lark-slides/references/xml-schema-quick-ref.md +249 -0
  338. package/skills/lark-slides/scripts/iconpark_tool.py +362 -0
  339. package/skills/lark-slides/scripts/iconpark_tool_test.py +177 -0
  340. package/skills/lark-slides/scripts/xml_text_overlap_lint.py +1220 -0
  341. package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +1170 -0
  342. package/skills/lark-task/SKILL.md +168 -0
  343. package/skills/lark-task/references/lark-task-assign.md +38 -0
  344. package/skills/lark-task/references/lark-task-comment.md +28 -0
  345. package/skills/lark-task/references/lark-task-complete.md +27 -0
  346. package/skills/lark-task/references/lark-task-create.md +70 -0
  347. package/skills/lark-task/references/lark-task-followers.md +35 -0
  348. package/skills/lark-task/references/lark-task-get-my-tasks.md +61 -0
  349. package/skills/lark-task/references/lark-task-get-related-tasks.md +53 -0
  350. package/skills/lark-task/references/lark-task-reminder.md +36 -0
  351. package/skills/lark-task/references/lark-task-reopen.md +27 -0
  352. package/skills/lark-task/references/lark-task-search.md +41 -0
  353. package/skills/lark-task/references/lark-task-set-ancestor.md +32 -0
  354. package/skills/lark-task/references/lark-task-tasklist-create.md +35 -0
  355. package/skills/lark-task/references/lark-task-tasklist-members.md +36 -0
  356. package/skills/lark-task/references/lark-task-tasklist-search.md +38 -0
  357. package/skills/lark-task/references/lark-task-tasklist-task-add.md +38 -0
  358. package/skills/lark-task/references/lark-task-update.md +37 -0
  359. package/skills/lark-task/references/lark-task-upload-attachment.md +59 -0
  360. package/skills/lark-vc/SKILL.md +205 -0
  361. package/skills/lark-vc/references/lark-vc-detail.md +44 -0
  362. package/skills/lark-vc/references/lark-vc-recording.md +152 -0
  363. package/skills/lark-vc/references/lark-vc-search.md +163 -0
  364. package/skills/lark-vc/references/vc-domain-boundaries.md +196 -0
  365. package/skills/lark-vc-agent/SKILL.md +201 -0
  366. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md +315 -0
  367. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-join.md +141 -0
  368. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-leave.md +105 -0
  369. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md +91 -0
  370. package/skills/lark-vc-agent/references/lark-vc-agent-meeting-message-send.md +134 -0
  371. package/skills/lark-whiteboard/SKILL.md +47 -0
  372. package/skills/lark-whiteboard/elements/connectors.md +102 -0
  373. package/skills/lark-whiteboard/elements/content.md +40 -0
  374. package/skills/lark-whiteboard/elements/image.md +80 -0
  375. package/skills/lark-whiteboard/elements/layout.md +374 -0
  376. package/skills/lark-whiteboard/elements/schema.md +357 -0
  377. package/skills/lark-whiteboard/elements/style.md +318 -0
  378. package/skills/lark-whiteboard/elements/typography.md +73 -0
  379. package/skills/lark-whiteboard/references/lark-whiteboard-query.md +60 -0
  380. package/skills/lark-whiteboard/references/lark-whiteboard-update.md +122 -0
  381. package/skills/lark-whiteboard/references/lark-whiteboard-workflow.md +97 -0
  382. package/skills/lark-whiteboard/routes/dsl.md +107 -0
  383. package/skills/lark-whiteboard/routes/mermaid.md +27 -0
  384. package/skills/lark-whiteboard/routes/svg-edit.md +85 -0
  385. package/skills/lark-whiteboard/routes/svg.md +54 -0
  386. package/skills/lark-whiteboard/scenes/architecture.md +433 -0
  387. package/skills/lark-whiteboard/scenes/bar-chart.md +187 -0
  388. package/skills/lark-whiteboard/scenes/comparison.md +135 -0
  389. package/skills/lark-whiteboard/scenes/fishbone.md +238 -0
  390. package/skills/lark-whiteboard/scenes/flowchart.md +185 -0
  391. package/skills/lark-whiteboard/scenes/flywheel.md +195 -0
  392. package/skills/lark-whiteboard/scenes/funnel.md +101 -0
  393. package/skills/lark-whiteboard/scenes/line-chart.md +214 -0
  394. package/skills/lark-whiteboard/scenes/mermaid.md +130 -0
  395. package/skills/lark-whiteboard/scenes/milestone.md +139 -0
  396. package/skills/lark-whiteboard/scenes/organization.md +173 -0
  397. package/skills/lark-whiteboard/scenes/photo-showcase.md +126 -0
  398. package/skills/lark-whiteboard/scenes/pyramid.md +99 -0
  399. package/skills/lark-whiteboard/scenes/swimlane.md +371 -0
  400. package/skills/lark-whiteboard/scenes/treemap.md +216 -0
  401. package/skills/lark-wiki/SKILL.md +114 -0
  402. package/skills/lark-wiki/references/lark-wiki-delete-space.md +205 -0
  403. package/skills/lark-wiki/references/lark-wiki-member-add.md +67 -0
  404. package/skills/lark-wiki/references/lark-wiki-member-list.md +76 -0
  405. package/skills/lark-wiki/references/lark-wiki-member-remove.md +61 -0
  406. package/skills/lark-wiki/references/lark-wiki-move-to-drive.md +122 -0
  407. package/skills/lark-wiki/references/lark-wiki-move.md +185 -0
  408. package/skills/lark-wiki/references/lark-wiki-node-copy.md +72 -0
  409. package/skills/lark-wiki/references/lark-wiki-node-create.md +127 -0
  410. package/skills/lark-wiki/references/lark-wiki-node-delete.md +62 -0
  411. package/skills/lark-wiki/references/lark-wiki-node-get.md +57 -0
  412. package/skills/lark-wiki/references/lark-wiki-node-list.md +95 -0
  413. package/skills/lark-wiki/references/lark-wiki-space-create.md +46 -0
  414. package/skills/lark-wiki/references/lark-wiki-space-list.md +68 -0
  415. package/skills/lark-workflow-meeting-summary/SKILL.md +122 -0
  416. package/skills/lark-workflow-standup-report/SKILL.md +122 -0
@@ -0,0 +1,1170 @@
1
+ # Copyright (c) 2026 Lark Technologies Pte. Ltd.
2
+ # SPDX-License-Identifier: MIT
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import subprocess
7
+ import sys
8
+ import tempfile
9
+ import unittest
10
+ from pathlib import Path
11
+
12
+ import xml_text_overlap_lint
13
+
14
+
15
+ class XmlTextOverlapLintTest(unittest.TestCase):
16
+ def assertNoXmlTextOverlapLintIssues(self, result: dict, sample_name: str) -> None:
17
+ issue_summaries = []
18
+ for slide in result.get("slides", []):
19
+ for issue in slide.get("issues", []):
20
+ issue_summaries.append(
21
+ f"slide {slide['slide_number']}: {issue['level']} {issue['code']} {issue['message']}"
22
+ )
23
+ if result.get("issues"):
24
+ for issue in result["issues"]:
25
+ issue_summaries.append(f"{issue['level']} {issue['code']} {issue['message']}")
26
+ self.assertEqual(
27
+ result["summary"]["error_count"],
28
+ 0,
29
+ f"{sample_name} has XML text overlap lint errors:\n" + "\n".join(issue_summaries),
30
+ )
31
+ self.assertEqual(
32
+ result["summary"]["warning_count"],
33
+ 0,
34
+ f"{sample_name} has XML text overlap lint warnings:\n" + "\n".join(issue_summaries),
35
+ )
36
+
37
+ def test_xml_text_overlap_lint_accepts_inline_fixture_xml_samples(self) -> None:
38
+ samples = {
39
+ "image-led-cover": """
40
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
41
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
42
+ <style><fill><fillColor color="rgb(15,23,42)"/></fill></style>
43
+ <data>
44
+ <img src="tok" topLeftX="560" topLeftY="0" width="400" height="540"/>
45
+ <shape type="text" topLeftX="64" topLeftY="150" width="420" height="70">
46
+ <content textType="title"><p><span fontSize="42">Quarterly Review</span></p></content>
47
+ </shape>
48
+ <shape type="text" topLeftX="64" topLeftY="235" width="420" height="36">
49
+ <content textType="sub-headline"><p><span fontSize="20">Focus, progress, and next steps</span></p></content>
50
+ </shape>
51
+ </data>
52
+ </slide>
53
+ </presentation>
54
+ """,
55
+ "content-grid": """
56
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
57
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
58
+ <data>
59
+ <shape type="text" topLeftX="60" topLeftY="44" width="620" height="46">
60
+ <content textType="title"><p><span fontSize="30">Execution Snapshot</span></p></content>
61
+ </shape>
62
+ <shape type="rect" topLeftX="60" topLeftY="126" width="250" height="150"/>
63
+ <shape type="text" topLeftX="84" topLeftY="152" width="200" height="36">
64
+ <content textType="headline"><p><span fontSize="22">Plan</span></p></content>
65
+ </shape>
66
+ <shape type="rect" topLeftX="355" topLeftY="126" width="250" height="150"/>
67
+ <shape type="text" topLeftX="379" topLeftY="152" width="200" height="36">
68
+ <content textType="headline"><p><span fontSize="22">Build</span></p></content>
69
+ </shape>
70
+ <shape type="rect" topLeftX="650" topLeftY="126" width="250" height="150"/>
71
+ <shape type="text" topLeftX="674" topLeftY="152" width="200" height="36">
72
+ <content textType="headline"><p><span fontSize="22">Launch</span></p></content>
73
+ </shape>
74
+ </data>
75
+ </slide>
76
+ </presentation>
77
+ """,
78
+ }
79
+ self.assertTrue(samples)
80
+ for sample_name, sample_xml in samples.items():
81
+ with self.subTest(sample=sample_name):
82
+ result = xml_text_overlap_lint.lint_xml(
83
+ sample_xml,
84
+ sample_name,
85
+ )
86
+ self.assertNoXmlTextOverlapLintIssues(result, sample_name)
87
+
88
+ def test_lint_xml_reports_unescaped_ampersand_in_text(self) -> None:
89
+ result = xml_text_overlap_lint.lint_xml(
90
+ """
91
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
92
+ <data>
93
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
94
+ <content textType="body"><p>Q&A</p></content>
95
+ </shape>
96
+ </data>
97
+ </slide>
98
+ """
99
+ )
100
+ issue = result["issues"][0]
101
+ self.assertEqual(result["summary"]["error_count"], 1)
102
+ self.assertEqual(issue["code"], "xml_not_well_formed")
103
+ self.assertIsInstance(issue["line"], int)
104
+ self.assertIsInstance(issue["column"], int)
105
+ self.assertIn("Q&A", issue["context"])
106
+ self.assertIn("&amp;", issue["hint"])
107
+
108
+ def test_lint_xml_reports_unescaped_ampersand_in_attribute(self) -> None:
109
+ result = xml_text_overlap_lint.lint_xml(
110
+ """
111
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
112
+ <data>
113
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
114
+ <content textType="body"><p><a href="https://example.com/?a=1&b=2">link</a></p></content>
115
+ </shape>
116
+ </data>
117
+ </slide>
118
+ """
119
+ )
120
+ issue = result["issues"][0]
121
+ self.assertEqual(issue["code"], "xml_not_well_formed")
122
+ self.assertIn("attribute", issue["hint"])
123
+ self.assertIn("a=1&amp;b=2", issue["hint"])
124
+
125
+ def test_lint_xml_reports_mismatched_xml_tag(self) -> None:
126
+ result = xml_text_overlap_lint.lint_xml(
127
+ """
128
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
129
+ <data>
130
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
131
+ <content textType="body"><p>Broken XML</content>
132
+ </shape>
133
+ </data>
134
+ </slide>
135
+ """
136
+ )
137
+ issue = result["issues"][0]
138
+ self.assertEqual(result["summary"]["slide_count"], 0)
139
+ self.assertEqual(result["summary"]["error_count"], 1)
140
+ self.assertEqual(issue["code"], "xml_not_well_formed")
141
+ self.assertIsInstance(issue["line"], int)
142
+ self.assertIsInstance(issue["column"], int)
143
+ self.assertIn("Broken XML", issue["context"])
144
+
145
+ def test_lint_xml_rejects_prefixed_sml_tags(self) -> None:
146
+ result = xml_text_overlap_lint.lint_xml(
147
+ """
148
+ <ns0:slide xmlns:ns0="http://www.larkoffice.com/sml/2.0">
149
+ <ns0:data>
150
+ <sml:shape xmlns:sml="http://www.larkoffice.com/sml/2.0" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
151
+ <sml:content textType="body"><sml:p>Prefixed SML</sml:p></sml:content>
152
+ </sml:shape>
153
+ </ns0:data>
154
+ </ns0:slide>
155
+ """
156
+ )
157
+ issues = result["issues"]
158
+ self.assertEqual(result["summary"]["error_count"], 5)
159
+ self.assertEqual([issue["code"] for issue in issues], ["sml_prefixed_tag"] * 5)
160
+ self.assertEqual(issues[0]["tag"], "ns0:slide")
161
+ self.assertIn("default namespace", issues[0]["hint"])
162
+
163
+ def test_lint_xml_allows_unprefixed_tags_without_namespace(self) -> None:
164
+ result = xml_text_overlap_lint.lint_xml(
165
+ """
166
+ <slide>
167
+ <data>
168
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
169
+ <content textType="body"><p>Unprefixed SML</p></content>
170
+ </shape>
171
+ </data>
172
+ </slide>
173
+ """
174
+ )
175
+ self.assertEqual(result["summary"]["error_count"], 0)
176
+
177
+ def test_lint_xml_accepts_escaped_entities_without_suspicious_entity_warning(self) -> None:
178
+ result = xml_text_overlap_lint.lint_xml(
179
+ """
180
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
181
+ <data>
182
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
183
+ <content textType="body"><p>Q&amp;A</p></content>
184
+ </shape>
185
+ </data>
186
+ </slide>
187
+ """
188
+ )
189
+ self.assertEqual(result["summary"]["error_count"], 0)
190
+ self.assertNotIn("issues", result)
191
+
192
+ def test_lint_xml_accepts_chinese_full_width_punctuation(self) -> None:
193
+ result = xml_text_overlap_lint.lint_xml(
194
+ """
195
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
196
+ <data>
197
+ <shape type="text" topLeftX="80" topLeftY="80" width="620" height="90">
198
+ <content textType="body"><p>承诺:按期交付;持续复盘|风险透明</p></content>
199
+ </shape>
200
+ </data>
201
+ </slide>
202
+ """
203
+ )
204
+ self.assertEqual(result["summary"]["error_count"], 0)
205
+
206
+ def test_lint_xml_single_slide_uses_default_canvas_without_bounds_checks(self) -> None:
207
+ result = xml_text_overlap_lint.lint_xml(
208
+ """
209
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
210
+ <data>
211
+ <shape type="text" topLeftX="1000" topLeftY="500" width="120" height="80">
212
+ <content textType="body"><p>Body text outside the canvas</p></content>
213
+ </shape>
214
+ </data>
215
+ </slide>
216
+ """
217
+ )
218
+ self.assertEqual(result["slide_size"], {"width": 960, "height": 540})
219
+ self.assertEqual(result["summary"]["slide_count"], 1)
220
+ self.assertEqual(result["summary"]["error_count"], 1)
221
+ self.assertEqual(result["slides"][0]["issues"][0]["code"], "shape_out_of_canvas")
222
+
223
+ def test_lint_xml_preserves_presentation_canvas_and_slide_order(self) -> None:
224
+ result = xml_text_overlap_lint.lint_xml(
225
+ """
226
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="1280" height="720">
227
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
228
+ <data>
229
+ <shape id="first" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
230
+ <content textType="body"><p>First slide</p></content>
231
+ </shape>
232
+ </data>
233
+ </slide>
234
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
235
+ <data>
236
+ <img id="second" src="tok" topLeftX="100" topLeftY="120" width="240" height="160"/>
237
+ <shape id="skipped" type="text" topLeftX="80" topLeftY="80" width="300">
238
+ <content textType="body"><p>Missing height</p></content>
239
+ </shape>
240
+ </data>
241
+ </slide>
242
+ </presentation>
243
+ """
244
+ )
245
+ self.assertEqual(result["slide_size"], {"width": 1280, "height": 720})
246
+ self.assertEqual(result["summary"]["slide_count"], 2)
247
+ self.assertEqual([slide["slide_number"] for slide in result["slides"]], [1, 2])
248
+ self.assertEqual([slide["element_count"] for slide in result["slides"]], [1, 1])
249
+ self.assertEqual(result["summary"]["error_count"], 0)
250
+ self.assertEqual(result["summary"]["warning_count"], 0)
251
+
252
+ def test_lint_xml_reports_sxsd_unsupported_tag_with_alias_hint(self) -> None:
253
+ cases = [
254
+ ("textbox", '<textbox topLeftX="80" topLeftY="80" width="300" height="60">Text</textbox>', '<shape type="text">'),
255
+ ("image", '<image src="tok" topLeftX="80" topLeftY="80" width="300" height="180"/>', "<img>"),
256
+ ]
257
+ for tag_name, element_xml, expected_hint in cases:
258
+ with self.subTest(tag=tag_name):
259
+ result = xml_text_overlap_lint.lint_xml(
260
+ f"""
261
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
262
+ <data>{element_xml}</data>
263
+ </slide>
264
+ """
265
+ )
266
+ issue = result["issues"][0]
267
+ self.assertEqual(result["summary"]["error_count"], 1)
268
+ self.assertEqual(issue["code"], "sxsd_unsupported_tag")
269
+ self.assertEqual(issue["tag"], tag_name)
270
+ self.assertIn(expected_hint, issue["hint"])
271
+
272
+ def test_lint_xml_reports_sxsd_unsupported_attr_with_alias_hint(self) -> None:
273
+ cases = [
274
+ ("shape", "x", "topLeftX", '<shape type="text" x="80" topLeftY="80" width="300" height="60"><content><p>Text</p></content></shape>'),
275
+ ("content", "fontColor", "color", '<shape type="text" topLeftX="80" topLeftY="80" width="300" height="60"><content fontColor="rgba(0, 0, 0, 1)"><p>Text</p></content></shape>'),
276
+ ]
277
+ for tag_name, attr_name, expected_attr, element_xml in cases:
278
+ with self.subTest(attr=attr_name):
279
+ result = xml_text_overlap_lint.lint_xml(
280
+ f"""
281
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
282
+ <data>{element_xml}</data>
283
+ </slide>
284
+ """
285
+ )
286
+ issue = result["issues"][0]
287
+ self.assertEqual(result["summary"]["error_count"], 1)
288
+ self.assertEqual(issue["code"], "sxsd_unsupported_attr")
289
+ self.assertEqual(issue["tag"], tag_name)
290
+ self.assertEqual(issue["attr"], attr_name)
291
+ self.assertIn(expected_attr, issue["hint"])
292
+
293
+ def test_lint_xml_ignores_server_filled_id_attrs(self) -> None:
294
+ result = xml_text_overlap_lint.lint_xml(
295
+ """
296
+ <slide id="server-slide-id" xmlns="http://www.larkoffice.com/sml/2.0">
297
+ <data>
298
+ <shape id="server-shape-id" type="rect" topLeftX="80" topLeftY="80" width="300" height="160">
299
+ <fill id="server-fill-id" unexpected="value">
300
+ <fillColor color="rgba(255, 255, 255, 1)"/>
301
+ </fill>
302
+ </shape>
303
+ </data>
304
+ </slide>
305
+ """
306
+ )
307
+
308
+ self.assertEqual(result["summary"]["error_count"], 1)
309
+ issue = result["issues"][0]
310
+ self.assertEqual(issue["code"], "sxsd_unsupported_attr")
311
+ self.assertEqual(issue["tag"], "fill")
312
+ self.assertEqual(issue["attr"], "unexpected")
313
+
314
+ def test_lint_xml_reports_gradient_shorthand_attrs_on_fill_color(self) -> None:
315
+ result = xml_text_overlap_lint.lint_xml(
316
+ """
317
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
318
+ <data>
319
+ <shape type="rect" topLeftX="80" topLeftY="80" width="300" height="160">
320
+ <fill>
321
+ <fillColor
322
+ type="gradient"
323
+ color1="rgba(255, 0, 0, 1)"
324
+ color2="rgba(0, 0, 255, 1)"
325
+ angle="45"
326
+ stop1="0%"
327
+ stop2="100%"/>
328
+ </fill>
329
+ </shape>
330
+ </data>
331
+ </slide>
332
+ """
333
+ )
334
+ unsupported_attrs = {issue["attr"] for issue in result["issues"]}
335
+ self.assertEqual(result["summary"]["error_count"], 6)
336
+ self.assertEqual(
337
+ unsupported_attrs,
338
+ {"type", "color1", "color2", "angle", "stop1", "stop2"},
339
+ )
340
+ self.assertTrue(all(issue["code"] == "sxsd_unsupported_attr" for issue in result["issues"]))
341
+ self.assertTrue(all(issue["tag"] == "fillColor" for issue in result["issues"]))
342
+
343
+ def test_lint_xml_accepts_chart_field_simple_content_attrs(self) -> None:
344
+ result = xml_text_overlap_lint.lint_xml(
345
+ """
346
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
347
+ <data>
348
+ <chart topLeftX="80" topLeftY="80" width="520" height="320">
349
+ <chartPlotArea>
350
+ <chartPlot type="line"/>
351
+ </chartPlotArea>
352
+ <chartData>
353
+ <dim1>
354
+ <chartField name="month" valueType="string">Jan, Feb</chartField>
355
+ </dim1>
356
+ <dim2>
357
+ <chartField name="value" valueType="number">1, 2</chartField>
358
+ </dim2>
359
+ </chartData>
360
+ </chart>
361
+ </data>
362
+ </slide>
363
+ """
364
+ )
365
+ self.assertEqual(result["summary"]["error_count"], 0)
366
+ self.assertNotIn("issues", result)
367
+
368
+ def test_lint_xml_does_not_load_iconpark_index_without_icons(self) -> None:
369
+ original_loader = xml_text_overlap_lint.load_iconpark_icon_types
370
+
371
+ def fail_if_loaded() -> set[str]:
372
+ raise AssertionError("iconpark index should not be loaded without <icon iconType>")
373
+
374
+ xml_text_overlap_lint.load_iconpark_icon_types = fail_if_loaded
375
+ try:
376
+ result = xml_text_overlap_lint.lint_xml(
377
+ """
378
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
379
+ <data>
380
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
381
+ <content><p>No icons here</p></content>
382
+ </shape>
383
+ </data>
384
+ </slide>
385
+ """
386
+ )
387
+ finally:
388
+ xml_text_overlap_lint.load_iconpark_icon_types = original_loader
389
+
390
+ self.assertEqual(result["summary"]["error_count"], 0)
391
+ self.assertNotIn("issues", result)
392
+
393
+ def test_lint_xml_accepts_iconpark_icon_type_from_index(self) -> None:
394
+ result = xml_text_overlap_lint.lint_xml(
395
+ """
396
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
397
+ <data>
398
+ <icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48">
399
+ <fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
400
+ </icon>
401
+ </data>
402
+ </slide>
403
+ """
404
+ )
405
+ self.assertEqual(result["summary"]["error_count"], 0)
406
+ self.assertNotIn("issues", result)
407
+
408
+ def test_lint_xml_reports_icon_missing_fill_color(self) -> None:
409
+ cases = [
410
+ '<icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48"/>',
411
+ '<icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48"><fill/></icon>',
412
+ (
413
+ '<icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48">'
414
+ "<fill><fillColor/></fill></icon>"
415
+ ),
416
+ ]
417
+ for icon_xml in cases:
418
+ with self.subTest(icon=icon_xml):
419
+ result = xml_text_overlap_lint.lint_xml(
420
+ f"""
421
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
422
+ <data>{icon_xml}</data>
423
+ </slide>
424
+ """
425
+ )
426
+
427
+ issue = result["issues"][0]
428
+ self.assertEqual(result["summary"]["error_count"], 1)
429
+ self.assertEqual(issue["code"], "icon_missing_fill_color")
430
+ self.assertEqual(issue["tag"], "icon")
431
+
432
+ def test_lint_xml_reports_icon_transparent_fill_color(self) -> None:
433
+ result = xml_text_overlap_lint.lint_xml(
434
+ """
435
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
436
+ <data>
437
+ <icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48">
438
+ <fill><fillColor color="rgba(37, 99, 235, 0)"/></fill>
439
+ </icon>
440
+ </data>
441
+ </slide>
442
+ """
443
+ )
444
+ issue = result["issues"][0]
445
+ self.assertEqual(result["summary"]["error_count"], 1)
446
+ self.assertEqual(issue["code"], "icon_transparent_fill_color")
447
+ self.assertEqual(issue["tag"], "icon")
448
+ self.assertEqual(issue["attr"], "fillColor")
449
+
450
+ def test_lint_xml_reports_iconpark_icon_type_outside_index(self) -> None:
451
+ result = xml_text_overlap_lint.lint_xml(
452
+ """
453
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
454
+ <data>
455
+ <icon iconType="iconpark/Base/settng.svg" topLeftX="80" topLeftY="80" width="48" height="48">
456
+ <fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
457
+ </icon>
458
+ </data>
459
+ </slide>
460
+ """
461
+ )
462
+ issue = result["issues"][0]
463
+ self.assertEqual(result["summary"]["error_count"], 1)
464
+ self.assertEqual(issue["code"], "iconpark_unsupported_icon_type")
465
+ self.assertEqual(issue["tag"], "icon")
466
+ self.assertEqual(issue["attr"], "iconType")
467
+ self.assertEqual(issue["iconType"], "iconpark/Base/settng.svg")
468
+ self.assertIn("iconpark-index.json", issue["hint"])
469
+ self.assertIn("iconpark/Base/setting.svg", issue["hint"])
470
+
471
+ def test_lint_xml_allows_namespaced_svg_inside_whiteboard(self) -> None:
472
+ result = xml_text_overlap_lint.lint_xml(
473
+ """
474
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
475
+ <data>
476
+ <whiteboard id="wb" topLeftX="80" topLeftY="80" width="300" height="180">
477
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 180">
478
+ <rect x="10" y="10" width="120" height="60"/>
479
+ </svg>
480
+ </whiteboard>
481
+ </data>
482
+ </slide>
483
+ """
484
+ )
485
+ self.assertEqual(result["summary"]["error_count"], 0)
486
+ self.assertNotIn("issues", result)
487
+
488
+ def test_lint_xml_reports_unqualified_svg_inside_whiteboard_with_namespace_hint(self) -> None:
489
+ result = xml_text_overlap_lint.lint_xml(
490
+ """
491
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
492
+ <data>
493
+ <whiteboard id="wb" topLeftX="80" topLeftY="80" width="300" height="180">
494
+ <svg viewBox="0 0 300 180">
495
+ <rect x="10" y="10" width="120" height="60"/>
496
+ </svg>
497
+ </whiteboard>
498
+ </data>
499
+ </slide>
500
+ """
501
+ )
502
+ issue = result["issues"][0]
503
+ self.assertEqual(result["summary"]["error_count"], 1)
504
+ self.assertEqual(issue["code"], "sxsd_unsupported_tag")
505
+ self.assertEqual(issue["tag"], "svg")
506
+ self.assertIn('xmlns="http://www.w3.org/2000/svg"', issue["hint"])
507
+
508
+ def test_lint_xml_detects_overlapping_text_boxes(self) -> None:
509
+ result = xml_text_overlap_lint.lint_xml(
510
+ """
511
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
512
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
513
+ <data>
514
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
515
+ <content textType="title"><p>Title</p></content>
516
+ </shape>
517
+ <shape type="text" topLeftX="80" topLeftY="80" width="300" height="80">
518
+ <content textType="body"><p>Body</p></content>
519
+ </shape>
520
+ </data>
521
+ </slide>
522
+ </presentation>
523
+ """
524
+ )
525
+ self.assertEqual(result["summary"]["error_count"], 1)
526
+ self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
527
+
528
+ def test_lint_xml_detects_current_itinerary_cjk_caption_occlusion(self) -> None:
529
+ result = xml_text_overlap_lint.lint_xml(
530
+ """
531
+ <slide id="pQO" xmlns="http://www.larkoffice.com/sml/2.0">
532
+ <data>
533
+ <shape width="190" height="80" topLeftX="580" topLeftY="170" presetHandlers="0" type="rect" id="blI">
534
+ <fill><fillColor color="rgba(255, 255, 255, 0.9)"/></fill>
535
+ <border color="rgba(220, 205, 185, 1)" width="1"/>
536
+ <content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
537
+ </shape>
538
+ <shape width="160" height="25" topLeftX="595" topLeftY="180" type="text" id="blX">
539
+ <content fontSize="14" fontFamily="思源黑体" color="rgba(120, 80, 40, 1)" bold="true"><p>日照金山</p></content>
540
+ </shape>
541
+ <shape width="160" height="40" topLeftX="595" topLeftY="205" type="text" id="blY">
542
+ <content textType="caption" fontSize="11" fontFamily="思源黑体" color="rgba(130, 100, 70, 1)"><p>清晨躺在床上看玉龙雪山日照金山奇观</p></content>
543
+ </shape>
544
+ <shape width="180" height="80" topLeftX="730" topLeftY="170" presetHandlers="0" type="rect" id="blH">
545
+ <fill><fillColor color="rgba(255, 255, 255, 0.9)"/></fill>
546
+ <border color="rgba(220, 205, 185, 1)" width="1"/>
547
+ <content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
548
+ </shape>
549
+ <shape width="150" height="25" topLeftX="745" topLeftY="180" type="text" id="blp">
550
+ <content fontSize="14" fontFamily="思源黑体" color="rgba(120, 80, 40, 1)" bold="true"><p>午餐返程</p></content>
551
+ </shape>
552
+ <shape width="150" height="40" topLeftX="745" topLeftY="205" type="text" id="blV">
553
+ <content textType="caption" fontSize="11" fontFamily="思源黑体" color="rgba(130, 100, 70, 1)"><p>享用特色午餐,带着美好回忆返程</p></content>
554
+ </shape>
555
+ <shape width="190" height="80" topLeftX="580" topLeftY="310" presetHandlers="0" type="rect" id="blP">
556
+ <fill><fillColor color="rgba(255, 255, 255, 0.9)"/></fill>
557
+ <border color="rgba(220, 205, 185, 1)" width="1"/>
558
+ <content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
559
+ </shape>
560
+ <shape width="160" height="25" topLeftX="595" topLeftY="320" type="text" id="blG">
561
+ <content fontSize="14" fontFamily="思源黑体" color="rgba(120, 80, 40, 1)" bold="true"><p>高路徒步</p></content>
562
+ </shape>
563
+ <shape width="160" height="40" topLeftX="595" topLeftY="345" type="text" id="blQ">
564
+ <content textType="caption" fontSize="11" fontFamily="思源黑体" color="rgba(130, 100, 70, 1)"><p>经典高路徒步,28道拐,龙洞瀑布,中虎跳峡</p></content>
565
+ </shape>
566
+ <shape width="180" height="80" topLeftX="730" topLeftY="310" presetHandlers="0" type="rect" id="blw">
567
+ <fill><fillColor color="rgba(255, 255, 255, 0.9)"/></fill>
568
+ <border color="rgba(220, 205, 185, 1)" width="1"/>
569
+ <content fontSize="16" fontFamily="思源黑体" color="rgba(31, 35, 41, 1)"/>
570
+ </shape>
571
+ <shape width="150" height="25" topLeftX="745" topLeftY="320" type="text" id="blZ">
572
+ <content fontSize="14" fontFamily="思源黑体" color="rgba(120, 80, 40, 1)" bold="true"><p>伴手礼</p></content>
573
+ </shape>
574
+ <shape width="150" height="40" topLeftX="745" topLeftY="345" type="text" id="blS">
575
+ <content textType="caption" fontSize="11" fontFamily="思源黑体" color="rgba(130, 100, 70, 1)"><p>酒店精心准备的归途伴手礼,留下难忘纪念</p></content>
576
+ </shape>
577
+ </data>
578
+ </slide>
579
+ """
580
+ )
581
+ overlap_pairs = {tuple(issue["elements"]) for issue in result["slides"][0]["issues"]}
582
+ self.assertEqual(result["summary"]["error_count"], 2)
583
+ self.assertIn(("blY", "blV"), overlap_pairs)
584
+ self.assertIn(("blQ", "blS"), overlap_pairs)
585
+
586
+ def test_lint_xml_detects_horizontal_text_overflow_across_declared_box_gap(self) -> None:
587
+ result = xml_text_overlap_lint.lint_xml(
588
+ """
589
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
590
+ <data>
591
+ <shape id="source" type="text" topLeftX="80" topLeftY="100" width="160" height="40">
592
+ <content fontSize="18"><p>这是一个足够长的中文文本用于检测跨越间隙的横向溢出</p></content>
593
+ </shape>
594
+ <shape id="target" type="text" topLeftX="260" topLeftY="100" width="160" height="40">
595
+ <content fontSize="18"><p>目标</p></content>
596
+ </shape>
597
+ </data>
598
+ </slide>
599
+ """
600
+ )
601
+ self.assertEqual(result["summary"]["error_count"], 1)
602
+ self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
603
+ self.assertEqual(result["slides"][0]["issues"][0]["elements"], ["source", "target"])
604
+
605
+ def test_lint_xml_reports_text_out_of_canvas_but_not_text_height(self) -> None:
606
+ result = xml_text_overlap_lint.lint_xml(
607
+ """
608
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
609
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
610
+ <data>
611
+ <shape type="text" topLeftX="80" topLeftY="80" width="180" height="20">
612
+ <content textType="body" fontSize="18"><p>This paragraph is intentionally much longer than the box can safely contain.</p></content>
613
+ </shape>
614
+ <shape type="text" topLeftX="1000" topLeftY="500" width="120" height="80">
615
+ <content textType="body"><p>Body text outside the canvas</p></content>
616
+ </shape>
617
+ </data>
618
+ </slide>
619
+ </presentation>
620
+ """
621
+ )
622
+ issue = result["slides"][0]["issues"][0]
623
+ self.assertEqual(result["summary"]["error_count"], 1)
624
+ self.assertEqual(result["summary"]["warning_count"], 0)
625
+ self.assertEqual(issue["code"], "shape_out_of_canvas")
626
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 160, "bottom": 40})
627
+
628
+ def test_lint_xml_allows_template_style_bleed_and_text_over_images(self) -> None:
629
+ result = xml_text_overlap_lint.lint_xml(
630
+ """
631
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
632
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
633
+ <data>
634
+ <img src="tok" topLeftX="-120" topLeftY="20" width="360" height="360"/>
635
+ <shape type="text" topLeftX="40" topLeftY="80" width="180" height="80">
636
+ <content textType="title" fontSize="44"><p>Title</p></content>
637
+ </shape>
638
+ <shape type="text" topLeftX="40" topLeftY="120" width="180" height="40">
639
+ <content textType="sub-headline" fontSize="20"><p>Subtitle</p></content>
640
+ </shape>
641
+ </data>
642
+ </slide>
643
+ </presentation>
644
+ """
645
+ )
646
+ self.assertEqual(result["summary"]["error_count"], 0)
647
+ self.assertEqual(result["summary"]["warning_count"], 0)
648
+
649
+ def test_extract_elements_preserves_supported_element_geometry_order_and_text_metadata(self) -> None:
650
+ elements = xml_text_overlap_lint.extract_elements(
651
+ """
652
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
653
+ <data>
654
+ <img id="photo" src="tok" topLeftX="10" topLeftY="20" width="100" height="80"/>
655
+ <shape id="headline" type="text" topLeftX="40" topLeftY="60" width="320" height="90">
656
+ <content textType="headline" textAlign="center" autoFit="normal-auto-fit" fontSize="28">
657
+ <p><![CDATA[Growth & scale]]></p>
658
+ <p>Focused execution</p>
659
+ </content>
660
+ </shape>
661
+ <table id="table" topLeftX="400" topLeftY="60" width="220" height="120"></table>
662
+ <chart id="chart" topLeftX="640" topLeftY="60" width="220" height="120"/>
663
+ <whiteboard id="wb" topLeftX="80" topLeftY="220" width="760" height="240"/>
664
+ <shape id="missing-height" type="text" topLeftX="80" topLeftY="480" width="320">
665
+ <content><p>Skipped</p></content>
666
+ </shape>
667
+ </data>
668
+ </slide>
669
+ """
670
+ )
671
+ self.assertEqual([element["id"] for element in elements], ["photo", "headline", "table", "chart", "wb"])
672
+ self.assertEqual([element["kind"] for element in elements], ["img", "shape", "table", "chart", "whiteboard"])
673
+ self.assertEqual([element["order"] for element in elements], [0, 1, 2, 3, 4])
674
+ self.assertEqual(elements[1]["type"], "text")
675
+ self.assertEqual(elements[1]["textType"], "headline")
676
+ self.assertEqual(elements[1]["textAlign"], "center")
677
+ self.assertEqual(elements[1]["autoFit"], "normal-auto-fit")
678
+ self.assertEqual(elements[1]["fontSize"], 28)
679
+ self.assertEqual(elements[1]["text"], "Growth & scale\nFocused execution")
680
+
681
+ def test_lint_xml_allows_small_out_of_bounds_images(self) -> None:
682
+ result = xml_text_overlap_lint.lint_xml(
683
+ """
684
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
685
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
686
+ <data>
687
+ <img src="tok" topLeftX="-20" topLeftY="20" width="120" height="120"/>
688
+ </data>
689
+ </slide>
690
+ </presentation>
691
+ """
692
+ )
693
+ self.assertEqual(result["summary"]["error_count"], 0)
694
+
695
+ def test_lint_xml_allows_out_of_canvas_images(self) -> None:
696
+ result = xml_text_overlap_lint.lint_xml(
697
+ """
698
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
699
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
700
+ <data>
701
+ <img src="right" topLeftX="780" topLeftY="0" width="500" height="540"/>
702
+ <img src="bottom" topLeftX="0" topLeftY="430" width="900" height="280"/>
703
+ </data>
704
+ </slide>
705
+ </presentation>
706
+ """
707
+ )
708
+ self.assertEqual(result["summary"]["error_count"], 0)
709
+
710
+ def test_lint_xml_allows_full_bleed_images(self) -> None:
711
+ result = xml_text_overlap_lint.lint_xml(
712
+ """
713
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
714
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
715
+ <data>
716
+ <img src="tok" topLeftX="-80" topLeftY="-20" width="1080" height="600"/>
717
+ </data>
718
+ </slide>
719
+ </presentation>
720
+ """
721
+ )
722
+ self.assertEqual(result["summary"]["error_count"], 0)
723
+
724
+ def test_lint_xml_reports_text_and_chart_out_of_canvas(self) -> None:
725
+ result = xml_text_overlap_lint.lint_xml(
726
+ """
727
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
728
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
729
+ <data>
730
+ <shape id="outside-shape" type="text" topLeftX="-10" topLeftY="40" width="50" height="50"/>
731
+ <img id="outside-img" src="token" topLeftX="120" topLeftY="-20" width="50" height="50"/>
732
+ <chart id="outside-chart" topLeftX="900" topLeftY="100" width="100" height="100"/>
733
+ <whiteboard id="outside-whiteboard" topLeftX="100" topLeftY="500" width="100" height="100"/>
734
+ </data>
735
+ </slide>
736
+ </presentation>
737
+ """
738
+ )
739
+ issues = result["slides"][0]["issues"]
740
+ self.assertEqual(result["summary"]["error_count"], 2)
741
+ self.assertEqual(
742
+ [(issue["code"], issue["elements"], issue["overflow"]) for issue in issues],
743
+ [
744
+ ("shape_out_of_canvas", ["outside-shape"], {"left": 10, "top": 0, "right": 0, "bottom": 0}),
745
+ ("chart_out_of_canvas", ["outside-chart"], {"left": 0, "top": 0, "right": 40, "bottom": 0}),
746
+ ],
747
+ )
748
+
749
+ def test_lint_xml_uses_rotated_text_and_chart_bounds_for_canvas_validation(self) -> None:
750
+ result = xml_text_overlap_lint.lint_xml(
751
+ """
752
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
753
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
754
+ <data>
755
+ <shape id="rotated-text" type="text" topLeftX="0" topLeftY="0" width="100" height="100" rotation="45"/>
756
+ <chart id="rotated-chart" topLeftX="860" topLeftY="200" width="100" height="100" rotation="45"/>
757
+ </data>
758
+ </slide>
759
+ </presentation>
760
+ """
761
+ )
762
+ issues_by_element = {issue["elements"][0]: issue for issue in result["slides"][0]["issues"]}
763
+ self.assertEqual(result["summary"]["error_count"], 2)
764
+ self.assertEqual(issues_by_element["rotated-text"]["code"], "shape_out_of_canvas")
765
+ self.assertAlmostEqual(issues_by_element["rotated-text"]["overflow"]["left"], 20.710678, places=5)
766
+ self.assertAlmostEqual(issues_by_element["rotated-text"]["overflow"]["top"], 20.710678, places=5)
767
+ self.assertEqual(issues_by_element["rotated-chart"]["code"], "chart_out_of_canvas")
768
+ self.assertAlmostEqual(issues_by_element["rotated-chart"]["overflow"]["right"], 20.710678, places=5)
769
+
770
+ def test_lint_xml_treats_non_finite_rotations_as_zero(self) -> None:
771
+ result = xml_text_overlap_lint.lint_xml(
772
+ """
773
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
774
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
775
+ <data>
776
+ <shape id="infinite" type="text" topLeftX="-10" topLeftY="0" width="20" height="20" rotation="inf"/>
777
+ <shape id="negative-infinite" type="text" topLeftX="0" topLeftY="-10" width="20" height="20" rotation="-inf"/>
778
+ <chart id="not-a-number" topLeftX="950" topLeftY="0" width="20" height="20" rotation="nan"/>
779
+ </data>
780
+ </slide>
781
+ </presentation>
782
+ """
783
+ )
784
+ issues_by_element = {issue["elements"][0]: issue for issue in result["slides"][0]["issues"]}
785
+ self.assertEqual(result["summary"]["error_count"], 3)
786
+ self.assertEqual(issues_by_element["infinite"]["overflow"], {"left": 10, "top": 0, "right": 0, "bottom": 0})
787
+ self.assertEqual(issues_by_element["negative-infinite"]["overflow"], {"left": 0, "top": 10, "right": 0, "bottom": 0})
788
+ self.assertEqual(issues_by_element["not-a-number"]["overflow"], {"left": 0, "top": 0, "right": 10, "bottom": 0})
789
+
790
+ def test_lint_xml_reports_table_bottom_overflow_from_declared_bounds(self) -> None:
791
+ result = xml_text_overlap_lint.lint_xml(
792
+ """
793
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
794
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
795
+ <data>
796
+ <table id="score-table" topLeftX="54" topLeftY="238" width="414" height="385">
797
+ <tr><td><content><p>Score</p></content></td></tr>
798
+ </table>
799
+ </data>
800
+ </slide>
801
+ </presentation>
802
+ """
803
+ )
804
+ issue = result["slides"][0]["issues"][0]
805
+ self.assertEqual(result["summary"]["error_count"], 1)
806
+ self.assertEqual(issue["code"], "table_out_of_canvas")
807
+ self.assertEqual(issue["elements"], ["score-table"])
808
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 0, "bottom": 83})
809
+ self.assertEqual(issue["bbox"], {"x": 54, "y": 238, "width": 414, "height": 385})
810
+
811
+ def test_lint_xml_reports_table_right_overflow_from_declared_bounds(self) -> None:
812
+ result = xml_text_overlap_lint.lint_xml(
813
+ """
814
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
815
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
816
+ <data>
817
+ <table id="wide-table" topLeftX="850" topLeftY="80" width="180" height="120">
818
+ <tr><td><content><p>Score</p></content></td></tr>
819
+ </table>
820
+ </data>
821
+ </slide>
822
+ </presentation>
823
+ """
824
+ )
825
+ issue = result["slides"][0]["issues"][0]
826
+ self.assertEqual(result["summary"]["error_count"], 1)
827
+ self.assertEqual(issue["code"], "table_out_of_canvas")
828
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 70, "bottom": 0})
829
+
830
+ def test_lint_xml_allows_table_with_declared_bounds_inside_canvas(self) -> None:
831
+ result = xml_text_overlap_lint.lint_xml(
832
+ """
833
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
834
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
835
+ <data>
836
+ <table id="inside-table" topLeftX="40" topLeftY="120" width="880" height="360">
837
+ <tr><td><content><p>Score</p></content></td></tr>
838
+ </table>
839
+ </data>
840
+ </slide>
841
+ </presentation>
842
+ """
843
+ )
844
+ self.assertEqual(result["summary"]["error_count"], 0)
845
+ self.assertEqual(result["summary"]["warning_count"], 0)
846
+
847
+ def test_lint_xml_reports_resolved_table_bounds_when_declared_sizes_are_missing(self) -> None:
848
+ result = xml_text_overlap_lint.lint_xml(
849
+ """
850
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
851
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
852
+ <data>
853
+ <table id="implicit-size-table" topLeftX="850" topLeftY="480">
854
+ <colgroup><col/><col/></colgroup>
855
+ <tr><td/><td/></tr>
856
+ <tr><td/><td/></tr>
857
+ </table>
858
+ </data>
859
+ </slide>
860
+ </presentation>
861
+ """
862
+ )
863
+ issue = result["slides"][0]["issues"][0]
864
+ self.assertEqual(result["summary"]["error_count"], 1)
865
+ self.assertEqual(issue["code"], "table_out_of_canvas")
866
+ self.assertEqual(issue["bbox"], {"x": 850, "y": 480, "width": 220, "height": 74})
867
+ self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 110, "bottom": 14})
868
+
869
+ def test_lint_xml_uses_resolved_table_bounds_for_canvas_validation(self) -> None:
870
+ result = xml_text_overlap_lint.lint_xml(
871
+ """
872
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
873
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
874
+ <data>
875
+ <table id="resolved-overflow-table" topLeftX="800" topLeftY="80" width="100" height="40">
876
+ <colgroup><col width="100"/><col width="100"/></colgroup>
877
+ <tr height="40"><td/><td/></tr>
878
+ </table>
879
+ </data>
880
+ </slide>
881
+ </presentation>
882
+ """
883
+ )
884
+ issues = result["slides"][0]["issues"]
885
+ canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
886
+ mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
887
+ self.assertEqual(result["summary"]["error_count"], 1)
888
+ self.assertEqual(canvas_issue["bbox"], {"x": 800, "y": 80, "width": 200, "height": 40})
889
+ self.assertEqual(canvas_issue["overflow"]["right"], 40)
890
+ self.assertEqual(mismatch_issue["dimension"], "width")
891
+ self.assertEqual(mismatch_issue["resolved_size"], canvas_issue["bbox"]["width"])
892
+
893
+ def test_lint_xml_uses_the_same_anonymous_table_id_for_all_table_diagnostics(self) -> None:
894
+ result = xml_text_overlap_lint.lint_xml(
895
+ """
896
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
897
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
898
+ <data>
899
+ <shape id="title" type="text" topLeftX="40" topLeftY="40" width="200" height="40"/>
900
+ <img id="logo" src="token" topLeftX="40" topLeftY="100" width="40" height="40"/>
901
+ <table topLeftX="900" topLeftY="80" width="100" height="40">
902
+ <colgroup><col width="100"/><col width="100"/></colgroup>
903
+ <tr height="40"><td/><td/></tr>
904
+ </table>
905
+ </data>
906
+ </slide>
907
+ </presentation>
908
+ """
909
+ )
910
+ issues = result["slides"][0]["issues"]
911
+ canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
912
+ mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
913
+ self.assertEqual(canvas_issue["elements"], ["table-3"])
914
+ self.assertEqual(mismatch_issue["elements"], ["table-3"])
915
+
916
+ def test_lint_xml_reports_info_when_table_target_size_resolves_larger_than_declared(self) -> None:
917
+ result = xml_text_overlap_lint.lint_xml(
918
+ """
919
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
920
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
921
+ <data>
922
+ <table id="size-mismatch" topLeftX="40" topLeftY="120" width="200" height="80">
923
+ <colgroup><col span="2" width="100"/><col width="50"/></colgroup>
924
+ <tr height="40"><td/><td/><td/></tr>
925
+ <tr height="60"><td/><td/><td/></tr>
926
+ </table>
927
+ </data>
928
+ </slide>
929
+ </presentation>
930
+ """
931
+ )
932
+ issues_by_dimension = {issue["dimension"]: issue for issue in result["slides"][0]["issues"]}
933
+ self.assertEqual(result["summary"]["error_count"], 0)
934
+ self.assertEqual(result["summary"]["warning_count"], 0)
935
+ self.assertEqual(result["summary"]["info_count"], 2)
936
+ self.assertEqual(issues_by_dimension["width"]["level"], "info")
937
+ self.assertEqual(issues_by_dimension["width"]["code"], "table_resolved_size_mismatch")
938
+ self.assertEqual(issues_by_dimension["width"]["resolved_sizes"], [100, 100, 50])
939
+ self.assertEqual(issues_by_dimension["width"]["resolved_size"], 250)
940
+ self.assertEqual(issues_by_dimension["height"]["resolved_sizes"], [40, 60])
941
+ self.assertEqual(issues_by_dimension["height"]["resolved_size"], 100)
942
+
943
+ def test_lint_xml_does_not_report_info_when_table_target_size_is_resolved_exactly(self) -> None:
944
+ result = xml_text_overlap_lint.lint_xml(
945
+ """
946
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
947
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
948
+ <data>
949
+ <table id="size-match" topLeftX="40" topLeftY="120" width="300" height="100">
950
+ <colgroup><col width="100"/><col/></colgroup>
951
+ <tr height="40"><td/><td/></tr>
952
+ <tr><td/><td/></tr>
953
+ </table>
954
+ </data>
955
+ </slide>
956
+ </presentation>
957
+ """
958
+ )
959
+ self.assertEqual(result["summary"]["error_count"], 0)
960
+ self.assertEqual(result["summary"]["warning_count"], 0)
961
+ self.assertEqual(result["summary"]["info_count"], 0)
962
+ self.assertEqual(result["slides"][0]["issues"], [])
963
+
964
+ def test_lint_xml_keeps_resolved_table_sizes_positive_when_target_is_too_small(self) -> None:
965
+ result = xml_text_overlap_lint.lint_xml(
966
+ """
967
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
968
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
969
+ <data>
970
+ <table id="narrow-table" topLeftX="40" topLeftY="120" width="1">
971
+ <colgroup><col/><col/></colgroup>
972
+ <tr><td/><td/></tr>
973
+ </table>
974
+ </data>
975
+ </slide>
976
+ </presentation>
977
+ """
978
+ )
979
+ issue = result["slides"][0]["issues"][0]
980
+ self.assertEqual(issue["dimension"], "width")
981
+ self.assertEqual(issue["resolved_sizes"], [1, 1])
982
+ self.assertEqual(issue["resolved_size"], 2)
983
+
984
+ def test_fill_last_size_gap_preserves_target_when_positive_sizes_are_possible(self) -> None:
985
+ final_sizes = xml_text_overlap_lint.fill_last_size_gap([10, 10], 3)
986
+ self.assertEqual(final_sizes, [2, 1])
987
+ self.assertEqual(sum(final_sizes), 3)
988
+
989
+ def test_cli_reports_table_layout_size_info_for_weighted_min_layout_cases(self) -> None:
990
+ cases = {
991
+ "target-exact": (
992
+ """
993
+ <table topLeftX="40" topLeftY="120" width="360" height="150">
994
+ <colgroup><col width="100"/><col width="200"/></colgroup>
995
+ <tr height="40"><td/><td/></tr><tr height="60"><td/><td/></tr>
996
+ </table>
997
+ """,
998
+ 0,
999
+ ),
1000
+ "declared-size-exceeds-target": (
1001
+ """
1002
+ <table topLeftX="40" topLeftY="120" width="200" height="80">
1003
+ <colgroup><col span="2" width="100"/><col width="50"/></colgroup>
1004
+ <tr height="40"><td/><td/><td/></tr><tr height="60"><td/><td/><td/></tr>
1005
+ </table>
1006
+ """,
1007
+ 2,
1008
+ ),
1009
+ "remaining-space-insufficient": (
1010
+ """
1011
+ <table topLeftX="40" topLeftY="120" width="80" height="30">
1012
+ <colgroup><col width="80"/><col/></colgroup>
1013
+ <tr height="40"><td/><td/></tr><tr><td/><td/></tr>
1014
+ </table>
1015
+ """,
1016
+ 2,
1017
+ ),
1018
+ "no-target-size": (
1019
+ """
1020
+ <table topLeftX="40" topLeftY="120">
1021
+ <colgroup><col width="80"/><col/></colgroup>
1022
+ <tr height="40"><td/><td/></tr><tr><td/><td/></tr>
1023
+ </table>
1024
+ """,
1025
+ 0,
1026
+ ),
1027
+ }
1028
+ script_path = Path(xml_text_overlap_lint.__file__).resolve()
1029
+ with tempfile.TemporaryDirectory() as temp_dir:
1030
+ for name, (table_xml, expected_info_count) in cases.items():
1031
+ with self.subTest(case=name):
1032
+ input_path = Path(temp_dir) / f"{name}.xml"
1033
+ input_path.write_text(
1034
+ f"""
1035
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1036
+ <slide xmlns="http://www.larkoffice.com/sml/2.0"><data>{table_xml}</data></slide>
1037
+ </presentation>
1038
+ """,
1039
+ encoding="utf-8",
1040
+ )
1041
+ completed = subprocess.run(
1042
+ [sys.executable, str(script_path), "--input", str(input_path)],
1043
+ capture_output=True,
1044
+ check=False,
1045
+ text=True,
1046
+ )
1047
+ result = json.loads(completed.stdout)
1048
+ self.assertEqual(completed.returncode, 0, completed.stderr)
1049
+ self.assertEqual(result["summary"]["error_count"], 0)
1050
+ self.assertEqual(result["summary"]["warning_count"], 0)
1051
+ self.assertEqual(result["summary"]["info_count"], expected_info_count)
1052
+ self.assertTrue(
1053
+ all(issue["level"] == "info" for issue in result["slides"][0]["issues"]),
1054
+ result["slides"][0]["issues"],
1055
+ )
1056
+
1057
+ def test_lint_xml_warns_for_whiteboard_external_boundary_overlap(self) -> None:
1058
+ result = xml_text_overlap_lint.lint_xml(
1059
+ """
1060
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1061
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1062
+ <data>
1063
+ <whiteboard id="wb" topLeftX="50" topLeftY="140" width="860" height="360"/>
1064
+ <shape id="card" type="rect" topLeftX="50" topLeftY="450" width="200" height="60">
1065
+ <fill><fillColor color="rgba(232, 186, 176, 0.2)"/></fill>
1066
+ <content/>
1067
+ </shape>
1068
+ <shape id="label" type="text" topLeftX="65" topLeftY="460" width="170" height="20">
1069
+ <content fontSize="12"><p>基础保障</p></content>
1070
+ </shape>
1071
+ </data>
1072
+ </slide>
1073
+ </presentation>
1074
+ """
1075
+ )
1076
+ issues = result["slides"][0]["issues"]
1077
+ self.assertEqual(result["summary"]["error_count"], 0)
1078
+ self.assertEqual(result["summary"]["warning_count"], 1)
1079
+ self.assertEqual(issues[0]["code"], "whiteboard_external_overlap")
1080
+ self.assertIn("static whiteboard container-bbox risk", issues[0]["hint"])
1081
+ self.assertIn("screenshot QA", issues[0]["hint"])
1082
+ self.assertEqual(issues[0]["elements"], ["wb", "card"])
1083
+ self.assertEqual(issues[0]["overlaps"][0]["overlap_height"], 50)
1084
+
1085
+ def test_lint_xml_allows_whiteboard_internal_labels(self) -> None:
1086
+ result = xml_text_overlap_lint.lint_xml(
1087
+ """
1088
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1089
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1090
+ <data>
1091
+ <whiteboard id="wb" topLeftX="100" topLeftY="120" width="420" height="260"/>
1092
+ <shape id="label" type="text" topLeftX="150" topLeftY="180" width="180" height="40">
1093
+ <content fontSize="14"><p>内部标注</p></content>
1094
+ </shape>
1095
+ </data>
1096
+ </slide>
1097
+ </presentation>
1098
+ """
1099
+ )
1100
+ self.assertEqual(result["summary"]["error_count"], 0)
1101
+ self.assertEqual(result["summary"]["warning_count"], 0)
1102
+
1103
+ def test_lint_xml_allows_bottom_layer_full_slide_whiteboard_background(self) -> None:
1104
+ result = xml_text_overlap_lint.lint_xml(
1105
+ """
1106
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1107
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1108
+ <data>
1109
+ <whiteboard id="background" topLeftX="0" topLeftY="0" width="960" height="540"/>
1110
+ <shape id="title" type="text" topLeftX="80" topLeftY="80" width="420" height="80">
1111
+ <content textType="title" fontSize="36"><p>Title</p></content>
1112
+ </shape>
1113
+ </data>
1114
+ </slide>
1115
+ </presentation>
1116
+ """
1117
+ )
1118
+ self.assertEqual(result["summary"]["error_count"], 0)
1119
+ self.assertEqual(result["summary"]["warning_count"], 0)
1120
+
1121
+ def test_lint_xml_allows_whiteboard_inside_background_panel(self) -> None:
1122
+ result = xml_text_overlap_lint.lint_xml(
1123
+ """
1124
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1125
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1126
+ <data>
1127
+ <shape id="panel" type="rect" topLeftX="40" topLeftY="100" width="880" height="340">
1128
+ <fill><fillColor color="rgba(232, 186, 176, 0.2)"/></fill>
1129
+ <content/>
1130
+ </shape>
1131
+ <whiteboard id="wb" topLeftX="60" topLeftY="120" width="840" height="300"/>
1132
+ </data>
1133
+ </slide>
1134
+ </presentation>
1135
+ """
1136
+ )
1137
+ self.assertEqual(result["summary"]["error_count"], 0)
1138
+ self.assertEqual(result["summary"]["warning_count"], 0)
1139
+
1140
+ def test_lint_xml_detects_invalid_template_text_stack_overlap(self) -> None:
1141
+ cases = [
1142
+ (
1143
+ "subtitle-too-high",
1144
+ """
1145
+ <shape type="text" topLeftX="40" topLeftY="80" width="240" height="90">
1146
+ <content textType="title" fontSize="44"><p>Title</p></content>
1147
+ </shape>
1148
+ <shape type="text" topLeftX="40" topLeftY="90" width="240" height="80">
1149
+ <content textType="sub-headline" fontSize="20"><p>Subtitle</p></content>
1150
+ </shape>
1151
+ """,
1152
+ ),
1153
+ ]
1154
+ for name, shapes in cases:
1155
+ with self.subTest(name=name):
1156
+ result = xml_text_overlap_lint.lint_xml(
1157
+ f"""
1158
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1159
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1160
+ <data>{shapes}</data>
1161
+ </slide>
1162
+ </presentation>
1163
+ """
1164
+ )
1165
+ self.assertEqual(result["summary"]["error_count"], 1)
1166
+ self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
1167
+
1168
+
1169
+ if __name__ == "__main__":
1170
+ unittest.main()