@amaster.ai/pi-lark 0.1.7 → 0.1.8

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 (135) hide show
  1. package/package.json +2 -2
  2. package/skills/lark-apps/SKILL.md +39 -6
  3. package/skills/lark-apps/references/lark-apps-cloud-dev.md +5 -4
  4. package/skills/lark-apps/references/lark-apps-create.md +6 -3
  5. package/skills/lark-apps/references/lark-apps-get.md +1 -1
  6. package/skills/lark-apps/references/lark-apps-list.md +1 -1
  7. package/skills/lark-apps/references/lark-apps-local-dev.md +27 -1
  8. package/skills/lark-apps/references/lark-apps-release-create.md +1 -1
  9. package/skills/lark-base/SKILL.md +14 -6
  10. package/skills/lark-base/references/lark-base-dashboard-block-get-data.md +17 -1
  11. package/skills/lark-base/references/lark-base-dashboard.md +17 -4
  12. package/skills/lark-base/references/lark-base-data-query-guide.md +8 -0
  13. package/skills/lark-base/references/lark-base-field-create.md +19 -8
  14. package/skills/lark-base/references/lark-base-field-json.md +5 -2
  15. package/skills/lark-calendar/SKILL.md +1 -1
  16. package/skills/lark-doc/SKILL.md +26 -61
  17. package/skills/lark-doc/references/genres/business-analysis.md +30 -0
  18. package/skills/lark-doc/references/genres/data-report.md +32 -0
  19. package/skills/lark-doc/references/genres/email.md +38 -0
  20. package/skills/lark-doc/references/genres/execution-plan.md +27 -0
  21. package/skills/lark-doc/references/genres/formal-doc.md +37 -0
  22. package/skills/lark-doc/references/genres/meeting-minutes.md +24 -0
  23. package/skills/lark-doc/references/genres/memo-brief.md +25 -0
  24. package/skills/lark-doc/references/genres/official-redhead.md +73 -0
  25. package/skills/lark-doc/references/genres/prd.md +26 -0
  26. package/skills/lark-doc/references/genres/proposal.md +24 -0
  27. package/skills/lark-doc/references/genres/research-report.md +32 -0
  28. package/skills/lark-doc/references/genres/retrospective.md +25 -0
  29. package/skills/lark-doc/references/genres/route-consumer.md +37 -0
  30. package/skills/lark-doc/references/genres/route-creative.md +36 -0
  31. package/skills/lark-doc/references/genres/route-knowledge.md +39 -0
  32. package/skills/lark-doc/references/genres/route-marketing.md +40 -0
  33. package/skills/lark-doc/references/genres/route-media.md +36 -0
  34. package/skills/lark-doc/references/genres/route-opinion.md +38 -0
  35. package/skills/lark-doc/references/genres/route-personal-brand.md +36 -0
  36. package/skills/lark-doc/references/genres/route-platform.md +9 -0
  37. package/skills/lark-doc/references/genres/route-report.md +10 -0
  38. package/skills/lark-doc/references/genres/route-workplace.md +17 -0
  39. package/skills/lark-doc/references/genres/sop-tutorial.md +41 -0
  40. package/skills/lark-doc/references/genres/technical-doc.md +39 -0
  41. package/skills/lark-doc/references/genres/wechat.md +39 -0
  42. package/skills/lark-doc/references/genres/weekly-report.md +24 -0
  43. package/skills/lark-doc/references/genres/white-paper.md +32 -0
  44. package/skills/lark-doc/references/genres/xiaohongshu.md +38 -0
  45. package/skills/lark-doc/references/lark-doc-create-workflow.md +121 -0
  46. package/skills/lark-doc/references/lark-doc-create.md +22 -48
  47. package/skills/lark-doc/references/lark-doc-fetch.md +75 -92
  48. package/skills/lark-doc/references/lark-doc-history.md +16 -15
  49. package/skills/lark-doc/references/lark-doc-md.md +5 -1
  50. package/skills/lark-doc/references/lark-doc-media-download.md +2 -1
  51. package/skills/lark-doc/references/lark-doc-script.md +76 -0
  52. package/skills/lark-doc/references/lark-doc-update.md +70 -222
  53. package/skills/lark-doc/references/lark-doc-whiteboard.md +5 -9
  54. package/skills/lark-doc/references/lark-doc-xml-extended-blocks.md +17 -12
  55. package/skills/lark-doc/references/lark-doc-xml.md +38 -167
  56. package/skills/lark-drive/SKILL.md +7 -5
  57. package/skills/lark-drive/references/lark-drive-apply-permission.md +1 -1
  58. package/skills/lark-drive/references/lark-drive-copy.md +87 -0
  59. package/skills/lark-drive/references/lark-drive-download.md +2 -1
  60. package/skills/lark-drive/references/lark-drive-export.md +3 -0
  61. package/skills/lark-drive/references/lark-drive-task-result.md +3 -0
  62. package/skills/lark-drive/references/lark-drive-update-title.md +78 -0
  63. package/skills/lark-event/SKILL.md +7 -4
  64. package/skills/lark-event/references/lark-event-vc.md +8 -2
  65. package/skills/lark-im/SKILL.md +8 -8
  66. package/skills/lark-im/references/lark-im-chat-list.md +9 -2
  67. package/skills/lark-im/references/lark-im-chat-members-list.md +7 -4
  68. package/skills/lark-im/references/lark-im-chat-messages-list.md +10 -3
  69. package/skills/lark-im/references/lark-im-chat-search.md +9 -2
  70. package/skills/lark-im/references/lark-im-feed-group-list-item.md +2 -2
  71. package/skills/lark-im/references/lark-im-feed-group-list.md +2 -2
  72. package/skills/lark-im/references/lark-im-feed-shortcut-list.md +1 -1
  73. package/skills/lark-im/references/lark-im-flag-list.md +2 -2
  74. package/skills/lark-im/references/lark-im-message-enrichment.md +1 -1
  75. package/skills/lark-im/references/lark-im-messages-resources-download.md +19 -25
  76. package/skills/lark-im/references/lark-im-messages-search.md +4 -5
  77. package/skills/lark-im/references/lark-im-threads-messages-list.md +8 -4
  78. package/skills/lark-mail/references/lark-mail-triage.md +19 -4
  79. package/skills/lark-minutes/SKILL.md +1 -1
  80. package/skills/lark-minutes/references/lark-minutes-search.md +6 -7
  81. package/skills/lark-shared/SKILL.md +3 -3
  82. package/skills/lark-sheets/SKILL.md +83 -82
  83. package/skills/lark-sheets/references/lark-sheets-batch-update.md +13 -58
  84. package/skills/lark-sheets/references/lark-sheets-chart.md +2 -1
  85. package/skills/lark-sheets/references/lark-sheets-conditional-format.md +1 -1
  86. package/skills/lark-sheets/references/lark-sheets-range-operations.md +5 -5
  87. package/skills/lark-sheets/references/lark-sheets-read-data.md +80 -6
  88. package/skills/lark-sheets/references/lark-sheets-sheet-structure.md +21 -10
  89. package/skills/lark-sheets/references/lark-sheets-styles-put.md +93 -0
  90. package/skills/lark-sheets/references/lark-sheets-visual-standards.md +2 -2
  91. package/skills/lark-sheets/references/lark-sheets-workbook.md +4 -3
  92. package/skills/lark-sheets/references/lark-sheets-write-cells.md +40 -12
  93. package/skills/lark-sheets/scripts/lark_detect_subtables.py +593 -0
  94. package/skills/lark-sheets/scripts/lark_inspect_workbook.py +188 -0
  95. package/skills/lark-sheets/scripts/lark_profile_table.py +614 -0
  96. package/skills/lark-sheets/scripts/lark_sheet_range.py +176 -0
  97. package/skills/lark-sheets/scripts/lark_sheet_read_cli.py +184 -0
  98. package/skills/lark-sheets/scripts/sheets_df.py +21 -3
  99. package/skills/lark-slides/SKILL.md +27 -44
  100. package/skills/lark-slides/references/lark-slides-add-slide.md +92 -0
  101. package/skills/lark-slides/references/lark-slides-create.md +77 -65
  102. package/skills/lark-slides/references/lark-slides-delete-slide.md +65 -0
  103. package/skills/lark-slides/references/lark-slides-edit-workflows.md +6 -7
  104. package/skills/lark-slides/references/lark-slides-media-upload.md +3 -25
  105. package/skills/lark-slides/references/lark-slides-replace-slide.md +22 -1
  106. package/skills/lark-slides/references/lark-slides-screenshot.md +31 -13
  107. package/skills/lark-slides/references/lark-slides-update-slide.md +146 -0
  108. package/skills/lark-slides/references/lark-slides-xml-presentations-get.md +31 -8
  109. package/skills/lark-slides/references/slides_chart_demo.xml +1 -2
  110. package/skills/lark-slides/references/slides_xml_schema_definition.xml +48 -4
  111. package/skills/lark-slides/references/troubleshooting.md +7 -8
  112. package/skills/lark-slides/references/validation-checklist.md +4 -4
  113. package/skills/lark-slides/references/xml-schema-quick-ref.md +23 -11
  114. package/skills/lark-slides/scripts/sxsd_validator.py +154 -10
  115. package/skills/lark-slides/scripts/xml_text_overlap_lint.py +360 -76
  116. package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +1138 -214
  117. package/skills/lark-whiteboard/SKILL.md +15 -8
  118. package/skills/lark-whiteboard/references/lark-whiteboard-export.md +4 -3
  119. package/skills/lark-whiteboard/references/lark-whiteboard-update.md +4 -4
  120. package/skills/lark-whiteboard/references/lark-whiteboard-workflow.md +19 -17
  121. package/skills/lark-whiteboard/routes/dsl.md +8 -2
  122. package/skills/lark-whiteboard/routes/mermaid.md +1 -1
  123. package/skills/lark-whiteboard/routes/svg-edit.md +5 -2
  124. package/skills/lark-whiteboard/routes/svg.md +3 -1
  125. package/skills/lark-whiteboard/scenes/mention.md +71 -0
  126. package/skills/lark-wiki/SKILL.md +5 -3
  127. package/skills/lark-wiki/references/lark-wiki-delete-space.md +6 -3
  128. package/skills/lark-doc/references/lark-doc-word-stat.md +0 -93
  129. package/skills/lark-doc/references/style/lark-doc-create-workflow.md +0 -47
  130. package/skills/lark-doc/references/style/lark-doc-style.md +0 -68
  131. package/skills/lark-doc/references/style/lark-doc-update-workflow.md +0 -48
  132. package/skills/lark-doc/scripts/doc_word_stat.py +0 -1243
  133. package/skills/lark-slides/references/lark-slides-replace-pages.md +0 -95
  134. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md +0 -219
  135. package/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md +0 -126
@@ -51,13 +51,41 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
51
51
  f"xml-text-overlap-lint error: unexpected argument: {input_path}, need --input\n",
52
52
  )
53
53
 
54
+ def test_cli_preserves_requested_symlink_path_in_result(self) -> None:
55
+ script_path = Path(xml_text_overlap_lint.__file__).resolve()
56
+ with tempfile.TemporaryDirectory() as temp_dir:
57
+ temp_path = Path(temp_dir)
58
+ resolved_path = temp_path / "resolved.xml"
59
+ requested_path = temp_path / "requested.xml"
60
+ resolved_path.write_text(
61
+ '<presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">'
62
+ '<slide xmlns="https://www.larkoffice.com/sml/2.0"><data>'
63
+ '<shape type="text" topLeftX="10" topLeftY="10" width="100" height="30">'
64
+ '<content><p><span>Test</span></p></content>'
65
+ '</shape></data></slide>'
66
+ '</presentation>',
67
+ encoding="utf-8",
68
+ )
69
+ requested_path.symlink_to(resolved_path)
70
+
71
+ completed = subprocess.run(
72
+ [sys.executable, str(script_path), "--input", str(requested_path)],
73
+ capture_output=True,
74
+ check=False,
75
+ text=True,
76
+ )
77
+
78
+ self.assertEqual(completed.returncode, 0, completed.stderr)
79
+ result = json.loads(completed.stdout)
80
+ self.assertEqual(result["file"], str(requested_path))
81
+
54
82
  def test_cli_reports_structured_slide_sxsd_error_outside_skill_directory(self) -> None:
55
83
  script_path = Path(xml_text_overlap_lint.__file__).resolve()
56
84
  with tempfile.TemporaryDirectory() as temp_dir:
57
85
  input_path = Path(temp_dir) / "invalid-slide.xml"
