@amaster.ai/pi-lark 0.1.5 → 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.
- package/package.json +3 -3
- package/skills/lark-approval/references/lark-approval-initiate.md +2 -5
- package/skills/lark-approval/references/lark-approval-instances-initiated.md +6 -0
- package/skills/lark-approval/references/lark-approval-tasks-query.md +9 -0
- package/skills/lark-approval/references/lark-approval-tasks-rollback.md +8 -2
- package/skills/lark-apps/SKILL.md +25 -7
- package/skills/lark-apps/references/lark-apps-access-scope-set.md +1 -1
- package/skills/lark-apps/references/lark-apps-automation.md +164 -0
- package/skills/lark-apps/references/lark-apps-db-execute.md +186 -2
- package/skills/lark-apps/references/lark-apps-db.md +3 -3
- package/skills/lark-apps/references/lark-apps-get.md +43 -0
- package/skills/lark-apps/references/lark-apps-html-publish.md +7 -2
- package/skills/lark-apps/references/lark-apps-init.md +1 -2
- package/skills/lark-apps/references/lark-apps-openapi-key.md +1 -1
- package/skills/lark-apps/references/lark-apps-release-create.md +3 -1
- package/skills/lark-apps/references/lark-apps-role.md +133 -0
- package/skills/lark-base/SKILL.md +7 -3
- package/skills/lark-base/references/dashboard-block-data-config.md +28 -2
- package/skills/lark-base/references/lark-base-cell-value.md +9 -4
- package/skills/lark-base/references/lark-base-dashboard-block-get-data.md +7 -7
- package/skills/lark-base/references/lark-base-dashboard.md +11 -2
- package/skills/lark-base/references/lark-base-data-query.md +9 -7
- package/skills/lark-base/references/lark-base-field-create.md +4 -2
- package/skills/lark-base/references/lark-base-field-json.md +52 -15
- package/skills/lark-base/references/lark-base-field-update.md +4 -2
- package/skills/lark-base/references/lark-base-view-set-filter.md +3 -1
- package/skills/lark-calendar/SKILL.md +89 -31
- package/skills/lark-calendar/references/lark-calendar-create.md +8 -39
- package/skills/lark-calendar/references/lark-calendar-room-find.md +5 -9
- package/skills/lark-calendar/references/lark-calendar-rsvp.md +1 -5
- package/skills/lark-calendar/references/lark-calendar-schedule-clear-time.md +59 -0
- package/skills/lark-calendar/references/lark-calendar-schedule-fuzzy-time.md +88 -0
- package/skills/lark-calendar/references/lark-calendar-schedule-meeting.md +67 -210
- package/skills/lark-calendar/references/lark-calendar-suggestion.md +1 -5
- package/skills/lark-calendar/references/lark-calendar-update.md +2 -7
- package/skills/lark-doc/SKILL.md +1 -1
- package/skills/lark-doc/references/lark-doc-fetch.md +4 -2
- package/skills/lark-doc/references/lark-doc-mindnote.md +17 -2
- package/skills/lark-doc/references/lark-doc-whiteboard.md +4 -0
- package/skills/lark-doc/references/lark-doc-xml-extended-blocks.md +35 -0
- package/skills/lark-doc/references/lark-doc-xml.md +3 -2
- package/skills/lark-drive/SKILL.md +20 -8
- package/skills/lark-drive/references/lark-drive-comment-location.md +16 -4
- package/skills/lark-drive/references/lark-drive-comments-guide.md +16 -8
- package/skills/lark-drive/references/lark-drive-delete.md +35 -11
- package/skills/lark-drive/references/lark-drive-export.md +39 -10
- package/skills/lark-drive/references/lark-drive-files-list.md +27 -2
- package/skills/lark-drive/references/lark-drive-inspect.md +2 -0
- package/skills/lark-drive/references/lark-drive-list-comments.md +125 -0
- package/skills/lark-drive/references/lark-drive-member-add.md +1 -1
- package/skills/lark-drive/references/lark-drive-move.md +5 -3
- package/skills/lark-drive/references/lark-drive-permission-guide.md +12 -0
- package/skills/lark-drive/references/lark-drive-pull.md +3 -3
- package/skills/lark-drive/references/lark-drive-push.md +33 -6
- package/skills/lark-drive/references/lark-drive-status.md +12 -14
- package/skills/lark-drive/references/lark-drive-task-result.md +58 -5
- package/skills/lark-drive/references/lark-drive-workflow-knowledge-organize.md +26 -20
- package/skills/lark-drive/references/lark-drive-workflow.md +2 -1
- package/skills/lark-event/SKILL.md +2 -1
- package/skills/lark-event/references/lark-event-approval.md +170 -0
- package/skills/lark-im/SKILL.md +5 -4
- package/skills/lark-im/references/lark-im-messages-reply.md +1 -1
- package/skills/lark-im/references/lark-im-messages-send.md +1 -1
- package/skills/lark-mail/SKILL.md +12 -9
- package/skills/lark-mail/references/lark-mail-forward.md +1 -1
- package/skills/lark-mail/references/lark-mail-message-modify.md +48 -0
- package/skills/lark-mail/references/lark-mail-message-trash.md +41 -0
- package/skills/lark-mail/references/lark-mail-reply-all.md +1 -1
- package/skills/lark-mail/references/lark-mail-reply.md +1 -1
- package/skills/lark-mail/references/lark-mail-watch.md +1 -1
- package/skills/lark-markdown/SKILL.md +3 -2
- package/skills/lark-markdown/references/lark-markdown-create.md +22 -2
- package/skills/lark-minutes/SKILL.md +19 -4
- package/skills/lark-minutes/references/lark-minutes-download.md +0 -2
- package/skills/lark-minutes/references/lark-minutes-search.md +0 -2
- package/skills/lark-minutes/references/lark-minutes-speaker-replace.md +0 -2
- package/skills/lark-minutes/references/lark-minutes-summary.md +0 -2
- package/skills/lark-minutes/references/lark-minutes-todo.md +2 -4
- package/skills/lark-minutes/references/lark-minutes-update.md +0 -2
- package/skills/lark-minutes/references/lark-minutes-upload.md +10 -10
- package/skills/lark-shared/SKILL.md +26 -8
- package/skills/lark-sheets/SKILL.md +98 -29
- package/skills/lark-sheets/references/lark-sheets-batch-update.md +18 -9
- package/skills/lark-sheets/references/lark-sheets-changeset.md +105 -0
- package/skills/lark-sheets/references/lark-sheets-chart.md +4 -2
- package/skills/lark-sheets/references/lark-sheets-conditional-format.md +2 -0
- package/skills/lark-sheets/references/lark-sheets-filter-view.md +1 -1
- package/skills/lark-sheets/references/lark-sheets-float-image.md +6 -6
- package/skills/lark-sheets/references/lark-sheets-formula-translation.md +12 -3
- package/skills/lark-sheets/references/lark-sheets-formula-verify.md +77 -0
- package/skills/lark-sheets/references/lark-sheets-history.md +93 -0
- package/skills/lark-sheets/references/lark-sheets-pivot-table.md +7 -2
- package/skills/lark-sheets/references/lark-sheets-range-operations.md +44 -14
- package/skills/lark-sheets/references/lark-sheets-read-data.md +3 -3
- package/skills/lark-sheets/references/lark-sheets-sheet-structure.md +4 -4
- package/skills/lark-sheets/references/lark-sheets-visual-standards.md +4 -4
- package/skills/lark-sheets/references/lark-sheets-workbook.md +29 -4
- package/skills/lark-sheets/references/lark-sheets-write-cells.md +21 -11
- package/skills/lark-slides/SKILL.md +29 -18
- package/skills/lark-slides/references/asset-planning.md +16 -5
- package/skills/lark-slides/references/examples.md +57 -227
- package/skills/lark-slides/references/iconpark.md +2 -2
- package/skills/lark-slides/references/lark-slides-create.md +21 -2
- package/skills/lark-slides/references/lark-slides-media-upload.md +0 -1
- package/skills/lark-slides/references/lark-slides-pptx-template-workflows.md +89 -0
- package/skills/lark-slides/references/lark-slides-replace-pages.md +1 -1
- package/skills/lark-slides/references/lark-slides-replace-slide.md +1 -1
- package/skills/lark-slides/references/lark-slides-screenshot.md +11 -8
- package/skills/lark-slides/references/lark-slides-whiteboard.md +31 -30
- package/skills/lark-slides/references/lark-slides-xml-get.md +100 -0
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md +9 -7
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-get.md +4 -4
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-replace.md +12 -10
- package/skills/lark-slides/references/lark-slides-xml-presentations-get.md +14 -13
- package/skills/lark-slides/references/planning-layer.md +32 -2
- package/skills/lark-slides/references/slides_chart_demo.xml +1 -0
- package/skills/lark-slides/references/slides_xml_schema_definition.xml +8 -3
- package/skills/lark-slides/references/troubleshooting.md +7 -25
- package/skills/lark-slides/references/validation-checklist.md +18 -9
- package/skills/lark-slides/references/visual-planning.md +4 -3
- package/skills/lark-slides/references/xml-format-guide.md +65 -1
- package/skills/lark-slides/references/xml-schema-quick-ref.md +7 -3
- package/skills/lark-slides/scripts/xml_text_overlap_lint.py +907 -54
- package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +876 -5
- package/skills/lark-task/SKILL.md +1 -0
- package/skills/lark-task/references/lark-task-create.md +14 -1
- package/skills/lark-vc/SKILL.md +6 -3
- package/skills/lark-vc/references/lark-vc-recording.md +0 -2
- package/skills/lark-vc/references/vc-domain-boundaries.md +9 -1
- package/skills/lark-vc-agent/SKILL.md +25 -15
- package/skills/lark-vc-agent/references/lark-vc-agent-meeting-events.md +65 -37
- package/skills/lark-vc-agent/references/lark-vc-agent-meeting-leave.md +1 -1
- package/skills/lark-vc-agent/references/lark-vc-agent-meeting-list-active.md +8 -8
- package/skills/lark-whiteboard/references/lark-whiteboard-workflow.md +5 -2
- package/skills/lark-wiki/SKILL.md +7 -3
- package/skills/lark-wiki/references/lark-wiki-move-to-drive.md +122 -0
- package/skills/lark-wiki/references/lark-wiki-move.md +5 -3
- package/skills/lark-wiki/references/lark-wiki-node-get.md +1 -1
- package/skills/lark-wiki/references/lark-wiki-node-list.md +9 -2
- package/skills/lark-calendar/references/lark-calendar-agenda.md +0 -78
- package/skills/lark-calendar/references/lark-calendar-freebusy.md +0 -124
- package/skills/lark-calendar/references/lark-calendar-search-event.md +0 -29
- package/skills/lark-sheets/references/lark-sheets-core-operations.md +0 -103
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md +0 -220
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
# SPDX-License-Identifier: MIT
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
5
9
|
import unittest
|
|
10
|
+
from pathlib import Path
|
|
6
11
|
|
|
7
12
|
import xml_text_overlap_lint
|
|
8
13
|
|
|
@@ -117,6 +122,58 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
117
122
|
self.assertIn("attribute", issue["hint"])
|
|
118
123
|
self.assertIn("a=1&b=2", issue["hint"])
|
|
119
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
|
+
|
|
120
177
|
def test_lint_xml_accepts_escaped_entities_without_suspicious_entity_warning(self) -> None:
|
|
121
178
|
result = xml_text_overlap_lint.lint_xml(
|
|
122
179
|
"""
|
|
@@ -160,7 +217,293 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
160
217
|
)
|
|
161
218
|
self.assertEqual(result["slide_size"], {"width": 960, "height": 540})
|
|
162
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
|
+
)
|
|
163
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"])
|
|
164
507
|
|
|
165
508
|
def test_lint_xml_detects_overlapping_text_boxes(self) -> None:
|
|
166
509
|
result = xml_text_overlap_lint.lint_xml(
|
|
@@ -182,7 +525,84 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
182
525
|
self.assertEqual(result["summary"]["error_count"], 1)
|
|
183
526
|
self.assertEqual(result["slides"][0]["issues"][0]["code"], "bbox_overlap")
|
|
184
527
|
|
|
185
|
-
def
|
|
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:
|
|
186
606
|
result = xml_text_overlap_lint.lint_xml(
|
|
187
607
|
"""
|
|
188
608
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
@@ -199,8 +619,11 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
199
619
|
</presentation>
|
|
200
620
|
"""
|
|
201
621
|
)
|
|
202
|
-
|
|
622
|
+
issue = result["slides"][0]["issues"][0]
|
|
623
|
+
self.assertEqual(result["summary"]["error_count"], 1)
|
|
203
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})
|
|
204
627
|
|
|
205
628
|
def test_lint_xml_allows_template_style_bleed_and_text_over_images(self) -> None:
|
|
206
629
|
result = xml_text_overlap_lint.lint_xml(
|
|
@@ -223,7 +646,39 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
223
646
|
self.assertEqual(result["summary"]["error_count"], 0)
|
|
224
647
|
self.assertEqual(result["summary"]["warning_count"], 0)
|
|
225
648
|
|
|
226
|
-
def
|
|
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:
|
|
227
682
|
result = xml_text_overlap_lint.lint_xml(
|
|
228
683
|
"""
|
|
229
684
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
@@ -237,7 +692,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
237
692
|
)
|
|
238
693
|
self.assertEqual(result["summary"]["error_count"], 0)
|
|
239
694
|
|
|
240
|
-
def
|
|
695
|
+
def test_lint_xml_allows_out_of_canvas_images(self) -> None:
|
|
241
696
|
result = xml_text_overlap_lint.lint_xml(
|
|
242
697
|
"""
|
|
243
698
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
@@ -252,7 +707,7 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
252
707
|
)
|
|
253
708
|
self.assertEqual(result["summary"]["error_count"], 0)
|
|
254
709
|
|
|
255
|
-
def
|
|
710
|
+
def test_lint_xml_allows_full_bleed_images(self) -> None:
|
|
256
711
|
result = xml_text_overlap_lint.lint_xml(
|
|
257
712
|
"""
|
|
258
713
|
<presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
@@ -266,6 +721,422 @@ class XmlTextOverlapLintTest(unittest.TestCase):
|
|
|
266
721
|
)
|
|
267
722
|
self.assertEqual(result["summary"]["error_count"], 0)
|
|
268
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
|
+
|
|
269
1140
|
def test_lint_xml_detects_invalid_template_text_stack_overlap(self) -> None:
|
|
270
1141
|
cases = [
|
|
271
1142
|
(
|