@amaster.ai/pi-lark 0.1.2-beta.51 → 0.1.2-beta.53
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 +10 -4
- package/skills/lark-apps/references/lark-apps-cloud-dev.md +5 -4
- package/skills/lark-apps/references/lark-apps-create.md +6 -3
- package/skills/lark-apps/references/lark-apps-get.md +1 -1
- package/skills/lark-apps/references/lark-apps-list.md +1 -1
- package/skills/lark-apps/references/lark-apps-local-dev.md +27 -1
- package/skills/lark-apps/references/lark-apps-release-create.md +1 -1
- package/skills/lark-base/SKILL.md +11 -4
- package/skills/lark-base/references/lark-base-dashboard-block-get-data.md +17 -1
- package/skills/lark-base/references/lark-base-dashboard.md +17 -4
- package/skills/lark-base/references/lark-base-field-json.md +2 -0
- package/skills/lark-calendar/SKILL.md +1 -1
- package/skills/lark-doc/SKILL.md +1 -0
- package/skills/lark-doc/references/lark-doc-history.md +13 -14
- package/skills/lark-drive/SKILL.md +7 -5
- package/skills/lark-drive/references/lark-drive-apply-permission.md +1 -1
- package/skills/lark-drive/references/lark-drive-copy.md +87 -0
- package/skills/lark-drive/references/lark-drive-export.md +3 -0
- package/skills/lark-drive/references/lark-drive-task-result.md +3 -0
- package/skills/lark-drive/references/lark-drive-update-title.md +78 -0
- package/skills/lark-event/SKILL.md +7 -4
- package/skills/lark-event/references/lark-event-vc.md +8 -2
- package/skills/lark-im/SKILL.md +5 -5
- package/skills/lark-im/references/lark-im-chat-list.md +9 -2
- package/skills/lark-im/references/lark-im-chat-members-list.md +7 -4
- package/skills/lark-im/references/lark-im-chat-messages-list.md +10 -3
- package/skills/lark-im/references/lark-im-chat-search.md +9 -2
- package/skills/lark-im/references/lark-im-feed-group-list-item.md +2 -2
- package/skills/lark-im/references/lark-im-feed-group-list.md +2 -2
- package/skills/lark-im/references/lark-im-feed-shortcut-list.md +1 -1
- package/skills/lark-im/references/lark-im-flag-list.md +2 -2
- package/skills/lark-im/references/lark-im-message-enrichment.md +1 -1
- package/skills/lark-im/references/lark-im-messages-search.md +4 -5
- package/skills/lark-im/references/lark-im-threads-messages-list.md +8 -4
- package/skills/lark-mail/references/lark-mail-triage.md +19 -4
- package/skills/lark-minutes/SKILL.md +1 -1
- package/skills/lark-minutes/references/lark-minutes-search.md +6 -7
- package/skills/lark-shared/SKILL.md +3 -3
- package/skills/lark-sheets/SKILL.md +83 -82
- package/skills/lark-sheets/references/lark-sheets-batch-update.md +13 -58
- package/skills/lark-sheets/references/lark-sheets-chart.md +2 -1
- package/skills/lark-sheets/references/lark-sheets-conditional-format.md +1 -1
- package/skills/lark-sheets/references/lark-sheets-range-operations.md +5 -5
- package/skills/lark-sheets/references/lark-sheets-read-data.md +80 -6
- package/skills/lark-sheets/references/lark-sheets-sheet-structure.md +21 -10
- package/skills/lark-sheets/references/lark-sheets-styles-put.md +93 -0
- package/skills/lark-sheets/references/lark-sheets-visual-standards.md +2 -2
- package/skills/lark-sheets/references/lark-sheets-workbook.md +4 -3
- package/skills/lark-sheets/references/lark-sheets-write-cells.md +40 -12
- package/skills/lark-sheets/scripts/lark_detect_subtables.py +593 -0
- package/skills/lark-sheets/scripts/lark_inspect_workbook.py +188 -0
- package/skills/lark-sheets/scripts/lark_profile_table.py +614 -0
- package/skills/lark-sheets/scripts/lark_sheet_range.py +176 -0
- package/skills/lark-sheets/scripts/lark_sheet_read_cli.py +184 -0
- package/skills/lark-sheets/scripts/sheets_df.py +21 -3
- package/skills/lark-slides/SKILL.md +25 -40
- package/skills/lark-slides/references/lark-slides-add-slide.md +92 -0
- package/skills/lark-slides/references/lark-slides-create.md +16 -35
- package/skills/lark-slides/references/lark-slides-delete-slide.md +65 -0
- package/skills/lark-slides/references/lark-slides-edit-workflows.md +5 -3
- package/skills/lark-slides/references/lark-slides-media-upload.md +3 -25
- package/skills/lark-slides/references/lark-slides-replace-pages.md +6 -4
- package/skills/lark-slides/references/lark-slides-replace-slide.md +22 -1
- package/skills/lark-slides/references/lark-slides-screenshot.md +31 -13
- package/skills/lark-slides/references/lark-slides-xml-presentations-get.md +5 -5
- package/skills/lark-slides/references/slides_chart_demo.xml +1 -1
- package/skills/lark-slides/references/slides_xml_schema_definition.xml +48 -4
- package/skills/lark-slides/references/troubleshooting.md +1 -2
- package/skills/lark-slides/references/validation-checklist.md +3 -3
- package/skills/lark-slides/references/xml-schema-quick-ref.md +23 -9
- package/skills/lark-slides/scripts/sxsd_validator.py +154 -10
- package/skills/lark-slides/scripts/xml_text_overlap_lint.py +360 -76
- package/skills/lark-slides/scripts/xml_text_overlap_lint_test.py +1138 -214
- package/skills/lark-whiteboard/SKILL.md +15 -8
- package/skills/lark-whiteboard/references/lark-whiteboard-export.md +4 -3
- package/skills/lark-whiteboard/references/lark-whiteboard-update.md +4 -4
- package/skills/lark-whiteboard/references/lark-whiteboard-workflow.md +19 -17
- package/skills/lark-whiteboard/routes/dsl.md +8 -2
- package/skills/lark-whiteboard/routes/mermaid.md +1 -1
- package/skills/lark-whiteboard/routes/svg-edit.md +5 -2
- package/skills/lark-whiteboard/routes/svg.md +3 -1
- package/skills/lark-whiteboard/scenes/mention.md +71 -0
- package/skills/lark-wiki/SKILL.md +5 -3
- package/skills/lark-wiki/references/lark-wiki-delete-space.md +6 -3
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-create.md +0 -219
- package/skills/lark-slides/references/lark-slides-xml-presentation-slide-delete.md +0 -126
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 最重要的规则
|
|
6
6
|
|
|
7
|
-
1. 协议标准写法应使用 `<presentation xmlns="
|
|
7
|
+
1. 协议标准写法应使用 `<presentation xmlns="https://www.larkoffice.com/sml/2.0">`;当前服务端实现可能兼容不带 `xmlns` 的输入,但不作为协议保证
|
|
8
8
|
2. `<presentation>` 直接子元素只有 `<title>`、`<theme>`、`<slide>`
|
|
9
9
|
3. `<slide>` 直接子元素只有 `<style>`、`<data>`、`<note>`
|
|
10
10
|
4. 页面中的文本通常通过 `<content>` 表达,而不是把 `<title>`、`<body>` 直接挂在 `<slide>` 下
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
```xml
|
|
15
15
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
16
|
-
<presentation xmlns="
|
|
16
|
+
<presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
17
17
|
<slide>
|
|
18
18
|
<data>
|
|
19
19
|
<shape type="text" topLeftX="80" topLeftY="80" width="800" height="120">
|
|
@@ -81,7 +81,7 @@ XSD 中的 `title`、`headline`、`sub-headline`、`body`、`caption` 主要出
|
|
|
81
81
|
**子元素:**
|
|
82
82
|
|
|
83
83
|
- `<style>?` - 页面样式,目前可放 `<fill>`
|
|
84
|
-
- `<data>?` - 页面元素容器,可放 `shape`、`line`、`polyline`、`img`、`table`、`icon`、`chart`、`undefined`
|
|
84
|
+
- `<data>?` - 页面元素容器,可放 `shape`、`line`、`polyline`、`img`、`table`、`icon`、`embed`、`chart`、`undefined`
|
|
85
85
|
- `<note>?` - 演讲者备注,内部可放 `<content>`
|
|
86
86
|
|
|
87
87
|
这意味着 `<title>`、`<headline>`、`<body>`、`<caption>` 不能直接放在 `<slide>` 下。
|
|
@@ -227,17 +227,17 @@ XSD 中的 `title`、`headline`、`sub-headline`、`body`、`caption` 主要出
|
|
|
227
227
|
### img
|
|
228
228
|
|
|
229
229
|
```xml
|
|
230
|
-
<img src="
|
|
230
|
+
<img src="file_token_或_@本地路径" topLeftX="80" topLeftY="120" width="320" height="180"/>
|
|
231
231
|
```
|
|
232
232
|
|
|
233
233
|
`img` 使用 `topLeftX` / `topLeftY`,不是 `x` / `y`。
|
|
234
234
|
|
|
235
|
-
`src` 只支持:`slides +media-upload` 返回的 `file_token`,或 `@<本地路径>`
|
|
235
|
+
`src` 只支持:`slides +media-upload` 返回的 `file_token`,或 `@<本地路径>` 占位符(`+create --slides` 和 `+add-slide` 会自动上传并替换)。**禁止使用 http(s) 外链 URL**——飞书 slides 渲染端不会代理外链图,外链 src 在 PPT 里通常不显示。本地图片详见 [lark-slides-create.md](lark-slides-create.md#本地图片path-占位符) / [lark-slides-media-upload.md](lark-slides-media-upload.md)。
|
|
236
236
|
|
|
237
237
|
本地图片的两种姿势:
|
|
238
238
|
|
|
239
239
|
- 新建带图 PPT:`+create --slides` 里直接写 `src="@./pic.png"`,CLI 在创空白 PPT 后、加 slides 前自动上传并替换 token
|
|
240
|
-
- 给已有 PPT
|
|
240
|
+
- 给已有 PPT 加带图新页:`+add-slide --slide` 的 XML 里直接写 `src="@./pic.png"`,CLI 上传后替换 token 再提交页面
|
|
241
241
|
|
|
242
242
|
> **注意**:`width`/`height` 是**裁剪后**的显示尺寸。比例和原图不一致时会自动裁剪(无法靠属性关闭),想避免裁剪就让 `width:height` 对齐原图比例。
|
|
243
243
|
|
|
@@ -355,6 +355,20 @@ XSD 中的 `title`、`headline`、`sub-headline`、`body`、`caption` 主要出
|
|
|
355
355
|
|
|
356
356
|
详细用法见 [slides_xml_schema_definition.xml](slides_xml_schema_definition.xml)。
|
|
357
357
|
|
|
358
|
+
### embed
|
|
359
|
+
|
|
360
|
+
嵌入内容容器:外层 `<embed>` 承载 Slides 的摆放和效果属性(`topLeftX`/`topLeftY`/`width`/`height` 必填,`rotation`/`flipX`/`flipY`/`alpha` 可选),内层承载外部标准内容。当前内层内容为标准 SVG,`<svg>` 必须使用 `http://www.w3.org/2000/svg` 命名空间,且仅描述嵌入内容本身,不承载 Slides 布局属性。
|
|
361
|
+
|
|
362
|
+
```xml
|
|
363
|
+
<embed topLeftX="80" topLeftY="120" width="200" height="120">
|
|
364
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 120">
|
|
365
|
+
<circle cx="100" cy="60" r="40" fill="rgba(37, 99, 235, 1)"/>
|
|
366
|
+
</svg>
|
|
367
|
+
</embed>
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
`<embed>` 直接子元素为一个 `<svg>`(必需),以及可选的 `<reflection>`(倒影)与 `<shadow>`(阴影)。
|
|
371
|
+
|
|
358
372
|
## 颜色与样式
|
|
359
373
|
|
|
360
374
|
### fill
|
|
@@ -418,7 +432,7 @@ XSD 中的 `title`、`headline`、`sub-headline`、`body`、`caption` 主要出
|
|
|
418
432
|
|
|
419
433
|
```xml
|
|
420
434
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
421
|
-
<presentation xmlns="
|
|
435
|
+
<presentation xmlns="https://www.larkoffice.com/sml/2.0" width="960" height="540">
|
|
422
436
|
<title>季度报告</title>
|
|
423
437
|
<theme>
|
|
424
438
|
<textStyles>
|
|
@@ -466,7 +480,7 @@ XSD 中的 `title`、`headline`、`sub-headline`、`body`、`caption` 主要出
|
|
|
466
480
|
|
|
467
481
|
## 最佳实践
|
|
468
482
|
|
|
469
|
-
1. 始终带上命名空间 `xmlns="
|
|
483
|
+
1. 始终带上命名空间 `xmlns="https://www.larkoffice.com/sml/2.0"`
|
|
470
484
|
2. 用 `shape type="text"` + `content` 表达页面文本
|
|
471
485
|
3. 用 `topLeftX` / `topLeftY`、`startX` / `startY` 等 schema 中定义的属性名
|
|
472
486
|
4. 优先使用 `rgb` / `rgba` 颜色格式;渐变必须使用 `rgba()` 且带百分比停靠点
|
|
@@ -481,5 +495,5 @@ XSD 中的 `title`、`headline`、`sub-headline`、`body`、`caption` 主要出
|
|
|
481
495
|
## Schema 版本信息
|
|
482
496
|
|
|
483
497
|
- **版本**: 2.0.0
|
|
484
|
-
- **命名空间**:
|
|
498
|
+
- **命名空间**: https://www.larkoffice.com/sml/2.0
|
|
485
499
|
- **发布日期**: 2025-11-03
|
|
@@ -16,11 +16,11 @@ from typing import Any
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
XS_NS = "{http://www.w3.org/2001/XMLSchema}"
|
|
19
|
-
SML_NAMESPACE = "
|
|
19
|
+
SML_NAMESPACE = "https://www.larkoffice.com/sml/2.0"
|
|
20
|
+
SML_LEGACY_HTTP_NAMESPACE = "http://www.larkoffice.com/sml/2.0"
|
|
20
21
|
SML_READBACK_NAMESPACE = "/sml/2.0"
|
|
21
|
-
SML_HTTPS_READBACK_NAMESPACE = "https://www.larkoffice.com/sml/2.0"
|
|
22
22
|
ACCEPTED_SML_NAMESPACES = frozenset(
|
|
23
|
-
(SML_NAMESPACE,
|
|
23
|
+
(SML_NAMESPACE, SML_LEGACY_HTTP_NAMESPACE, SML_READBACK_NAMESPACE)
|
|
24
24
|
)
|
|
25
25
|
|
|
26
26
|
|
|
@@ -79,6 +79,15 @@ class ChildRule:
|
|
|
79
79
|
order: int | None
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class WildcardRule:
|
|
84
|
+
namespace: str
|
|
85
|
+
process_contents: str
|
|
86
|
+
min_occurs: int
|
|
87
|
+
max_occurs: int | None
|
|
88
|
+
order: int | None
|
|
89
|
+
|
|
90
|
+
|
|
82
91
|
@dataclass(frozen=True)
|
|
83
92
|
class ChoiceRequirement:
|
|
84
93
|
names: tuple[str, ...]
|
|
@@ -307,12 +316,13 @@ def multiplied_max(left: int | None, right: int | None) -> int | None:
|
|
|
307
316
|
|
|
308
317
|
def child_rules_for_complex_type(
|
|
309
318
|
complex_type: ET.Element,
|
|
310
|
-
) -> tuple[list[ChildRule], list[ChoiceRequirement]]:
|
|
319
|
+
) -> tuple[list[ChildRule], list[WildcardRule], list[ChoiceRequirement]]:
|
|
311
320
|
particle = particle_for_complex_type(complex_type)
|
|
312
321
|
if particle is None:
|
|
313
|
-
return [], []
|
|
322
|
+
return [], [], []
|
|
314
323
|
|
|
315
324
|
rules: list[ChildRule] = []
|
|
325
|
+
wildcard_rules: list[WildcardRule] = []
|
|
316
326
|
requirements: list[ChoiceRequirement] = []
|
|
317
327
|
next_order = 0
|
|
318
328
|
|
|
@@ -337,6 +347,25 @@ def child_rules_for_complex_type(
|
|
|
337
347
|
)
|
|
338
348
|
)
|
|
339
349
|
|
|
350
|
+
def add_wildcard(
|
|
351
|
+
wildcard: ET.Element,
|
|
352
|
+
*,
|
|
353
|
+
order: int | None,
|
|
354
|
+
optional_by_choice: bool,
|
|
355
|
+
max_multiplier: int | None,
|
|
356
|
+
) -> None:
|
|
357
|
+
minimum = occurs_value(wildcard.attrib.get("minOccurs"), 1) or 0
|
|
358
|
+
maximum = occurs_value(wildcard.attrib.get("maxOccurs"), 1)
|
|
359
|
+
wildcard_rules.append(
|
|
360
|
+
WildcardRule(
|
|
361
|
+
namespace=wildcard.attrib.get("namespace", "##any"),
|
|
362
|
+
process_contents=wildcard.attrib.get("processContents", "strict"),
|
|
363
|
+
min_occurs=0 if optional_by_choice else minimum,
|
|
364
|
+
max_occurs=multiplied_max(maximum, max_multiplier),
|
|
365
|
+
order=order,
|
|
366
|
+
)
|
|
367
|
+
)
|
|
368
|
+
|
|
340
369
|
def walk_group(
|
|
341
370
|
group: ET.Element,
|
|
342
371
|
*,
|
|
@@ -369,6 +398,13 @@ def child_rules_for_complex_type(
|
|
|
369
398
|
optional_by_choice=True,
|
|
370
399
|
max_multiplier=effective_max,
|
|
371
400
|
)
|
|
401
|
+
elif child_kind == "any":
|
|
402
|
+
add_wildcard(
|
|
403
|
+
child,
|
|
404
|
+
order=choice_order,
|
|
405
|
+
optional_by_choice=True,
|
|
406
|
+
max_multiplier=effective_max,
|
|
407
|
+
)
|
|
372
408
|
elif child_kind in {"sequence", "all", "choice"}:
|
|
373
409
|
walk_group(
|
|
374
410
|
child,
|
|
@@ -395,6 +431,17 @@ def child_rules_for_complex_type(
|
|
|
395
431
|
optional_by_choice=optional_by_choice or group_min == 0,
|
|
396
432
|
max_multiplier=effective_max,
|
|
397
433
|
)
|
|
434
|
+
elif child_kind == "any":
|
|
435
|
+
child_order = fixed_order
|
|
436
|
+
if child_order is None and ordered and group_ordered:
|
|
437
|
+
child_order = next_order
|
|
438
|
+
next_order += 1
|
|
439
|
+
add_wildcard(
|
|
440
|
+
child,
|
|
441
|
+
order=child_order,
|
|
442
|
+
optional_by_choice=optional_by_choice or group_min == 0,
|
|
443
|
+
max_multiplier=effective_max,
|
|
444
|
+
)
|
|
398
445
|
elif child_kind in {"sequence", "all", "choice"}:
|
|
399
446
|
walk_group(
|
|
400
447
|
child,
|
|
@@ -405,7 +452,7 @@ def child_rules_for_complex_type(
|
|
|
405
452
|
)
|
|
406
453
|
|
|
407
454
|
walk_group(particle, ordered=local_name(particle.tag) == "sequence")
|
|
408
|
-
return rules, requirements
|
|
455
|
+
return rules, wildcard_rules, requirements
|
|
409
456
|
|
|
410
457
|
|
|
411
458
|
def issue(
|
|
@@ -688,6 +735,31 @@ def element_namespace(tag: str) -> str | None:
|
|
|
688
735
|
return tag[1:].split("}", 1)[0]
|
|
689
736
|
|
|
690
737
|
|
|
738
|
+
def wildcard_matches_namespace(namespace_rule: str, namespace: str | None) -> bool:
|
|
739
|
+
tokens = namespace_rule.split()
|
|
740
|
+
if "##any" in tokens:
|
|
741
|
+
return True
|
|
742
|
+
if "##local" in tokens and namespace is None:
|
|
743
|
+
return True
|
|
744
|
+
if "##targetNamespace" in tokens and namespace == SML_NAMESPACE:
|
|
745
|
+
return True
|
|
746
|
+
if "##other" in tokens and namespace not in {None, SML_NAMESPACE}:
|
|
747
|
+
return True
|
|
748
|
+
return namespace in tokens
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
def wildcard_namespace_description(namespace_rule: str) -> str:
|
|
752
|
+
if namespace_rule == "##any":
|
|
753
|
+
return "any namespace"
|
|
754
|
+
if namespace_rule == "##local":
|
|
755
|
+
return "the local namespace"
|
|
756
|
+
if namespace_rule == "##targetNamespace":
|
|
757
|
+
return f"the target namespace {SML_NAMESPACE}"
|
|
758
|
+
if namespace_rule == "##other":
|
|
759
|
+
return "a namespace other than the SXSD target namespace"
|
|
760
|
+
return f"namespace {namespace_rule}"
|
|
761
|
+
|
|
762
|
+
|
|
691
763
|
def validate_element_children(
|
|
692
764
|
element: ET.Element,
|
|
693
765
|
path: str,
|
|
@@ -696,8 +768,10 @@ def validate_element_children(
|
|
|
696
768
|
) -> tuple[list[dict[str, Any]], dict[int, ElementRule]]:
|
|
697
769
|
tag = local_name(element.tag)
|
|
698
770
|
complex_type = complex_type_for_element(element_rule, model)
|
|
699
|
-
child_rules, choice_requirements = (
|
|
700
|
-
child_rules_for_complex_type(complex_type)
|
|
771
|
+
child_rules, wildcard_rules, choice_requirements = (
|
|
772
|
+
child_rules_for_complex_type(complex_type)
|
|
773
|
+
if complex_type is not None
|
|
774
|
+
else ([], [], [])
|
|
701
775
|
)
|
|
702
776
|
rules_by_name: dict[str, list[ChildRule]] = {}
|
|
703
777
|
for child_rule in child_rules:
|
|
@@ -706,19 +780,37 @@ def validate_element_children(
|
|
|
706
780
|
issues: list[dict[str, Any]] = []
|
|
707
781
|
matched: dict[int, ElementRule] = {}
|
|
708
782
|
counts: dict[str, int] = {}
|
|
783
|
+
wildcard_counts: dict[int, int] = {}
|
|
709
784
|
latest_order = -1
|
|
710
785
|
for child in element:
|
|
711
786
|
child_name = local_name(child.tag)
|
|
712
787
|
child_path = f"{path}/{child_name}"
|
|
713
788
|
candidates = rules_by_name.get(child_name, [])
|
|
714
|
-
|
|
789
|
+
wildcard_match = next(
|
|
790
|
+
(
|
|
791
|
+
(index, wildcard_rule)
|
|
792
|
+
for index, wildcard_rule in enumerate(wildcard_rules)
|
|
793
|
+
if wildcard_matches_namespace(
|
|
794
|
+
wildcard_rule.namespace,
|
|
795
|
+
element_namespace(child.tag),
|
|
796
|
+
)
|
|
797
|
+
),
|
|
798
|
+
None,
|
|
799
|
+
)
|
|
800
|
+
if not candidates and wildcard_match is None:
|
|
801
|
+
expected_children = sorted(rules_by_name)
|
|
802
|
+
expected_children.extend(
|
|
803
|
+
wildcard_namespace_description(rule.namespace) for rule in wildcard_rules
|
|
804
|
+
)
|
|
715
805
|
issues.append(
|
|
716
806
|
issue(
|
|
717
807
|
"sxsd_unexpected_child",
|
|
718
808
|
child_path,
|
|
719
809
|
child_name,
|
|
720
810
|
attr=None,
|
|
721
|
-
expected="one of: " + ", ".join(
|
|
811
|
+
expected="one of: " + ", ".join(expected_children)
|
|
812
|
+
if expected_children
|
|
813
|
+
else "no child elements",
|
|
722
814
|
actual=child_name,
|
|
723
815
|
message=f"unexpected SXSD child <{child_name}> under <{tag}> at {child_path}",
|
|
724
816
|
hint=f"Move or remove <{child_name}> so <{tag}> follows the SXSD child structure.",
|
|
@@ -726,6 +818,40 @@ def validate_element_children(
|
|
|
726
818
|
)
|
|
727
819
|
continue
|
|
728
820
|
|
|
821
|
+
if not candidates and wildcard_match is not None:
|
|
822
|
+
wildcard_index, wildcard_rule = wildcard_match
|
|
823
|
+
if wildcard_rule.order is not None:
|
|
824
|
+
if wildcard_rule.order < latest_order:
|
|
825
|
+
issues.append(
|
|
826
|
+
issue(
|
|
827
|
+
"sxsd_invalid_child_order",
|
|
828
|
+
child_path,
|
|
829
|
+
child_name,
|
|
830
|
+
attr=None,
|
|
831
|
+
expected="children in xs:sequence order",
|
|
832
|
+
actual=child_name,
|
|
833
|
+
message=f"SXSD child <{child_name}> is out of order under <{tag}> at {child_path}",
|
|
834
|
+
hint=f"Reorder <{child_name}> according to the SXSD sequence for <{tag}>.",
|
|
835
|
+
)
|
|
836
|
+
)
|
|
837
|
+
latest_order = max(latest_order, wildcard_rule.order)
|
|
838
|
+
wildcard_counts[wildcard_index] = wildcard_counts.get(wildcard_index, 0) + 1
|
|
839
|
+
actual_count = wildcard_counts[wildcard_index]
|
|
840
|
+
if wildcard_rule.max_occurs is not None and actual_count > wildcard_rule.max_occurs:
|
|
841
|
+
issues.append(
|
|
842
|
+
issue(
|
|
843
|
+
"sxsd_too_many_children",
|
|
844
|
+
child_path,
|
|
845
|
+
child_name,
|
|
846
|
+
attr=None,
|
|
847
|
+
expected=f"at most {wildcard_rule.max_occurs} child from {wildcard_namespace_description(wildcard_rule.namespace)}",
|
|
848
|
+
actual=actual_count,
|
|
849
|
+
message=f"too many wildcard SXSD children under <{tag}> at {path}",
|
|
850
|
+
hint=f"Keep at most {wildcard_rule.max_occurs} child from {wildcard_namespace_description(wildcard_rule.namespace)} under <{tag}>.",
|
|
851
|
+
)
|
|
852
|
+
)
|
|
853
|
+
continue
|
|
854
|
+
|
|
729
855
|
child_rule = candidates[0]
|
|
730
856
|
if child_rule.order is not None:
|
|
731
857
|
if child_rule.order < latest_order:
|
|
@@ -777,6 +903,24 @@ def validate_element_children(
|
|
|
777
903
|
)
|
|
778
904
|
)
|
|
779
905
|
|
|
906
|
+
for wildcard_index, wildcard_rule in enumerate(wildcard_rules):
|
|
907
|
+
actual_count = wildcard_counts.get(wildcard_index, 0)
|
|
908
|
+
if wildcard_rule.min_occurs <= actual_count:
|
|
909
|
+
continue
|
|
910
|
+
namespace_description = wildcard_namespace_description(wildcard_rule.namespace)
|
|
911
|
+
issues.append(
|
|
912
|
+
issue(
|
|
913
|
+
"sxsd_missing_required_child",
|
|
914
|
+
path,
|
|
915
|
+
tag,
|
|
916
|
+
attr=None,
|
|
917
|
+
expected=f"at least {wildcard_rule.min_occurs} child from {namespace_description}",
|
|
918
|
+
actual=actual_count,
|
|
919
|
+
message=f"missing required wildcard SXSD child under <{tag}> at {path}",
|
|
920
|
+
hint=f"Add at least {wildcard_rule.min_occurs} child from {namespace_description} under <{tag}>.",
|
|
921
|
+
)
|
|
922
|
+
)
|
|
923
|
+
|
|
780
924
|
for requirement in choice_requirements:
|
|
781
925
|
actual_count = sum(counts.get(name, 0) for name in requirement.names)
|
|
782
926
|
expected_names = ", ".join(requirement.names)
|