58
86
  input_path.write_text(
59
87
  """
60
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
88
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
61
89
  <data><shape type="text" topLeftX="10" topLeftY="20" width="300"/></data>
62
90
  </slide>
63
91
  """,
@@ -85,8 +113,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
85
113
  def test_xml_text_overlap_lint_accepts_inline_fixture_xml_samples(self) -> None:
86
114
  samples = {
87
115
  "image-led-cover": """
88
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
89
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
116
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
117
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
90
118
  <style><fill><fillColor color="rgb(15,23,42)"/></fill></style>
91
119
  <data>
92
120
  <img src="tok" topLeftX="560" topLeftY="0" width="400" height="540"/>
@@ -101,8 +129,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
101
129
  </presentation>
102
130
  """,
103
131
  "content-grid": """
104
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
105
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
132
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
133
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
106
134
  <data>
107
135
  <shape type="text" topLeftX="60" topLeftY="44" width="620" height="46">
108
136
  <content textType="title"><p><span fontSize="30">Execution Snapshot</span></p></content>
@@ -136,7 +164,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
136
164
  def test_lint_xml_reports_unescaped_ampersand_in_text(self) -> None:
137
165
  result = xml_text_overlap_lint.lint_xml(
138
166
  """
139
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
167
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
140
168
  <data>
141
169
  <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
142
170
  <content textType="body"><p>Q&A</p></content>
@@ -156,7 +184,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
156
184
  def test_lint_xml_reports_unescaped_ampersand_in_attribute(self) -> None:
157
185
  result = xml_text_overlap_lint.lint_xml(
158
186
  """
159
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
187
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
160
188
  <data>
161
189
  <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
162
190
  <content textType="body"><p><a href="https://example.com/?a=1&b=2">link</a></p></content>
@@ -173,7 +201,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
173
201
  def test_lint_xml_reports_mismatched_xml_tag(self) -> None:
174
202
  result = xml_text_overlap_lint.lint_xml(
175
203
  """
176
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
204
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
177
205
  <data>
178
206
  <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
179
207
  <content textType="body"><p>Broken XML</content>
@@ -189,13 +217,15 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
189
217
  self.assertIsInstance(issue["line"], int)
190
218
  self.assertIsInstance(issue["column"], int)
191
219
  self.assertIn("Broken XML", issue["context"])
220
+ self.assertEqual(issue["related_objects"], [])
221
+ self.assertNotIn("Locate via related_objects[].xml_path.", issue["hint"])
192
222
 
193
223
  def test_lint_xml_rejects_prefixed_sml_tags(self) -> None:
194
224
  result = xml_text_overlap_lint.lint_xml(
195
225
  """
196
- <ns0:slide xmlns:ns0="http://www.larkoffice.com/sml/2.0">
226
+ <ns0:slide xmlns:ns0="https://www.larkoffice.com/sml/2.0">
197
227
  <ns0:data>
198
- <sml:shape xmlns:sml="http://www.larkoffice.com/sml/2.0" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
228
+ <sml:shape xmlns:sml="https://www.larkoffice.com/sml/2.0" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
199
229
  <sml:content textType="body"><sml:p>Prefixed SML</sml:p></sml:content>
200
230
  </sml:shape>
201
231
  </ns0:data>
@@ -207,6 +237,29 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
207
237
  self.assertEqual([issue["code"] for issue in issues], ["sml_prefixed_tag"] * 5)
208
238
  self.assertEqual(issues[0]["tag"], "ns0:slide")
209
239
  self.assertIn("default namespace", issues[0]["hint"])
240
+ self.assertEqual({issue["namespace"] for issue in issues}, {SML_NAMESPACE})
241
+
242
+ def test_lint_xml_reports_bound_namespace_for_prefixed_sml_tags(self) -> None:
243
+ for namespace in (
244
+ "http://www.larkoffice.com/sml/2.0",
245
+ "/sml/2.0",
246
+ SML_NAMESPACE,
247
+ ):
248
+ with self.subTest(namespace=namespace):
249
+ result = xml_text_overlap_lint.lint_xml(
250
+ f"""
251
+ <sml:slide xmlns:sml="{namespace}">
252
+ <sml:data>
253
+ <sml:shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
254
+ <sml:content textType="body"><sml:p>Prefixed SML</sml:p></sml:content>
255
+ </sml:shape>
256
+ </sml:data>
257
+ </sml:slide>
258
+ """
259
+ )
260
+ issues = result["issues"]
261
+ self.assertEqual([issue["code"] for issue in issues], ["sml_prefixed_tag"] * 5)
262
+ self.assertEqual({issue["namespace"] for issue in issues}, {namespace})
210
263
 
211
264
  def test_lint_xml_accepts_server_readback_slide_without_namespace(self) -> None:
212
265
  result = xml_text_overlap_lint.lint_xml(
@@ -258,6 +311,24 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
258
311
  self.assertEqual(result["summary"]["slide_count"], 1)
259
312
  self.assertEqual(result["summary"]["error_count"], 0)
260
313
 
314
+ def test_lint_xml_accepts_legacy_http_namespace(self) -> None:
315
+ result = xml_text_overlap_lint.lint_xml(
316
+ """
317
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540" id="legacy-http-id">
318
+ <slide id="server-slide-id">
319
+ <data>
320
+ <shape id="server-shape-id" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
321
+ <content textType="body"><p>Legacy HTTP namespace</p></content>
322
+ </shape>
323
+ </data>
324
+ </slide>
325
+ </presentation>
326
+ """
327
+ )
328
+
329
+ self.assertEqual(result["summary"]["slide_count"], 1)
330
+ self.assertEqual(result["summary"]["error_count"], 0)
331
+
261
332
  def test_lint_xml_rejects_server_readback_presentation_wrong_namespace(self) -> None:
262
333
  result = xml_text_overlap_lint.lint_xml(
263
334
  """
@@ -272,7 +343,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
272
343
 
273
344
  def test_lint_xml_rejects_xml_declaration(self) -> None:
274
345
  result = xml_text_overlap_lint.lint_xml(
275
- '<?xml version="1.0"?><slide xmlns="http://www.larkoffice.com/sml/2.0"><data/></slide>'
346
+ '<?xml version="1.0"?><slide xmlns="https://www.larkoffice.com/sml/2.0"><data/></slide>'
276
347
  )
277
348
 
278
349
  self.assertEqual(result["summary"]["error_count"], 1)
@@ -281,7 +352,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
281
352
  def test_lint_xml_reports_missing_required_sxsd_attribute(self) -> None:
282
353
  result = xml_text_overlap_lint.lint_xml(
283
354
  """
284
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
355
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
285
356
  <data><shape type="text" topLeftX="80" topLeftY="80" width="300"/></data>
286
357
  </slide>
287
358
  """
@@ -296,7 +367,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
296
367
  def test_lint_xml_rejects_child_order_that_violates_xsd_sequence(self) -> None:
297
368
  result = xml_text_overlap_lint.lint_xml(
298
369
  """
299
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
370
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
300
371
  <slide/>
301
372
  <title>Late title</title>
302
373
  </presentation>
@@ -309,7 +380,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
309
380
  def test_lint_xml_accepts_escaped_entities_without_suspicious_entity_warning(self) -> None:
310
381
  result = xml_text_overlap_lint.lint_xml(
311
382
  """
312
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
383
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
313
384
  <data>
314
385
  <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
315
386
  <content textType="body"><p>Q&amp;A</p></content>
@@ -324,7 +395,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
324
395
  def test_lint_xml_accepts_chinese_full_width_punctuation(self) -> None:
325
396
  result = xml_text_overlap_lint.lint_xml(
326
397
  """
327
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
398
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
328
399
  <data>
329
400
  <shape type="text" topLeftX="80" topLeftY="80" width="620" height="90">
330
401
  <content textType="body"><p>承诺:按期交付;持续复盘|风险透明</p></content>
@@ -338,7 +409,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
338
409
  def test_lint_xml_single_slide_reports_out_of_canvas_and_blank_slide_errors(self) -> None:
339
410
  result = xml_text_overlap_lint.lint_xml(
340
411
  """
341
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
412
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
342
413
  <data>
343
414
  <shape type="text" topLeftX="1000" topLeftY="500" width="120" height="80">
344
415
  <content textType="body"><p>Body text outside the canvas</p></content>
@@ -358,15 +429,15 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
358
429
  def test_lint_xml_preserves_presentation_canvas_and_slide_order(self) -> None:
359
430
  result = xml_text_overlap_lint.lint_xml(
360
431
  """
361
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="1280" height="720">
362
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
432
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="1280" height="720">
433
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
363
434
  <data>
364
435
  <shape id="first" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
365
436
  <content textType="body"><p>First slide</p></content>
366
437
  </shape>
367
438
  </data>
368
439
  </slide>
369
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
440
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
370
441
  <data>
371
442
  <img id="second" src="tok" topLeftX="100" topLeftY="120" width="240" height="160"/>
372
443
  <shape id="second-shape" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
@@ -387,7 +458,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
387
458
  def test_lint_xml_handles_self_closing_slide_before_normal_slide(self) -> None:
388
459
  result = xml_text_overlap_lint.lint_xml(
389
460
  """
390
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
461
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
391
462
  <slide/>
392
463
  <slide>
393
464
  <data>
@@ -410,7 +481,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
410
481
  def test_lint_xml_keeps_trailing_self_closing_slide(self) -> None:
411
482
  result = xml_text_overlap_lint.lint_xml(
412
483
  """
413
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
484
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
414
485
  <slide>
415
486
  <data>
416
487
  <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
@@ -432,7 +503,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
432
503
  def test_lint_xml_ignores_slide_markup_inside_xml_comments(self) -> None:
433
504
  result = xml_text_overlap_lint.lint_xml(
434
505
  """
435
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
506
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
436
507
  <slide>
437
508
  <data>
438
509
  <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
@@ -451,7 +522,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
451
522
  def test_lint_xml_ignores_invalid_slide_markup_inside_xml_comments(self) -> None:
452
523
  result = xml_text_overlap_lint.lint_xml(
453
524
  """
454
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
525
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
455
526
  <slide><data/></slide>
456
527
  <!-- Old draft: <slide bogus="x"/> -->
457
528
  </presentation>
@@ -467,7 +538,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
467
538
  def test_lint_xml_skips_only_invalid_slide_and_continues_geometry_checks(self) -> None:
468
539
  result = xml_text_overlap_lint.lint_xml(
469
540
  """
470
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
541
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
471
542
  <slide>
472
543
  <data>
473
544
  <shape id="invalid" type="text" topLeftX="1000" topLeftY="80" width="300">
@@ -504,7 +575,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
504
575
  def test_lint_xml_scopes_invalid_slide_root_attribute_to_that_slide(self) -> None:
505
576
  result = xml_text_overlap_lint.lint_xml(
506
577
  """
507
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
578
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
508
579
  <slide bogusAttr="oops">
509
580
  <data><shape type="rect" topLeftX="80" topLeftY="80" width="100" height="100"/></data>
510
581
  </slide>
@@ -525,6 +596,118 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
525
596
  [issue["code"] for issue in result["slides"][1]["errors"]],
526
597
  )
527
598
 
599
+ def test_lint_xml_allows_svg_subtree_inside_embed(self) -> None:
600
+ result = xml_text_overlap_lint.lint_xml(
601
+ """
602
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
603
+ <data>
604
+ <embed topLeftX="80" topLeftY="120" width="240" height="140">
605
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 140">
606
+ <rect x="10" y="10" width="220" height="120" rx="12" fill="#EFF6FF"/>
607
+ <circle cx="70" cy="70" r="34" fill="#2563EB"/>
608
+ <text x="130" y="76" font-size="18" fill="#1E3A8A">SVG OK</text>
609
+ <foreignObject x="0" y="0" width="1" height="1">
610
+ <embed xmlns="http://www.w3.org/1999/xhtml">
611
+ <rect xmlns="http://www.w3.org/2000/svg" width="1" height="1"/>
612
+ </embed>
613
+ </foreignObject>
614
+ </svg>
615
+ </embed>
616
+ </data>
617
+ </slide>
618
+ """
619
+ )
620
+ codes = [issue["code"] for issue in result["slides"][0]["issues"]]
621
+ self.assertNotIn("sxsd_unsupported_tag", codes)
622
+ self.assertNotIn("sxsd_unsupported_attr", codes)
623
+ self.assertEqual(result["summary"]["error_count"], 0)
624
+
625
+ def test_lint_xml_enforces_embed_svg_contract(self) -> None:
626
+ cases = [
627
+ ("missing SVG", "", "sxsd_missing_required_child"),
628
+ ("wrong namespace", '<svg viewBox="0 0 20 20"/>', "sxsd_unsupported_tag"),
629
+ (
630
+ "multiple SVG roots",
631
+ '<svg xmlns="http://www.w3.org/2000/svg"/><svg xmlns="http://www.w3.org/2000/svg"/>',
632
+ "sxsd_too_many_children",
633
+ ),
634
+ (
635
+ "non-SVG root element",
636
+ '<rect xmlns="http://www.w3.org/2000/svg" width="20" height="20"/>',
637
+ "sxsd_unexpected_child",
638
+ ),
639
+ (
640
+ "SVG after reflection",
641
+ '<reflection/><svg xmlns="http://www.w3.org/2000/svg"/>',
642
+ "sxsd_invalid_child_order",
643
+ ),
644
+ ]
645
+ for name, children, expected_code in cases:
646
+ with self.subTest(name=name):
647
+ result = xml_text_overlap_lint.lint_xml(
648
+ f"""
649
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
650
+ <data>
651
+ <embed topLeftX="80" topLeftY="120" width="240" height="140">
652
+ {children}
653
+ </embed>
654
+ </data>
655
+ </slide>
656
+ """
657
+ )
658
+ codes = [issue["code"] for issue in result["slides"][0]["issues"]]
659
+ self.assertIn(expected_code, codes)
660
+
661
+ def test_lint_xml_enforces_embed_svg_root_for_readback_namespaces(self) -> None:
662
+ document_templates = [
663
+ ("bare slide", "<slide>{content}</slide>"),
664
+ (
665
+ "short namespace",
666
+ '<presentation xmlns="/sml/2.0" width="960" height="540">'
667
+ "<slide>{content}</slide>"
668
+ "</presentation>",
669
+ ),
670
+ (
671
+ "HTTPS namespace",
672
+ '<presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">'
673
+ "<slide>{content}</slide>"
674
+ "</presentation>",
675
+ ),
676
+ ]
677
+ embed_template = """
678
+ <data>
679
+ <embed topLeftX="80" topLeftY="120" width="240" height="140">
680
+ {svg_root}
681
+ </embed>
682
+ </data>
683
+ """
684
+ roots = [
685
+ ("valid SVG root", '<svg xmlns="http://www.w3.org/2000/svg"/>', False),
686
+ (
687
+ "non-SVG root element",
688
+ '<rect xmlns="http://www.w3.org/2000/svg" width="20" height="20"/>',
689
+ True,
690
+ ),
691
+ ]
692
+
693
+ for document_name, document_template in document_templates:
694
+ for root_name, svg_root, should_error in roots:
695
+ with self.subTest(document=document_name, root=root_name):
696
+ content = embed_template.format(svg_root=svg_root)
697
+ result = xml_text_overlap_lint.lint_xml(
698
+ document_template.format(content=content)
699
+ )
700
+ codes = [
701
+ issue["code"]
702
+ for slide in result["slides"]
703
+ for issue in slide["issues"]
704
+ ]
705
+ if should_error:
706
+ self.assertIn("sxsd_unexpected_child", codes)
707
+ else:
708
+ self.assertNotIn("sxsd_unexpected_child", codes)
709
+ self.assertEqual(result["summary"]["error_count"], 0)
710
+
528
711
  def test_lint_xml_reports_sxsd_unsupported_tag_with_alias_hint(self) -> None:
529
712
  cases = [
530
713
  ("textbox", '<textbox topLeftX="80" topLeftY="80" width="300" height="60">Text</textbox>', '<shape type="text">'),
@@ -534,7 +717,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
534
717
  with self.subTest(tag=tag_name):
535
718
  result = xml_text_overlap_lint.lint_xml(
536
719
  f"""
537
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
720
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
538
721
  <data>{element_xml}</data>
539
722
  </slide>
540
723
  """
@@ -562,7 +745,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
562
745
  with self.subTest(attr=attr_name):
563
746
  result = xml_text_overlap_lint.lint_xml(
564
747
  f"""
565
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
748
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
566
749
  <data>{element_xml}</data>
567
750
  </slide>
568
751
  """
@@ -584,7 +767,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
584
767
  def test_lint_xml_keeps_unrelated_unsupported_and_missing_attrs(self) -> None:
585
768
  result = xml_text_overlap_lint.lint_xml(
586
769
  """
587
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
770
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
588
771
  <data>
589
772
  <shape type="text" bogus="x" topLeftX="80" topLeftY="80" width="300"/>
590
773
  </data>
@@ -600,7 +783,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
600
783
  def test_lint_xml_keeps_missing_attrs_when_suggestion_is_ambiguous(self) -> None:
601
784
  result = xml_text_overlap_lint.lint_xml(
602
785
  """
603
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
786
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
604
787
  <data>
605
788
  <shape type="text" topLeft="80" width="300" height="60"/>
606
789
  </data>
@@ -620,7 +803,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
620
803
  def test_lint_xml_suppresses_only_missing_attr_that_resolves_ambiguous_suggestion(self) -> None:
621
804
  result = xml_text_overlap_lint.lint_xml(
622
805
  """
623
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
806
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
624
807
  <data>
625
808
  <shape type="text" topLeftXX="80" topLeftY="80" width="300" height="60"/>
626
809
  </data>
@@ -639,7 +822,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
639
822
  def test_lint_xml_ignores_server_filled_id_attrs(self) -> None:
640
823
  result = xml_text_overlap_lint.lint_xml(
641
824
  """
642
- <slide id="server-slide-id" xmlns="http://www.larkoffice.com/sml/2.0">
825
+ <slide id="server-slide-id" xmlns="https://www.larkoffice.com/sml/2.0">
643
826
  <data>
644
827
  <shape id="server-shape-id" type="rect" topLeftX="80" topLeftY="80" width="300" height="160">
645
828
  <fill id="server-fill-id" unexpected="value">
@@ -660,7 +843,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
660
843
  def test_lint_xml_ignores_chart_roundtrip_attrs(self) -> None:
661
844
  result = xml_text_overlap_lint.lint_xml(
662
845
  """
663
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
846
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
664
847
  <data>
665
848
  <chart updated="true" topLeftX="80" topLeftY="80" width="300" height="160">
666
849
  <chartPlotArea><chartPlot type="line"/></chartPlotArea>
@@ -679,7 +862,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
679
862
  def test_lint_xml_ignores_chart_parsed_values_roundtrip_tags(self) -> None:
680
863
  result = xml_text_overlap_lint.lint_xml(
681
864
  """
682
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
865
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
683
866
  <data>
684
867
  <chart topLeftX="80" topLeftY="80" width="300" height="160">
685
868
  <chartPlotArea><chartPlot type="line"/></chartPlotArea>
@@ -702,7 +885,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
702
885
  def test_lint_xml_ignores_chart_parsed_values_roundtrip_tag(self) -> None:
703
886
  result = xml_text_overlap_lint.lint_xml(
704
887
  """
705
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
888
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
706
889
  <data>
707
890
  <chart topLeftX="80" topLeftY="80" width="300" height="160">
708
891
  <chartPlotArea><chartPlot type="line"/></chartPlotArea>
@@ -726,7 +909,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
726
909
  def test_lint_xml_rejects_chart_parsed_values_outside_chart_field(self) -> None:
727
910
  result = xml_text_overlap_lint.lint_xml(
728
911
  """
729
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
912
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
730
913
  <data>
731
914
  <shape type="rect" topLeftX="80" topLeftY="80" width="300" height="160">
732
915
  <chartParsedValues>unexpected</chartParsedValues>
@@ -749,7 +932,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
749
932
  def test_lint_xml_limits_chart_roundtrip_attrs_to_matching_tags(self) -> None:
750
933
  result = xml_text_overlap_lint.lint_xml(
751
934
  """
752
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
935
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
753
936
  <data>
754
937
  <chart isStaticData="true" topLeftX="80" topLeftY="80" width="300" height="160">
755
938
  <chartPlotArea><chartPlot type="line"/></chartPlotArea>
@@ -774,7 +957,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
774
957
  def test_lint_xml_reports_gradient_shorthand_attrs_on_fill_color(self) -> None:
775
958
  result = xml_text_overlap_lint.lint_xml(
776
959
  """
777
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
960
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
778
961
  <data>
779
962
  <shape type="rect" topLeftX="80" topLeftY="80" width="300" height="160">
780
963
  <fill>
@@ -804,7 +987,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
804
987
  def test_lint_xml_accepts_chart_field_simple_content_attrs(self) -> None:
805
988
  result = xml_text_overlap_lint.lint_xml(
806
989
  """
807
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
990
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
808
991
  <data>
809
992
  <chart topLeftX="80" topLeftY="80" width="520" height="320">
810
993
  <chartPlotArea>
@@ -836,7 +1019,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
836
1019
  try:
837
1020
  result = xml_text_overlap_lint.lint_xml(
838
1021
  """
839
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1022
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
840
1023
  <data>
841
1024
  <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
842
1025
  <content><p>No icons here</p></content>
@@ -854,7 +1037,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
854
1037
  def test_lint_xml_accepts_iconpark_icon_type_from_index(self) -> None:
855
1038
  result = xml_text_overlap_lint.lint_xml(
856
1039
  """
857
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1040
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
858
1041
  <data>
859
1042
  <icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48">
860
1043
  <fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
@@ -879,7 +1062,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
879
1062
  with self.subTest(icon=icon_xml):
880
1063
  result = xml_text_overlap_lint.lint_xml(
881
1064
  f"""
882
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1065
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
883
1066
  <data>{icon_xml}</data>
884
1067
  </slide>
885
1068
  """
@@ -893,7 +1076,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
893
1076
  def test_lint_xml_reports_icon_transparent_fill_color(self) -> None:
894
1077
  result = xml_text_overlap_lint.lint_xml(
895
1078
  """
896
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1079
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
897
1080
  <data>
898
1081
  <icon iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="80" width="48" height="48">
899
1082
  <fill><fillColor color="rgba(37, 99, 235, 0)"/></fill>
@@ -911,7 +1094,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
911
1094
  def test_lint_xml_reports_iconpark_icon_type_outside_index(self) -> None:
912
1095
  result = xml_text_overlap_lint.lint_xml(
913
1096
  """
914
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1097
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
915
1098
  <data>
916
1099
  <icon iconType="iconpark/Base/settng.svg" topLeftX="80" topLeftY="80" width="48" height="48">
917
1100
  <fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
@@ -929,11 +1112,34 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
929
1112
  self.assertIn("iconpark-index.json", issue["hint"])
930
1113
  self.assertIn("iconpark/Base/setting.svg", issue["hint"])
931
1114
 
1115
+ def test_lint_xml_skips_iconpark_validation_inside_embedded_svg(self) -> None:
1116
+ result = xml_text_overlap_lint.lint_xml(
1117
+ """
1118
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
1119
+ <data>
1120
+ <embed topLeftX="80" topLeftY="120" width="240" height="140">
1121
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 140">
1122
+ <rect x="10" y="10" width="220" height="120" fill="#EFF6FF"/>
1123
+ <icon iconType="not-an-iconpark-name"/>
1124
+ <foreignObject x="0" y="0" width="60" height="60">
1125
+ <icon xmlns="http://www.w3.org/1999/xhtml" iconType="not-an-iconpark-name"/>
1126
+ </foreignObject>
1127
+ </svg>
1128
+ </embed>
1129
+ </data>
1130
+ </slide>
1131
+ """
1132
+ )
1133
+ codes = [issue["code"] for issue in result["document"]["errors"]]
1134
+ self.assertNotIn("iconpark_unsupported_icon_type", codes)
1135
+ self.assertNotIn("icon_missing_fill_color", codes)
1136
+ self.assertEqual(result["summary"]["error_count"], 0)
1137
+
932
1138
  def test_lint_xml_detects_overlapping_text_boxes(self) -> None:
933
1139
  result = xml_text_overlap_lint.lint_xml(
934
1140
  """
935
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
936
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1141
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
1142
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
937
1143
  <data>
938
1144
  <shape type="text" topLeftX="80" topLeftY="80" width="300" height="60">
939
1145
  <content textType="title"><p>Title</p></content>
@@ -953,7 +1159,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
953
1159
  def test_lint_xml_detects_current_itinerary_cjk_caption_occlusion(self) -> None:
954
1160
  result = xml_text_overlap_lint.lint_xml(
955
1161
  """
956
- <slide id="pQO" xmlns="http://www.larkoffice.com/sml/2.0">
1162
+ <slide id="pQO" xmlns="https://www.larkoffice.com/sml/2.0">
957
1163
  <data>
958
1164
  <shape width="190" height="80" topLeftX="580" topLeftY="170" presetHandlers="0" type="rect" id="blI">
959
1165
  <fill><fillColor color="rgba(255, 255, 255, 0.9)"/></fill>
@@ -1011,7 +1217,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1011
1217
  def test_lint_xml_detects_horizontal_text_overflow_across_declared_box_gap(self) -> None:
1012
1218
  result = xml_text_overlap_lint.lint_xml(
1013
1219
  """
1014
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1220
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1015
1221
  <data>
1016
1222
  <shape id="source" type="text" topLeftX="80" topLeftY="100" width="160" height="40">
1017
1223
  <content fontSize="18" wrap="false"><p>这是一个足够长的中文文本用于检测跨越间隙的横向溢出</p></content>
@@ -1034,7 +1240,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1034
1240
  def test_lint_xml_allows_horizontal_text_with_default_wrap(self) -> None:
1035
1241
  result = xml_text_overlap_lint.lint_xml(
1036
1242
  """
1037
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1243
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1038
1244
  <data>
1039
1245
  <shape id="source" type="text" topLeftX="80" topLeftY="100" width="160" height="40">
1040
1246
  <content fontSize="18"><p>这是一个足够长的中文文本用于检测默认自动换行</p></content>
@@ -1055,8 +1261,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1055
1261
  def test_lint_xml_reports_text_out_of_canvas_and_warns_for_text_height(self) -> None:
1056
1262
  result = xml_text_overlap_lint.lint_xml(
1057
1263
  """
1058
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1059
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1264
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
1265
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1060
1266
  <data>
1061
1267
  <shape type="text" topLeftX="80" topLeftY="80" width="180" height="20">
1062
1268
  <content textType="body" fontSize="18"><p>This paragraph is intentionally much longer than the box can safely contain.</p></content>
@@ -1078,7 +1284,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1078
1284
  def test_lint_xml_warns_when_text_may_overflow_its_own_shape(self) -> None:
1079
1285
  result = xml_text_overlap_lint.lint_xml(
1080
1286
  """
1081
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1287
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1082
1288
  <data>
1083
1289
  <shape id="overflowing" type="text" topLeftX="80" topLeftY="80" width="360" height="80">
1084
1290
  <content fontSize="20" lineSpacing="multiple:1.5" autoFit="no-auto-fit">
@@ -1122,7 +1328,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1122
1328
  def test_lint_xml_uses_fixed_line_spacing_for_text_height_warning(self) -> None:
1123
1329
  result = xml_text_overlap_lint.lint_xml(
1124
1330
  """
1125
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1331
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1126
1332
  <data>
1127
1333
  <shape id="fixed-overflow" type="text" topLeftX="80" topLeftY="80" width="360" height="50">
1128
1334
  <content fontSize="20" lineSpacing="fixed:20" autoFit="no-auto-fit">
@@ -1144,7 +1350,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1144
1350
  def test_lint_xml_ignores_subpixel_text_height_overflow_tolerance(self) -> None:
1145
1351
  result = xml_text_overlap_lint.lint_xml(
1146
1352
  """
1147
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1353
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1148
1354
  <data>
1149
1355
  <shape id="minor-overflow" type="text" topLeftX="80" topLeftY="80" width="360" height="39.8">
1150
1356
  <content fontSize="20" lineSpacing="fixed:20" autoFit="no-auto-fit">
@@ -1165,7 +1371,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1165
1371
  def test_lint_xml_allows_single_line_width_estimation_jitter(self) -> None:
1166
1372
  result = xml_text_overlap_lint.lint_xml(
1167
1373
  """
1168
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1374
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1169
1375
  <data>
1170
1376
  <shape id="metric" type="text" topLeftX="80" topLeftY="80" width="152" height="54">
1171
1377
  <content fontSize="36" lineSpacing="multiple:1.2" autoFit="no-auto-fit"><p>4.16万亿</p></content>
@@ -1184,7 +1390,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1184
1390
  def test_lint_xml_allows_short_metric_text_with_separators_as_single_line(self) -> None:
1185
1391
  result = xml_text_overlap_lint.lint_xml(
1186
1392
  """
1187
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1393
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1188
1394
  <data>
1189
1395
  <shape id="metric" type="text" topLeftX="80" topLeftY="80" width="150" height="50">
1190
1396
  <content textType="title" fontSize="36" autoFit="no-auto-fit"><p>4.16万亿</p></content>
@@ -1206,7 +1412,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1206
1412
  def test_lint_xml_reports_plain_short_metric_when_it_wraps(self) -> None:
1207
1413
  result = xml_text_overlap_lint.lint_xml(
1208
1414
  """
1209
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1415
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1210
1416
  <data>
1211
1417
  <shape id="plain-age" type="text" topLeftX="80" topLeftY="80" width="50" height="80">
1212
1418
  <content textType="title" fontSize="36" bold="true" autoFit="no-auto-fit"><p>82岁</p></content>
@@ -1226,7 +1432,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1226
1432
  def test_lint_xml_allows_centered_short_label_near_fit_as_single_line(self) -> None:
1227
1433
  result = xml_text_overlap_lint.lint_xml(
1228
1434
  """
1229
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1435
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1230
1436
  <data>
1231
1437
  <shape id="centered-label" type="text" topLeftX="80" topLeftY="80" width="200" height="30">
1232
1438
  <content fontSize="14" bold="true" textAlign="center" autoFit="no-auto-fit">
@@ -1247,7 +1453,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1247
1453
  def test_lint_xml_allows_headline_near_fit_as_single_line(self) -> None:
1248
1454
  result = xml_text_overlap_lint.lint_xml(
1249
1455
  """
1250
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1456
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1251
1457
  <data>
1252
1458
  <shape id="headline" type="text" topLeftX="80" topLeftY="80" width="700" height="50">
1253
1459
  <content textType="headline" fontSize="26" bold="true" lineSpacing="multiple:1.3" autoFit="no-auto-fit">
@@ -1268,7 +1474,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1268
1474
  def test_lint_xml_allows_dense_body_line_spacing_estimation_slack(self) -> None:
1269
1475
  result = xml_text_overlap_lint.lint_xml(
1270
1476
  """
1271
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1477
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1272
1478
  <data>
1273
1479
  <shape id="dense-body" type="text" topLeftX="80" topLeftY="80" width="360" height="140">
1274
1480
  <content fontSize="13" bold="true" lineSpacing="multiple:1.7" autoFit="no-auto-fit">
@@ -1294,7 +1500,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1294
1500
  def test_lint_xml_reports_dense_body_when_adjusted_height_still_overflows(self) -> None:
1295
1501
  result = xml_text_overlap_lint.lint_xml(
1296
1502
  """
1297
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1503
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1298
1504
  <data>
1299
1505
  <shape id="dense-body" type="text" topLeftX="80" topLeftY="80" width="360" height="100">
1300
1506
  <content fontSize="13" bold="true" lineSpacing="multiple:1.7" autoFit="no-auto-fit">
@@ -1321,7 +1527,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1321
1527
  def test_lint_xml_reports_letter_spaced_caption_near_fit(self) -> None:
1322
1528
  result = xml_text_overlap_lint.lint_xml(
1323
1529
  """
1324
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1530
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1325
1531
  <data>
1326
1532
  <shape id="caption" type="text" topLeftX="80" topLeftY="80" width="120" height="20">
1327
1533
  <content textType="caption" fontSize="11" letterSpacing="1" autoFit="no-auto-fit">
@@ -1343,7 +1549,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1343
1549
  def test_lint_xml_reports_micro_caption_when_wrapping_overflows(self) -> None:
1344
1550
  result = xml_text_overlap_lint.lint_xml(
1345
1551
  """
1346
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1552
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1347
1553
  <data>
1348
1554
  <shape id="micro-caption" type="text" topLeftX="80" topLeftY="60" width="200" height="16">
1349
1555
  <content textType="caption" fontSize="3" lineSpacing="multiple:1.3" letterSpacing="160" autoFit="no-auto-fit">
@@ -1365,7 +1571,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1365
1571
  def test_lint_xml_text_may_overflow_shape_upgrades_to_error_above_threshold(self) -> None:
1366
1572
  result = xml_text_overlap_lint.lint_xml(
1367
1573
  """
1368
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1574
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1369
1575
  <data>
1370
1576
  <shape id="just-warning" type="text" topLeftX="80" topLeftY="80" width="360" height="50">
1371
1577
  <content fontSize="20" lineSpacing="fixed:20" autoFit="no-auto-fit">
@@ -1392,7 +1598,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1392
1598
  def test_lint_xml_text_may_overflow_shape_downgrades_background_decoration_to_info(self) -> None:
1393
1599
  result = xml_text_overlap_lint.lint_xml(
1394
1600
  """
1395
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1601
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1396
1602
  <data>
1397
1603
  <shape id="bg-deco" type="text" topLeftX="0" topLeftY="0" width="600" height="80" alpha="0.3">
1398
1604
  <content fontSize="120" lineSpacing="fixed:120" autoFit="no-auto-fit"><p>2026</p></content>
@@ -1418,7 +1624,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1418
1624
  def test_lint_xml_reports_shape_alpha_ghost_text_out_of_canvas_but_allows_overlap(self) -> None:
1419
1625
  result = xml_text_overlap_lint.lint_xml(
1420
1626
  """
1421
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1627
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1422
1628
  <data>
1423
1629
  <shape id="ghost-number" type="text" topLeftX="-60" topLeftY="30" width="360" height="180" alpha="0.2">
1424
1630
  <content fontSize="160" lineSpacing="fixed:160" wrap="false"><p>01</p></content>
@@ -1438,7 +1644,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1438
1644
  def test_lint_xml_reports_content_color_alpha_ghost_text_out_of_canvas_but_allows_overlap(self) -> None:
1439
1645
  result = xml_text_overlap_lint.lint_xml(
1440
1646
  """
1441
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1647
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1442
1648
  <data>
1443
1649
  <shape id="ghost-year" type="text" topLeftX="760" topLeftY="20" width="260" height="160">
1444
1650
  <content fontSize="140" color="rgba(0,0,0,0.2)" lineSpacing="fixed:140" wrap="false"><p>2026</p></content>
@@ -1458,7 +1664,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1458
1664
  def test_lint_xml_reports_faint_medium_ghost_text_out_of_canvas_but_allows_overlap(self) -> None:
1459
1665
  result = xml_text_overlap_lint.lint_xml(
1460
1666
  """
1461
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1667
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1462
1668
  <data>
1463
1669
  <shape id="medium-ghost" type="text" topLeftX="820" topLeftY="300" width="270" height="72" alpha="0.32">
1464
1670
  <content fontSize="40" lineSpacing="fixed:40" wrap="false"><p>OFF EDGE</p></content>
@@ -1478,7 +1684,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1478
1684
  def test_lint_xml_allows_ghost_text_image_overlap(self) -> None:
1479
1685
  result = xml_text_overlap_lint.lint_xml(
1480
1686
  """
1481
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1687
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1482
1688
  <data>
1483
1689
  <shape id="ghost-label" type="text" topLeftX="100" topLeftY="40" width="560" height="160" alpha="0.2">
1484
1690
  <content fontSize="120" lineSpacing="fixed:120" wrap="false"><p>2026</p></content>
@@ -1498,7 +1704,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1498
1704
  def test_lint_slide_allows_ghost_text_whiteboard_overlap(self) -> None:
1499
1705
  result = xml_text_overlap_lint.lint_slide(
1500
1706
  """
1501
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1707
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1502
1708
  <data>
1503
1709
  <whiteboard id="board" topLeftX="180" topLeftY="70" width="420" height="300"/>
1504
1710
  <shape id="ghost-label" type="text" topLeftX="100" topLeftY="40" width="560" height="160" alpha="0.2">
@@ -1518,7 +1724,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1518
1724
  def test_lint_xml_reports_faint_ghost_text_out_of_canvas_without_area_threshold(self) -> None:
1519
1725
  result = xml_text_overlap_lint.lint_xml(
1520
1726
  """
1521
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1727
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1522
1728
  <data>
1523
1729
  <shape id="small-ghost" type="text" topLeftX="940" topLeftY="300" width="40" height="40" alpha="0.32">
1524
1730
  <content fontSize="36" lineSpacing="fixed:36" wrap="false"><p>土</p></content>
@@ -1533,7 +1739,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1533
1739
  def test_lint_xml_keeps_out_of_canvas_error_for_medium_text_without_faint_alpha(self) -> None:
1534
1740
  result = xml_text_overlap_lint.lint_xml(
1535
1741
  """
1536
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1742
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1537
1743
  <data>
1538
1744
  <shape id="medium-not-ghost" type="text" topLeftX="820" topLeftY="300" width="270" height="72" alpha="0.36">
1539
1745
  <content fontSize="54" lineSpacing="fixed:54" wrap="false"><p>OFF EDGE</p></content>
@@ -1549,7 +1755,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1549
1755
  def test_lint_xml_keeps_out_of_canvas_error_for_half_alpha_large_text(self) -> None:
1550
1756
  result = xml_text_overlap_lint.lint_xml(
1551
1757
  """
1552
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1758
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1553
1759
  <data>
1554
1760
  <shape id="half-alpha" type="text" topLeftX="760" topLeftY="20" width="260" height="160">
1555
1761
  <content fontSize="140" color="rgba(0,0,0,0.5)" lineSpacing="fixed:140" wrap="false"><p>2026</p></content>
@@ -1565,7 +1771,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1565
1771
  def test_lint_xml_text_may_overflow_shape_keeps_error_when_alpha_not_low(self) -> None:
1566
1772
  result = xml_text_overlap_lint.lint_xml(
1567
1773
  """
1568
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1774
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1569
1775
  <data>
1570
1776
  <shape id="opaque-big" type="text" topLeftX="0" topLeftY="0" width="600" height="80" alpha="0.9">
1571
1777
  <content fontSize="120" lineSpacing="fixed:120" autoFit="no-auto-fit"><p>2026</p></content>
@@ -1587,7 +1793,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1587
1793
  def test_lint_xml_text_may_overflow_shape_keeps_error_when_no_foreground_text(self) -> None:
1588
1794
  result = xml_text_overlap_lint.lint_xml(
1589
1795
  """
1590
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1796
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1591
1797
  <data>
1592
1798
  <shape id="lonely-big" type="text" topLeftX="0" topLeftY="0" width="600" height="80" alpha="0.3">
1593
1799
  <content fontSize="120" lineSpacing="fixed:120" autoFit="no-auto-fit"><p>2026</p></content>
@@ -1606,7 +1812,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1606
1812
  def test_lint_xml_text_may_overflow_shape_keeps_error_when_foreground_alpha_zero(self) -> None:
1607
1813
  result = xml_text_overlap_lint.lint_xml(
1608
1814
  """
1609
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1815
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1610
1816
  <data>
1611
1817
  <shape id="bg-deco" type="text" topLeftX="0" topLeftY="0" width="600" height="80" alpha="0.3">
1612
1818
  <content fontSize="120" lineSpacing="fixed:120" autoFit="no-auto-fit"><p>2026</p></content>
@@ -1628,7 +1834,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1628
1834
  def test_lint_xml_text_may_overflow_shape_keeps_error_when_foreground_is_below_in_order(self) -> None:
1629
1835
  result = xml_text_overlap_lint.lint_xml(
1630
1836
  """
1631
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1837
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1632
1838
  <data>
1633
1839
  <shape id="foreground" type="text" topLeftX="40" topLeftY="20" width="400" height="60">
1634
1840
  <content fontSize="20" lineSpacing="fixed:24"><p>Annual Report</p></content>
@@ -1650,7 +1856,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1650
1856
  def test_lint_xml_uses_paragraph_spacing_overrides_for_text_height_warning(self) -> None:
1651
1857
  result = xml_text_overlap_lint.lint_xml(
1652
1858
  """
1653
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1859
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1654
1860
  <data>
1655
1861
  <shape id="paragraph-overflow" type="text" topLeftX="80" topLeftY="80" width="360" height="30">
1656
1862
  <content fontSize="20" lineSpacing="multiple:1.5" autoFit="no-auto-fit">
@@ -1677,7 +1883,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1677
1883
  def test_lint_xml_uses_letter_spacing_for_text_overflow_warning(self) -> None:
1678
1884
  result = xml_text_overlap_lint.lint_xml(
1679
1885
  """
1680
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1886
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1681
1887
  <data>
1682
1888
  <shape id="baseline" type="text" topLeftX="0" topLeftY="0" width="120" height="30">
1683
1889
  <content fontSize="20" lineSpacing="multiple:1.5" autoFit="no-auto-fit"><p>一二三四五六</p></content>
@@ -1712,8 +1918,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1712
1918
  def test_lint_xml_allows_template_style_images_outside_canvas(self) -> None:
1713
1919
  result = xml_text_overlap_lint.lint_xml(
1714
1920
  """
1715
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1716
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1921
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
1922
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1717
1923
  <data>
1718
1924
  <img src="tok" topLeftX="-120" topLeftY="20" width="360" height="360"/>
1719
1925
  <shape type="text" topLeftX="40" topLeftY="80" width="180" height="80">
@@ -1733,7 +1939,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1733
1939
  def test_extract_elements_preserves_supported_element_geometry_order_and_text_metadata(self) -> None:
1734
1940
  elements = xml_text_overlap_lint.extract_elements(
1735
1941
  """
1736
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1942
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1737
1943
  <data>
1738
1944
  <img id="photo" src="tok" topLeftX="10" topLeftY="20" width="100" height="80"/>
1739
1945
  <shape id="headline" type="text" topLeftX="40" topLeftY="60" width="320" height="90">
@@ -1745,6 +1951,9 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1745
1951
  <table id="table" topLeftX="400" topLeftY="60" width="220" height="120"></table>
1746
1952
  <chart id="chart" topLeftX="640" topLeftY="60" width="220" height="120"/>
1747
1953
  <whiteboard id="wb" topLeftX="80" topLeftY="220" width="760" height="240"/>
1954
+ <embed id="emb" topLeftX="600" topLeftY="320" width="240" height="140">
1955
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 140"><rect x="0" y="0" width="240" height="140"/></svg>
1956
+ </embed>
1748
1957
  <shape id="missing-height" type="text" topLeftX="80" topLeftY="480" width="320">
1749
1958
  <content><p>Skipped</p></content>
1750
1959
  </shape>
@@ -1752,9 +1961,9 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1752
1961
  </slide>
1753
1962
  """
1754
1963
  )
1755
- self.assertEqual([element["id"] for element in elements], ["photo", "headline", "table", "chart", "wb"])
1756
- self.assertEqual([element["kind"] for element in elements], ["img", "shape", "table", "chart", "whiteboard"])
1757
- self.assertEqual([element["order"] for element in elements], [0, 1, 2, 3, 4])
1964
+ self.assertEqual([element["id"] for element in elements], ["photo", "headline", "table", "chart", "wb", "emb"])
1965
+ self.assertEqual([element["kind"] for element in elements], ["img", "shape", "table", "chart", "whiteboard", "embed"])
1966
+ self.assertEqual([element["order"] for element in elements], [0, 1, 2, 3, 4, 5])
1758
1967
  self.assertEqual(elements[1]["type"], "text")
1759
1968
  self.assertEqual(elements[1]["textType"], "headline")
1760
1969
  self.assertEqual(elements[1]["textAlign"], "center")
@@ -1765,8 +1974,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1765
1974
  def test_lint_xml_ignores_small_out_of_bounds_images(self) -> None:
1766
1975
  result = xml_text_overlap_lint.lint_xml(
1767
1976
  """
1768
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1769
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1977
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
1978
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1770
1979
  <data>
1771
1980
  <img src="tok" topLeftX="-20" topLeftY="20" width="120" height="120"/>
1772
1981
  </data>
@@ -1779,8 +1988,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1779
1988
  def test_lint_xml_ignores_out_of_canvas_images(self) -> None:
1780
1989
  result = xml_text_overlap_lint.lint_xml(
1781
1990
  """
1782
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1783
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
1991
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
1992
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1784
1993
  <data>
1785
1994
  <img src="right" topLeftX="780" topLeftY="0" width="500" height="540"/>
1786
1995
  <img src="bottom" topLeftX="0" topLeftY="430" width="900" height="280"/>
@@ -1794,8 +2003,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1794
2003
  def test_lint_xml_ignores_full_bleed_images_outside_canvas(self) -> None:
1795
2004
  result = xml_text_overlap_lint.lint_xml(
1796
2005
  """
1797
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1798
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2006
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2007
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1799
2008
  <data>
1800
2009
  <img src="tok" topLeftX="-80" topLeftY="-20" width="1080" height="600"/>
1801
2010
  </data>
@@ -1808,8 +2017,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1808
2017
  def test_lint_xml_reports_text_and_chart_but_not_image_out_of_canvas(self) -> None:
1809
2018
  result = xml_text_overlap_lint.lint_xml(
1810
2019
  """
1811
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
1812
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2020
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2021
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1813
2022
  <data>
1814
2023
  <shape id="outside-shape" type="text" topLeftX="-10" topLeftY="40" width="50" height="50"/>
1815
2024
  <img id="outside-img" src="token" topLeftX="120" topLeftY="-20" width="50" height="50"/>
@@ -1838,7 +2047,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1838
2047
  def test_lint_xml_ignores_line_out_of_canvas(self) -> None:
1839
2048
  result = xml_text_overlap_lint.lint_xml(
1840
2049
  """
1841
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2050
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1842
2051
  <data>
1843
2052
  <shape id="body" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
1844
2053
  <content fontSize="18"><p>Visible content</p></content>
@@ -1855,7 +2064,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1855
2064
  def test_lint_xml_reports_horizontal_line_crossing_headline_glyphs(self) -> None:
1856
2065
  result = xml_text_overlap_lint.lint_xml(
1857
2066
  """
1858
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2067
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1859
2068
  <data>
1860
2069
  <shape id="title" type="text" topLeftX="80" topLeftY="200" width="500" height="90">
1861
2070
  <content fontSize="60"><p>测试文字 ABC</p></content>
@@ -1876,7 +2085,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1876
2085
  def test_lint_xml_reports_vertical_line_crossing_multiline_text(self) -> None:
1877
2086
  result = xml_text_overlap_lint.lint_xml(
1878
2087
  """
1879
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2088
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1880
2089
  <data>
1881
2090
  <shape id="col" type="text" topLeftX="700" topLeftY="180" width="240" height="180">
1882
2091
  <content fontSize="20"><p>第一行文字内容</p><p>第二行文字内容</p><p>第三行文字内容</p></content>
@@ -1896,7 +2105,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1896
2105
  def test_lint_xml_reports_diagonal_line_crossing_text_block(self) -> None:
1897
2106
  result = xml_text_overlap_lint.lint_xml(
1898
2107
  """
1899
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2108
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1900
2109
  <data>
1901
2110
  <shape id="para" type="text" topLeftX="80" topLeftY="400" width="420" height="140">
1902
2111
  <content fontSize="18"><p>这是一段测试文字用于验证线条穿过</p></content>
@@ -1918,7 +2127,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1918
2127
  # through empty space in the opposite corner -- a naive bbox test would false-positive here.
1919
2128
  result = xml_text_overlap_lint.lint_xml(
1920
2129
  """
1921
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2130
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1922
2131
  <data>
1923
2132
  <shape id="corner-text" type="text" topLeftX="80" topLeftY="80" width="120" height="40">
1924
2133
  <content fontSize="18"><p>corner</p></content>
@@ -1938,7 +2147,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1938
2147
  def test_lint_xml_ignores_line_touching_text_frame_but_not_glyphs(self) -> None:
1939
2148
  result = xml_text_overlap_lint.lint_xml(
1940
2149
  """
1941
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2150
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1942
2151
  <data>
1943
2152
  <shape id="lbl" type="text" topLeftX="80" topLeftY="80" width="300" height="200">
1944
2153
  <content fontSize="18" verticalAlign="top"><p>短标签</p></content>
@@ -1955,7 +2164,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1955
2164
  def test_lint_xml_ignores_invisible_line_crossing_text(self) -> None:
1956
2165
  result = xml_text_overlap_lint.lint_xml(
1957
2166
  """
1958
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2167
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1959
2168
  <data>
1960
2169
  <shape id="title" type="text" topLeftX="80" topLeftY="200" width="500" height="90">
1961
2170
  <content fontSize="60"><p>测试文字 ABC</p></content>
@@ -1974,7 +2183,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1974
2183
  # frame's left edge renders before the first glyph, so it must not be flagged.
1975
2184
  result = xml_text_overlap_lint.lint_xml(
1976
2185
  """
1977
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2186
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1978
2187
  <data>
1979
2188
  <shape width="240" height="60" topLeftX="120" topLeftY="100" type="text" id="bmm">
1980
2189
  <content fontSize="20" fontFamily="Arial" color="rgba(31, 35, 41, 1)" lineSpacing="fixed:24">
@@ -1995,7 +2204,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
1995
2204
  # <line> only, so a <polyline> over text is not flagged.
1996
2205
  result = xml_text_overlap_lint.lint_xml(
1997
2206
  """
1998
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2207
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
1999
2208
  <data>
2000
2209
  <shape width="240" height="60" topLeftX="120" topLeftY="100" type="text" id="bmr">
2001
2210
  <content fontSize="20" fontFamily="Arial" color="rgba(31, 35, 41, 1)" lineSpacing="fixed:24">
@@ -2017,7 +2226,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2017
2226
  # under the visual glyph box (underline look) and must not be flagged.
2018
2227
  result = xml_text_overlap_lint.lint_xml(
2019
2228
  """
2020
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2229
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2021
2230
  <data>
2022
2231
  <shape width="240" height="80" topLeftX="120" topLeftY="100" type="text" id="bmB">
2023
2232
  <content fontSize="20" fontFamily="Arial" color="rgba(31, 35, 41, 1)" lineSpacing="fixed:24">
@@ -2036,8 +2245,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2036
2245
  def test_lint_xml_uses_rotated_text_and_chart_bounds_for_canvas_validation(self) -> None:
2037
2246
  result = xml_text_overlap_lint.lint_xml(
2038
2247
  """
2039
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2040
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2248
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2249
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2041
2250
  <data>
2042
2251
  <shape id="rotated-text" type="text" topLeftX="0" topLeftY="0" width="100" height="100" rotation="45"/>
2043
2252
  <chart id="rotated-chart" topLeftX="860" topLeftY="200" width="100" height="100" rotation="45">
@@ -2063,8 +2272,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2063
2272
  def test_lint_xml_uses_declared_bounds_for_rect_and_ignores_images(self) -> None:
2064
2273
  result = xml_text_overlap_lint.lint_xml(
2065
2274
  """
2066
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2067
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2275
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2276
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2068
2277
  <data>
2069
2278
  <shape id="rotated-rect" type="rect" topLeftX="900" topLeftY="0" width="100" height="100" rotation="45"/>
2070
2279
  <img id="rotated-image" src="token" topLeftX="860" topLeftY="200" width="100" height="100" rotation="45"/>
@@ -2126,8 +2335,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2126
2335
  def test_lint_xml_rejects_non_finite_rotation_values_from_xsd(self) -> None:
2127
2336
  result = xml_text_overlap_lint.lint_xml(
2128
2337
  """
2129
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2130
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2338
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2339
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2131
2340
  <data>
2132
2341
  <shape id="infinite" type="text" topLeftX="-10" topLeftY="0" width="20" height="20" rotation="inf"/>
2133
2342
  <shape id="negative-infinite" type="text" topLeftX="0" topLeftY="-10" width="20" height="20" rotation="-inf"/>
@@ -2151,8 +2360,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2151
2360
  def test_lint_xml_reports_table_bottom_overflow_from_declared_bounds(self) -> None:
2152
2361
  result = xml_text_overlap_lint.lint_xml(
2153
2362
  """
2154
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2155
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2363
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2364
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2156
2365
  <data>
2157
2366
  <table id="score-table" topLeftX="54" topLeftY="238" width="414" height="385">
2158
2367
  <tr><td><content><p>Score</p></content></td></tr>
@@ -2172,8 +2381,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2172
2381
  def test_lint_xml_reports_table_right_overflow_from_declared_bounds(self) -> None:
2173
2382
  result = xml_text_overlap_lint.lint_xml(
2174
2383
  """
2175
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2176
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2384
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2385
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2177
2386
  <data>
2178
2387
  <table id="wide-table" topLeftX="850" topLeftY="80" width="180" height="120">
2179
2388
  <tr><td><content><p>Score</p></content></td></tr>
@@ -2191,8 +2400,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2191
2400
  def test_lint_xml_allows_table_with_declared_bounds_inside_canvas(self) -> None:
2192
2401
  result = xml_text_overlap_lint.lint_xml(
2193
2402
  """
2194
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2195
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2403
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2404
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2196
2405
  <data>
2197
2406
  <table id="inside-table" topLeftX="40" topLeftY="120" width="880" height="360">
2198
2407
  <tr><td><content><p>Score</p></content></td></tr>
@@ -2208,8 +2417,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2208
2417
  def test_lint_xml_reports_resolved_table_bounds_when_declared_sizes_are_missing(self) -> None:
2209
2418
  result = xml_text_overlap_lint.lint_xml(
2210
2419
  """
2211
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2212
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2420
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2421
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2213
2422
  <data>
2214
2423
  <table id="implicit-size-table" topLeftX="850" topLeftY="480">
2215
2424
  <colgroup><col/><col/></colgroup>
@@ -2227,76 +2436,635 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2227
2436
  self.assertEqual(issue["bbox"], {"x": 850, "y": 480, "width": 220, "height": 74})
2228
2437
  self.assertEqual(issue["overflow"], {"left": 0, "top": 0, "right": 110, "bottom": 14})
2229
2438
 
2230
- def test_lint_xml_uses_resolved_table_bounds_for_canvas_validation(self) -> None:
2439
+ def test_lint_xml_xml_path_preserves_source_index_after_filtered_table(self) -> None:
2231
2440
  result = xml_text_overlap_lint.lint_xml(
2232
2441
  """
2233
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2234
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2235
- <data>
2236
- <table id="resolved-overflow-table" topLeftX="800" topLeftY="80" width="100" height="40">
2237
- <colgroup><col width="100"/><col width="100"/></colgroup>
2238
- <tr height="40"><td/><td/></tr>
2239
- </table>
2240
- </data>
2241
- </slide>
2242
- </presentation>
2442
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
2443
+ <data>
2444
+ <table id="t1" topLeftX="20" topLeftY="20">
2445
+ <tr><td/></tr>
2446
+ </table>
2447
+ <table id="t2" topLeftX="20" topLeftY="100" width="9999" height="100">
2448
+ <tr><td/></tr>
2449
+ </table>
2450
+ </data>
2451
+ </slide>
2243
2452
  """
2244
2453
  )
2245
- issues = result["slides"][0]["issues"]
2246
- canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
2247
- mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
2248
- self.assertEqual(result["summary"]["error_count"], 1)
2249
- self.assertEqual(canvas_issue["bbox"], {"x": 800, "y": 80, "width": 200, "height": 40})
2250
- self.assertEqual(canvas_issue["overflow"]["right"], 40)
2251
- self.assertEqual(mismatch_issue["dimension"], "width")
2252
- self.assertEqual(mismatch_issue["resolved_size"], canvas_issue["bbox"]["width"])
2253
2454
 
2254
- def test_lint_xml_uses_the_same_anonymous_table_id_for_all_table_diagnostics(self) -> None:
2455
+ issue = next(
2456
+ issue
2457
+ for issue in result["slides"][0]["issues"]
2458
+ if issue["code"] == "table_out_of_canvas"
2459
+ )
2460
+ self.assertEqual(issue["element_ids"], ["t2"])
2461
+ self.assertEqual(
2462
+ issue["related_objects"][0]["xml_path"],
2463
+ "slide[1]/data/table[2]",
2464
+ )
2465
+
2466
+ def test_lint_xml_duplicate_id_keeps_issue_bound_to_original_shape(self) -> None:
2255
2467
  result = xml_text_overlap_lint.lint_xml(
2256
2468
  """
2257
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2258
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2259
- <data>
2260
- <shape id="title" type="text" topLeftX="40" topLeftY="40" width="200" height="40"/>
2261
- <img id="logo" src="token" topLeftX="40" topLeftY="100" width="40" height="40"/>
2262
- <table topLeftX="900" topLeftY="80" width="100" height="40">
2263
- <colgroup><col width="100"/><col width="100"/></colgroup>
2264
- <tr height="40"><td/><td/></tr>
2265
- </table>
2266
- </data>
2267
- </slide>
2268
- </presentation>
2469
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2470
+ <data>
2471
+ <shape id="dup" type="rect" topLeftX="-20" topLeftY="40" width="50" height="50"/>
2472
+ <shape id="dup" type="rect" topLeftX="100" topLeftY="40" width="50" height="50"/>
2473
+ </data>
2474
+ </slide>
2269
2475
  """
2270
2476
  )
2271
- issues = result["slides"][0]["issues"]
2272
- canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
2273
- mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
2274
- self.assertEqual(canvas_issue["elements"], ["table-3"])
2275
- self.assertEqual(mismatch_issue["elements"], ["table-3"])
2276
2477
 
2277
- def test_lint_xml_reports_info_when_table_target_size_resolves_larger_than_declared(self) -> None:
2478
+ canvas_issue = next(
2479
+ issue for issue in result["slides"][0]["issues"]
2480
+ if issue["code"] == "shape_out_of_canvas"
2481
+ )
2482
+ self.assertEqual(canvas_issue["element_ids"], ["dup"])
2483
+ self.assertEqual(
2484
+ canvas_issue["related_objects"],
2485
+ [
2486
+ {
2487
+ "element_id": "dup",
2488
+ "kind": "shape",
2489
+ "type": "rect",
2490
+ "bbox": {"x": -20, "y": 40, "width": 50, "height": 50},
2491
+ "xml_path": "slide[1]/data/shape[1]",
2492
+ }
2493
+ ],
2494
+ )
2495
+ self.assertTrue(
2496
+ canvas_issue["hint"].startswith(
2497
+ "Locate via related_objects[].xml_path. "
2498
+ )
2499
+ )
2500
+ duplicate_issue = next(
2501
+ issue for issue in result["slides"][0]["issues"]
2502
+ if issue["code"] == "duplicate_element_id"
2503
+ )
2504
+ self.assertEqual(
2505
+ duplicate_issue["hint"],
2506
+ "Locate via related_objects[].xml_path. "
2507
+ "Do not invent replacement IDs. For newly authored elements, remove the id attribute. "
2508
+ "When updating read-back XML, keep the server ID on the original element only and remove it "
2509
+ "from copied or new elements.",
2510
+ )
2511
+ self.assertEqual(duplicate_issue["element_ids"], ["dup", "dup"])
2512
+ self.assertEqual(
2513
+ [obj["xml_path"] for obj in duplicate_issue["related_objects"]],
2514
+ ["slide[1]/data/shape[1]", "slide[1]/data/shape[2]"],
2515
+ )
2516
+
2517
+ def test_lint_xml_blocks_duplicate_table_cell_ids(self) -> None:
2278
2518
  result = xml_text_overlap_lint.lint_xml(
2279
2519
  """
2280
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2281
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
2282
- <data>
2283
- <table id="size-mismatch" topLeftX="40" topLeftY="120" width="200" height="80">
2284
- <colgroup><col span="2" width="100"/><col width="50"/></colgroup>
2285
- <tr height="40"><td/><td/><td/></tr>
2286
- <tr height="60"><td/><td/><td/></tr>
2287
- </table>
2288
- </data>
2289
- </slide>
2290
- </presentation>
2520
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2521
+ <data>
2522
+ <table id="table-1" topLeftX="80" topLeftY="80" width="800" height="120">
2523
+ <colgroup><col width="400"/><col width="400"/></colgroup>
2524
+ <tr height="120">
2525
+ <td id="bjs"><content fontSize="24"><p>Original cell</p></content></td>
2526
+ <td id="bjs"><content fontSize="24"><p>Copied cell</p></content></td>
2527
+ </tr>
2528
+ </table>
2529
+ </data>
2530
+ </slide>
2291
2531
  """
2292
2532
  )
2293
- issues_by_dimension = {issue["dimension"]: issue for issue in result["slides"][0]["issues"]}
2294
- self.assertEqual(result["summary"]["error_count"], 0)
2295
- self.assertEqual(result["summary"]["warning_count"], 0)
2296
- self.assertEqual(result["summary"]["info_count"], 2)
2297
- self.assertEqual(issues_by_dimension["width"]["level"], "info")
2298
- self.assertEqual(issues_by_dimension["width"]["code"], "table_resolved_size_mismatch")
2299
- self.assertEqual(issues_by_dimension["width"]["resolved_sizes"], [100, 100, 50])
2533
+
2534
+ issue = next(
2535
+ issue
2536
+ for issue in result["slides"][0]["issues"]
2537
+ if issue["code"] == "duplicate_element_id"
2538
+ )
2539
+ self.assertFalse(result["summary"]["release_ready"])
2540
+ self.assertEqual(issue["element_ids"], ["bjs", "bjs"])
2541
+ self.assertEqual(
2542
+ issue["related_objects"],
2543
+ [
2544
+ {
2545
+ "element_id": "bjs",
2546
+ "kind": "td",
2547
+ "type": "td",
2548
+ "xml_path": "slide[1]/data/table[1]/tr[1]/td[1]",
2549
+ },
2550
+ {
2551
+ "element_id": "bjs",
2552
+ "kind": "td",
2553
+ "type": "td",
2554
+ "xml_path": "slide[1]/data/table[1]/tr[1]/td[2]",
2555
+ },
2556
+ ],
2557
+ )
2558
+
2559
+ def test_lint_xml_blocks_duplicate_id_shared_by_shape_and_table_cell(self) -> None:
2560
+ result = xml_text_overlap_lint.lint_xml(
2561
+ """
2562
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2563
+ <data>
2564
+ <shape id="baa" type="rect" topLeftX="40" topLeftY="40" width="80" height="80"/>
2565
+ <table id="table-1" topLeftX="160" topLeftY="40" width="200" height="80">
2566
+ <tr height="80"><td id="baa"><content fontSize="12"><p>Cell</p></content></td></tr>
2567
+ </table>
2568
+ </data>
2569
+ </slide>
2570
+ """
2571
+ )
2572
+
2573
+ issue = next(
2574
+ issue
2575
+ for issue in result["slides"][0]["issues"]
2576
+ if issue["code"] == "duplicate_element_id"
2577
+ )
2578
+ self.assertEqual(issue["element_ids"], ["baa", "baa"])
2579
+ self.assertEqual(
2580
+ [obj["xml_path"] for obj in issue["related_objects"]],
2581
+ ["slide[1]/data/shape[1]", "slide[1]/data/table[1]/tr[1]/td[1]"],
2582
+ )
2583
+
2584
+ def test_lint_xml_blocks_duplicate_id_shared_by_shape_and_undefined(self) -> None:
2585
+ result = xml_text_overlap_lint.lint_xml(
2586
+ """
2587
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2588
+ <data>
2589
+ <shape id="dup" type="rect" topLeftX="40" topLeftY="40" width="80" height="80"/>
2590
+ <undefined id="dup" type="video"/>
2591
+ </data>
2592
+ </slide>
2593
+ """
2594
+ )
2595
+
2596
+ issue = next(
2597
+ issue
2598
+ for issue in result["slides"][0]["issues"]
2599
+ if issue["code"] == "duplicate_element_id"
2600
+ )
2601
+ self.assertFalse(result["summary"]["release_ready"])
2602
+ self.assertEqual(issue["element_ids"], ["dup", "dup"])
2603
+ self.assertEqual(
2604
+ issue["related_objects"],
2605
+ [
2606
+ {
2607
+ "element_id": "dup",
2608
+ "kind": "shape",
2609
+ "type": "rect",
2610
+ "bbox": {"x": 40, "y": 40, "width": 80, "height": 80},
2611
+ "xml_path": "slide[1]/data/shape[1]",
2612
+ },
2613
+ {
2614
+ "element_id": "dup",
2615
+ "kind": "undefined",
2616
+ "type": "video",
2617
+ "xml_path": "slide[1]/data/undefined[1]",
2618
+ },
2619
+ ],
2620
+ )
2621
+
2622
+ def test_lint_xml_does_not_report_unique_table_cell_and_note_ids(self) -> None:
2623
+ result = xml_text_overlap_lint.lint_xml(
2624
+ """
2625
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2626
+ <data>
2627
+ <table id="table-1" topLeftX="80" topLeftY="80" width="800" height="120">
2628
+ <colgroup><col width="400"/><col width="400"/></colgroup>
2629
+ <tr height="120"><td id="baa"/><td id="bab"/></tr>
2630
+ </table>
2631
+ </data>
2632
+ <note id="bac"><content fontSize="12"><p>Note</p></content></note>
2633
+ </slide>
2634
+ """
2635
+ )
2636
+
2637
+ self.assertNotIn(
2638
+ "duplicate_element_id",
2639
+ [issue["code"] for issue in result["slides"][0]["issues"]],
2640
+ )
2641
+
2642
+ def test_lint_xml_blocks_duplicate_ids_across_slides(self) -> None:
2643
+ result = xml_text_overlap_lint.lint_xml(
2644
+ """
2645
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2646
+ <slide>
2647
+ <data>
2648
+ <shape id="dup" type="rect" topLeftX="40" topLeftY="40" width="80" height="80"/>
2649
+ </data>
2650
+ </slide>
2651
+ <slide>
2652
+ <data>
2653
+ <shape id="dup" type="rect" topLeftX="140" topLeftY="40" width="80" height="80"/>
2654
+ </data>
2655
+ </slide>
2656
+ </presentation>
2657
+ """
2658
+ )
2659
+
2660
+ issue = next(
2661
+ issue
2662
+ for issue in result["document"]["errors"]
2663
+ if issue["code"] == "duplicate_element_id"
2664
+ )
2665
+ self.assertFalse(result["summary"]["release_ready"])
2666
+ self.assertEqual(issue["element_ids"], ["dup", "dup"])
2667
+ self.assertEqual(
2668
+ issue["related_objects"],
2669
+ [
2670
+ {
2671
+ "element_id": "dup",
2672
+ "kind": "shape",
2673
+ "type": "rect",
2674
+ "bbox": {"x": 40, "y": 40, "width": 80, "height": 80},
2675
+ "xml_path": "slide[1]/data/shape[1]",
2676
+ },
2677
+ {
2678
+ "element_id": "dup",
2679
+ "kind": "shape",
2680
+ "type": "rect",
2681
+ "bbox": {"x": 140, "y": 40, "width": 80, "height": 80},
2682
+ "xml_path": "slide[2]/data/shape[1]",
2683
+ },
2684
+ ],
2685
+ )
2686
+
2687
+ def test_lint_xml_does_not_treat_slide_ids_as_element_ids(self) -> None:
2688
+ result = xml_text_overlap_lint.lint_xml(
2689
+ """
2690
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2691
+ <slide id="dup"><data/></slide>
2692
+ <slide id="dup"><data/></slide>
2693
+ </presentation>
2694
+ """
2695
+ )
2696
+
2697
+ self.assertNotIn(
2698
+ "duplicate_element_id",
2699
+ [issue["code"] for issue in result["document"]["errors"]],
2700
+ )
2701
+
2702
+ def test_lint_xml_does_not_treat_presentation_id_as_element_id(self) -> None:
2703
+ result = xml_text_overlap_lint.lint_xml(
2704
+ """
2705
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" id="dup" width="960" height="540">
2706
+ <slide>
2707
+ <data><undefined id="dup" type="video"/></data>
2708
+ </slide>
2709
+ </presentation>
2710
+ """
2711
+ )
2712
+
2713
+ self.assertNotIn(
2714
+ "duplicate_element_id",
2715
+ [
2716
+ issue["code"]
2717
+ for issue in [
2718
+ *result["document"]["errors"],
2719
+ *result["slides"][0]["errors"],
2720
+ ]
2721
+ ],
2722
+ )
2723
+
2724
+ def test_lint_xml_blocks_duplicate_note_ids_across_slides(self) -> None:
2725
+ result = xml_text_overlap_lint.lint_xml(
2726
+ """
2727
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2728
+ <slide>
2729
+ <note id="baa"><content fontSize="12"><p>First note</p></content></note>
2730
+ </slide>
2731
+ <slide>
2732
+ <note id="baa"><content fontSize="12"><p>Copied note</p></content></note>
2733
+ </slide>
2734
+ </presentation>
2735
+ """
2736
+ )
2737
+
2738
+ issue = next(
2739
+ issue
2740
+ for issue in result["document"]["errors"]
2741
+ if issue["code"] == "duplicate_element_id"
2742
+ )
2743
+ self.assertFalse(result["summary"]["release_ready"])
2744
+ self.assertEqual(issue["element_ids"], ["baa", "baa"])
2745
+ self.assertEqual(
2746
+ issue["related_objects"],
2747
+ [
2748
+ {
2749
+ "element_id": "baa",
2750
+ "kind": "note",
2751
+ "type": "note",
2752
+ "xml_path": "slide[1]/note[1]",
2753
+ },
2754
+ {
2755
+ "element_id": "baa",
2756
+ "kind": "note",
2757
+ "type": "note",
2758
+ "xml_path": "slide[2]/note[1]",
2759
+ },
2760
+ ],
2761
+ )
2762
+
2763
+ def test_lint_xml_cross_kind_duplicate_id_does_not_change_related_object_kind(self) -> None:
2764
+ result = xml_text_overlap_lint.lint_xml(
2765
+ """
2766
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2767
+ <data>
2768
+ <shape id="dup" type="rect" topLeftX="-20" topLeftY="40" width="50" height="50"/>
2769
+ <img id="dup" src="token" topLeftX="100" topLeftY="40" width="50" height="50"/>
2770
+ </data>
2771
+ </slide>
2772
+ """
2773
+ )
2774
+
2775
+ issue = next(
2776
+ issue for issue in result["slides"][0]["issues"]
2777
+ if issue["code"] == "shape_out_of_canvas"
2778
+ )
2779
+ self.assertEqual(issue["related_objects"][0]["kind"], "shape")
2780
+ self.assertEqual(
2781
+ issue["related_objects"][0]["xml_path"],
2782
+ "slide[1]/data/shape[1]",
2783
+ )
2784
+ duplicate_issue = next(
2785
+ issue
2786
+ for issue in result["slides"][0]["issues"]
2787
+ if issue["code"] == "duplicate_element_id"
2788
+ )
2789
+ self.assertEqual(
2790
+ [obj["xml_path"] for obj in duplicate_issue["related_objects"]],
2791
+ ["slide[1]/data/shape[1]", "slide[1]/data/img[1]"],
2792
+ )
2793
+
2794
+ def test_normalize_issue_does_not_repeat_xml_path_hint_prefix(self) -> None:
2795
+ xml_path = "slide[1]/data/shape[1]"
2796
+ element = {
2797
+ "id": "box",
2798
+ "_source_id": "box",
2799
+ "_ref": xml_path,
2800
+ "xml_path": xml_path,
2801
+ "kind": "shape",
2802
+ "type": "rect",
2803
+ "x": 0,
2804
+ "y": 0,
2805
+ "width": 40,
2806
+ "height": 40,
2807
+ }
2808
+ prefix = "Locate via related_objects[].xml_path."
2809
+
2810
+ issue = xml_text_overlap_lint.normalize_issue(
2811
+ {
2812
+ "level": "error",
2813
+ "code": "shape_out_of_canvas",
2814
+ "elements": [xml_path],
2815
+ "canvas": {"width": 960, "height": 540},
2816
+ "bbox": {"x": -10, "y": 0, "width": 40, "height": 40},
2817
+ "overflow": {"left": 10, "top": 0, "right": 0, "bottom": 0},
2818
+ "hint": f"{prefix} Move the shape inside the canvas.",
2819
+ },
2820
+ 1,
2821
+ {xml_path: element},
2822
+ )
2823
+
2824
+ self.assertEqual(issue["hint"].count(prefix), 1)
2825
+
2826
+ def test_lint_xml_elements_keep_locator_for_anonymous_related_object(self) -> None:
2827
+ result = xml_text_overlap_lint.lint_xml(
2828
+ """
2829
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2830
+ <data>
2831
+ <shape type="text" topLeftX="100" topLeftY="100" width="300" height="100">
2832
+ <content fontSize="24"><p>Important text</p></content>
2833
+ </shape>
2834
+ <img id="srv-42" src="token" topLeftX="100" topLeftY="100" width="300" height="100"/>
2835
+ </data>
2836
+ </slide>
2837
+ """
2838
+ )
2839
+
2840
+ issue = next(
2841
+ issue
2842
+ for issue in result["slides"][0]["issues"]
2843
+ if issue["code"] == "image_covers_text"
2844
+ )
2845
+ self.assertEqual(
2846
+ issue["elements"],
2847
+ ["srv-42", "slide[1]/data/shape[1]"],
2848
+ )
2849
+ self.assertEqual(issue["element_ids"], ["srv-42"])
2850
+ self.assertEqual(len(issue["related_objects"]), 2)
2851
+
2852
+ def test_normalize_issue_deduplicates_repeated_element_refs(self) -> None:
2853
+ xml_path = "slide[1]/data/shape[1]"
2854
+ element = {
2855
+ "id": "srv-42",
2856
+ "_source_id": "srv-42",
2857
+ "_ref": xml_path,
2858
+ "xml_path": xml_path,
2859
+ "kind": "shape",
2860
+ "type": "rect",
2861
+ "x": 0,
2862
+ "y": 0,
2863
+ "width": 40,
2864
+ "height": 40,
2865
+ }
2866
+
2867
+ issue = xml_text_overlap_lint.normalize_issue(
2868
+ {
2869
+ "level": "warning",
2870
+ "code": "blank_slide",
2871
+ "measurement": {
2872
+ "visible_element_count": 0,
2873
+ "declared_element_count": 1,
2874
+ },
2875
+ "elements": [xml_path, xml_path],
2876
+ },
2877
+ 1,
2878
+ {xml_path: element},
2879
+ )
2880
+
2881
+ self.assertEqual(issue["elements"], ["srv-42"])
2882
+ self.assertEqual(issue["element_ids"], ["srv-42"])
2883
+ self.assertEqual(len(issue["related_objects"]), 1)
2884
+
2885
+ def test_lint_xml_reports_duplicate_ids_for_every_linted_element_kind(self) -> None:
2886
+ duplicate_pairs = {
2887
+ "shape": (
2888
+ '<shape id="dup" type="rect" topLeftX="10" topLeftY="10" width="40" height="40"/>',
2889
+ '<shape id="dup" type="rect" topLeftX="60" topLeftY="10" width="40" height="40"/>',
2890
+ ),
2891
+ "chart": (
2892
+ '<chart id="dup" topLeftX="10" topLeftY="10" width="40" height="40"><chartPlotArea><chartPlot type="line"/></chartPlotArea><chartData><dim1><chartField name="category" valueType="string">A</chartField></dim1><dim2><chartField name="value" valueType="number">1</chartField></dim2></chartData></chart>',
2893
+ '<chart id="dup" topLeftX="60" topLeftY="10" width="40" height="40"><chartPlotArea><chartPlot type="line"/></chartPlotArea><chartData><dim1><chartField name="category" valueType="string">A</chartField></dim1><dim2><chartField name="value" valueType="number">1</chartField></dim2></chartData></chart>',
2894
+ ),
2895
+ "table": (
2896
+ '<table id="dup" topLeftX="10" topLeftY="10" width="40" height="40"><colgroup><col width="40"/></colgroup><tr height="40"><td/></tr></table>',
2897
+ '<table id="dup" topLeftX="60" topLeftY="10" width="40" height="40"><colgroup><col width="40"/></colgroup><tr height="40"><td/></tr></table>',
2898
+ ),
2899
+ "img": (
2900
+ '<img id="dup" src="token" topLeftX="10" topLeftY="10" width="40" height="40"/>',
2901
+ '<img id="dup" src="token" topLeftX="60" topLeftY="10" width="40" height="40"/>',
2902
+ ),
2903
+ "line": (
2904
+ '<line id="dup" startX="10" startY="10" endX="40" endY="40"><border color="rgb(0, 0, 0)"/></line>',
2905
+ '<line id="dup" startX="60" startY="10" endX="90" endY="40"><border color="rgb(0, 0, 0)"/></line>',
2906
+ ),
2907
+ "icon": (
2908
+ '<icon id="dup" iconType="iconpark/Base/setting.svg" topLeftX="10" topLeftY="10" width="40" height="40"><fill><fillColor color="rgb(0, 0, 0)"/></fill></icon>',
2909
+ '<icon id="dup" iconType="iconpark/Base/setting.svg" topLeftX="60" topLeftY="10" width="40" height="40"><fill><fillColor color="rgb(0, 0, 0)"/></fill></icon>',
2910
+ ),
2911
+ "polyline": (
2912
+ '<polyline id="dup" topLeftX="10" topLeftY="10" width="40" height="40"><border color="rgb(0, 0, 0)"/></polyline>',
2913
+ '<polyline id="dup" topLeftX="60" topLeftY="10" width="40" height="40"><border color="rgb(0, 0, 0)"/></polyline>',
2914
+ ),
2915
+ }
2916
+ for kind, pair in duplicate_pairs.items():
2917
+ with self.subTest(kind=kind):
2918
+ result = xml_text_overlap_lint.lint_xml(
2919
+ f'<slide xmlns="https://www.larkoffice.com/sml/2.0"><data>{pair[0]}{pair[1]}</data></slide>'
2920
+ )
2921
+ issue = next(
2922
+ issue
2923
+ for issue in result["slides"][0]["issues"]
2924
+ if issue["code"] == "duplicate_element_id"
2925
+ )
2926
+ self.assertEqual(issue["element_ids"], ["dup", "dup"])
2927
+ self.assertEqual(
2928
+ [obj["xml_path"] for obj in issue["related_objects"]],
2929
+ [
2930
+ f"slide[1]/data/{kind}[1]",
2931
+ f"slide[1]/data/{kind}[2]",
2932
+ ],
2933
+ )
2934
+
2935
+ def test_lint_xml_missing_id_does_not_collide_with_explicit_synthetic_like_id(self) -> None:
2936
+ result = xml_text_overlap_lint.lint_xml(
2937
+ """
2938
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2939
+ <data>
2940
+ <shape type="rect" topLeftX="-20" topLeftY="40" width="50" height="50"/>
2941
+ <shape id="shape-1" type="rect" topLeftX="100" topLeftY="40" width="50" height="50"/>
2942
+ </data>
2943
+ </slide>
2944
+ """
2945
+ )
2946
+
2947
+ issue = next(
2948
+ issue for issue in result["slides"][0]["issues"]
2949
+ if issue["code"] == "shape_out_of_canvas"
2950
+ )
2951
+ self.assertEqual(issue["element_ids"], [])
2952
+ self.assertNotIn("element_id", issue["related_objects"][0])
2953
+ self.assertEqual(
2954
+ issue["related_objects"][0]["xml_path"],
2955
+ "slide[1]/data/shape[1]",
2956
+ )
2957
+ self.assertTrue(
2958
+ issue["hint"].startswith("Locate via related_objects[].xml_path. ")
2959
+ )
2960
+ self.assertNotIn(
2961
+ "duplicate_element_id",
2962
+ [candidate["code"] for candidate in result["slides"][0]["issues"]],
2963
+ )
2964
+
2965
+ def test_lint_xml_empty_id_is_not_exposed_as_an_element_id(self) -> None:
2966
+ result = xml_text_overlap_lint.lint_xml(
2967
+ """
2968
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2969
+ <data>
2970
+ <shape id="" type="rect" topLeftX="-20" topLeftY="40" width="50" height="50"/>
2971
+ </data>
2972
+ </slide>
2973
+ """
2974
+ )
2975
+
2976
+ issue = next(
2977
+ issue
2978
+ for issue in result["slides"][0]["issues"]
2979
+ if issue["code"] == "shape_out_of_canvas"
2980
+ )
2981
+ self.assertEqual(issue["element_ids"], [])
2982
+ self.assertNotIn("element_id", issue["related_objects"][0])
2983
+ self.assertEqual(
2984
+ issue["related_objects"][0]["xml_path"],
2985
+ "slide[1]/data/shape[1]",
2986
+ )
2987
+
2988
+ def test_lint_xml_uses_resolved_table_bounds_for_canvas_validation(self) -> None:
2989
+ result = xml_text_overlap_lint.lint_xml(
2990
+ """
2991
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2992
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2993
+ <data>
2994
+ <table id="resolved-overflow-table" topLeftX="800" topLeftY="80" width="100" height="40">
2995
+ <colgroup><col width="100"/><col width="100"/></colgroup>
2996
+ <tr height="40"><td/><td/></tr>
2997
+ </table>
2998
+ </data>
2999
+ </slide>
3000
+ </presentation>
3001
+ """
3002
+ )
3003
+ issues = result["slides"][0]["issues"]
3004
+ canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
3005
+ mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
3006
+ self.assertEqual(result["summary"]["error_count"], 1)
3007
+ self.assertEqual(canvas_issue["bbox"], {"x": 800, "y": 80, "width": 200, "height": 40})
3008
+ self.assertEqual(canvas_issue["overflow"]["right"], 40)
3009
+ self.assertEqual(mismatch_issue["dimension"], "width")
3010
+ self.assertEqual(mismatch_issue["resolved_size"], canvas_issue["bbox"]["width"])
3011
+
3012
+ def test_lint_xml_uses_the_same_anonymous_table_path_for_all_table_diagnostics(self) -> None:
3013
+ result = xml_text_overlap_lint.lint_xml(
3014
+ """
3015
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
3016
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3017
+ <data>
3018
+ <shape id="title" type="text" topLeftX="40" topLeftY="40" width="200" height="40"/>
3019
+ <img id="logo" src="token" topLeftX="40" topLeftY="100" width="40" height="40"/>
3020
+ <table topLeftX="900" topLeftY="80" width="100" height="40">
3021
+ <colgroup><col width="100"/><col width="100"/></colgroup>
3022
+ <tr height="40"><td/><td/></tr>
3023
+ </table>
3024
+ </data>
3025
+ </slide>
3026
+ </presentation>
3027
+ """
3028
+ )
3029
+ issues = result["slides"][0]["issues"]
3030
+ canvas_issue = next(issue for issue in issues if issue["code"] == "table_out_of_canvas")
3031
+ mismatch_issue = next(issue for issue in issues if issue["code"] == "table_resolved_size_mismatch")
3032
+ self.assertEqual(canvas_issue["elements"], ["slide[1]/data/table[1]"])
3033
+ self.assertEqual(mismatch_issue["elements"], ["slide[1]/data/table[1]"])
3034
+ self.assertEqual(
3035
+ canvas_issue["related_objects"][0]["xml_path"],
3036
+ "slide[1]/data/table[1]",
3037
+ )
3038
+ self.assertEqual(
3039
+ mismatch_issue["related_objects"][0]["xml_path"],
3040
+ "slide[1]/data/table[1]",
3041
+ )
3042
+ self.assertNotIn("element_id", canvas_issue["related_objects"][0])
3043
+ self.assertNotIn("element_id", mismatch_issue["related_objects"][0])
3044
+
3045
+ def test_lint_xml_reports_info_when_table_target_size_resolves_larger_than_declared(self) -> None:
3046
+ result = xml_text_overlap_lint.lint_xml(
3047
+ """
3048
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
3049
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3050
+ <data>
3051
+ <table id="size-mismatch" topLeftX="40" topLeftY="120" width="200" height="80">
3052
+ <colgroup><col span="2" width="100"/><col width="50"/></colgroup>
3053
+ <tr height="40"><td/><td/><td/></tr>
3054
+ <tr height="60"><td/><td/><td/></tr>
3055
+ </table>
3056
+ </data>
3057
+ </slide>
3058
+ </presentation>
3059
+ """
3060
+ )
3061
+ issues_by_dimension = {issue["dimension"]: issue for issue in result["slides"][0]["issues"]}
3062
+ self.assertEqual(result["summary"]["error_count"], 0)
3063
+ self.assertEqual(result["summary"]["warning_count"], 0)
3064
+ self.assertEqual(result["summary"]["info_count"], 2)
3065
+ self.assertEqual(issues_by_dimension["width"]["level"], "info")
3066
+ self.assertEqual(issues_by_dimension["width"]["code"], "table_resolved_size_mismatch")
3067
+ self.assertEqual(issues_by_dimension["width"]["resolved_sizes"], [100, 100, 50])
2300
3068
  self.assertEqual(issues_by_dimension["width"]["resolved_size"], 250)
2301
3069
  self.assertEqual(issues_by_dimension["height"]["resolved_sizes"], [40, 60])
2302
3070
  self.assertEqual(issues_by_dimension["height"]["resolved_size"], 100)
@@ -2304,8 +3072,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2304
3072
  def test_lint_xml_does_not_report_info_when_table_target_size_is_resolved_exactly(self) -> None:
2305
3073
  result = xml_text_overlap_lint.lint_xml(
2306
3074
  """
2307
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2308
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3075
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
3076
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2309
3077
  <data>
2310
3078
  <table id="size-match" topLeftX="40" topLeftY="120" width="300" height="100">
2311
3079
  <colgroup><col width="100"/><col/></colgroup>
@@ -2324,8 +3092,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2324
3092
  def test_lint_xml_keeps_resolved_table_sizes_positive_when_target_is_too_small(self) -> None:
2325
3093
  result = xml_text_overlap_lint.lint_xml(
2326
3094
  """
2327
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2328
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3095
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
3096
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2329
3097
  <data>
2330
3098
  <table id="narrow-table" topLeftX="40" topLeftY="120" width="1">
2331
3099
  <colgroup><col/><col/></colgroup>
@@ -2392,8 +3160,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2392
3160
  input_path = Path(temp_dir) / f"{name}.xml"
2393
3161
  input_path.write_text(
2394
3162
  f"""
2395
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2396
- <slide xmlns="http://www.larkoffice.com/sml/2.0"><data>{table_xml}</data></slide>
3163
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
3164
+ <slide xmlns="https://www.larkoffice.com/sml/2.0"><data>{table_xml}</data></slide>
2397
3165
  </presentation>
2398
3166
  """,
2399
3167
  encoding="utf-8",
@@ -2432,8 +3200,8 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2432
3200
  with self.subTest(name=name):
2433
3201
  result = xml_text_overlap_lint.lint_xml(
2434
3202
  f"""
2435
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
2436
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3203
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
3204
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2437
3205
  <data>{shapes}</data>
2438
3206
  </slide>
2439
3207
  </presentation>
@@ -2446,7 +3214,7 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2446
3214
  def test_lint_xml_reports_vertical_text_image_overlap_as_warning(self) -> None:
2447
3215
  result = xml_text_overlap_lint.lint_xml(
2448
3216
  """
2449
- <slide xmlns="http://www.larkoffice.com/sml/2.0"><data>
3217
+ <slide xmlns="https://www.larkoffice.com/sml/2.0"><data>
2450
3218
  <shape id="text" type="text" vert="vert" topLeftX="100" topLeftY="100" width="100" height="100">
2451
3219
  <content><p>Vertical</p></content>
2452
3220
  </shape>
@@ -2459,12 +3227,119 @@ class XmlTextOverlapLintGeometryTest(unittest.TestCase):
2459
3227
  self.assertEqual(result["summary"]["error_count"], 0)
2460
3228
  self.assertEqual(result["summary"]["info_count"], 1)
2461
3229
 
3230
+ def test_lint_xml_related_objects_include_source_xml_paths(self) -> None:
3231
+ result = xml_text_overlap_lint.lint_xml(
3232
+ """
3233
+ <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
3234
+ <slide>
3235
+ <data>
3236
+ <shape type="text" topLeftX="80" topLeftY="80" width="400" height="60">
3237
+ <content fontSize="24"><p>Control slide</p></content>
3238
+ </shape>
3239
+ </data>
3240
+ </slide>
3241
+ <slide>
3242
+ <data>
3243
+ <shape type="text" topLeftX="70" topLeftY="55" width="820" height="70">
3244
+ <content fontSize="32"><p>shape-3 mapping experiment</p></content>
3245
+ </shape>
3246
+ <shape type="text" topLeftX="80" topLeftY="165" width="400" height="70">
3247
+ <content fontSize="18"><p>First, preserve source order.</p></content>
3248
+ </shape>
3249
+ <shape type="text" topLeftX="80" topLeftY="315" width="400" height="64">
3250
+ <content fontSize="26"><p>TARGET_SHAPE_THREE</p></content>
3251
+ </shape>
3252
+ <img src="token" topLeftX="80" topLeftY="305" width="400" height="110"/>
3253
+ </data>
3254
+ </slide>
3255
+ </presentation>
3256
+ """
3257
+ )
3258
+
3259
+ issue = next(
3260
+ issue
3261
+ for issue in result["slides"][1]["issues"]
3262
+ if issue["code"] == "image_covers_text"
3263
+ )
3264
+ self.assertEqual(
3265
+ [(obj["kind"], obj["xml_path"]) for obj in issue["related_objects"]],
3266
+ [
3267
+ ("img", "slide[2]/data/img[1]"),
3268
+ ("shape", "slide[2]/data/shape[3]"),
3269
+ ],
3270
+ )
3271
+ self.assertTrue(
3272
+ all("element_id" not in obj for obj in issue["related_objects"])
3273
+ )
3274
+
3275
+ def test_lint_xml_related_objects_include_line_xml_path(self) -> None:
3276
+ result = xml_text_overlap_lint.lint_xml(
3277
+ """
3278
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
3279
+ <data>
3280
+ <shape id="label" type="text" topLeftX="100" topLeftY="100" width="200" height="80">
3281
+ <content fontSize="24"><p>Crossed text</p></content>
3282
+ </shape>
3283
+ <line id="connector" startX="80" startY="130" endX="330" endY="130">
3284
+ <border color="rgb(15, 23, 42)" width="2"/>
3285
+ </line>
3286
+ </data>
3287
+ </slide>
3288
+ """
3289
+ )
3290
+
3291
+ issue = next(
3292
+ issue
3293
+ for issue in result["slides"][0]["issues"]
3294
+ if issue["code"] == "bbox_overlap" and issue["elements"][0] == "connector"
3295
+ )
3296
+ self.assertEqual(
3297
+ {
3298
+ obj["element_id"]: obj["xml_path"]
3299
+ for obj in issue["related_objects"]
3300
+ },
3301
+ {
3302
+ "connector": "slide[1]/data/line[1]",
3303
+ "label": "slide[1]/data/shape[1]",
3304
+ },
3305
+ )
3306
+
2462
3307
 
2463
3308
  class XmlTextOverlapLintDensityTest(unittest.TestCase):
3309
+ def test_lint_xml_sparse_container_related_objects_include_icon_xml_path(self) -> None:
3310
+ result = xml_text_overlap_lint.lint_xml(
3311
+ """
3312
+ <slide xmlns="http://www.larkoffice.com/sml/2.0">
3313
+ <data>
3314
+ <shape id="card" type="rect" topLeftX="60" topLeftY="120" width="400" height="300"/>
3315
+ <icon id="visual" iconType="iconpark/Base/setting.svg" topLeftX="80" topLeftY="140" width="32" height="32">
3316
+ <fill><fillColor color="rgb(37, 99, 235)"/></fill>
3317
+ </icon>
3318
+ </data>
3319
+ </slide>
3320
+ """
3321
+ )
3322
+
3323
+ issue = next(
3324
+ issue
3325
+ for issue in result["slides"][0]["issues"]
3326
+ if issue["code"] == "sparse_container_content"
3327
+ )
3328
+ self.assertEqual(
3329
+ {
3330
+ obj["element_id"]: obj["xml_path"]
3331
+ for obj in issue["related_objects"]
3332
+ },
3333
+ {
3334
+ "card": "slide[1]/data/shape[1]",
3335
+ "visual": "slide[1]/data/icon[1]",
3336
+ },
3337
+ )
3338
+
2464
3339
  def test_lint_xml_blocks_blank_slide(self) -> None:
2465
3340
  result = xml_text_overlap_lint.lint_xml(
2466
3341
  """
2467
- <presentation xmlns="http://www.larkoffice.com/sml/2.0" width="960" height="540">
3342
+ <presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
2468
3343
  <slide id="content-slide">
2469
3344
  <data>
2470
3345
  <shape id="title" type="text" topLeftX="60" topLeftY="60" width="400" height="50">
@@ -2499,7 +3374,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2499
3374
  def test_lint_xml_blocks_blank_slide_with_only_transparent_image(self) -> None:
2500
3375
  result = xml_text_overlap_lint.lint_xml(
2501
3376
  """
2502
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3377
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2503
3378
  <data>
2504
3379
  <img id="ghost" src="token" topLeftX="60" topLeftY="60" width="200" height="200" alpha="0"/>
2505
3380
  </data>
@@ -2514,7 +3389,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2514
3389
  def test_lint_xml_warns_when_large_container_is_mostly_empty(self) -> None:
2515
3390
  result = xml_text_overlap_lint.lint_xml(
2516
3391
  """
2517
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3392
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2518
3393
  <data>
2519
3394
  <shape id="trend-card" type="rect" topLeftX="500" topLeftY="135" width="410" height="370"/>
2520
3395
  <shape id="trend-title" type="text" topLeftX="515" topLeftY="147" width="380" height="28">
@@ -2534,6 +3409,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2534
3409
  self.assertEqual(issue["target"], {
2535
3410
  "slide_number": 1,
2536
3411
  "container_id": "trend-card",
3412
+ "container_xml_path": "slide[1]/data/shape[1]",
2537
3413
  "container_type": "rect",
2538
3414
  "bbox": {"x": 500, "y": 135, "width": 410, "height": 370},
2539
3415
  })
@@ -2555,10 +3431,39 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2555
3431
  self.assertEqual(result["slides"][0]["status"], "needs_screenshot_review")
2556
3432
  self.assertEqual(result["slides"][0]["warnings"], result["slides"][0]["issues"])
2557
3433
 
3434
+ def test_lint_xml_uses_xml_path_in_anonymous_sparse_container_message(self) -> None:
3435
+ result = xml_text_overlap_lint.lint_xml(
3436
+ """
3437
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3438
+ <data>
3439
+ <shape type="rect" topLeftX="500" topLeftY="135" width="410" height="370"/>
3440
+ <shape id="trend-title" type="text" topLeftX="515" topLeftY="147" width="380" height="28">
3441
+ <content fontSize="15"><p>Core trends</p></content>
3442
+ </shape>
3443
+ </data>
3444
+ </slide>
3445
+ """
3446
+ )
3447
+
3448
+ issue = next(
3449
+ issue
3450
+ for issue in result["slides"][0]["issues"]
3451
+ if issue["code"] == "sparse_container_content"
3452
+ )
3453
+ self.assertNotIn("container_id", issue["target"])
3454
+ self.assertEqual(
3455
+ issue["target"]["container_xml_path"],
3456
+ "slide[1]/data/shape[1]",
3457
+ )
3458
+ self.assertEqual(
3459
+ issue["message"],
3460
+ "large card slide[1]/data/shape[1] content coverage 1.0% is below 15.0%",
3461
+ )
3462
+
2558
3463
  def test_lint_xml_warns_for_sparse_short_cards(self) -> None:
2559
3464
  result = xml_text_overlap_lint.lint_xml(
2560
3465
  """
2561
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3466
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2562
3467
  <data>
2563
3468
  <shape id="card-1" type="rect" topLeftX="60" topLeftY="180" width="400" height="105"/>
2564
3469
  <shape id="text-1" type="text" topLeftX="80" topLeftY="220" width="360" height="30">
@@ -2604,7 +3509,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2604
3509
  def test_lint_xml_warns_when_whole_slide_has_too_little_effective_content(self) -> None:
2605
3510
  result = xml_text_overlap_lint.lint_xml(
2606
3511
  """
2607
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3512
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2608
3513
  <data>
2609
3514
  <shape id="background" type="rect" topLeftX="0" topLeftY="0" width="960" height="540"/>
2610
3515
  <shape id="text-1" type="text" topLeftX="60" topLeftY="80" width="200" height="30">
@@ -2636,7 +3541,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2636
3541
  def test_lint_xml_ignores_isolated_short_layout_bar(self) -> None:
2637
3542
  result = xml_text_overlap_lint.lint_xml(
2638
3543
  """
2639
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3544
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2640
3545
  <data>
2641
3546
  <shape id="summary-bar" type="rect" topLeftX="52" topLeftY="82" width="856" height="105"/>
2642
3547
  <shape id="summary" type="text" topLeftX="72" topLeftY="115" width="816" height="30">
@@ -2652,7 +3557,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2652
3557
  def test_lint_xml_counts_rect_own_content_as_visible_content(self) -> None:
2653
3558
  result = xml_text_overlap_lint.lint_xml(
2654
3559
  """
2655
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3560
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2656
3561
  <data>
2657
3562
  <shape id="load-card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
2658
3563
  <content fontSize="18">
@@ -2671,7 +3576,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2671
3576
  def test_lint_xml_reports_nonzero_coverage_for_rect_own_content_reproduction(self) -> None:
2672
3577
  result = xml_text_overlap_lint.lint_xml(
2673
3578
  """
2674
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3579
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2675
3580
  <data>
2676
3581
  <shape id="load-card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
2677
3582
  <content fontSize="18">
@@ -2693,7 +3598,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2693
3598
  def test_lint_xml_still_warns_for_sparse_rect_own_content(self) -> None:
2694
3599
  result = xml_text_overlap_lint.lint_xml(
2695
3600
  """
2696
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3601
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2697
3602
  <data>
2698
3603
  <shape id="sparse-card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
2699
3604
  <content fontSize="12"><p>A</p></content>
@@ -2712,7 +3617,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2712
3617
  def test_lint_xml_unions_rect_own_content_with_child_content(self) -> None:
2713
3618
  self_only = xml_text_overlap_lint.lint_xml(
2714
3619
  """
2715
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3620
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2716
3621
  <data>
2717
3622
  <shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
2718
3623
  <content fontSize="12"><p>A</p></content>
@@ -2723,7 +3628,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2723
3628
  )
2724
3629
  with_overlapping_child = xml_text_overlap_lint.lint_xml(
2725
3630
  """
2726
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3631
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2727
3632
  <data>
2728
3633
  <shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
2729
3634
  <content fontSize="12"><p>A</p></content>
@@ -2747,7 +3652,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2747
3652
  def test_extract_density_elements_reads_nested_font_size_from_rect_content(self) -> None:
2748
3653
  elements = xml_text_overlap_lint.extract_density_elements(
2749
3654
  """
2750
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3655
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2751
3656
  <data>
2752
3657
  <shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184">
2753
3658
  <content fontSize="12"><p><span fontSize="36">32.0 t</span></p></content>
@@ -2762,7 +3667,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2762
3667
  def test_extract_density_elements_does_not_attach_following_text_to_self_closing_rect(self) -> None:
2763
3668
  elements = xml_text_overlap_lint.extract_density_elements(
2764
3669
  """
2765
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3670
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2766
3671
  <data>
2767
3672
  <shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184"/>
2768
3673
  <shape id="title" type="text" topLeftX="80" topLeftY="160" width="180" height="30">
@@ -2779,7 +3684,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2779
3684
  def test_lint_xml_allows_container_with_large_visual_child(self) -> None:
2780
3685
  result = xml_text_overlap_lint.lint_xml(
2781
3686
  """
2782
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3687
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2783
3688
  <data>
2784
3689
  <shape id="chart-card" type="rect" topLeftX="500" topLeftY="135" width="410" height="300"/>
2785
3690
  <chart id="chart" topLeftX="525" topLeftY="170" width="350" height="220">
@@ -2799,7 +3704,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2799
3704
  def test_lint_xml_does_not_let_transparent_visual_child_suppress_sparse_warning(self) -> None:
2800
3705
  result = xml_text_overlap_lint.lint_xml(
2801
3706
  """
2802
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3707
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2803
3708
  <data>
2804
3709
  <shape id="title" type="text" topLeftX="40" topLeftY="40" width="300" height="40">
2805
3710
  <content fontSize="20"><p>Section title</p></content>
@@ -2825,7 +3730,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2825
3730
  def test_lint_xml_warns_for_small_empty_visual_placeholder_cards(self) -> None:
2826
3731
  result = xml_text_overlap_lint.lint_xml(
2827
3732
  """
2828
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3733
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2829
3734
  <data>
2830
3735
  <shape id="letter-placeholder" type="rect" topLeftX="520" topLeftY="180" width="200" height="200"/>
2831
3736
  <shape id="letter" type="text" topLeftX="540" topLeftY="250" width="160" height="70">
@@ -2847,7 +3752,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2847
3752
  def test_lint_xml_applies_global_threshold_to_normal_text_card(self) -> None:
2848
3753
  result = xml_text_overlap_lint.lint_xml(
2849
3754
  """
2850
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3755
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2851
3756
  <data>
2852
3757
  <shape id="card" type="rect" topLeftX="70" topLeftY="184" width="260" height="288"/>
2853
3758
  <shape id="title" type="text" topLeftX="90" topLeftY="215" width="220" height="30">
@@ -2868,7 +3773,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2868
3773
  def test_lint_xml_allows_image_overlay_rect(self) -> None:
2869
3774
  result = xml_text_overlap_lint.lint_xml(
2870
3775
  """
2871
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3776
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2872
3777
  <data>
2873
3778
  <img id="hero" topLeftX="560" topLeftY="0" width="400" height="540"/>
2874
3779
  <shape id="tint" type="rect" topLeftX="560" topLeftY="0" width="400" height="540"/>
@@ -2882,7 +3787,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2882
3787
  def test_lint_xml_does_not_let_transparent_image_overlay_suppress_sparse_warning(self) -> None:
2883
3788
  result = xml_text_overlap_lint.lint_xml(
2884
3789
  """
2885
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3790
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2886
3791
  <data>
2887
3792
  <shape id="title" type="text" topLeftX="40" topLeftY="40" width="300" height="40">
2888
3793
  <content fontSize="20"><p>Section title</p></content>
@@ -2902,7 +3807,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2902
3807
  def test_lint_xml_allows_edge_spanning_layout_panel_and_nested_decoration(self) -> None:
2903
3808
  result = xml_text_overlap_lint.lint_xml(
2904
3809
  """
2905
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3810
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2906
3811
  <data>
2907
3812
  <shape id="panel" type="rect" topLeftX="600" topLeftY="0" width="360" height="540"/>
2908
3813
  <shape id="decoration" type="rect" topLeftX="660" topLeftY="150" width="240" height="240"/>
@@ -2916,7 +3821,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2916
3821
  def test_lint_xml_counts_icons_as_visible_content(self) -> None:
2917
3822
  result = xml_text_overlap_lint.lint_xml(
2918
3823
  """
2919
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3824
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2920
3825
  <data>
2921
3826
  <shape id="card" type="rect" topLeftX="80" topLeftY="140" width="320" height="240"/>
2922
3827
  <icon id="visual" iconType="iconpark/Safe/shield.svg" topLeftX="100" topLeftY="160" width="180" height="180">
@@ -2932,7 +3837,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2932
3837
  def test_lint_xml_does_not_count_transparent_icon_as_visible_content(self) -> None:
2933
3838
  result = xml_text_overlap_lint.lint_xml(
2934
3839
  """
2935
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3840
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2936
3841
  <data>
2937
3842
  <shape id="title" type="text" topLeftX="40" topLeftY="40" width="300" height="40">
2938
3843
  <content fontSize="20"><p>Section title</p></content>
@@ -2955,7 +3860,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2955
3860
  def test_lint_xml_warns_when_coverage_is_below_global_threshold(self) -> None:
2956
3861
  result = xml_text_overlap_lint.lint_xml(
2957
3862
  """
2958
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3863
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2959
3864
  <data>
2960
3865
  <shape id="card" type="rect" topLeftX="80" topLeftY="140" width="200" height="200"/>
2961
3866
  <icon id="visual" iconType="iconpark/Safe/shield.svg" topLeftX="100" topLeftY="160" width="70" height="70">
@@ -2974,7 +3879,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2974
3879
  def test_lint_xml_allows_quarter_coverage_under_lower_threshold(self) -> None:
2975
3880
  result = xml_text_overlap_lint.lint_xml(
2976
3881
  """
2977
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3882
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2978
3883
  <data>
2979
3884
  <shape id="card" type="rect" topLeftX="80" topLeftY="140" width="200" height="200"/>
2980
3885
  <icon id="visual" iconType="iconpark/Safe/shield.svg" topLeftX="100" topLeftY="160" width="100" height="100">
@@ -2990,7 +3895,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
2990
3895
  def test_lint_xml_allows_large_metric_card_above_lower_threshold(self) -> None:
2991
3896
  result = xml_text_overlap_lint.lint_xml(
2992
3897
  """
2993
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3898
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
2994
3899
  <data>
2995
3900
  <shape id="metric-card" type="rect" topLeftX="80" topLeftY="140" width="360" height="300"/>
2996
3901
  <shape id="metric" type="text" topLeftX="104" topLeftY="190" width="340" height="90">
@@ -3003,10 +3908,29 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
3003
3908
 
3004
3909
  self.assertEqual(result["slides"][0]["issues"], [])
3005
3910
 
3006
- def test_lint_xml_does_not_report_blank_slide_for_line_only_content(self) -> None:
3911
+ def test_lint_xml_does_not_report_blank_slide_for_embed_only_content(self) -> None:
3007
3912
  result = xml_text_overlap_lint.lint_xml(
3008
3913
  """
3009
3914
  <slide xmlns="http://www.larkoffice.com/sml/2.0">
3915
+ <data>
3916
+ <embed id="emb" topLeftX="280" topLeftY="130" width="400" height="280">
3917
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 280">
3918
+ <circle cx="200" cy="140" r="100" fill="#2563EB"/>
3919
+ </svg>
3920
+ </embed>
3921
+ </data>
3922
+ </slide>
3923
+ """
3924
+ )
3925
+
3926
+ self.assertEqual(result["summary"]["error_count"], 0)
3927
+ codes = [issue["code"] for issue in result["slides"][0]["issues"]]
3928
+ self.assertNotIn("blank_slide", codes)
3929
+
3930
+ def test_lint_xml_does_not_report_blank_slide_for_line_only_content(self) -> None:
3931
+ result = xml_text_overlap_lint.lint_xml(
3932
+ """
3933
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3010
3934
  <data>
3011
3935
  <line id="l1" startX="100" startY="100" endX="800" endY="100"><border/></line>
3012
3936
  <line id="l2" startX="100" startY="200" endX="800" endY="200"><border/></line>
@@ -3024,7 +3948,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
3024
3948
  def test_lint_xml_reports_bbox_overlap_measurement_from_decision_time_visual_bbox(self) -> None:
3025
3949
  result = xml_text_overlap_lint.lint_xml(
3026
3950
  """
3027
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3951
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3028
3952
  <data>
3029
3953
  <shape id="left" type="text" topLeftX="80" topLeftY="80" width="300" height="60">
3030
3954
  <content fontSize="14"><p>overlap text <span fontSize="96">big</span></p></content>
@@ -3061,7 +3985,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
3061
3985
  def test_lint_xml_reports_schema_version_2_for_sparse_issues(self) -> None:
3062
3986
  result = xml_text_overlap_lint.lint_xml(
3063
3987
  """
3064
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
3988
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3065
3989
  <data>
3066
3990
  <shape id="card" type="rect" topLeftX="60" topLeftY="140" width="220" height="184"/>
3067
3991
  </data>
@@ -3077,7 +4001,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
3077
4001
  def test_lint_xml_does_not_report_blank_slide_for_textless_decorative_shapes(self) -> None:
3078
4002
  result = xml_text_overlap_lint.lint_xml(
3079
4003
  """
3080
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
4004
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3081
4005
  <data>
3082
4006
  <shape id="deco1" type="ellipse" topLeftX="60" topLeftY="60" width="300" height="300">
3083
4007
  <fill><fillColor color="rgba(37, 99, 235, 1)"/></fill>
@@ -3101,7 +4025,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
3101
4025
  # trivially "pass" that density check.
3102
4026
  result = xml_text_overlap_lint.lint_xml(
3103
4027
  """
3104
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
4028
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3105
4029
  <data>
3106
4030
  <shape id="background" type="rect" topLeftX="0" topLeftY="0" width="960" height="540"/>
3107
4031
  <shape id="text-1" type="text" topLeftX="60" topLeftY="80" width="200" height="30">
@@ -3127,7 +4051,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
3127
4051
  def test_lint_xml_accepts_whitespace_around_attribute_equals_sign(self) -> None:
3128
4052
  result = xml_text_overlap_lint.lint_xml(
3129
4053
  """
3130
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
4054
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3131
4055
  <data>
3132
4056
  <shape id="visible" type="text" topLeftX = "80" topLeftY = "80" width = "300" height = "60">
3133
4057
  <content><p>hello</p></content>
@@ -3144,7 +4068,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
3144
4068
  def test_lint_xml_reports_blank_slide_for_full_canvas_background_only(self) -> None:
3145
4069
  result = xml_text_overlap_lint.lint_xml(
3146
4070
  """
3147
- <slide xmlns="http://www.larkoffice.com/sml/2.0">
4071
+ <slide xmlns="https://www.larkoffice.com/sml/2.0">
3148
4072
  <data>
3149
4073
  <shape id="background" type="rect" topLeftX="0" topLeftY="0" width="960" height="540">
3150
4074
  <fill><fillColor color="rgba(240, 235, 220, 1)"/></fill>
@@ -3173,7 +4097,7 @@ class XmlTextOverlapLintDensityTest(unittest.TestCase):
3173
4097
  )
3174
4098
 
3175
4099
 
3176
- SML_NAMESPACE = "http://www.larkoffice.com/sml/2.0"
4100
+ SML_NAMESPACE = "https://www.larkoffice.com/sml/2.0"
3177
4101
 
3178
4102
 
3179
4103
  class SxsdSyntaxTestCase(unittest.TestCase):