@amaster.ai/pi-lark 0.1.2-beta.48 → 0.1.2-beta.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/skills/lark-apps/SKILL.md +1 -0
- package/skills/lark-apps/references/lark-apps-cache.md +61 -0
- package/skills/lark-base/SKILL.md +12 -5
- package/skills/lark-base/references/lark-base-data-query.md +11 -4
- package/skills/lark-base/references/lark-base-form-questions-create.md +6 -3
- package/skills/lark-base/references/lark-base-role-guide.md +11 -0
- package/skills/lark-base/references/role-config.md +31 -5
- package/skills/lark-calendar/SKILL.md +2 -2
- package/skills/lark-calendar/references/lark-calendar-recurring.md +1 -0
- package/skills/lark-contact/SKILL.md +1 -1
- package/skills/lark-drive/SKILL.md +16 -43
- package/skills/lark-drive/references/lark-drive-add-comment.md +2 -4
- package/skills/lark-drive/references/lark-drive-add-reply.md +47 -0
- package/skills/lark-drive/references/lark-drive-apply-permission.md +2 -2
- package/skills/lark-drive/references/lark-drive-batch-query-comments.md +46 -0
- package/skills/lark-drive/references/lark-drive-comment-content.md +50 -0
- package/skills/lark-drive/references/lark-drive-comment-location.md +7 -13
- package/skills/lark-drive/references/lark-drive-delete-reply.md +48 -0
- package/skills/lark-drive/references/lark-drive-download.md +5 -1
- package/skills/lark-drive/references/lark-drive-list-comments.md +25 -68
- package/skills/lark-drive/references/lark-drive-list-replies.md +54 -0
- package/skills/lark-drive/references/lark-drive-member-add.md +2 -2
- package/skills/lark-drive/references/lark-drive-member-list.md +2 -2
- package/skills/lark-drive/references/lark-drive-permission-get-setting.md +2 -2
- package/skills/lark-drive/references/lark-drive-preview.md +11 -1
- package/skills/lark-drive/references/lark-drive-react-reply.md +51 -0
- package/skills/lark-drive/references/lark-drive-reactions.md +27 -25
- package/skills/lark-drive/references/lark-drive-resolve-comment.md +45 -0
- package/skills/lark-drive/references/lark-drive-restore-comment.md +46 -0
- package/skills/lark-drive/references/lark-drive-update-reply.md +46 -0
- package/skills/lark-drive/references/lark-drive-workflow-permission-governance.md +1 -1
- package/skills/lark-slides/references/lark-slides-media-upload.md +1 -1
- package/skills/lark-slides/scripts/sxsd_validator.py +908 -0
- package/skills/lark-slides/scripts/xml_text_overlap_lint.py +209 -92
- package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +1079 -42
- package/skills/lark-drive/references/lark-drive-comments-guide.md +0 -80
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
+
import copy
|
|
8
9
|
import json
|
|
9
10
|
import math
|
|
10
11
|
import re
|
|
@@ -16,6 +17,8 @@ from difflib import SequenceMatcher, get_close_matches
|
|
|
16
17
|
from pathlib import Path
|
|
17
18
|
from typing import Any
|
|
18
19
|
|
|
20
|
+
import sxsd_validator
|
|
21
|
+
|
|
19
22
|
|
|
20
23
|
XS_NS = "{http://www.w3.org/2001/XMLSchema}"
|
|
21
24
|
XML_NS = "{http://www.w3.org/XML/1998/namespace}"
|
|
@@ -44,9 +47,8 @@ ROUNDTRIP_SXSD_ATTRS = {
|
|
|
44
47
|
("chartData", "isStaticData"),
|
|
45
48
|
}
|
|
46
49
|
# Slides readback echoes each chartField's CSV text as per-value <chartParsedValues> children;
|
|
47
|
-
# it
|
|
48
|
-
|
|
49
|
-
ROUNDTRIP_SXSD_TAGS = {"chartParsedValues"}
|
|
50
|
+
# it is server-emitted and absent from the write schema, so it must not block page linting.
|
|
51
|
+
ROUNDTRIP_SXSD_TAGS = {("chartField", "chartParsedValues")}
|
|
50
52
|
DEFAULT_TABLE_COLUMN_WIDTH = 110
|
|
51
53
|
DEFAULT_TABLE_ROW_HEIGHT = 37
|
|
52
54
|
DEFAULT_TEXT_LINE_SPACING_MULTIPLE = 1.5
|
|
@@ -310,77 +312,13 @@ def xml_namespace(tag: str) -> str | None:
|
|
|
310
312
|
return tag.split("}", 1)[0] + "}" if tag.startswith("{") else None
|
|
311
313
|
|
|
312
314
|
|
|
313
|
-
def strip_xsd_prefix(value: str | None) -> str | None:
|
|
314
|
-
if value is None:
|
|
315
|
-
return None
|
|
316
|
-
return value.rsplit(":", 1)[-1]
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
def iter_direct_xsd_children(element: ET.Element, local_name: str) -> list[ET.Element]:
|
|
320
|
-
return [child for child in element if child.tag == f"{XS_NS}{local_name}"]
|
|
321
|
-
|
|
322
|
-
|
|
323
315
|
def load_sxsd_tag_attributes() -> dict[str, set[str]]:
|
|
324
316
|
global _SXSD_TAG_ATTRIBUTES_CACHE
|
|
325
317
|
if _SXSD_TAG_ATTRIBUTES_CACHE is not None:
|
|
326
318
|
return _SXSD_TAG_ATTRIBUTES_CACHE
|
|
327
319
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
complex_type.attrib["name"]: complex_type
|
|
331
|
-
for complex_type in schema_root.findall(f"{XS_NS}complexType")
|
|
332
|
-
if complex_type.attrib.get("name")
|
|
333
|
-
}
|
|
334
|
-
resolving: set[str] = set()
|
|
335
|
-
|
|
336
|
-
def attributes_for_complex_type(complex_type: ET.Element) -> set[str]:
|
|
337
|
-
attrs: set[str] = {
|
|
338
|
-
attribute.attrib["name"]
|
|
339
|
-
for attribute in iter_direct_xsd_children(complex_type, "attribute")
|
|
340
|
-
if attribute.attrib.get("name")
|
|
341
|
-
}
|
|
342
|
-
for content_name in ("simpleContent", "complexContent"):
|
|
343
|
-
for complex_content in iter_direct_xsd_children(complex_type, content_name):
|
|
344
|
-
for extension in iter_direct_xsd_children(complex_content, "extension"):
|
|
345
|
-
base_type = strip_xsd_prefix(extension.attrib.get("base"))
|
|
346
|
-
if base_type:
|
|
347
|
-
attrs.update(attributes_for_type(base_type))
|
|
348
|
-
attrs.update(
|
|
349
|
-
attribute.attrib["name"]
|
|
350
|
-
for attribute in iter_direct_xsd_children(extension, "attribute")
|
|
351
|
-
if attribute.attrib.get("name")
|
|
352
|
-
)
|
|
353
|
-
return attrs
|
|
354
|
-
|
|
355
|
-
def attributes_for_type(type_name: str) -> set[str]:
|
|
356
|
-
if type_name in resolving:
|
|
357
|
-
return set()
|
|
358
|
-
complex_type = named_complex_types.get(type_name)
|
|
359
|
-
if complex_type is None:
|
|
360
|
-
return set()
|
|
361
|
-
resolving.add(type_name)
|
|
362
|
-
try:
|
|
363
|
-
return attributes_for_complex_type(complex_type)
|
|
364
|
-
finally:
|
|
365
|
-
resolving.remove(type_name)
|
|
366
|
-
|
|
367
|
-
tag_attributes: dict[str, set[str]] = {}
|
|
368
|
-
for element in schema_root.iter(f"{XS_NS}element"):
|
|
369
|
-
tag_name = element.attrib.get("name")
|
|
370
|
-
if not tag_name:
|
|
371
|
-
continue
|
|
372
|
-
|
|
373
|
-
attrs: set[str] = set()
|
|
374
|
-
type_name = strip_xsd_prefix(element.attrib.get("type"))
|
|
375
|
-
if type_name:
|
|
376
|
-
attrs.update(attributes_for_type(type_name))
|
|
377
|
-
for complex_type in iter_direct_xsd_children(element, "complexType"):
|
|
378
|
-
attrs.update(attributes_for_complex_type(complex_type))
|
|
379
|
-
|
|
380
|
-
tag_attributes.setdefault(tag_name, set()).update(attrs)
|
|
381
|
-
|
|
382
|
-
_SXSD_TAG_ATTRIBUTES_CACHE = tag_attributes
|
|
383
|
-
return tag_attributes
|
|
320
|
+
_SXSD_TAG_ATTRIBUTES_CACHE = sxsd_validator.load_tag_attributes(SXSD_SCHEMA_PATH)
|
|
321
|
+
return _SXSD_TAG_ATTRIBUTES_CACHE
|
|
384
322
|
|
|
385
323
|
|
|
386
324
|
def load_iconpark_icon_types() -> set[str]:
|
|
@@ -417,13 +355,19 @@ def build_sxsd_tag_hint(tag_name: str, supported_tags: set[str]) -> str:
|
|
|
417
355
|
return "Unsupported SXSD tag. Use only tags defined in slides_xml_schema_definition.xml."
|
|
418
356
|
|
|
419
357
|
|
|
420
|
-
def
|
|
358
|
+
def suggest_sxsd_attrs(attr_name: str, allowed_attrs: set[str]) -> list[str]:
|
|
421
359
|
alias = SXSD_ATTR_ALIASES.get(attr_name)
|
|
422
360
|
if alias and alias in allowed_attrs:
|
|
423
|
-
return
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
361
|
+
return [alias]
|
|
362
|
+
return get_close_matches(attr_name, sorted(allowed_attrs), n=3, cutoff=0.68)
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def build_sxsd_attr_hint(tag_name: str, attr_name: str, allowed_attrs: set[str]) -> str:
|
|
366
|
+
suggestions = suggest_sxsd_attrs(attr_name, allowed_attrs)
|
|
367
|
+
if suggestions:
|
|
368
|
+
if SXSD_ATTR_ALIASES.get(attr_name) == suggestions[0]:
|
|
369
|
+
return f'Use "{suggestions[0]}" on <{tag_name}> instead of "{attr_name}".'
|
|
370
|
+
return "Unsupported SXSD attribute. Did you mean " + ", ".join(f'"{match}"' for match in suggestions) + "?"
|
|
427
371
|
allowed_summary = ", ".join(sorted(allowed_attrs)[:8])
|
|
428
372
|
if len(allowed_attrs) > 8:
|
|
429
373
|
allowed_summary += ", ..."
|
|
@@ -438,10 +382,33 @@ def should_skip_sxsd_attribute(tag_name: str, attr_name: str) -> bool:
|
|
|
438
382
|
return attr_name in SERVER_FILLED_SXSD_ATTRS or (tag_name, attr_name) in ROUNDTRIP_SXSD_ATTRS
|
|
439
383
|
|
|
440
384
|
|
|
441
|
-
def
|
|
385
|
+
def should_skip_sxsd_tag(parent_name: str | None, tag_name: str) -> bool:
|
|
386
|
+
return (parent_name, tag_name) in ROUNDTRIP_SXSD_TAGS
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
def without_server_filled_sxsd_fields(root: ET.Element) -> ET.Element:
|
|
390
|
+
sanitized_root = copy.deepcopy(root)
|
|
391
|
+
|
|
392
|
+
def sanitize(element: ET.Element) -> None:
|
|
393
|
+
tag_name = xml_local_name(element.tag)
|
|
394
|
+
for raw_attr_name in list(element.attrib):
|
|
395
|
+
if should_skip_sxsd_attribute(tag_name, xml_local_name(raw_attr_name)):
|
|
396
|
+
del element.attrib[raw_attr_name]
|
|
397
|
+
for child in list(element):
|
|
398
|
+
if should_skip_sxsd_tag(tag_name, xml_local_name(child.tag)):
|
|
399
|
+
element.remove(child)
|
|
400
|
+
continue
|
|
401
|
+
sanitize(child)
|
|
402
|
+
|
|
403
|
+
sanitize(sanitized_root)
|
|
404
|
+
return sanitized_root
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def validate_sxsd_document(xml: str, root: ET.Element) -> list[dict[str, Any]]:
|
|
442
408
|
tag_attributes = load_sxsd_tag_attributes()
|
|
443
409
|
supported_tags = set(tag_attributes)
|
|
444
410
|
issues: list[dict[str, Any]] = []
|
|
411
|
+
suggested_attr_candidates: dict[tuple[str, str], list[set[str]]] = {}
|
|
445
412
|
|
|
446
413
|
def visit(element: ET.Element, ancestors: list[str], path: str) -> None:
|
|
447
414
|
if should_skip_sxsd_subtree(element, ancestors):
|
|
@@ -449,7 +416,8 @@ def validate_sxsd_tag_attributes(root: ET.Element) -> list[dict[str, Any]]:
|
|
|
449
416
|
|
|
450
417
|
tag_name = xml_local_name(element.tag)
|
|
451
418
|
current_path = f"{path}/{tag_name}" if path else tag_name
|
|
452
|
-
if
|
|
419
|
+
parent_name = ancestors[-1] if ancestors else None
|
|
420
|
+
if should_skip_sxsd_tag(parent_name, tag_name):
|
|
453
421
|
return
|
|
454
422
|
if tag_name not in supported_tags:
|
|
455
423
|
issues.append(
|
|
@@ -473,6 +441,11 @@ def validate_sxsd_tag_attributes(root: ET.Element) -> list[dict[str, Any]]:
|
|
|
473
441
|
continue
|
|
474
442
|
if attr_name in allowed_attrs:
|
|
475
443
|
continue
|
|
444
|
+
suggestions = suggest_sxsd_attrs(attr_name, allowed_attrs)
|
|
445
|
+
if suggestions:
|
|
446
|
+
suggested_attr_candidates.setdefault((current_path, tag_name), []).append(
|
|
447
|
+
set(suggestions)
|
|
448
|
+
)
|
|
476
449
|
issues.append(
|
|
477
450
|
{
|
|
478
451
|
"level": "error",
|
|
@@ -489,6 +462,76 @@ def validate_sxsd_tag_attributes(root: ET.Element) -> list[dict[str, Any]]:
|
|
|
489
462
|
visit(child, [*ancestors, tag_name], current_path)
|
|
490
463
|
|
|
491
464
|
visit(root, [], "")
|
|
465
|
+
existing = {
|
|
466
|
+
(issue.get("code"), issue.get("path"), issue.get("tag"), issue.get("attr"))
|
|
467
|
+
for issue in issues
|
|
468
|
+
}
|
|
469
|
+
unsupported_tag_locations = {
|
|
470
|
+
(issue.get("path"), issue.get("tag"))
|
|
471
|
+
for issue in issues
|
|
472
|
+
if issue.get("code") == "sxsd_unsupported_tag"
|
|
473
|
+
}
|
|
474
|
+
schema_issues = _validate_sxsd_schema_constraints(xml, root)
|
|
475
|
+
missing_attrs_by_location: dict[tuple[str, str], set[str]] = {}
|
|
476
|
+
for schema_issue in schema_issues:
|
|
477
|
+
if schema_issue.get("code") != "sxsd_missing_required_attr":
|
|
478
|
+
continue
|
|
479
|
+
location = (schema_issue.get("path"), schema_issue.get("tag"))
|
|
480
|
+
missing_attrs_by_location.setdefault(location, set()).add(schema_issue.get("attr"))
|
|
481
|
+
|
|
482
|
+
suggested_attrs: set[tuple[str, str, str]] = set()
|
|
483
|
+
for location, candidate_groups in suggested_attr_candidates.items():
|
|
484
|
+
missing_attrs = missing_attrs_by_location.get(location, set())
|
|
485
|
+
for candidates in candidate_groups:
|
|
486
|
+
matching_missing_attrs = candidates & missing_attrs
|
|
487
|
+
if len(matching_missing_attrs) == 1:
|
|
488
|
+
suggested_attrs.add((*location, next(iter(matching_missing_attrs))))
|
|
489
|
+
|
|
490
|
+
for schema_issue in schema_issues:
|
|
491
|
+
if schema_issue.get("code") == "sxsd_unexpected_child" and (
|
|
492
|
+
schema_issue.get("path"),
|
|
493
|
+
schema_issue.get("tag"),
|
|
494
|
+
) in unsupported_tag_locations:
|
|
495
|
+
continue
|
|
496
|
+
if schema_issue.get("code") == "sxsd_missing_required_attr" and (
|
|
497
|
+
schema_issue.get("path"),
|
|
498
|
+
schema_issue.get("tag"),
|
|
499
|
+
schema_issue.get("attr"),
|
|
500
|
+
) in suggested_attrs:
|
|
501
|
+
continue
|
|
502
|
+
key = (
|
|
503
|
+
schema_issue.get("code"),
|
|
504
|
+
schema_issue.get("path"),
|
|
505
|
+
schema_issue.get("tag"),
|
|
506
|
+
schema_issue.get("attr"),
|
|
507
|
+
)
|
|
508
|
+
if key not in existing:
|
|
509
|
+
issues.append(schema_issue)
|
|
510
|
+
return issues
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
def _validate_sxsd_schema_constraints(xml: str, root: ET.Element) -> list[dict[str, Any]]:
|
|
514
|
+
issues: list[dict[str, Any]] = []
|
|
515
|
+
if re.match(r"^\s*<\?xml\b", xml):
|
|
516
|
+
issues.append(
|
|
517
|
+
{
|
|
518
|
+
"level": "error",
|
|
519
|
+
"code": "sxsd_unsupported_declaration",
|
|
520
|
+
"path": xml_local_name(root.tag),
|
|
521
|
+
"tag": xml_local_name(root.tag),
|
|
522
|
+
"expected": "SXSD document without an XML declaration",
|
|
523
|
+
"actual": "<?xml ...?>",
|
|
524
|
+
"message": "XML declarations are not supported by the Slides SXSD write format",
|
|
525
|
+
"hint": "Remove the <?xml ...?> declaration and keep the SXSD root element.",
|
|
526
|
+
}
|
|
527
|
+
)
|
|
528
|
+
|
|
529
|
+
issues.extend(
|
|
530
|
+
sxsd_validator.validate_sxsd(
|
|
531
|
+
without_server_filled_sxsd_fields(root),
|
|
532
|
+
SXSD_SCHEMA_PATH,
|
|
533
|
+
)
|
|
534
|
+
)
|
|
492
535
|
return issues
|
|
493
536
|
|
|
494
537
|
|
|
@@ -692,18 +735,39 @@ def validate_xml_well_formed(xml: str) -> dict[str, Any] | None:
|
|
|
692
735
|
return xml_error
|
|
693
736
|
|
|
694
737
|
|
|
695
|
-
def
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
738
|
+
def serialize_slide_for_layout(slide_root: ET.Element) -> str:
|
|
739
|
+
slide_copy = copy.deepcopy(slide_root)
|
|
740
|
+
for element in slide_copy.iter():
|
|
741
|
+
if not isinstance(element.tag, str):
|
|
742
|
+
continue
|
|
743
|
+
element.tag = xml_local_name(element.tag)
|
|
744
|
+
attributes = {
|
|
745
|
+
xml_local_name(attribute_name): value
|
|
746
|
+
for attribute_name, value in element.attrib.items()
|
|
702
747
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
748
|
+
element.attrib.clear()
|
|
749
|
+
element.attrib.update(attributes)
|
|
750
|
+
return ET.tostring(slide_copy, encoding="unicode")
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
def parse_presentation(root: ET.Element) -> dict[str, Any]:
|
|
754
|
+
root_name = xml_local_name(root.tag)
|
|
755
|
+
if root_name == "slide":
|
|
756
|
+
slide_roots = [root]
|
|
757
|
+
width = 960
|
|
758
|
+
height = 540
|
|
759
|
+
elif root_name == "presentation":
|
|
760
|
+
slide_roots = [child for child in root if xml_local_name(child.tag) == "slide"]
|
|
761
|
+
width = int(float(root.attrib.get("width", 960)))
|
|
762
|
+
height = int(float(root.attrib.get("height", 540)))
|
|
763
|
+
else:
|
|
764
|
+
fail("input must contain a <presentation> or <slide> root")
|
|
765
|
+
return {
|
|
766
|
+
"width": width,
|
|
767
|
+
"height": height,
|
|
768
|
+
"slides": [serialize_slide_for_layout(slide_root) for slide_root in slide_roots],
|
|
769
|
+
"slide_roots": slide_roots,
|
|
770
|
+
}
|
|
707
771
|
|
|
708
772
|
|
|
709
773
|
def extract_elements(slide_xml: str) -> list[dict[str, Any]]:
|
|
@@ -2417,6 +2481,21 @@ def slide_status(errors: list[dict[str, Any]], warnings: list[dict[str, Any]]) -
|
|
|
2417
2481
|
return "passed"
|
|
2418
2482
|
|
|
2419
2483
|
|
|
2484
|
+
def is_slide_scoped_sxsd_issue(issue: dict[str, Any], root_name: str) -> bool:
|
|
2485
|
+
if issue.get("code") == "sxsd_unsupported_declaration":
|
|
2486
|
+
return False
|
|
2487
|
+
if root_name == "slide":
|
|
2488
|
+
return True
|
|
2489
|
+
path = issue.get("path")
|
|
2490
|
+
if not isinstance(path, str):
|
|
2491
|
+
return False
|
|
2492
|
+
if path.startswith("presentation/slide/"):
|
|
2493
|
+
return True
|
|
2494
|
+
return path == "presentation/slide" and (
|
|
2495
|
+
issue.get("attr") is not None or issue.get("code") == "sxsd_invalid_namespace"
|
|
2496
|
+
)
|
|
2497
|
+
|
|
2498
|
+
|
|
2420
2499
|
def build_result(
|
|
2421
2500
|
source_path: str | None,
|
|
2422
2501
|
slide_size: dict[str, int | float],
|
|
@@ -2472,11 +2551,20 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
|
|
|
2472
2551
|
raise AssertionError("parse_xml_root must return a root or error")
|
|
2473
2552
|
|
|
2474
2553
|
namespace_issues = validate_sml_tag_prefixes(xml)
|
|
2475
|
-
|
|
2554
|
+
root_name = xml_local_name(root.tag)
|
|
2555
|
+
sxsd_issues = validate_sxsd_document(xml, root)
|
|
2476
2556
|
iconpark_issues = validate_iconpark_icon_types(root)
|
|
2477
2557
|
top_level_issues = [
|
|
2478
2558
|
normalize_issue(issue, None, {})
|
|
2479
|
-
for issue in [
|
|
2559
|
+
for issue in [
|
|
2560
|
+
*namespace_issues,
|
|
2561
|
+
*[
|
|
2562
|
+
issue
|
|
2563
|
+
for issue in sxsd_issues
|
|
2564
|
+
if not is_slide_scoped_sxsd_issue(issue, root_name)
|
|
2565
|
+
],
|
|
2566
|
+
*iconpark_issues,
|
|
2567
|
+
]
|
|
2480
2568
|
]
|
|
2481
2569
|
if any(issue["level"] == "error" for issue in top_level_issues):
|
|
2482
2570
|
return build_result(
|
|
@@ -2486,10 +2574,36 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
|
|
|
2486
2574
|
[],
|
|
2487
2575
|
)
|
|
2488
2576
|
|
|
2489
|
-
presentation = parse_presentation(
|
|
2577
|
+
presentation = parse_presentation(root)
|
|
2578
|
+
slide_roots = presentation["slide_roots"]
|
|
2490
2579
|
slides: list[dict[str, Any]] = []
|
|
2491
2580
|
for index, slide_xml in enumerate(presentation["slides"]):
|
|
2492
2581
|
slide_number = index + 1
|
|
2582
|
+
slide_root = slide_roots[index]
|
|
2583
|
+
slide_sxsd_issues = [
|
|
2584
|
+
normalize_issue(issue, slide_number, {})
|
|
2585
|
+
for issue in validate_sxsd_document(slide_xml, slide_root)
|
|
2586
|
+
]
|
|
2587
|
+
slide_sxsd_errors = [
|
|
2588
|
+
issue for issue in slide_sxsd_issues if issue["level"] == "error"
|
|
2589
|
+
]
|
|
2590
|
+
if slide_sxsd_errors:
|
|
2591
|
+
slide_sxsd_warnings = [
|
|
2592
|
+
issue for issue in slide_sxsd_issues if issue["level"] == "warning"
|
|
2593
|
+
]
|
|
2594
|
+
slides.append(
|
|
2595
|
+
{
|
|
2596
|
+
"slide_number": slide_number,
|
|
2597
|
+
"status": slide_status(slide_sxsd_errors, slide_sxsd_warnings),
|
|
2598
|
+
"element_count": 0,
|
|
2599
|
+
"errors": slide_sxsd_errors,
|
|
2600
|
+
"warnings": slide_sxsd_warnings,
|
|
2601
|
+
"infos": [],
|
|
2602
|
+
"issues": slide_sxsd_issues,
|
|
2603
|
+
}
|
|
2604
|
+
)
|
|
2605
|
+
continue
|
|
2606
|
+
|
|
2493
2607
|
geometry = lint_slide(
|
|
2494
2608
|
slide_xml,
|
|
2495
2609
|
slide_number,
|
|
@@ -2535,8 +2649,11 @@ def lint_xml(xml: str, source_path: str | None = None) -> dict[str, Any]:
|
|
|
2535
2649
|
),
|
|
2536
2650
|
]
|
|
2537
2651
|
issues = [
|
|
2538
|
-
|
|
2539
|
-
|
|
2652
|
+
*slide_sxsd_issues,
|
|
2653
|
+
*[
|
|
2654
|
+
normalize_issue(issue, slide_number, elements_by_id)
|
|
2655
|
+
for issue in raw_issues
|
|
2656
|
+
],
|
|
2540
2657
|
]
|
|
2541
2658
|
errors = [issue for issue in issues if issue["level"] == "error"]
|
|
2542
2659
|
warnings = [issue for issue in issues if issue["level"] == "warning"]
|