@cardenelabs/dragon 0.28.0 → 0.32.0
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/README.md +391 -16
- package/dist/index.cjs +13479 -7403
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2360 -208
- package/dist/index.d.ts +2360 -208
- package/dist/index.js +13462 -7405
- package/dist/index.js.map +1 -1
- package/package.json +12 -13
- package/src/compile/actor-ids.ts +297 -0
- package/src/compile/actor-option-notice.ts +379 -0
- package/src/compile/actors.ts +103 -0
- package/src/compile/c4.ts +74 -0
- package/src/compile/chain.ts +93 -0
- package/src/compile/chart-fields.ts +193 -0
- package/src/compile/deep-rewrite.ts +30 -0
- package/src/compile/derived-source.ts +18 -0
- package/src/compile/direction.ts +102 -0
- package/src/compile/edge-option-notice.ts +112 -0
- package/src/compile/er-relation.ts +200 -0
- package/src/compile/flow.ts +54 -0
- package/src/compile/flowchart.ts +344 -0
- package/src/compile/funnel.ts +87 -0
- package/src/compile/gantt.ts +337 -0
- package/src/compile/generic.ts +696 -0
- package/src/compile/hierarchy.ts +92 -0
- package/src/compile/journey.ts +93 -0
- package/src/compile/kinds.ts +148 -0
- package/src/compile/lanes.ts +138 -0
- package/src/compile/metro.ts +271 -0
- package/src/compile/mind.ts +393 -0
- package/src/compile/node-title.ts +24 -0
- package/src/compile/notice.ts +141 -0
- package/src/compile/order.ts +62 -0
- package/src/compile/parts-lookup.ts +17 -0
- package/src/compile/parts.ts +2709 -0
- package/src/compile/placement-apply.ts +689 -0
- package/src/compile/placement.ts +15 -0
- package/src/compile/quadrant.ts +265 -0
- package/src/compile/record.ts +75 -0
- package/src/compile/row-marks.ts +128 -0
- package/src/compile/rows.ts +40 -0
- package/src/compile/sequence.ts +305 -0
- package/src/compile/slug.ts +37 -0
- package/src/compile/stages.ts +247 -0
- package/src/compile/subtitle.ts +35 -0
- package/src/compile/swimlane-marks.ts +87 -0
- package/src/compile/swimlane.ts +82 -0
- package/src/compile/timeline.ts +213 -0
- package/src/compile/topology.ts +44 -0
- package/src/compile/tree.ts +69 -0
- package/src/compile/value-chart.ts +337 -0
- package/src/compile/values.ts +432 -0
- package/src/compile/word-state.ts +82 -0
- package/src/compile.ts +1806 -6971
- package/src/focus.ts +2 -2
- package/src/index.ts +42 -9
- package/src/json-line-map.ts +30 -0
- package/src/json-parser.ts +1760 -168
- package/src/keywords.ts +251 -17
- package/src/near-name.ts +42 -0
- package/src/notation-lint.ts +41 -64
- package/src/relative-pos.ts +71 -2
- package/src/schema.ts +1 -1
- package/src/schemas/diagram.json +680 -23
- package/src/types.ts +337 -36
- package/src/v05/parser.ts +2553 -354
package/src/json-parser.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* で確実に生成できるよう、 flat な object array を優先した shape になっている。
|
|
7
7
|
*
|
|
8
8
|
* 使い方 (LLM):
|
|
9
|
-
* 1. `packages/dragon/schemas/diagram.json` の JSON Schema を LLM の tool schema に注入
|
|
9
|
+
* 1. `packages/dragon/src/schemas/diagram.json` の JSON Schema を LLM の tool schema に注入
|
|
10
10
|
* 2. LLM が JSON を返す
|
|
11
11
|
* 3. `jsonToDiagram(json)` で CdlDiagram に変換
|
|
12
12
|
* 4. validation error は throw、 retry loop で LLM に修正させる
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
EDGE_HEAD_VALUES,
|
|
25
25
|
NODE_KIND_VALID,
|
|
26
26
|
PRESET_TYPES,
|
|
27
|
+
TYPE_ALIASES,
|
|
27
28
|
STYLE_VALID,
|
|
28
29
|
resolveNodeKind,
|
|
29
30
|
resolveTone,
|
|
@@ -34,15 +35,69 @@ import {
|
|
|
34
35
|
部品の組の表,
|
|
35
36
|
つまみの表,
|
|
36
37
|
EVENT_KINDS,
|
|
38
|
+
EVENT_TARGET_KEYS,
|
|
39
|
+
EVENT_REQUIRED_KEYS,
|
|
40
|
+
CHART_LINE_DASH_VALUES,
|
|
41
|
+
CHART_LINE_MARKER_VALUES,
|
|
42
|
+
CHART_PIE_TABLE_COLUMN_VALUES,
|
|
43
|
+
CHART_PIE_RING_WIDTH_VALUES,
|
|
44
|
+
CHART_STACKED_LEGEND_POSITION_VALUES,
|
|
45
|
+
CHART_WAFFLE_LEGEND_POSITION_VALUES,
|
|
46
|
+
FUNNEL_FORM_VALUES,
|
|
47
|
+
FUNNEL_RATE_VALUES,
|
|
48
|
+
QUADRANT_POINT_LABEL_SIDE_VALUES,
|
|
49
|
+
TREE_NODE_FORM_VALUES,
|
|
50
|
+
TREE_EDGE_TONE_VALUES,
|
|
51
|
+
TREE_EDGE_HEAD_VALUES,
|
|
52
|
+
MIND_FORM_VALUES,
|
|
53
|
+
JOURNEY_FORM_VALUES,
|
|
54
|
+
JOURNEY_LINE_FORM_VALUES,
|
|
55
|
+
JOURNEY_OPPORTUNITY_POSITION_VALUES,
|
|
56
|
+
JOURNEY_LABEL_KEYS,
|
|
57
|
+
CORNER_FORM_VALUES,
|
|
58
|
+
CHART_BAR_SHAPE_VALUES,
|
|
59
|
+
QUADRANT_LABEL_POSITION_VALUES,
|
|
60
|
+
LABEL_COLLISION_VALUES,
|
|
61
|
+
GANTT_TASK_LABEL_ALIGN_VALUES,
|
|
62
|
+
CDL_ICON_VALUES,
|
|
63
|
+
KIND_FORM_VALUES,
|
|
64
|
+
TITLE_ALIGN_VALUES,
|
|
65
|
+
LEGEND_LINE_STYLE_VALUES,
|
|
66
|
+
LEGEND_NOTE_ALIGN_VALUES,
|
|
67
|
+
ROW_MARK_FORM_VALUES,
|
|
68
|
+
ROW_MARK_TONE_VALUES,
|
|
69
|
+
ROW_DIVIDER_FORM_VALUES,
|
|
70
|
+
ROW_WELL_KEYS,
|
|
71
|
+
SEQUENCE_ACTOR_FORM_VALUES,
|
|
72
|
+
SEQUENCE_LABEL_FORM_VALUES,
|
|
73
|
+
SEQUENCE_RETURN_HEAD_VALUES,
|
|
74
|
+
SEQUENCE_KIND_LABEL_KEYS,
|
|
75
|
+
SEQUENCE_SELF_LOOP_KEYS,
|
|
37
76
|
type 図形の定義,
|
|
38
77
|
} from "./v05/parser";
|
|
39
|
-
//
|
|
78
|
+
// 図の意匠 (#1553 / #2790)。 記法の読み手と同じ解決を通す = 別名の受け方が
|
|
40
79
|
// 記法と JSON でずれない
|
|
41
|
-
import {
|
|
42
|
-
|
|
80
|
+
import {
|
|
81
|
+
PHASE_BODY_KEYS,
|
|
82
|
+
DIAGRAM_SHAPES,
|
|
83
|
+
THEMES,
|
|
84
|
+
THEME_ALIAS,
|
|
85
|
+
resolveOrder,
|
|
86
|
+
resolveDiagramShape,
|
|
87
|
+
shapesForDiagramType,
|
|
88
|
+
resolveTheme,
|
|
89
|
+
resolveLegendMark,
|
|
90
|
+
} from "./keywords";
|
|
91
|
+
// 区画の語の表 (#2667)。 記法と同じ表から直す = 同じ区画を 2 通りで呼ばない
|
|
92
|
+
import { 区画 } from "./compile/word-state";
|
|
93
|
+
// 行頭の印の古い語の読み替え (#2782)。 記法と同じ表を通す = JSON だけ古い語が印を持たない
|
|
94
|
+
// 形にならない
|
|
95
|
+
import { 行頭の語へ読み替える } from "./compile/row-marks";
|
|
96
|
+
import type { DslDiagramShape, DslShape } from "./keywords";
|
|
43
97
|
import type { CompileToCdlOpts } from "./compile";
|
|
44
98
|
import type {
|
|
45
99
|
CdlDiagram,
|
|
100
|
+
CdlLegendItem,
|
|
46
101
|
NodeKind,
|
|
47
102
|
Tone,
|
|
48
103
|
EdgeStyle,
|
|
@@ -56,6 +111,8 @@ import type {
|
|
|
56
111
|
import { extractIdentifiers, parseFormula } from "@cardenelabs/cdl";
|
|
57
112
|
import type {
|
|
58
113
|
DslDocument,
|
|
114
|
+
DslStageHeader,
|
|
115
|
+
DslRegions,
|
|
59
116
|
DslActor,
|
|
60
117
|
DslStep,
|
|
61
118
|
DslAnimate,
|
|
@@ -71,7 +128,18 @@ import type {
|
|
|
71
128
|
DslDynShape,
|
|
72
129
|
} from "./types";
|
|
73
130
|
import { checkValueExpression, isValueName, valueNameIssue } from "./value-syntax";
|
|
131
|
+
import { 近い名前 } from "./near-name";
|
|
132
|
+
import { 書いた場所の鍵, type 書いた行の表 } from "./json-line-map";
|
|
74
133
|
import { compileToCdl } from "./compile";
|
|
134
|
+
import {
|
|
135
|
+
RELATIVE_DIRECTIONS,
|
|
136
|
+
findRelativeProblems,
|
|
137
|
+
type RelativeDirection,
|
|
138
|
+
type RelativePos,
|
|
139
|
+
} from "./relative-pos";
|
|
140
|
+
|
|
141
|
+
/** 古い綴りで書いた図種 (#2655 / #2657)。 受ける値は `TYPE_ALIASES` が SSOT */
|
|
142
|
+
type 図種の別名 = "solidity" | DslShape;
|
|
75
143
|
|
|
76
144
|
/**
|
|
77
145
|
* LLM 向け JSON DSL の入力 shape。 YAML DSL と 1:1 対応、 top-level は flat な object。
|
|
@@ -79,12 +147,84 @@ import { compileToCdl } from "./compile";
|
|
|
79
147
|
export interface DragonJson {
|
|
80
148
|
/** 図の title (必須) */
|
|
81
149
|
title: string;
|
|
82
|
-
/**
|
|
83
|
-
|
|
150
|
+
/**
|
|
151
|
+
* preset type (必須)。 受け付ける値は `PRESET_TYPES` と `TYPE_ALIASES` が SSOT。
|
|
152
|
+
*
|
|
153
|
+
* 別名 (#2655 / #2657) は読み取りで図種と語の 2 つに開く = `solidity` と書いた JSON は
|
|
154
|
+
* 順序図 + 種類で並べ替え、`pie` と書いた JSON は 数を描く図 + 円の形 として読まれる。
|
|
155
|
+
*/
|
|
156
|
+
type: PresetType | 図種の別名;
|
|
157
|
+
/** 円グラフの見せ方。 */
|
|
158
|
+
form?: string;
|
|
159
|
+
/** `form` の和名。両方あれば `form` を採る。 */
|
|
160
|
+
見せ方?: string;
|
|
161
|
+
/** ガントチャートの目盛り。 */
|
|
162
|
+
ticks?: string[];
|
|
163
|
+
/** `ticks` の和名。両方あれば `ticks` を採る。 */
|
|
164
|
+
目盛り?: string[];
|
|
165
|
+
/** ガントチャートの今日の線の位置と札。 */
|
|
166
|
+
ganttToday?: { index: number; label: string };
|
|
167
|
+
/** 工程の端とは独立した目盛り名。 */
|
|
168
|
+
ganttTickLabels?: string[];
|
|
169
|
+
ganttBarEnd?: "position";
|
|
170
|
+
ganttBarThickness?: "thin";
|
|
171
|
+
chartSeriesSkipMuted?: DslDocument["chartSeriesSkipMuted"];
|
|
172
|
+
chartLineSeries?: DslDocument["chartLineSeries"];
|
|
173
|
+
chartPieCenterLabel?: DslDocument["chartPieCenterLabel"];
|
|
174
|
+
chartPieTableColumns?: DslDocument["chartPieTableColumns"];
|
|
175
|
+
chartPieRingWidth?: DslDocument["chartPieRingWidth"];
|
|
176
|
+
chartSlopePeriods?: DslDocument["chartSlopePeriods"];
|
|
177
|
+
chartSlopeEmphasisIds?: DslDocument["chartSlopeEmphasisIds"];
|
|
178
|
+
chartSlopeUnit?: DslDocument["chartSlopeUnit"];
|
|
179
|
+
chartStackedPeriods?: DslDocument["chartStackedPeriods"];
|
|
180
|
+
chartStackedRateId?: DslDocument["chartStackedRateId"];
|
|
181
|
+
chartStackedLegendPosition?: DslDocument["chartStackedLegendPosition"];
|
|
182
|
+
chartGaugeValue?: DslDocument["chartGaugeValue"];
|
|
183
|
+
chartWaffleLegendPosition?: DslDocument["chartWaffleLegendPosition"];
|
|
184
|
+
funnelForm?: DslDocument["funnelForm"];
|
|
185
|
+
funnelRate?: DslDocument["funnelRate"];
|
|
186
|
+
quadrantPointLabelSide?: DslDocument["quadrantPointLabelSide"];
|
|
187
|
+
treeNodeForm?: DslDocument["treeNodeForm"];
|
|
188
|
+
treeSize?: DslDocument["treeSize"];
|
|
189
|
+
treeEdgeTone?: DslDocument["treeEdgeTone"];
|
|
190
|
+
treeEdgeHead?: DslDocument["treeEdgeHead"];
|
|
191
|
+
mindForm?: DslDocument["mindForm"];
|
|
192
|
+
mindSize?: DslDocument["mindSize"];
|
|
193
|
+
edgeEndGap?: DslDocument["edgeEndGap"];
|
|
194
|
+
journeyForm?: DslDocument["journeyForm"];
|
|
195
|
+
journeyLineForm?: DslDocument["journeyLineForm"];
|
|
196
|
+
journeyLabels?: DslDocument["journeyLabels"];
|
|
197
|
+
journeySize?: DslDocument["journeySize"];
|
|
198
|
+
sequenceActorForm?: DslDocument["sequenceActorForm"];
|
|
199
|
+
sequenceLabelForm?: DslDocument["sequenceLabelForm"];
|
|
200
|
+
sequenceReturnHead?: DslDocument["sequenceReturnHead"];
|
|
201
|
+
sequenceKindLabels?: DslDocument["sequenceKindLabels"];
|
|
202
|
+
sequenceRowGap?: DslDocument["sequenceRowGap"];
|
|
203
|
+
sequenceGutter?: DslDocument["sequenceGutter"];
|
|
204
|
+
sequenceBandWidth?: DslDocument["sequenceBandWidth"];
|
|
205
|
+
sequenceSelfLoop?: DslDocument["sequenceSelfLoop"];
|
|
206
|
+
figureCard?: DslDocument["figureCard"];
|
|
207
|
+
figureSize?: DslDocument["figureSize"];
|
|
208
|
+
terminalDecoration?: DslDocument["terminalDecoration"];
|
|
209
|
+
cornerForm?: DslDocument["cornerForm"];
|
|
210
|
+
figureKindLabel?: DslDocument["figureKindLabel"];
|
|
211
|
+
figureInset?: DslDocument["figureInset"];
|
|
212
|
+
chartBarSize?: DslDocument["chartBarSize"];
|
|
213
|
+
chartBarShape?: DslDocument["chartBarShape"];
|
|
214
|
+
chartLineSize?: DslDocument["chartLineSize"];
|
|
215
|
+
chartPieSize?: DslDocument["chartPieSize"];
|
|
216
|
+
chartWaffleSize?: DslDocument["chartWaffleSize"];
|
|
217
|
+
chartSlopeLabelTemplate?: DslDocument["chartSlopeLabelTemplate"];
|
|
218
|
+
chartSlopeSize?: DslDocument["chartSlopeSize"];
|
|
219
|
+
chartStackedLayout?: DslDocument["chartStackedLayout"];
|
|
220
|
+
chartGaugeSize?: DslDocument["chartGaugeSize"];
|
|
221
|
+
ganttSize?: DslDocument["ganttSize"];
|
|
222
|
+
quadrantLabelPosition?: DslDocument["quadrantLabelPosition"];
|
|
223
|
+
quadrantSize?: DslDocument["quadrantSize"];
|
|
84
224
|
/**
|
|
85
225
|
* 図表の箱の上に出す小見出し (optional)。 記法の最上位 `eyebrow:` と同じ (#1247)。
|
|
86
226
|
*
|
|
87
|
-
* 効くのは図全体を 1 箱にする図種 (`
|
|
227
|
+
* 効くのは図全体を 1 箱にする図種 (`chart` / `funnel` / `tree` / `journey` /
|
|
88
228
|
* `quadrant` / `mind` / `gantt`) だけ。 箱ごとに分かれる図種では相手が決まらないため、
|
|
89
229
|
* 組み立て側が知らせを出す。 そちらは `actors[].eyebrow` に書く。
|
|
90
230
|
*/
|
|
@@ -98,6 +238,19 @@ export interface DragonJson {
|
|
|
98
238
|
* 他の図種には軸が無いため、書かれていたら組み立て側が知らせる。
|
|
99
239
|
*/
|
|
100
240
|
axes?: JsonAxes;
|
|
241
|
+
/**
|
|
242
|
+
* 2 軸で仕分ける図の区画の名前 (#2667)。 記法の `regions:` と同じ。
|
|
243
|
+
*
|
|
244
|
+
* 鍵は項目の欄と同じ 4 語 (`左上` / `右上` / `左下` / `右下`)。 別の言い方を足すと
|
|
245
|
+
* 同じ区画を 2 通りで呼ぶことになる。
|
|
246
|
+
*/
|
|
247
|
+
regions?: JsonRegions;
|
|
248
|
+
/** 図の凡例の字の大きさ。 */
|
|
249
|
+
legendFontSize?: number;
|
|
250
|
+
legendOffset?: DslDocument["legendOffset"];
|
|
251
|
+
/** 段の箱で全意匠に共通して使う見出しの寸法。 */
|
|
252
|
+
stageHeader?: DslStageHeader;
|
|
253
|
+
stageLayout?: DslDocument["stageLayout"];
|
|
101
254
|
/** 登場人物 (必須): 文字列 or { name, kind, ... } object */
|
|
102
255
|
actors: (string | JsonActor)[];
|
|
103
256
|
/** flow step 配列 (必須): { from, to, label, ... } */
|
|
@@ -174,6 +327,8 @@ export interface DragonJson {
|
|
|
174
327
|
x?: number;
|
|
175
328
|
width?: number;
|
|
176
329
|
label?: string;
|
|
330
|
+
/** 路線図の担当の名札で、名前の下へ出す補足。 */
|
|
331
|
+
subtitle?: string;
|
|
177
332
|
contain?: boolean;
|
|
178
333
|
lifeline?: boolean;
|
|
179
334
|
/**
|
|
@@ -199,16 +354,34 @@ export interface DragonJson {
|
|
|
199
354
|
relations?: RelationFocus;
|
|
200
355
|
/** 図の並ぶ向き (#1494)。 記法の最上位 `direction:` と同じ。 JSON は英語の語で書く */
|
|
201
356
|
direction?: "vertical" | "horizontal";
|
|
202
|
-
/**
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
357
|
+
/** 箱を並べ替える軸 (#2655)。 記法の最上位 `order:` と同じ。 JSON は英語の語で書く */
|
|
358
|
+
order?: "kind";
|
|
359
|
+
/** 図種ごとに選ぶ形 (#2657 / #2797)。 記法の最上位 `shape:` と同じ */
|
|
360
|
+
shape?: DslDiagramShape;
|
|
361
|
+
/** 図の意匠 (#1553 / #2790)。 記法の最上位 `theme:` と同じ。 */
|
|
362
|
+
theme?: string;
|
|
363
|
+
/** `theme` の別名。 両方書いた時は `theme` を使う。 */
|
|
364
|
+
palette?: string;
|
|
365
|
+
/** 図の下へ置く凡例。 印は記法と同じ英語名または和名で書く。 */
|
|
366
|
+
legend?: CdlLegendItem[];
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* 位置を他の要素からの相対で書く指定の、JSON 側の形 (#2039)。
|
|
371
|
+
*
|
|
372
|
+
* 記法の `位置: Web の右 200` を 3 つの欄に開いたもの。 内側の値は `RelativePos` と同じで、
|
|
373
|
+
* 向きの語も同じ 4 つ (`right` / `left` / `above` / `below`) を使う。
|
|
374
|
+
*
|
|
375
|
+
* **向きの語を並べ直さない**。 `RelativeDirection` をそのまま使うことで、本文側に向きを
|
|
376
|
+
* 足した時に JSON 側が黙って取り残される形を作らない。
|
|
377
|
+
*/
|
|
378
|
+
export interface JsonRelativePos {
|
|
379
|
+
/** 基準にする相手の名前。 `actors:` に書かれた名前をそのまま書く */
|
|
380
|
+
anchor: string;
|
|
381
|
+
/** 基準からどちら側に置くか */
|
|
382
|
+
dir: RelativeDirection;
|
|
383
|
+
/** 相手との間隔。 書かなければ `RELATIVE_GAP_DEFAULT` */
|
|
384
|
+
gap?: number;
|
|
212
385
|
}
|
|
213
386
|
|
|
214
387
|
export interface JsonActor {
|
|
@@ -239,14 +412,31 @@ export interface JsonActor {
|
|
|
239
412
|
renderOffsetX?: number | string;
|
|
240
413
|
renderOffsetY?: number | string;
|
|
241
414
|
/**
|
|
242
|
-
* CAR-1657 unified syntax = 既存 NodeKind (
|
|
243
|
-
*
|
|
415
|
+
* CAR-1657 unified syntax = 既存 NodeKind (一覧は `v05/parser.ts` の `NODE_KIND_VALID`) に
|
|
416
|
+
* 加えて parts identifier (arc-gauge 等) を accept する。
|
|
417
|
+
* 未知 kind 値は parts 候補として partId に格納、 compile 側 partsCatalog で解決。
|
|
244
418
|
* LLM structured output の typing 制約を緩めるため union に string 追加。
|
|
245
419
|
* `string & {}` = NodeKind の候補を IDE 補完で提示しつつ任意 string も許容する idiom。
|
|
246
420
|
* 素の `NodeKind | string` は no-redundant-type-constituents に抵触し補完も潰れる (#865)。
|
|
247
421
|
*/
|
|
248
422
|
kind?: NodeKind | (string & {});
|
|
249
423
|
subtitle?: string;
|
|
424
|
+
kindForm?: "icon";
|
|
425
|
+
icon?: DslActor["icon"];
|
|
426
|
+
emptyRowsNote?: string;
|
|
427
|
+
titleAlign?: "center";
|
|
428
|
+
rowRules?: boolean;
|
|
429
|
+
rowStripe?: boolean;
|
|
430
|
+
rowLayout?: DslActor["rowLayout"];
|
|
431
|
+
rowDividerForm?: DslActor["rowDividerForm"];
|
|
432
|
+
rowWell?: DslActor["rowWell"];
|
|
433
|
+
rowMarkForm?: "tone";
|
|
434
|
+
nestIn?: string;
|
|
435
|
+
figureCard?: DslActor["figureCard"];
|
|
436
|
+
stationNamePosition?: "bottom";
|
|
437
|
+
subtitlePlacement?: "right";
|
|
438
|
+
titleFontSize?: number;
|
|
439
|
+
markGap?: number;
|
|
250
440
|
eyebrow?: string;
|
|
251
441
|
value?: string;
|
|
252
442
|
/**
|
|
@@ -257,6 +447,8 @@ export interface JsonActor {
|
|
|
257
447
|
*/
|
|
258
448
|
previous?: string;
|
|
259
449
|
rows?: string[];
|
|
450
|
+
/** 段階ごとの箱で、この箱を入れる段階。 記法の `stage` / `段階` と同じ (#2797)。 */
|
|
451
|
+
stage?: string;
|
|
260
452
|
lane?: string;
|
|
261
453
|
stack?: number;
|
|
262
454
|
initial?: boolean;
|
|
@@ -266,6 +458,17 @@ export interface JsonActor {
|
|
|
266
458
|
* と同じで、読み方は `DslActor.layoutPos` が持つ。
|
|
267
459
|
*/
|
|
268
460
|
pos?: LayoutPos;
|
|
461
|
+
/**
|
|
462
|
+
* 位置を他の要素からの相対で書く指定 (#2039)。 記法の `位置: Web の右 200` と同じ。
|
|
463
|
+
*
|
|
464
|
+
* 記法は 1 本の文字列に「誰の」「どちら側に」「どれだけ離して」 を詰めるが、JSON では
|
|
465
|
+
* 3 つの欄に分ける。 文字列を組み立てて渡す形にすると、書く側が記法の並び順と区切りを
|
|
466
|
+
* 覚える必要が出る = JSON を書くのは主に LLM で、読み取りの規則は欄の名前で示す方が短い。
|
|
467
|
+
*
|
|
468
|
+
* 組み立ての段階で基準の実座標から `posX` / `posY` に直すため、解決した後は座標を直接
|
|
469
|
+
* 書いた時と同じ経路を通る。 `pos` (配置後のずらし幅) とは別の欄。
|
|
470
|
+
*/
|
|
471
|
+
posRel?: JsonRelativePos;
|
|
269
472
|
/**
|
|
270
473
|
* CAR-1657 parts state override (kind = parts identifier 時のみ有効)。
|
|
271
474
|
* LLM JSON DSL では nested 明示 = `{ "state": { "v": 50 } }` が natural、 human 側の
|
|
@@ -301,14 +504,22 @@ export interface JsonActor {
|
|
|
301
504
|
* (記法側と同じ扱い)。
|
|
302
505
|
*/
|
|
303
506
|
end?: string;
|
|
507
|
+
startLabel?: string;
|
|
508
|
+
milestone?: boolean;
|
|
509
|
+
emphasis?: "primary";
|
|
510
|
+
/** 四象限の座標 `[x, y]`。 */
|
|
511
|
+
at?: [number | string, number | string];
|
|
512
|
+
/** `at` の和名。両方あれば `at` を採る。 */
|
|
513
|
+
点の位置?: [number | string, number | string];
|
|
304
514
|
/**
|
|
305
|
-
*
|
|
515
|
+
* ユーザージャーニー (`type: journey`) で、その段階が起きる場所 (#1294)。 記法の `touchpoint:` と同じ。
|
|
306
516
|
*/
|
|
307
517
|
touchpoint?: string;
|
|
308
518
|
/**
|
|
309
|
-
*
|
|
519
|
+
* ユーザージャーニー (`type: journey`) で、その段階の改善の余地 (#1294)。 記法の `opportunity:` と同じ。
|
|
310
520
|
*/
|
|
311
521
|
opportunity?: string;
|
|
522
|
+
opportunityPosition?: DslActor["opportunityPosition"];
|
|
312
523
|
/**
|
|
313
524
|
* 箱を置く絶対座標と大きさ (#1294)。 記法の `posX:` / `posY:` / `posW:` / `posH:` と同じ。
|
|
314
525
|
*
|
|
@@ -325,14 +536,33 @@ export interface JsonActor {
|
|
|
325
536
|
* 読めない項目名として知らせる)。
|
|
326
537
|
*/
|
|
327
538
|
scale?: number;
|
|
539
|
+
/**
|
|
540
|
+
* 見本が持つ段を使うか (#2125)。 記法の `{ kind: wave-gauge, phase: false }` と同じ。
|
|
541
|
+
*
|
|
542
|
+
* 既定 (書かない) は見本の段を書いた段と番号ごとに並べて再生する。 `false` を書くと
|
|
543
|
+
* 見本の段の中身 (点灯と値の変化) を空にし、値は書いた段だけが動かす。
|
|
544
|
+
*
|
|
545
|
+
* 見本 (parts) にしか効かない。 中身は状態の上書き (`state`) に `phase` として入る =
|
|
546
|
+
* `"state": { "phase": false }` と同じ所へ届く。 記法が中括弧の中に 1 つの名前として
|
|
547
|
+
* 書ける以上、JSON でも同じ名前で書けるようにする (入口で書き方が分かれない)。
|
|
548
|
+
*/
|
|
549
|
+
phase?: boolean;
|
|
328
550
|
/** 行頭の印 (#1466)。 行と対で読む。 語の意味は図の種類が決める */
|
|
329
|
-
marks?: string
|
|
551
|
+
marks?: Array<string | { mark: string; tone?: "primary" | "muted" }>;
|
|
330
552
|
}
|
|
331
553
|
|
|
332
554
|
/** 2 軸で仕分ける図の軸の名前 (#1294)。 記法の `axes:` と同じ形 */
|
|
333
555
|
export interface JsonAxes {
|
|
334
|
-
x?: { left?: string; right?: string };
|
|
335
|
-
y?: { bottom?: string; top?: string };
|
|
556
|
+
x?: { left?: string; right?: string; label?: never; direction?: never } | { label: string; direction: true };
|
|
557
|
+
y?: { bottom?: string; top?: string; label?: never; direction?: never } | { label: string; direction: true };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
/** 2 軸で仕分ける図の区画の名前 (#2667)。 記法の `regions:` と同じ形 */
|
|
561
|
+
export interface JsonRegions {
|
|
562
|
+
左上?: string;
|
|
563
|
+
右上?: string;
|
|
564
|
+
左下?: string;
|
|
565
|
+
右下?: string;
|
|
336
566
|
}
|
|
337
567
|
|
|
338
568
|
export interface JsonStep {
|
|
@@ -342,6 +572,8 @@ export interface JsonStep {
|
|
|
342
572
|
sub?: string;
|
|
343
573
|
/** 矢印がどの辺から出るか (#1385)。 記法の `side:` と同じ */
|
|
344
574
|
side?: "top" | "right" | "bottom" | "left";
|
|
575
|
+
fromSide?: "top" | "right" | "bottom" | "left";
|
|
576
|
+
toSide?: "top" | "right" | "bottom" | "left";
|
|
345
577
|
/**
|
|
346
578
|
* 矢印の先の形 (#1462)。 記法の `{ head: triangle }` と同じ。
|
|
347
579
|
*
|
|
@@ -353,7 +585,7 @@ export interface JsonStep {
|
|
|
353
585
|
tailHead?: EdgeHead;
|
|
354
586
|
headFill?: EdgeHeadFill;
|
|
355
587
|
tailHeadFill?: EdgeHeadFill;
|
|
356
|
-
/**
|
|
588
|
+
/** UML 関係の出どころ側の多重度 (#1771 / #2783)。記法の `{ tailSub: "1" }` と同じ */
|
|
357
589
|
tailSub?: string;
|
|
358
590
|
/**
|
|
359
591
|
* 辺の役目 (cdl#618)。 記法の `{ role: main }` と同じ。
|
|
@@ -363,7 +595,7 @@ export interface JsonStep {
|
|
|
363
595
|
role?: "main";
|
|
364
596
|
/** 名前の下地を敷くか (cdl#618)。 記法の `{ labelPlate: false }` と同じ。 既定は敷く */
|
|
365
597
|
labelPlate?: boolean;
|
|
366
|
-
/**
|
|
598
|
+
/** record の UML 関係の語 (#1466 / #2783)。書くと端の形 / 塗り / 線種がまとめて決まる */
|
|
367
599
|
relation?: ClassRelationType;
|
|
368
600
|
/** 順序図の言づての種類 (#1466)。 `call` / `return` / `fire` */
|
|
369
601
|
kind?: SequenceMessageKind;
|
|
@@ -385,6 +617,13 @@ export interface JsonStep {
|
|
|
385
617
|
dashOffsetBind?: string;
|
|
386
618
|
labelOffsetX?: number;
|
|
387
619
|
labelOffsetY?: number;
|
|
620
|
+
posX1?: number;
|
|
621
|
+
posY1?: number;
|
|
622
|
+
posX2?: number;
|
|
623
|
+
posY2?: number;
|
|
624
|
+
waypoints?: DslStep["waypoints"];
|
|
625
|
+
labelCollision?: DslStep["labelCollision"];
|
|
626
|
+
labelSize?: DslStep["labelSize"];
|
|
388
627
|
/** true で説明文を矢印の線の上に重ねる。 分岐図の条件ラベル用。 */
|
|
389
628
|
overlay?: boolean;
|
|
390
629
|
/**
|
|
@@ -408,6 +647,13 @@ export interface JsonPhase {
|
|
|
408
647
|
focus?: string[];
|
|
409
648
|
/** body 説明文 */
|
|
410
649
|
body?: string;
|
|
650
|
+
/**
|
|
651
|
+
* 説明文の別名 (#2621)。 記法が `description:` を受けるので JSON でも受ける。
|
|
652
|
+
*
|
|
653
|
+
* **正の語は `body`**。 両方書いた段は `body` を採る = 正の語を優先すると決めておくと、
|
|
654
|
+
* 読み手が「どちらが効くのか」 を実装を読まずに判断できる。
|
|
655
|
+
*/
|
|
656
|
+
description?: string;
|
|
411
657
|
/** badge label */
|
|
412
658
|
badge?: string;
|
|
413
659
|
/**
|
|
@@ -451,11 +697,13 @@ export interface JsonDslError {
|
|
|
451
697
|
* 受け付ける種類 (`kind`)。 **記法側と同じ集合を使う** (`v05/parser.ts` の `NODE_KIND_VALID`)。
|
|
452
698
|
*
|
|
453
699
|
* ここに無い値は見本 (parts) の名前とみなし、`partId` へ退避して箱を `actor` に倒す
|
|
454
|
-
* (CAR-1657 の unified syntax)。
|
|
700
|
+
* (CAR-1657 の unified syntax)。 つまりこの集合が小さいほど、**書いた種類が箱の見た目に効かなくなる**。
|
|
701
|
+
* 部品を持つ一覧を渡した組み立てでは一覧に無い名前を知らせる (`part-not-found`、#2113) が、
|
|
702
|
+
* 一覧を渡さない組み立てと部品を持たない一覧の組み立てでは `console.warn` にしか残らない。
|
|
455
703
|
*
|
|
456
704
|
* 以前はここに一覧を写していたため、記法に種類が増えても JSON 経路が古い一覧のまま
|
|
457
705
|
* 見本の名前として扱った (実測 = 記法が知る 108 種のうち 77 種が該当、`shape-*` は全滅)。
|
|
458
|
-
*
|
|
706
|
+
* 当時はカタログに無い名前が `console.warn` にしか残らず `onNotice` を呼ばなかったため、
|
|
459
707
|
* 画面には何も出ないまま `actor` の箱が描かれていた (#1293)。
|
|
460
708
|
*
|
|
461
709
|
* 型が 2 箇所に散らばると、必ずどちらかが古くなる。 `VALID_PRESETS` (図種) と同じ扱いにする。
|
|
@@ -470,6 +718,13 @@ const VALID_KIND_SET: ReadonlySet<string> = NODE_KIND_VALID;
|
|
|
470
718
|
*/
|
|
471
719
|
const VALID_PRESETS: readonly PresetType[] = [...PRESET_TYPES];
|
|
472
720
|
|
|
721
|
+
/**
|
|
722
|
+
* 受け付ける図種の別名 (#2655)。 **記法側と同じ表を使う** (`v05/parser.ts` の `TYPE_ALIASES`)。
|
|
723
|
+
*
|
|
724
|
+
* 写すと片方だけ古くなる。 図種の一覧を 1 か所に寄せたのと同じ理由。
|
|
725
|
+
*/
|
|
726
|
+
const VALID_ALIASES: readonly string[] = [...TYPE_ALIASES.keys()];
|
|
727
|
+
|
|
473
728
|
/**
|
|
474
729
|
* 受け付ける項目の一覧 (#1295)。 **知らない項目を誤りにする判定と、公開 schema との
|
|
475
730
|
* 突き合わせが、どちらもここを見る**。
|
|
@@ -487,8 +742,70 @@ export const ACCEPTED_KEYS = {
|
|
|
487
742
|
root: [
|
|
488
743
|
"title",
|
|
489
744
|
"type",
|
|
745
|
+
"form",
|
|
746
|
+
"見せ方",
|
|
747
|
+
"ticks",
|
|
748
|
+
"目盛り",
|
|
749
|
+
"ganttToday",
|
|
750
|
+
"ganttTickLabels",
|
|
751
|
+
"ganttBarEnd",
|
|
752
|
+
"ganttBarThickness",
|
|
753
|
+
"chartSeriesSkipMuted",
|
|
754
|
+
"chartLineSeries",
|
|
755
|
+
"chartPieCenterLabel",
|
|
756
|
+
"chartPieTableColumns",
|
|
757
|
+
"chartPieRingWidth",
|
|
758
|
+
"chartSlopePeriods",
|
|
759
|
+
"chartSlopeEmphasisIds",
|
|
760
|
+
"chartSlopeUnit",
|
|
761
|
+
"chartStackedPeriods",
|
|
762
|
+
"chartStackedRateId",
|
|
763
|
+
"chartStackedLegendPosition",
|
|
764
|
+
"chartGaugeValue",
|
|
765
|
+
"chartWaffleLegendPosition",
|
|
766
|
+
"funnelForm",
|
|
767
|
+
"funnelRate",
|
|
768
|
+
"quadrantPointLabelSide",
|
|
769
|
+
"treeNodeForm",
|
|
770
|
+
"treeSize",
|
|
771
|
+
"treeEdgeTone",
|
|
772
|
+
"treeEdgeHead",
|
|
773
|
+
"mindForm",
|
|
774
|
+
"mindSize",
|
|
775
|
+
"edgeEndGap",
|
|
776
|
+
"journeyForm",
|
|
777
|
+
"journeyLineForm",
|
|
778
|
+
"journeyLabels",
|
|
779
|
+
"journeySize",
|
|
780
|
+
"sequenceActorForm",
|
|
781
|
+
"sequenceLabelForm",
|
|
782
|
+
"sequenceReturnHead",
|
|
783
|
+
"sequenceKindLabels",
|
|
784
|
+
"sequenceRowGap",
|
|
785
|
+
"sequenceGutter",
|
|
786
|
+
"sequenceBandWidth",
|
|
787
|
+
"sequenceSelfLoop",
|
|
788
|
+
"figureCard",
|
|
789
|
+
"figureSize",
|
|
790
|
+
"terminalDecoration",
|
|
791
|
+
"cornerForm",
|
|
792
|
+
"figureKindLabel",
|
|
793
|
+
"figureInset",
|
|
794
|
+
"chartBarSize",
|
|
795
|
+
"chartBarShape",
|
|
796
|
+
"chartLineSize",
|
|
797
|
+
"chartPieSize",
|
|
798
|
+
"chartWaffleSize",
|
|
799
|
+
"chartSlopeLabelTemplate",
|
|
800
|
+
"chartSlopeSize",
|
|
801
|
+
"chartStackedLayout",
|
|
802
|
+
"chartGaugeSize",
|
|
803
|
+
"ganttSize",
|
|
804
|
+
"quadrantLabelPosition",
|
|
805
|
+
"quadrantSize",
|
|
490
806
|
"eyebrow",
|
|
491
807
|
"axes",
|
|
808
|
+
"regions",
|
|
492
809
|
"actors",
|
|
493
810
|
"flow",
|
|
494
811
|
"states",
|
|
@@ -514,18 +831,45 @@ export const ACCEPTED_KEYS = {
|
|
|
514
831
|
"relations",
|
|
515
832
|
// 図の並ぶ向き (#1494)
|
|
516
833
|
"direction",
|
|
517
|
-
//
|
|
834
|
+
// 箱を並べ替える軸 (#2655)
|
|
835
|
+
"order",
|
|
836
|
+
// 数を描く図の形 (#2657)
|
|
837
|
+
"shape",
|
|
838
|
+
// 図の意匠とその別名 (#1553 / #2790)
|
|
839
|
+
"theme",
|
|
518
840
|
"palette",
|
|
841
|
+
"legend",
|
|
842
|
+
"legendFontSize",
|
|
843
|
+
"legendOffset",
|
|
844
|
+
"stageHeader",
|
|
845
|
+
"stageLayout",
|
|
519
846
|
],
|
|
520
847
|
actor: [
|
|
521
848
|
"name",
|
|
522
849
|
"kind",
|
|
523
850
|
"subtitle",
|
|
851
|
+
"kindForm",
|
|
852
|
+
"icon",
|
|
853
|
+
"emptyRowsNote",
|
|
854
|
+
"titleAlign",
|
|
855
|
+
"rowRules",
|
|
856
|
+
"rowStripe",
|
|
857
|
+
"rowLayout",
|
|
858
|
+
"rowDividerForm",
|
|
859
|
+
"rowWell",
|
|
860
|
+
"rowMarkForm",
|
|
861
|
+
"nestIn",
|
|
862
|
+
"figureCard",
|
|
863
|
+
"stationNamePosition",
|
|
864
|
+
"subtitlePlacement",
|
|
865
|
+
"titleFontSize",
|
|
866
|
+
"markGap",
|
|
524
867
|
"eyebrow",
|
|
525
868
|
"value",
|
|
526
869
|
// 前の時点の値 (#1450)。 内訳の変化を帯で示す図と、値 1 つを大きく示す図が読む
|
|
527
870
|
"previous",
|
|
528
871
|
"rows",
|
|
872
|
+
"stage",
|
|
529
873
|
"lane",
|
|
530
874
|
"stack",
|
|
531
875
|
"initial",
|
|
@@ -534,8 +878,14 @@ export const ACCEPTED_KEYS = {
|
|
|
534
878
|
"color",
|
|
535
879
|
"owner",
|
|
536
880
|
"end",
|
|
881
|
+
"startLabel",
|
|
882
|
+
"milestone",
|
|
883
|
+
"emphasis",
|
|
884
|
+
"at",
|
|
885
|
+
"点の位置",
|
|
537
886
|
"touchpoint",
|
|
538
887
|
"opportunity",
|
|
888
|
+
"opportunityPosition",
|
|
539
889
|
"posX",
|
|
540
890
|
"posY",
|
|
541
891
|
"posW",
|
|
@@ -557,6 +907,10 @@ export const ACCEPTED_KEYS = {
|
|
|
557
907
|
"renderOffsetY",
|
|
558
908
|
// 行頭の印 (#1466)。 行ごとに 1 つ、図の種類ごとの語で書く
|
|
559
909
|
"marks",
|
|
910
|
+
// 位置を他の要素からの相対で書く指定 (#2039)
|
|
911
|
+
"posRel",
|
|
912
|
+
// 見本が持つ段を使うか (#2125)
|
|
913
|
+
"phase",
|
|
560
914
|
],
|
|
561
915
|
step: [
|
|
562
916
|
"from",
|
|
@@ -565,13 +919,15 @@ export const ACCEPTED_KEYS = {
|
|
|
565
919
|
"sub",
|
|
566
920
|
// 矢印がどの辺から出るか (#1385)
|
|
567
921
|
"side",
|
|
922
|
+
"fromSide",
|
|
923
|
+
"toSide",
|
|
568
924
|
// 矢印の先の形 (#1462)
|
|
569
925
|
"head",
|
|
570
926
|
// 端の印の残り 3 欄と、関係の語 / 言づての種類 (#1466)
|
|
571
927
|
"tailHead",
|
|
572
928
|
"headFill",
|
|
573
929
|
"tailHeadFill",
|
|
574
|
-
//
|
|
930
|
+
// UML 関係の出どころ側の多重度 (#1771 / #2783)
|
|
575
931
|
"tailSub",
|
|
576
932
|
"relation",
|
|
577
933
|
// 辺の役目と名前の下地 (cdl#618)
|
|
@@ -588,20 +944,32 @@ export const ACCEPTED_KEYS = {
|
|
|
588
944
|
"dashOffsetBind",
|
|
589
945
|
"labelOffsetX",
|
|
590
946
|
"labelOffsetY",
|
|
947
|
+
"posX1",
|
|
948
|
+
"posY1",
|
|
949
|
+
"posX2",
|
|
950
|
+
"posY2",
|
|
951
|
+
"waypoints",
|
|
952
|
+
"labelCollision",
|
|
953
|
+
"labelSize",
|
|
591
954
|
"overlay",
|
|
592
955
|
"pos",
|
|
593
956
|
// 部品の端で繋ぐ要素の id (#1979)
|
|
594
957
|
"fromPartNode",
|
|
595
958
|
"toPartNode",
|
|
596
959
|
],
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
960
|
+
// 説明の語は `PHASE_BODY_KEYS` から導く (#2621)。 書き写すと記法の入口とずれる
|
|
961
|
+
phase: ["step", "duration", "focus", ...PHASE_BODY_KEYS, "badge", "tween", "set", "draw", "drawRatio"],
|
|
962
|
+
viewport: ["width", "height", "contentOffsetX", "scale", "laneWidth", "gap", "laneGap", "nodeGap", "labelMargin"],
|
|
963
|
+
lane: ["x", "width", "label", "subtitle", "contain", "lifeline", "pos"],
|
|
600
964
|
group: ["label", "lanes"],
|
|
601
965
|
axes: ["x", "y"],
|
|
602
|
-
axesX: ["left", "right"],
|
|
603
|
-
axesY: ["bottom", "top"],
|
|
966
|
+
axesX: ["left", "right", "label", "direction"],
|
|
967
|
+
axesY: ["bottom", "top", "label", "direction"],
|
|
968
|
+
// 区画を指す語は項目の欄と同じ 4 語 (#2667)
|
|
969
|
+
regions: ["左上", "右上", "左下", "右下"],
|
|
604
970
|
layoutPos: ["x", "y"],
|
|
971
|
+
// 位置を他の要素からの相対で書く指定 (#2039)。 間隔は書かなくてよい
|
|
972
|
+
posRel: ["anchor", "dir", "gap"],
|
|
605
973
|
} as const satisfies Record<string, readonly string[]>;
|
|
606
974
|
|
|
607
975
|
/** 受ける項目を持つ階層の名前 */
|
|
@@ -643,8 +1011,18 @@ export type 欄の型 =
|
|
|
643
1011
|
| "線種"
|
|
644
1012
|
| "辺"
|
|
645
1013
|
| "端の形"
|
|
1014
|
+
// 相対で置く時の向き (#2039)。 書けば必ず要る = 向きが無いとどちら側か決まらない
|
|
1015
|
+
| "必須の向き"
|
|
646
1016
|
| "色か色番号"
|
|
647
1017
|
| "描くもの"
|
|
1018
|
+
| "意匠"
|
|
1019
|
+
| "図の形"
|
|
1020
|
+
| "ガントの帯の終わり"
|
|
1021
|
+
| "ガントの帯の厚み"
|
|
1022
|
+
| "札の角"
|
|
1023
|
+
| "四象限の区画名位置"
|
|
1024
|
+
| "線札の衝突"
|
|
1025
|
+
| "主役"
|
|
648
1026
|
| "必須の図種"
|
|
649
1027
|
| "object"
|
|
650
1028
|
| "並び"
|
|
@@ -655,8 +1033,70 @@ export const 欄の型表 = {
|
|
|
655
1033
|
root: {
|
|
656
1034
|
title: "必須の非空文字列",
|
|
657
1035
|
type: "必須の図種",
|
|
1036
|
+
form: "非空の文字列",
|
|
1037
|
+
見せ方: "非空の文字列",
|
|
1038
|
+
ticks: "文字列の並び",
|
|
1039
|
+
目盛り: "文字列の並び",
|
|
1040
|
+
ganttToday: "object",
|
|
1041
|
+
ganttTickLabels: "文字列の並び",
|
|
1042
|
+
ganttBarEnd: "ガントの帯の終わり",
|
|
1043
|
+
ganttBarThickness: "ガントの帯の厚み",
|
|
1044
|
+
chartSeriesSkipMuted: "真偽",
|
|
1045
|
+
chartLineSeries: "並び",
|
|
1046
|
+
chartPieCenterLabel: "文字列",
|
|
1047
|
+
chartPieTableColumns: "文字列",
|
|
1048
|
+
chartPieRingWidth: "文字列",
|
|
1049
|
+
chartSlopePeriods: "文字列の並び",
|
|
1050
|
+
chartSlopeEmphasisIds: "文字列の並び",
|
|
1051
|
+
chartSlopeUnit: "文字列",
|
|
1052
|
+
chartStackedPeriods: "並び",
|
|
1053
|
+
chartStackedRateId: "文字列",
|
|
1054
|
+
chartStackedLegendPosition: "文字列",
|
|
1055
|
+
chartGaugeValue: "object",
|
|
1056
|
+
chartWaffleLegendPosition: "文字列",
|
|
1057
|
+
funnelForm: "文字列",
|
|
1058
|
+
funnelRate: "文字列",
|
|
1059
|
+
quadrantPointLabelSide: "文字列",
|
|
1060
|
+
treeNodeForm: "文字列",
|
|
1061
|
+
treeSize: "object",
|
|
1062
|
+
treeEdgeTone: "文字列",
|
|
1063
|
+
treeEdgeHead: "文字列",
|
|
1064
|
+
mindForm: "文字列",
|
|
1065
|
+
mindSize: "object",
|
|
1066
|
+
edgeEndGap: "数",
|
|
1067
|
+
journeyForm: "文字列",
|
|
1068
|
+
journeyLineForm: "文字列",
|
|
1069
|
+
journeyLabels: "object",
|
|
1070
|
+
journeySize: "object",
|
|
1071
|
+
sequenceActorForm: "非空の文字列",
|
|
1072
|
+
sequenceLabelForm: "非空の文字列",
|
|
1073
|
+
sequenceReturnHead: "非空の文字列",
|
|
1074
|
+
sequenceKindLabels: "object",
|
|
1075
|
+
sequenceRowGap: "数",
|
|
1076
|
+
sequenceGutter: "数",
|
|
1077
|
+
sequenceBandWidth: "数",
|
|
1078
|
+
sequenceSelfLoop: "object",
|
|
1079
|
+
figureCard: "object",
|
|
1080
|
+
figureSize: "object",
|
|
1081
|
+
terminalDecoration: "object",
|
|
1082
|
+
cornerForm: "札の角",
|
|
1083
|
+
figureKindLabel: "object",
|
|
1084
|
+
figureInset: "object",
|
|
1085
|
+
chartBarSize: "object",
|
|
1086
|
+
chartBarShape: "非空の文字列",
|
|
1087
|
+
chartLineSize: "object",
|
|
1088
|
+
chartPieSize: "object",
|
|
1089
|
+
chartWaffleSize: "object",
|
|
1090
|
+
chartSlopeLabelTemplate: "object",
|
|
1091
|
+
chartSlopeSize: "object",
|
|
1092
|
+
chartStackedLayout: "object",
|
|
1093
|
+
chartGaugeSize: "object",
|
|
1094
|
+
ganttSize: "object",
|
|
1095
|
+
quadrantLabelPosition: "四象限の区画名位置",
|
|
1096
|
+
quadrantSize: "object",
|
|
658
1097
|
eyebrow: "文字列",
|
|
659
1098
|
axes: "object",
|
|
1099
|
+
regions: "object",
|
|
660
1100
|
actors: "必須の非空の並び",
|
|
661
1101
|
flow: "必須の並び",
|
|
662
1102
|
states: "object",
|
|
@@ -682,17 +1122,44 @@ export const 欄の型表 = {
|
|
|
682
1122
|
relations: "非空の文字列",
|
|
683
1123
|
// 図の並ぶ向き (#1494)
|
|
684
1124
|
direction: "非空の文字列",
|
|
685
|
-
//
|
|
686
|
-
|
|
1125
|
+
// 箱を並べ替える軸 (#2655)
|
|
1126
|
+
order: "非空の文字列",
|
|
1127
|
+
// 図種ごとの形 (#2657 / #2797)
|
|
1128
|
+
shape: "図の形",
|
|
1129
|
+
// 図の意匠とその別名 (#1553 / #2790)
|
|
1130
|
+
theme: "意匠",
|
|
1131
|
+
palette: "意匠",
|
|
1132
|
+
legend: "並び",
|
|
1133
|
+
legendFontSize: "数",
|
|
1134
|
+
legendOffset: "object",
|
|
1135
|
+
stageHeader: "object",
|
|
1136
|
+
stageLayout: "object",
|
|
687
1137
|
},
|
|
688
1138
|
actor: {
|
|
689
1139
|
name: "必須の非空文字列",
|
|
690
1140
|
kind: "非空の文字列",
|
|
691
1141
|
subtitle: "文字列",
|
|
1142
|
+
kindForm: "非空の文字列",
|
|
1143
|
+
icon: "非空の文字列",
|
|
1144
|
+
emptyRowsNote: "文字列",
|
|
1145
|
+
titleAlign: "非空の文字列",
|
|
1146
|
+
rowRules: "真偽",
|
|
1147
|
+
rowStripe: "真偽",
|
|
1148
|
+
rowLayout: "object",
|
|
1149
|
+
rowDividerForm: "非空の文字列",
|
|
1150
|
+
rowWell: "object",
|
|
1151
|
+
rowMarkForm: "非空の文字列",
|
|
1152
|
+
nestIn: "非空の文字列",
|
|
1153
|
+
figureCard: "object",
|
|
1154
|
+
stationNamePosition: "非空の文字列",
|
|
1155
|
+
subtitlePlacement: "非空の文字列",
|
|
1156
|
+
titleFontSize: "数",
|
|
1157
|
+
markGap: "数",
|
|
692
1158
|
eyebrow: "文字列",
|
|
693
1159
|
value: "文字列",
|
|
694
1160
|
previous: "文字列",
|
|
695
1161
|
rows: "文字列の並び",
|
|
1162
|
+
stage: "非空の文字列",
|
|
696
1163
|
lane: "文字列",
|
|
697
1164
|
stack: "数",
|
|
698
1165
|
initial: "真偽",
|
|
@@ -701,8 +1168,14 @@ export const 欄の型表 = {
|
|
|
701
1168
|
color: "色か色番号",
|
|
702
1169
|
owner: "文字列",
|
|
703
1170
|
end: "文字列",
|
|
1171
|
+
startLabel: "文字列",
|
|
1172
|
+
milestone: "真偽",
|
|
1173
|
+
emphasis: "主役",
|
|
1174
|
+
at: "並び",
|
|
1175
|
+
点の位置: "並び",
|
|
704
1176
|
touchpoint: "文字列",
|
|
705
1177
|
opportunity: "文字列",
|
|
1178
|
+
opportunityPosition: "文字列",
|
|
706
1179
|
posX: "数",
|
|
707
1180
|
posY: "数",
|
|
708
1181
|
posW: "数",
|
|
@@ -723,7 +1196,11 @@ export const 欄の型表 = {
|
|
|
723
1196
|
renderOffsetX: "数か文字列",
|
|
724
1197
|
renderOffsetY: "数か文字列",
|
|
725
1198
|
// 行頭の印 (#1466)
|
|
726
|
-
marks: "
|
|
1199
|
+
marks: "並び",
|
|
1200
|
+
// 相対で置く指定 (#2039)。 中身の形は `validateRelativePos` が見る
|
|
1201
|
+
posRel: "object",
|
|
1202
|
+
// 見本が持つ段を使うか (#2125)
|
|
1203
|
+
phase: "真偽",
|
|
727
1204
|
},
|
|
728
1205
|
step: {
|
|
729
1206
|
from: "必須の文字列",
|
|
@@ -732,12 +1209,14 @@ export const 欄の型表 = {
|
|
|
732
1209
|
sub: "文字列",
|
|
733
1210
|
// 矢印がどの辺から出るか (#1385)
|
|
734
1211
|
side: "辺",
|
|
1212
|
+
fromSide: "辺",
|
|
1213
|
+
toSide: "辺",
|
|
735
1214
|
head: "端の形",
|
|
736
1215
|
// 端の印の残り 3 欄と、関係の語 / 言づての種類 (#1466)
|
|
737
1216
|
tailHead: "端の形",
|
|
738
1217
|
headFill: "非空の文字列",
|
|
739
1218
|
tailHeadFill: "非空の文字列",
|
|
740
|
-
//
|
|
1219
|
+
// UML 関係の出どころ側の多重度 (#1771 / #2783)
|
|
741
1220
|
tailSub: "文字列",
|
|
742
1221
|
relation: "非空の文字列",
|
|
743
1222
|
// 辺の役目と名前の下地 (cdl#618)
|
|
@@ -754,6 +1233,13 @@ export const 欄の型表 = {
|
|
|
754
1233
|
dashOffsetBind: "非空の文字列",
|
|
755
1234
|
labelOffsetX: "数",
|
|
756
1235
|
labelOffsetY: "数",
|
|
1236
|
+
posX1: "数",
|
|
1237
|
+
posY1: "数",
|
|
1238
|
+
posX2: "数",
|
|
1239
|
+
posY2: "数",
|
|
1240
|
+
waypoints: "並び",
|
|
1241
|
+
labelCollision: "線札の衝突",
|
|
1242
|
+
labelSize: "object",
|
|
757
1243
|
overlay: "真偽",
|
|
758
1244
|
pos: "object",
|
|
759
1245
|
// 部品の端で繋ぐ要素の id (#1979)。 空の id は要素を指せないので受けない
|
|
@@ -764,7 +1250,9 @@ export const 欄の型表 = {
|
|
|
764
1250
|
step: "必須の非空文字列",
|
|
765
1251
|
duration: "数",
|
|
766
1252
|
focus: "文字列の並び",
|
|
1253
|
+
// 説明は正の語と別名のどちらでも同じ型。 表の欄は `ACCEPTED_KEYS.phase` と揃える (#2621)
|
|
767
1254
|
body: "文字列",
|
|
1255
|
+
description: "文字列",
|
|
768
1256
|
badge: "文字列",
|
|
769
1257
|
tween: "object",
|
|
770
1258
|
set: "object",
|
|
@@ -774,6 +1262,7 @@ export const 欄の型表 = {
|
|
|
774
1262
|
viewport: {
|
|
775
1263
|
width: "数",
|
|
776
1264
|
height: "数",
|
|
1265
|
+
contentOffsetX: "数",
|
|
777
1266
|
scale: "数",
|
|
778
1267
|
laneWidth: "数",
|
|
779
1268
|
gap: "数",
|
|
@@ -785,16 +1274,20 @@ export const 欄の型表 = {
|
|
|
785
1274
|
x: "数",
|
|
786
1275
|
width: "数",
|
|
787
1276
|
label: "文字列",
|
|
1277
|
+
subtitle: "文字列",
|
|
788
1278
|
contain: "真偽",
|
|
789
1279
|
lifeline: "真偽",
|
|
790
1280
|
pos: "object",
|
|
791
1281
|
},
|
|
792
1282
|
group: { label: "文字列", lanes: "文字列の並び" },
|
|
793
1283
|
axes: { x: "object", y: "object" },
|
|
794
|
-
axesX: { left: "文字列", right: "文字列" },
|
|
795
|
-
axesY: { bottom: "文字列", top: "文字列" },
|
|
1284
|
+
axesX: { left: "文字列", right: "文字列", label: "文字列", direction: "真偽" },
|
|
1285
|
+
axesY: { bottom: "文字列", top: "文字列", label: "文字列", direction: "真偽" },
|
|
1286
|
+
regions: { 左上: "文字列", 右上: "文字列", 左下: "文字列", 右下: "文字列" },
|
|
796
1287
|
// 位置は書けば x と y の両方が要る。 片方だけでは寄せ幅が決まらない
|
|
797
1288
|
layoutPos: { x: "必須の数", y: "必須の数" },
|
|
1289
|
+
// 相対で置く指定 (#2039)。 相手と向きは必ず要り、間隔は書かなければ既定で置く
|
|
1290
|
+
posRel: { anchor: "必須の非空文字列", dir: "必須の向き", gap: "数" },
|
|
798
1291
|
} as const satisfies {
|
|
799
1292
|
[層 in 階層]: { [欄 in (typeof ACCEPTED_KEYS)[層][number]]: 欄の型 };
|
|
800
1293
|
};
|
|
@@ -839,6 +1332,8 @@ function JSONの色名か(v: string): boolean {
|
|
|
839
1332
|
export const 見本にしか効かない欄 = [
|
|
840
1333
|
"state",
|
|
841
1334
|
"scale",
|
|
1335
|
+
// 見本が持つ段を使うか (#2125)。 普通の箱は自分の段を持たないので効かない
|
|
1336
|
+
"phase",
|
|
842
1337
|
] as const satisfies readonly (typeof ACCEPTED_KEYS.actor)[number][];
|
|
843
1338
|
|
|
844
1339
|
/**
|
|
@@ -860,10 +1355,28 @@ export const 見本にしか効かない欄 = [
|
|
|
860
1355
|
*/
|
|
861
1356
|
export const 見本に効かない欄 = [
|
|
862
1357
|
"tone",
|
|
1358
|
+
"kindForm",
|
|
1359
|
+
"icon",
|
|
1360
|
+
"emptyRowsNote",
|
|
1361
|
+
"titleAlign",
|
|
1362
|
+
"rowRules",
|
|
1363
|
+
"rowStripe",
|
|
1364
|
+
"rowLayout",
|
|
1365
|
+
"rowDividerForm",
|
|
1366
|
+
"rowWell",
|
|
1367
|
+
"rowMarkForm",
|
|
1368
|
+
"nestIn",
|
|
1369
|
+
"figureCard",
|
|
1370
|
+
"at",
|
|
863
1371
|
"owner",
|
|
864
1372
|
"end",
|
|
1373
|
+
"startLabel",
|
|
1374
|
+
"milestone",
|
|
1375
|
+
"emphasis",
|
|
865
1376
|
"touchpoint",
|
|
866
1377
|
"opportunity",
|
|
1378
|
+
"opportunityPosition",
|
|
1379
|
+
"stage",
|
|
867
1380
|
// 箱の中に描く図形 (#1374)。 見本は自分の形を持つため、外から図形を差し替えられない
|
|
868
1381
|
"shape",
|
|
869
1382
|
// その箱を出すかどうかの条件 (#1381)。 見本は自分の出方を持つ
|
|
@@ -962,6 +1475,36 @@ function 値を検査(
|
|
|
962
1475
|
if (v === undefined) return;
|
|
963
1476
|
if (typeof v !== "boolean") 型違い("true or false if present");
|
|
964
1477
|
return;
|
|
1478
|
+
case "ガントの帯の終わり":
|
|
1479
|
+
if (v === undefined) return;
|
|
1480
|
+
if (v !== "position") 型違い('"position" if present');
|
|
1481
|
+
return;
|
|
1482
|
+
case "ガントの帯の厚み":
|
|
1483
|
+
if (v === undefined) return;
|
|
1484
|
+
if (v !== "thin") 型違い('"thin" if present');
|
|
1485
|
+
return;
|
|
1486
|
+
case "札の角":
|
|
1487
|
+
if (v === undefined) return;
|
|
1488
|
+
if (!(CORNER_FORM_VALUES as readonly unknown[]).includes(v)) {
|
|
1489
|
+
型違い(`one of: ${CORNER_FORM_VALUES.join(", ")}`);
|
|
1490
|
+
}
|
|
1491
|
+
return;
|
|
1492
|
+
case "四象限の区画名位置":
|
|
1493
|
+
if (v === undefined) return;
|
|
1494
|
+
if (!(QUADRANT_LABEL_POSITION_VALUES as readonly unknown[]).includes(v)) {
|
|
1495
|
+
型違い(`one of: ${QUADRANT_LABEL_POSITION_VALUES.join(", ")}`);
|
|
1496
|
+
}
|
|
1497
|
+
return;
|
|
1498
|
+
case "線札の衝突":
|
|
1499
|
+
if (v === undefined) return;
|
|
1500
|
+
if (!(LABEL_COLLISION_VALUES as readonly unknown[]).includes(v)) {
|
|
1501
|
+
型違い(`one of: ${LABEL_COLLISION_VALUES.join(", ")}`);
|
|
1502
|
+
}
|
|
1503
|
+
return;
|
|
1504
|
+
case "主役":
|
|
1505
|
+
if (v === undefined) return;
|
|
1506
|
+
if (v !== "primary") 型違い('"primary" if present');
|
|
1507
|
+
return;
|
|
965
1508
|
case "色":
|
|
966
1509
|
if (v === undefined) return;
|
|
967
1510
|
if (typeof v !== "string") {
|
|
@@ -997,6 +1540,17 @@ function 値を検査(
|
|
|
997
1540
|
});
|
|
998
1541
|
}
|
|
999
1542
|
return;
|
|
1543
|
+
case "必須の向き":
|
|
1544
|
+
// 受ける語は記法と同じ一覧を見る (`RELATIVE_DIRECTIONS`)。 写すと向きが増えた時に
|
|
1545
|
+
// 片方だけ古くなる
|
|
1546
|
+
if (typeof v !== "string" || !(RELATIVE_DIRECTIONS as readonly string[]).includes(v)) {
|
|
1547
|
+
errors.push({
|
|
1548
|
+
path,
|
|
1549
|
+
message: `${名前} must be one of: ${RELATIVE_DIRECTIONS.join(", ")}`,
|
|
1550
|
+
hint: typeof v === "string" ? `got "${v}"` : `got ${v === undefined ? "nothing" : typeof v}`,
|
|
1551
|
+
});
|
|
1552
|
+
}
|
|
1553
|
+
return;
|
|
1000
1554
|
case "端の形":
|
|
1001
1555
|
if (v === undefined) return;
|
|
1002
1556
|
// 受ける語は記法と同じ一覧を見る (`EDGE_HEAD_VALUES`)。 写すと描画側が形を増やした時に
|
|
@@ -1020,6 +1574,28 @@ function 値を検査(
|
|
|
1020
1574
|
});
|
|
1021
1575
|
}
|
|
1022
1576
|
return;
|
|
1577
|
+
case "意匠":
|
|
1578
|
+
if (v === undefined) return;
|
|
1579
|
+
if (typeof v !== "string" || resolveTheme(v) === null) {
|
|
1580
|
+
errors.push({
|
|
1581
|
+
path,
|
|
1582
|
+
message: `${名前} must be one of: ${THEMES.join(", ")}`,
|
|
1583
|
+
hint: `使える語 = ${Object.keys(THEME_ALIAS).join(" / ")} (got ${
|
|
1584
|
+
typeof v === "string" ? JSON.stringify(v) : typeof v
|
|
1585
|
+
})`,
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
return;
|
|
1589
|
+
case "図の形":
|
|
1590
|
+
if (v === undefined) return;
|
|
1591
|
+
if (typeof v !== "string" || resolveDiagramShape(v) === null) {
|
|
1592
|
+
errors.push({
|
|
1593
|
+
path,
|
|
1594
|
+
message: `${名前} must be one of: ${Object.values(DIAGRAM_SHAPES).flat().join(", ")}`,
|
|
1595
|
+
hint: `使える語 = ${Object.values(DIAGRAM_SHAPES).flat().join(" / ")}`,
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
return;
|
|
1023
1599
|
case "色か色番号":
|
|
1024
1600
|
if (v === undefined) return;
|
|
1025
1601
|
if (typeof v !== "string") {
|
|
@@ -1046,10 +1622,13 @@ function 値を検査(
|
|
|
1046
1622
|
}
|
|
1047
1623
|
return;
|
|
1048
1624
|
case "必須の図種":
|
|
1049
|
-
if (
|
|
1625
|
+
if (
|
|
1626
|
+
typeof v !== "string" ||
|
|
1627
|
+
!(VALID_PRESETS.includes(v as PresetType) || VALID_ALIASES.includes(v))
|
|
1628
|
+
) {
|
|
1050
1629
|
errors.push({
|
|
1051
1630
|
path,
|
|
1052
|
-
message: `${名前} must be one of: ${VALID_PRESETS.join(", ")}`,
|
|
1631
|
+
message: `${名前} must be one of: ${[...VALID_PRESETS, ...VALID_ALIASES].join(", ")}`,
|
|
1053
1632
|
hint: typeof v === "string" ? `got "${v}"` : undefined,
|
|
1054
1633
|
});
|
|
1055
1634
|
}
|
|
@@ -1086,48 +1665,6 @@ function 表で検査(
|
|
|
1086
1665
|
}
|
|
1087
1666
|
}
|
|
1088
1667
|
|
|
1089
|
-
/**
|
|
1090
|
-
* 綴り違いの候補を返す (#1295)。
|
|
1091
|
-
*
|
|
1092
|
-
* 「知らない項目です」 だけだと、`animations` と書いた人は正しい綴りを探しに行く必要がある。
|
|
1093
|
-
* 1 文字の違い (足りない / 多い / 入れ替わり / 別の字) までを候補とする。
|
|
1094
|
-
*
|
|
1095
|
-
* 遠い名前は勧めない。 無関係な項目名を勧めると、書いた人がそちらへ直して二度手間になる。
|
|
1096
|
-
*/
|
|
1097
|
-
function 近い項目名(key: string, 候補: readonly string[]): string | undefined {
|
|
1098
|
-
const 小文字 = key.toLowerCase();
|
|
1099
|
-
let 最短: { 名: string; 距離: number } | undefined;
|
|
1100
|
-
for (const c of 候補) {
|
|
1101
|
-
const d = 編集距離(小文字, c.toLowerCase(), 2);
|
|
1102
|
-
if (d <= 2 && (最短 === undefined || d < 最短.距離)) 最短 = { 名: c, 距離: d };
|
|
1103
|
-
}
|
|
1104
|
-
return 最短?.名;
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
/**
|
|
1108
|
-
* 2 つの語の編集距離 (上限付き)。
|
|
1109
|
-
*
|
|
1110
|
-
* 上限を持つのは、長い語どうしで表を全部埋めないため。 上限を超えた時点で打ち切る。
|
|
1111
|
-
*/
|
|
1112
|
-
function 編集距離(a: string, b: string, 上限: number): number {
|
|
1113
|
-
if (Math.abs(a.length - b.length) > 上限) return 上限 + 1;
|
|
1114
|
-
let 前 = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
1115
|
-
for (let i = 1; i <= a.length; i += 1) {
|
|
1116
|
-
const 今: number[] = [i];
|
|
1117
|
-
let 行の最小 = i;
|
|
1118
|
-
for (let j = 1; j <= b.length; j += 1) {
|
|
1119
|
-
const 費用 = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
1120
|
-
const v = Math.min(今[j - 1]! + 1, 前[j]! + 1, 前[j - 1]! + 費用);
|
|
1121
|
-
今.push(v);
|
|
1122
|
-
if (v < 行の最小) 行の最小 = v;
|
|
1123
|
-
}
|
|
1124
|
-
// その行の最小が上限を超えたら、以降どう進んでも上限以下にはならない
|
|
1125
|
-
if (行の最小 > 上限) return 上限 + 1;
|
|
1126
|
-
前 = 今;
|
|
1127
|
-
}
|
|
1128
|
-
return 前[b.length]!;
|
|
1129
|
-
}
|
|
1130
|
-
|
|
1131
1668
|
/**
|
|
1132
1669
|
* 知らない項目を誤りとして積む (#1295)。
|
|
1133
1670
|
*
|
|
@@ -1139,7 +1676,7 @@ function checkUnknownKeys(v: unknown, 層: 階層, path: string, errors: JsonDsl
|
|
|
1139
1676
|
const 受ける = ACCEPTED_KEYS[層] as readonly string[];
|
|
1140
1677
|
for (const key of Object.keys(v)) {
|
|
1141
1678
|
if (受ける.includes(key)) continue;
|
|
1142
|
-
const 候補 =
|
|
1679
|
+
const 候補 = 近い名前(key, 受ける);
|
|
1143
1680
|
errors.push({
|
|
1144
1681
|
path: `${path}.${key}`,
|
|
1145
1682
|
message: `unknown key "${key}"`,
|
|
@@ -1148,10 +1685,6 @@ function checkUnknownKeys(v: unknown, 層: 階層, path: string, errors: JsonDsl
|
|
|
1148
1685
|
}
|
|
1149
1686
|
}
|
|
1150
1687
|
|
|
1151
|
-
/**
|
|
1152
|
-
* shape validation。 layer 1 = 必須 field + 型 check、 layer 2 は compile 側の validation に委譲。
|
|
1153
|
-
* fail-fast ではなく全 error 収集して返す (LLM に一括で修正させるため)。
|
|
1154
|
-
*/
|
|
1155
1688
|
/**
|
|
1156
1689
|
* CAR-1693 Phase 1: DSL 表面 `pos: {x, y}` の型 check helper。 finite number pair を必須にし、
|
|
1157
1690
|
* `NaN` / `Infinity` / non-number は reject する (組み立ての出口 `applyLayoutOffsets` が配置後の位置に足すため)。
|
|
@@ -1167,6 +1700,77 @@ function validateLayoutPos(v: unknown, path: string, errors: JsonDslError[]): vo
|
|
|
1167
1700
|
表で検査(p, "layoutPos", path, "pos", errors);
|
|
1168
1701
|
}
|
|
1169
1702
|
|
|
1703
|
+
/**
|
|
1704
|
+
* 相対で置く指定の形を見る (#2039)。
|
|
1705
|
+
*
|
|
1706
|
+
* 相手の名前が実在するかはここでは見ない。 組み立て側 (`resolveRelativeDoc`) が figure 全体を
|
|
1707
|
+
* 組んだ後に照合し、いない相手を指した時は知らせを出す。 入口ごとに別の場所で判定すると、
|
|
1708
|
+
* 本文で書いた時と JSON で書いた時で返るものが変わる。
|
|
1709
|
+
*/
|
|
1710
|
+
function validateRelativePos(v: unknown, path: string, errors: JsonDslError[]): void {
|
|
1711
|
+
if (v === undefined) return;
|
|
1712
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
|
1713
|
+
errors.push({ path, message: "posRel must be an object with anchor and dir" });
|
|
1714
|
+
return;
|
|
1715
|
+
}
|
|
1716
|
+
const p = v as Record<string, unknown>;
|
|
1717
|
+
checkUnknownKeys(p, "posRel", path, errors);
|
|
1718
|
+
表で検査(p, "posRel", path, "posRel", errors);
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
/**
|
|
1722
|
+
* 図全体として相対の指定が解けるかを見る (#2039)。
|
|
1723
|
+
*
|
|
1724
|
+
* 判定は `relative-pos.ts` の `findRelativeProblems` が持つ。 記法側も同じ判定を呼ぶので、
|
|
1725
|
+
* 同じ書き方が入口によって通ったり通らなかったりしない。
|
|
1726
|
+
*
|
|
1727
|
+
* 誤りの文だけをここで作る = 記法側は行番号を、こちらは欄の場所を添えるため。
|
|
1728
|
+
*/
|
|
1729
|
+
function validateRelativeAnchors(actors: readonly unknown[], errors: JsonDslError[]): void {
|
|
1730
|
+
const items: Array<{ name: string; rel?: RelativePos }> = [];
|
|
1731
|
+
const 場所 = new Map<string, number>();
|
|
1732
|
+
actors.forEach((a, i) => {
|
|
1733
|
+
if (!a || typeof a !== "object" || Array.isArray(a)) return;
|
|
1734
|
+
const o = a as Record<string, unknown>;
|
|
1735
|
+
if (typeof o.name !== "string") return;
|
|
1736
|
+
const rel = o.posRel;
|
|
1737
|
+
// 形の誤りは `validateRelativePos` が既に返している。 ここは形の整った指定だけを見る
|
|
1738
|
+
const 整った =
|
|
1739
|
+
rel !== null &&
|
|
1740
|
+
typeof rel === "object" &&
|
|
1741
|
+
!Array.isArray(rel) &&
|
|
1742
|
+
typeof (rel as Record<string, unknown>).anchor === "string" &&
|
|
1743
|
+
typeof (rel as Record<string, unknown>).dir === "string";
|
|
1744
|
+
items.push({ name: o.name, rel: 整った ? (rel as RelativePos) : undefined });
|
|
1745
|
+
if (!場所.has(o.name)) 場所.set(o.name, i);
|
|
1746
|
+
});
|
|
1747
|
+
|
|
1748
|
+
const named = items.map((i) => i.name);
|
|
1749
|
+
for (const p of findRelativeProblems(items)) {
|
|
1750
|
+
const i = 場所.get(p.name) ?? 0;
|
|
1751
|
+
errors.push({
|
|
1752
|
+
path: `$.actors[${i}].posRel`,
|
|
1753
|
+
...(p.kind === "self"
|
|
1754
|
+
? {
|
|
1755
|
+
message: `posRel.anchor must not be the actor itself: "${p.name}"`,
|
|
1756
|
+
hint: "別の登場人物の名前を書く",
|
|
1757
|
+
}
|
|
1758
|
+
: p.kind === "missing-anchor"
|
|
1759
|
+
? {
|
|
1760
|
+
message: `posRel.anchor must name an actor: "${p.anchor ?? ""}"`,
|
|
1761
|
+
hint:
|
|
1762
|
+
named.length > 0
|
|
1763
|
+
? `actors に書かれている名前 = ${named.join(", ")}`
|
|
1764
|
+
: "actors に基準にする登場人物を書く",
|
|
1765
|
+
}
|
|
1766
|
+
: {
|
|
1767
|
+
message: `posRel.anchor must not form a cycle: "${p.name}"`,
|
|
1768
|
+
hint: "どれか 1 つは posX / posY か自動配置にする",
|
|
1769
|
+
}),
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
|
|
1170
1774
|
/**
|
|
1171
1775
|
* 見本 (parts) の名前として扱う値かどうか (#1294)。
|
|
1172
1776
|
*
|
|
@@ -1196,22 +1800,13 @@ function validateViewport(v: unknown, errors: JsonDslError[]): void {
|
|
|
1196
1800
|
}
|
|
1197
1801
|
|
|
1198
1802
|
/**
|
|
1199
|
-
*
|
|
1803
|
+
* 選択肢の組が受ける項目。 どれも必ず書く (#2258)。
|
|
1200
1804
|
*
|
|
1201
|
-
*
|
|
1202
|
-
*
|
|
1203
|
-
*
|
|
1204
|
-
* #1304 まで外側の形が違う入力 (`lanes: 5`) は走査ごと飛ばされ、誤りが 1 件も返らなかった。
|
|
1205
|
-
* 形が違うものを黙って捨てると、書いた縦列が 1 つも効かない図が知らせなしで出る。
|
|
1206
|
-
*/
|
|
1207
|
-
/**
|
|
1208
|
-
* 図形と部品の中身を、記法と **同じ表** で検査する (#1374)。
|
|
1209
|
-
*
|
|
1210
|
-
* 表を 2 つ持つと片方だけ直してずれる。 記法側 (`v05/parser.ts`) が持つ表をそのまま引く。
|
|
1211
|
-
*
|
|
1212
|
-
* 記法は値が全て文字列で届くため読み替えが要るが、JSON は型のまま届く。 ここでは
|
|
1213
|
-
* 「知らない種類」 「知らない欄」 「足りない必須の欄」 「欄の型違い」 の 4 つを見る。
|
|
1805
|
+
* **判定と知らせが同じ集合を読む**。 別々に並べていた間、判定に項目を足しても
|
|
1806
|
+
* 知らせは古い一覧を教え続ける形だった。
|
|
1214
1807
|
*/
|
|
1808
|
+
const 選択肢の項目 = ["value", "label"] as const;
|
|
1809
|
+
|
|
1215
1810
|
/**
|
|
1216
1811
|
* 選択肢の並び (`["green", { "value": "red", "label": "失敗" }]`) を要素ごとに検査する (#1920)。
|
|
1217
1812
|
*
|
|
@@ -1240,13 +1835,21 @@ function 選択肢の並びを検査する(値: unknown, path: string, errors: J
|
|
|
1240
1835
|
}
|
|
1241
1836
|
const 組 = x as Record<string, unknown>;
|
|
1242
1837
|
for (const k of Object.keys(組)) {
|
|
1243
|
-
if (
|
|
1244
|
-
errors.push({
|
|
1838
|
+
if ((選択肢の項目 as readonly string[]).includes(k)) continue;
|
|
1839
|
+
errors.push({
|
|
1840
|
+
path: `${here}.${k}`,
|
|
1841
|
+
message: `unknown key "${k}"`,
|
|
1842
|
+
hint: `使える項目 = ${選択肢の項目.join(", ")}`,
|
|
1843
|
+
});
|
|
1245
1844
|
}
|
|
1246
|
-
for (const k of
|
|
1845
|
+
for (const k of 選択肢の項目) {
|
|
1247
1846
|
const v = 組[k];
|
|
1248
1847
|
if (v === undefined) {
|
|
1249
|
-
errors.push({
|
|
1848
|
+
errors.push({
|
|
1849
|
+
path: `${here}.${k}`,
|
|
1850
|
+
message: `${k} is required`,
|
|
1851
|
+
hint: `必須の項目 = ${選択肢の項目.join(", ")}`,
|
|
1852
|
+
});
|
|
1250
1853
|
} else if (typeof v !== "string") {
|
|
1251
1854
|
errors.push({ path: `${here}.${k}`, message: `${k} must be 文字列`, hint: `got ${v === null ? "null" : typeof v}` });
|
|
1252
1855
|
} else if (v === "") {
|
|
@@ -1260,6 +1863,14 @@ function 選択肢の並びを検査する(値: unknown, path: string, errors: J
|
|
|
1260
1863
|
});
|
|
1261
1864
|
}
|
|
1262
1865
|
|
|
1866
|
+
/**
|
|
1867
|
+
* 図形と部品の中身を、記法と **同じ表** で検査する (#1374)。
|
|
1868
|
+
*
|
|
1869
|
+
* 表を 2 つ持つと片方だけ直してずれる。 記法側 (`v05/parser.ts`) が持つ表をそのまま引く。
|
|
1870
|
+
*
|
|
1871
|
+
* 記法は値が全て文字列で届くため読み替えが要るが、JSON は型のまま届く。 ここでは
|
|
1872
|
+
* 「知らない種類」 「知らない欄」 「足りない必須の欄」 「欄の型違い」 の 4 つを見る。
|
|
1873
|
+
*/
|
|
1263
1874
|
function 表で中身を検査する(
|
|
1264
1875
|
o: Record<string, unknown>,
|
|
1265
1876
|
表: Record<string, 図形の定義>,
|
|
@@ -1344,18 +1955,30 @@ function 表で中身を検査する(
|
|
|
1344
1955
|
}
|
|
1345
1956
|
}
|
|
1346
1957
|
|
|
1958
|
+
/** 帯に書く相手の項目。 値が文字列で、他の 2 つと型が違う */
|
|
1959
|
+
const 帯の相手の項目 = ["actor"] as const;
|
|
1960
|
+
/** 帯に書く段の項目。 値が 0 以上の整数 */
|
|
1961
|
+
const 帯の段の項目 = ["from", "to"] as const;
|
|
1347
1962
|
/**
|
|
1348
|
-
*
|
|
1963
|
+
* 帯が受ける項目。 判定と知らせの両方がここから作る (#2258)。
|
|
1349
1964
|
*
|
|
1350
|
-
*
|
|
1351
|
-
*
|
|
1352
|
-
* 素通りする。
|
|
1965
|
+
* 公開 schema の `$.bands` も同じ 3 語を閉じた集合として宣言しており、
|
|
1966
|
+
* `closed-level-coverage.test.ts` が両者の一致を見る。
|
|
1353
1967
|
*/
|
|
1968
|
+
export const 帯の項目: readonly string[] = [...帯の相手の項目, ...帯の段の項目];
|
|
1969
|
+
|
|
1354
1970
|
/**
|
|
1355
1971
|
* 順序図の帯の並びを検査する (#1466)。
|
|
1356
1972
|
*
|
|
1357
1973
|
* **外側の形もここで見る**。 `欄の型表` は「並び」 とだけ宣言し、中身の検査は専用の検査に
|
|
1358
1974
|
* 委ねる作りなので、ここで見ないと `bands: 1` が素通りする。
|
|
1975
|
+
*
|
|
1976
|
+
* ## 知らない項目も見る (#2262)
|
|
1977
|
+
*
|
|
1978
|
+
* 書いてある 3 つの型だけを見て、**知らない項目があるかを確かめていなかった**。
|
|
1979
|
+
* 公開 schema は `$.bands` を閉じた階層として宣言しているので、
|
|
1980
|
+
* schema を信じて書いた入力がこちらでは通り、schema 検証器では落ちる状態だった。
|
|
1981
|
+
* 公開 schema が閉じた階層のうち、素通りしていたのはここだけだった。
|
|
1359
1982
|
*/
|
|
1360
1983
|
function validateBands(v: unknown, errors: JsonDslError[]): void {
|
|
1361
1984
|
if (v === undefined) return;
|
|
@@ -1374,10 +1997,20 @@ function validateBands(v: unknown, errors: JsonDslError[]): void {
|
|
|
1374
1997
|
return;
|
|
1375
1998
|
}
|
|
1376
1999
|
const o = b as Record<string, unknown>;
|
|
1377
|
-
|
|
1378
|
-
|
|
2000
|
+
for (const k of Object.keys(o)) {
|
|
2001
|
+
if (帯の項目.includes(k)) continue;
|
|
2002
|
+
errors.push({
|
|
2003
|
+
path: `${path}.${k}`,
|
|
2004
|
+
message: `unknown key "${k}"`,
|
|
2005
|
+
hint: `使える項目 = ${帯の項目.join(", ")}`,
|
|
2006
|
+
});
|
|
2007
|
+
}
|
|
2008
|
+
for (const k of 帯の相手の項目) {
|
|
2009
|
+
if (typeof o[k] !== "string" || o[k] === "") {
|
|
2010
|
+
errors.push({ path: `${path}.${k}`, message: `band.${k} must be a non-empty string` });
|
|
2011
|
+
}
|
|
1379
2012
|
}
|
|
1380
|
-
for (const k of
|
|
2013
|
+
for (const k of 帯の段の項目) {
|
|
1381
2014
|
if (typeof o[k] !== "number" || !Number.isInteger(o[k]) || o[k] < 0) {
|
|
1382
2015
|
errors.push({ path: `${path}.${k}`, message: `band.${k} must be a non-negative integer` });
|
|
1383
2016
|
}
|
|
@@ -1385,6 +2018,98 @@ function validateBands(v: unknown, errors: JsonDslError[]): void {
|
|
|
1385
2018
|
});
|
|
1386
2019
|
}
|
|
1387
2020
|
|
|
2021
|
+
const LEGEND_KEYS = ["mark", "text", "tone", "lineStyle", "align", "lead"] as const;
|
|
2022
|
+
const LEGEND_LINE_MARKS = ["line-triangle-hollow", "line-diamond-hollow", "line-diamond-solid"] as const;
|
|
2023
|
+
|
|
2024
|
+
/** JSON の凡例を、外側の並びから各項目の必須値まで検査する。 */
|
|
2025
|
+
function validateLegend(v: unknown, errors: JsonDslError[]): void {
|
|
2026
|
+
if (v === undefined) return;
|
|
2027
|
+
if (!Array.isArray(v)) {
|
|
2028
|
+
errors.push({ path: "$.legend", message: "legend must be an array" });
|
|
2029
|
+
return;
|
|
2030
|
+
}
|
|
2031
|
+
v.forEach((item, index) => {
|
|
2032
|
+
const path = `$.legend[${index}]`;
|
|
2033
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) {
|
|
2034
|
+
errors.push({ path, message: "legend item must be an object" });
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
2037
|
+
const o = item as Record<string, unknown>;
|
|
2038
|
+
for (const key of Object.keys(o)) {
|
|
2039
|
+
if ((LEGEND_KEYS as readonly string[]).includes(key)) continue;
|
|
2040
|
+
errors.push({
|
|
2041
|
+
path: `${path}.${key}`,
|
|
2042
|
+
message: `unknown key "${key}"`,
|
|
2043
|
+
hint: `使える項目 = ${LEGEND_KEYS.join(", ")}`,
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
const mark = typeof o.mark === "string" ? resolveLegendMark(o.mark) : undefined;
|
|
2047
|
+
if (o.mark !== undefined && (typeof o.mark !== "string" || mark === null)) {
|
|
2048
|
+
errors.push({ path: `${path}.mark`, message: "legend mark is not recognized" });
|
|
2049
|
+
}
|
|
2050
|
+
if (typeof o.text !== "string" || o.text.trim().length === 0) {
|
|
2051
|
+
errors.push({ path: `${path}.text`, message: "legend text must be a non-empty string" });
|
|
2052
|
+
}
|
|
2053
|
+
if (o.mark === undefined) {
|
|
2054
|
+
if (o.tone !== undefined || o.lineStyle !== undefined) errors.push({ path, message: "legend note cannot have tone or lineStyle" });
|
|
2055
|
+
if (o.align !== undefined && !(LEGEND_NOTE_ALIGN_VALUES as readonly unknown[]).includes(o.align)) errors.push({ path: `${path}.align`, message: `align must be ${LEGEND_NOTE_ALIGN_VALUES.join(" or ")}` });
|
|
2056
|
+
if (o.lead !== undefined && typeof o.lead !== "string") errors.push({ path: `${path}.lead`, message: "lead must be a string" });
|
|
2057
|
+
} else {
|
|
2058
|
+
if (o.align !== undefined || o.lead !== undefined) errors.push({ path, message: "marked legend item cannot have align or lead" });
|
|
2059
|
+
if (o.tone !== undefined && (typeof o.tone !== "string" || resolveTone(o.tone) === undefined)) errors.push({ path: `${path}.tone`, message: "tone is not recognized" });
|
|
2060
|
+
if (o.lineStyle !== undefined) {
|
|
2061
|
+
if (!(LEGEND_LINE_STYLE_VALUES as readonly unknown[]).includes(o.lineStyle)) errors.push({ path: `${path}.lineStyle`, message: `lineStyle must be ${LEGEND_LINE_STYLE_VALUES.join(" or ")}` });
|
|
2062
|
+
if (mark === undefined || mark === null || !(LEGEND_LINE_MARKS as readonly string[]).includes(mark)) errors.push({ path: `${path}.lineStyle`, message: "lineStyle requires a line-end legend mark" });
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
});
|
|
2066
|
+
}
|
|
2067
|
+
|
|
2068
|
+
const STAGE_HEADER_KEYS = ["leftPad", "topPad", "numberSize", "gap", "nameSize", "bottomPad"] as const;
|
|
2069
|
+
const REQUIRED_STAGE_HEADER_KEYS = ["topPad", "numberSize", "gap", "nameSize", "bottomPad"] as const;
|
|
2070
|
+
|
|
2071
|
+
/** JSON の段の見出し寸法を、閉じた欄・必須の正数まで検査する。 */
|
|
2072
|
+
function validateStageHeader(v: unknown, errors: JsonDslError[]): void {
|
|
2073
|
+
if (v === undefined) return;
|
|
2074
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
|
2075
|
+
errors.push({ path: "$.stageHeader", message: "stageHeader must be an object" });
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
const path = "$.stageHeader";
|
|
2079
|
+
const o = v as Record<string, unknown>;
|
|
2080
|
+
for (const key of Object.keys(o)) {
|
|
2081
|
+
if ((STAGE_HEADER_KEYS as readonly string[]).includes(key)) continue;
|
|
2082
|
+
errors.push({
|
|
2083
|
+
path: `${path}.${key}`,
|
|
2084
|
+
message: `unknown key "${key}"`,
|
|
2085
|
+
hint: `使える項目 = ${STAGE_HEADER_KEYS.join(", ")}`,
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
for (const key of REQUIRED_STAGE_HEADER_KEYS) {
|
|
2089
|
+
if (o[key] === undefined) {
|
|
2090
|
+
errors.push({ path: `${path}.${key}`, message: `${key} is required` });
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
for (const key of STAGE_HEADER_KEYS) {
|
|
2094
|
+
const value = o[key];
|
|
2095
|
+
if (value === undefined) continue;
|
|
2096
|
+
const zeroAllowed = key === "leftPad" || key === "topPad" || key === "gap" || key === "bottomPad";
|
|
2097
|
+
if (typeof value !== "number" || !Number.isFinite(value) || (zeroAllowed ? value < 0 : value <= 0)) {
|
|
2098
|
+
errors.push({
|
|
2099
|
+
path: `${path}.${key}`,
|
|
2100
|
+
message: `${key} must be ${zeroAllowed ? "a non-negative" : "a positive"} number`,
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
/**
|
|
2107
|
+
* 値を見せる部品の並びを検査する (#1374)。
|
|
2108
|
+
*
|
|
2109
|
+
* **外側の形もここで見る**。 `欄の型表` は「並び」 とだけ宣言し、中身の検査は専用の検査に
|
|
2110
|
+
* 委ねる作りなので (`checkFieldType` の `case "並び"`)、ここで見ないと `readouts: 1` が
|
|
2111
|
+
* 素通りする。
|
|
2112
|
+
*/
|
|
1388
2113
|
function validateReadouts(v: unknown, errors: JsonDslError[]): void {
|
|
1389
2114
|
if (v === undefined) return;
|
|
1390
2115
|
if (!Array.isArray(v)) {
|
|
@@ -1578,6 +2303,11 @@ function validateFormulas(v: unknown, inputs: unknown, errors: JsonDslError[]):
|
|
|
1578
2303
|
*/
|
|
1579
2304
|
type 式の検査結果 = { readonly 読めた: true; readonly 値: unknown } | { readonly 読めた: false };
|
|
1580
2305
|
|
|
2306
|
+
/**
|
|
2307
|
+
* 式の組が受ける項目 (#2258)。 判定と知らせが同じ集合を読む。
|
|
2308
|
+
*/
|
|
2309
|
+
const 式の組の項目 = ["expression", "label"] as const;
|
|
2310
|
+
|
|
1581
2311
|
/**
|
|
1582
2312
|
* 式の値が組の形 (`{ expression, label }`) なら組を検査し、式の文字列を返す (#1916)。
|
|
1583
2313
|
*
|
|
@@ -1589,12 +2319,12 @@ function 式の組を検査する(値: unknown, path: string, errors: JsonDslErr
|
|
|
1589
2319
|
const 組 = 値 as Record<string, unknown>;
|
|
1590
2320
|
let 読めた = true;
|
|
1591
2321
|
for (const 鍵 of Object.keys(組)) {
|
|
1592
|
-
if (
|
|
2322
|
+
if ((式の組の項目 as readonly string[]).includes(鍵)) continue;
|
|
1593
2323
|
読めた = false;
|
|
1594
2324
|
errors.push({
|
|
1595
2325
|
path: `${path}.${鍵}`,
|
|
1596
2326
|
message: `unknown formula field: ${鍵}`,
|
|
1597
|
-
hint:
|
|
2327
|
+
hint: `使える項目 = ${式の組の項目.join(", ")}`,
|
|
1598
2328
|
});
|
|
1599
2329
|
}
|
|
1600
2330
|
if (typeof 組.expression !== "string" || 組.expression.trim() === "") {
|
|
@@ -1632,7 +2362,11 @@ function validateEvents(v: unknown, errors: JsonDslError[]): void {
|
|
|
1632
2362
|
});
|
|
1633
2363
|
return;
|
|
1634
2364
|
}
|
|
1635
|
-
|
|
2365
|
+
// 相手の指し方は記法の側が 1 か所で決める (#2256)。 同じ 4 語をここにも並べていた間、
|
|
2366
|
+
// 1:1 を名乗りながら受ける項目が両側で別々に決まっていた。
|
|
2367
|
+
// 必ず書く 2 つも同じ定数から取る (#2258) = 知らせが判定と別に並べる形をやめる
|
|
2368
|
+
const 相手の鍵: readonly string[] = EVENT_TARGET_KEYS;
|
|
2369
|
+
const 出来事の項目: readonly string[] = [...EVENT_REQUIRED_KEYS, ...EVENT_TARGET_KEYS];
|
|
1636
2370
|
v.forEach((e, i) => {
|
|
1637
2371
|
const path = `$.events[${i}]`;
|
|
1638
2372
|
if (!e || typeof e !== "object" || Array.isArray(e)) {
|
|
@@ -1641,11 +2375,11 @@ function validateEvents(v: unknown, errors: JsonDslError[]): void {
|
|
|
1641
2375
|
}
|
|
1642
2376
|
const o = e as Record<string, unknown>;
|
|
1643
2377
|
for (const k of Object.keys(o)) {
|
|
1644
|
-
if (
|
|
2378
|
+
if (出来事の項目.includes(k)) continue;
|
|
1645
2379
|
errors.push({
|
|
1646
2380
|
path: `${path}.${k}`,
|
|
1647
2381
|
message: `unknown key "${k}"`,
|
|
1648
|
-
hint: `使える項目 =
|
|
2382
|
+
hint: `使える項目 = ${出来事の項目.join(", ")}`,
|
|
1649
2383
|
});
|
|
1650
2384
|
}
|
|
1651
2385
|
if (typeof o.on !== "string" || !(EVENT_KINDS as readonly string[]).includes(o.on)) {
|
|
@@ -1700,6 +2434,16 @@ function validateEvents(v: unknown, errors: JsonDslError[]): void {
|
|
|
1700
2434
|
});
|
|
1701
2435
|
}
|
|
1702
2436
|
|
|
2437
|
+
/**
|
|
2438
|
+
* 巻き上げに応じて進む値が受ける項目 (#2258)。
|
|
2439
|
+
*
|
|
2440
|
+
* **数を書く項目と文字を書く項目を分けて持つ** = 受け方が違うので判定が分かれるが、
|
|
2441
|
+
* 知らせは 2 つを繋いで 1 つの一覧として出す。 別々に並べていた間、判定が 3 語で
|
|
2442
|
+
* 知らせが 4 語という食い違いが同じ関数の中にあった。
|
|
2443
|
+
*/
|
|
2444
|
+
const 送りの数の項目 = ["start", "end", "scrub"] as const;
|
|
2445
|
+
const 送りの文の項目 = ["label"] as const;
|
|
2446
|
+
|
|
1703
2447
|
/**
|
|
1704
2448
|
* 巻き上げに応じて進む値を検査する (#1393)。
|
|
1705
2449
|
*
|
|
@@ -1749,17 +2493,17 @@ function validateScrolls(
|
|
|
1749
2493
|
continue;
|
|
1750
2494
|
}
|
|
1751
2495
|
for (const [k, x] of Object.entries(spec as Record<string, unknown>)) {
|
|
1752
|
-
if (
|
|
2496
|
+
if ((送りの文の項目 as readonly string[]).includes(k)) {
|
|
1753
2497
|
if (typeof x !== "string") {
|
|
1754
|
-
errors.push({ path: `${path}
|
|
2498
|
+
errors.push({ path: `${path}.${k}`, message: `${k} must be a string` });
|
|
1755
2499
|
}
|
|
1756
2500
|
continue;
|
|
1757
2501
|
}
|
|
1758
|
-
if (
|
|
2502
|
+
if (!(送りの数の項目 as readonly string[]).includes(k)) {
|
|
1759
2503
|
errors.push({
|
|
1760
2504
|
path: `${path}.${k}`,
|
|
1761
2505
|
message: `unknown key "${k}"`,
|
|
1762
|
-
hint:
|
|
2506
|
+
hint: `使える項目 = ${[...送りの数の項目, ...送りの文の項目].join(", ")}`,
|
|
1763
2507
|
});
|
|
1764
2508
|
continue;
|
|
1765
2509
|
}
|
|
@@ -1794,6 +2538,15 @@ function validateActorShape(v: unknown, path: string, errors: JsonDslError[]): v
|
|
|
1794
2538
|
表で中身を検査する(v as Record<string, unknown>, 図形の表, path, "shape", errors);
|
|
1795
2539
|
}
|
|
1796
2540
|
|
|
2541
|
+
/**
|
|
2542
|
+
* 鍵を利用者が決める入れ物 (縦列 / 群) を見る (#1304)。
|
|
2543
|
+
*
|
|
2544
|
+
* `lanes` / `groups` は id を鍵に持つため、鍵そのものは縛れない。 縛れるのは
|
|
2545
|
+
* 「入れ物が plain object か」 と「各 id の中身の項目名と値の型」 の 2 つ。
|
|
2546
|
+
*
|
|
2547
|
+
* #1304 まで外側の形が違う入力 (`lanes: 5`) は走査ごと飛ばされ、誤りが 1 件も返らなかった。
|
|
2548
|
+
* 形が違うものを黙って捨てると、書いた縦列が 1 つも効かない図が知らせなしで出る。
|
|
2549
|
+
*/
|
|
1797
2550
|
function validateIdMap(
|
|
1798
2551
|
v: unknown,
|
|
1799
2552
|
欄: "lanes" | "groups",
|
|
@@ -1850,9 +2603,450 @@ function validateAxes(v: unknown, errors: JsonDslError[]): void {
|
|
|
1850
2603
|
const o = 一方 as Record<string, unknown>;
|
|
1851
2604
|
checkUnknownKeys(o, 層の名[名], `$.axes.${名}`, errors);
|
|
1852
2605
|
表で検査(o, 層の名[名], `$.axes.${名}`, `axes.${名}`, errors);
|
|
2606
|
+
if (o.direction === true) {
|
|
2607
|
+
if (typeof o.label !== "string") {
|
|
2608
|
+
errors.push({ path: `$.axes.${名}.label`, message: `axes.${名}.label must be a string for a directional axis` });
|
|
2609
|
+
}
|
|
2610
|
+
} else if (o.label !== undefined || o.direction !== undefined) {
|
|
2611
|
+
errors.push({ path: `$.axes.${名}.direction`, message: `axes.${名}.direction must be true when label is used` });
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
/** #2854 で足した図表の複合欄と閉じた語を検査する。 */
|
|
2617
|
+
function validateChartPresentationFields(j: Record<string, unknown>, errors: JsonDslError[]): void {
|
|
2618
|
+
const enumField = (name: string, allowed: readonly string[]): void => {
|
|
2619
|
+
const value = j[name];
|
|
2620
|
+
if (value === undefined || typeof value !== "string") return;
|
|
2621
|
+
if (!allowed.includes(value)) {
|
|
2622
|
+
errors.push({ path: `$.${name}`, message: `${name} must be one of: ${allowed.join(", ")}` });
|
|
2623
|
+
}
|
|
2624
|
+
};
|
|
2625
|
+
enumField("chartPieTableColumns", CHART_PIE_TABLE_COLUMN_VALUES);
|
|
2626
|
+
enumField("chartPieRingWidth", CHART_PIE_RING_WIDTH_VALUES);
|
|
2627
|
+
enumField("chartStackedLegendPosition", CHART_STACKED_LEGEND_POSITION_VALUES);
|
|
2628
|
+
enumField("chartWaffleLegendPosition", CHART_WAFFLE_LEGEND_POSITION_VALUES);
|
|
2629
|
+
enumField("funnelForm", FUNNEL_FORM_VALUES);
|
|
2630
|
+
enumField("funnelRate", FUNNEL_RATE_VALUES);
|
|
2631
|
+
enumField("quadrantPointLabelSide", QUADRANT_POINT_LABEL_SIDE_VALUES);
|
|
2632
|
+
enumField("treeNodeForm", TREE_NODE_FORM_VALUES);
|
|
2633
|
+
enumField("treeEdgeTone", TREE_EDGE_TONE_VALUES);
|
|
2634
|
+
enumField("treeEdgeHead", TREE_EDGE_HEAD_VALUES);
|
|
2635
|
+
enumField("mindForm", MIND_FORM_VALUES);
|
|
2636
|
+
enumField("journeyForm", JOURNEY_FORM_VALUES);
|
|
2637
|
+
enumField("journeyLineForm", JOURNEY_LINE_FORM_VALUES);
|
|
2638
|
+
enumField("sequenceActorForm", SEQUENCE_ACTOR_FORM_VALUES);
|
|
2639
|
+
enumField("sequenceLabelForm", SEQUENCE_LABEL_FORM_VALUES);
|
|
2640
|
+
enumField("sequenceReturnHead", SEQUENCE_RETURN_HEAD_VALUES);
|
|
2641
|
+
enumField("cornerForm", CORNER_FORM_VALUES);
|
|
2642
|
+
enumField("chartBarShape", CHART_BAR_SHAPE_VALUES);
|
|
2643
|
+
enumField("quadrantLabelPosition", QUADRANT_LABEL_POSITION_VALUES);
|
|
2644
|
+
|
|
2645
|
+
const closedObject = (
|
|
2646
|
+
name: "journeyLabels" | "figureCard" | "figureSize" | "treeSize" | "mindSize" |
|
|
2647
|
+
"terminalDecoration" | "figureKindLabel" | "figureInset" | "journeySize" | "chartBarSize" |
|
|
2648
|
+
"chartLineSize" | "chartPieSize" | "chartWaffleSize" | "chartSlopeLabelTemplate" | "chartSlopeSize" |
|
|
2649
|
+
"chartStackedLayout" | "chartGaugeSize" | "ganttSize" | "quadrantSize" | "legendOffset" | "stageLayout",
|
|
2650
|
+
keys: readonly string[],
|
|
2651
|
+
): Record<string, unknown> | undefined => {
|
|
2652
|
+
const value = j[name];
|
|
2653
|
+
if (value === undefined) return undefined;
|
|
2654
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2655
|
+
errors.push({ path: `$.${name}`, message: `${name} must be an object` });
|
|
2656
|
+
return undefined;
|
|
2657
|
+
}
|
|
2658
|
+
const object = value as Record<string, unknown>;
|
|
2659
|
+
for (const key of Object.keys(object)) {
|
|
2660
|
+
if (!keys.includes(key)) errors.push({ path: `$.${name}.${key}`, message: `unknown key "${key}"` });
|
|
2661
|
+
}
|
|
2662
|
+
return object;
|
|
2663
|
+
};
|
|
2664
|
+
const labels = closedObject("journeyLabels", JOURNEY_LABEL_KEYS);
|
|
2665
|
+
if (labels !== undefined) {
|
|
2666
|
+
for (const key of JOURNEY_LABEL_KEYS) {
|
|
2667
|
+
if (typeof labels[key] !== "string" || labels[key].trim() === "") {
|
|
2668
|
+
errors.push({ path: `$.journeyLabels.${key}`, message: `${key} must be a non-empty string` });
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
if (j.sequenceKindLabels !== undefined) {
|
|
2673
|
+
if (!j.sequenceKindLabels || typeof j.sequenceKindLabels !== "object" || Array.isArray(j.sequenceKindLabels)) {
|
|
2674
|
+
errors.push({ path: "$.sequenceKindLabels", message: "sequenceKindLabels must be an object" });
|
|
2675
|
+
} else {
|
|
2676
|
+
const labels = j.sequenceKindLabels as Record<string, unknown>;
|
|
2677
|
+
for (const [key, value] of Object.entries(labels)) {
|
|
2678
|
+
if (!(SEQUENCE_KIND_LABEL_KEYS as readonly string[]).includes(key)) errors.push({ path: `$.sequenceKindLabels.${key}`, message: `unknown key "${key}"` });
|
|
2679
|
+
else if (typeof value !== "string") errors.push({ path: `$.sequenceKindLabels.${key}`, message: `${key} must be a string` });
|
|
2680
|
+
}
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
if (
|
|
2684
|
+
j.sequenceRowGap !== undefined &&
|
|
2685
|
+
(typeof j.sequenceRowGap !== "number" || !Number.isFinite(j.sequenceRowGap) || j.sequenceRowGap <= 0)
|
|
2686
|
+
) {
|
|
2687
|
+
errors.push({ path: "$.sequenceRowGap", message: "sequenceRowGap must be a positive finite number" });
|
|
2688
|
+
}
|
|
2689
|
+
for (const key of ["sequenceGutter", "sequenceBandWidth"] as const) {
|
|
2690
|
+
if (j[key] !== undefined && (typeof j[key] !== "number" || !Number.isFinite(j[key]) || j[key] <= 0)) {
|
|
2691
|
+
errors.push({ path: `$.${key}`, message: `${key} must be a positive finite number` });
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
if (j.sequenceSelfLoop !== undefined) {
|
|
2695
|
+
if (!j.sequenceSelfLoop || typeof j.sequenceSelfLoop !== "object" || Array.isArray(j.sequenceSelfLoop)) {
|
|
2696
|
+
errors.push({ path: "$.sequenceSelfLoop", message: "sequenceSelfLoop must be an object" });
|
|
2697
|
+
} else {
|
|
2698
|
+
const loop = j.sequenceSelfLoop as Record<string, unknown>;
|
|
2699
|
+
for (const key of Object.keys(loop)) {
|
|
2700
|
+
if (!(SEQUENCE_SELF_LOOP_KEYS as readonly string[]).includes(key)) errors.push({ path: `$.sequenceSelfLoop.${key}`, message: `unknown key "${key}"` });
|
|
2701
|
+
}
|
|
2702
|
+
if (loop.form !== undefined && loop.form !== "orthogonal") errors.push({ path: "$.sequenceSelfLoop.form", message: "form must be orthogonal" });
|
|
2703
|
+
for (const key of ["width", "drop"] as const) {
|
|
2704
|
+
if (loop[key] !== undefined && (typeof loop[key] !== "number" || !Number.isFinite(loop[key]) || loop[key] <= 0)) errors.push({ path: `$.sequenceSelfLoop.${key}`, message: "must be a positive finite number" });
|
|
2705
|
+
}
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
const card = closedObject("figureCard", ["label", "note"]);
|
|
2709
|
+
if (card !== undefined) {
|
|
2710
|
+
if (typeof card.label !== "string")
|
|
2711
|
+
errors.push({ path: "$.figureCard.label", message: "label must be a string" });
|
|
2712
|
+
if (card.note !== undefined && typeof card.note !== "string")
|
|
2713
|
+
errors.push({ path: "$.figureCard.note", message: "note must be a string" });
|
|
2714
|
+
}
|
|
2715
|
+
const size = closedObject("figureSize", ["width", "height"]);
|
|
2716
|
+
if (size !== undefined) {
|
|
2717
|
+
for (const key of ["width", "height"] as const) {
|
|
2718
|
+
if (typeof size[key] !== "number" || !Number.isFinite(size[key]) || size[key] <= 0)
|
|
2719
|
+
errors.push({ path: `$.figureSize.${key}`, message: `${key} must be a positive finite number` });
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
const positiveNumber = (value: unknown, path: string): boolean => {
|
|
2723
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return true;
|
|
2724
|
+
errors.push({ path, message: "must be a positive finite number" });
|
|
2725
|
+
return false;
|
|
2726
|
+
};
|
|
2727
|
+
const nonNegativeNumber = (value: unknown, path: string): boolean => {
|
|
2728
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) return true;
|
|
2729
|
+
errors.push({ path, message: "must be a non-negative finite number" });
|
|
2730
|
+
return false;
|
|
2731
|
+
};
|
|
2732
|
+
if (j.edgeEndGap !== undefined) nonNegativeNumber(j.edgeEndGap, "$.edgeEndGap");
|
|
2733
|
+
const treeSize = closedObject("treeSize", [
|
|
2734
|
+
"nodeWidths",
|
|
2735
|
+
"nodeHeight",
|
|
2736
|
+
"titleFontSize",
|
|
2737
|
+
"subtitleFontSize",
|
|
2738
|
+
"siblingGap",
|
|
2739
|
+
"levelGap",
|
|
2740
|
+
]);
|
|
2741
|
+
if (treeSize !== undefined) {
|
|
2742
|
+
if (treeSize.nodeWidths !== undefined) {
|
|
2743
|
+
if (!Array.isArray(treeSize.nodeWidths) || treeSize.nodeWidths.length === 0) {
|
|
2744
|
+
errors.push({ path: "$.treeSize.nodeWidths", message: "must be a non-empty array" });
|
|
2745
|
+
} else {
|
|
2746
|
+
treeSize.nodeWidths.forEach((value, index) => {
|
|
2747
|
+
positiveNumber(value, `$.treeSize.nodeWidths[${index}]`);
|
|
2748
|
+
});
|
|
2749
|
+
}
|
|
2750
|
+
}
|
|
2751
|
+
for (const key of ["nodeHeight", "titleFontSize", "subtitleFontSize"] as const) {
|
|
2752
|
+
if (treeSize[key] !== undefined) positiveNumber(treeSize[key], `$.treeSize.${key}`);
|
|
2753
|
+
}
|
|
2754
|
+
for (const key of ["siblingGap", "levelGap"] as const) {
|
|
2755
|
+
if (treeSize[key] !== undefined) nonNegativeNumber(treeSize[key], `$.treeSize.${key}`);
|
|
2756
|
+
}
|
|
2757
|
+
}
|
|
2758
|
+
const mindSize = closedObject("mindSize", [
|
|
2759
|
+
"contentOffset",
|
|
2760
|
+
"root",
|
|
2761
|
+
"branch",
|
|
2762
|
+
"leaf",
|
|
2763
|
+
"rootBranchGap",
|
|
2764
|
+
"branchLeafGap",
|
|
2765
|
+
"branchRowGap",
|
|
2766
|
+
"branchRowOffset",
|
|
2767
|
+
"leafRowGap",
|
|
2768
|
+
"leafRowOffset",
|
|
2769
|
+
]);
|
|
2770
|
+
if (mindSize !== undefined) {
|
|
2771
|
+
if (mindSize.contentOffset !== undefined) {
|
|
2772
|
+
const value = mindSize.contentOffset;
|
|
2773
|
+
const path = "$.mindSize.contentOffset";
|
|
2774
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2775
|
+
errors.push({ path, message: "contentOffset must be an object" });
|
|
2776
|
+
} else {
|
|
2777
|
+
const offset = value as Record<string, unknown>;
|
|
2778
|
+
for (const key of Object.keys(offset)) {
|
|
2779
|
+
if (key !== "x" && key !== "y")
|
|
2780
|
+
errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
2781
|
+
}
|
|
2782
|
+
for (const key of ["x", "y"] as const) {
|
|
2783
|
+
if (typeof offset[key] !== "number" || !Number.isFinite(offset[key]))
|
|
2784
|
+
errors.push({ path: `${path}.${key}`, message: "must be a finite number" });
|
|
2785
|
+
}
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
const nestedSize = (
|
|
2789
|
+
key: "root" | "branch" | "leaf",
|
|
2790
|
+
allowed: readonly string[],
|
|
2791
|
+
): Record<string, unknown> | undefined => {
|
|
2792
|
+
const value = mindSize[key];
|
|
2793
|
+
if (value === undefined) return undefined;
|
|
2794
|
+
const path = `$.mindSize.${key}`;
|
|
2795
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
2796
|
+
errors.push({ path, message: `${key} must be an object` });
|
|
2797
|
+
return undefined;
|
|
2798
|
+
}
|
|
2799
|
+
const object = value as Record<string, unknown>;
|
|
2800
|
+
for (const nestedKey of Object.keys(object)) {
|
|
2801
|
+
if (!allowed.includes(nestedKey))
|
|
2802
|
+
errors.push({ path: `${path}.${nestedKey}`, message: `unknown key "${nestedKey}"` });
|
|
2803
|
+
}
|
|
2804
|
+
return object;
|
|
2805
|
+
};
|
|
2806
|
+
for (const [key, allowed] of [
|
|
2807
|
+
["root", ["width", "height", "fontSize"]],
|
|
2808
|
+
["branch", ["width", "height", "fontSize"]],
|
|
2809
|
+
["leaf", ["fontSize"]],
|
|
2810
|
+
] as const) {
|
|
2811
|
+
const nested = nestedSize(key, allowed);
|
|
2812
|
+
if (nested === undefined) continue;
|
|
2813
|
+
for (const nestedKey of allowed) {
|
|
2814
|
+
if (nested[nestedKey] !== undefined)
|
|
2815
|
+
positiveNumber(nested[nestedKey], `$.mindSize.${key}.${nestedKey}`);
|
|
2816
|
+
}
|
|
2817
|
+
}
|
|
2818
|
+
for (const key of ["rootBranchGap", "branchLeafGap", "branchRowGap", "leafRowGap"] as const) {
|
|
2819
|
+
if (mindSize[key] !== undefined) nonNegativeNumber(mindSize[key], `$.mindSize.${key}`);
|
|
2820
|
+
}
|
|
2821
|
+
for (const key of ["branchRowOffset", "leafRowOffset"] as const) {
|
|
2822
|
+
const value = mindSize[key];
|
|
2823
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value))) {
|
|
2824
|
+
errors.push({ path: `$.mindSize.${key}`, message: "must be a finite number" });
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2827
|
+
}
|
|
2828
|
+
|
|
2829
|
+
const specs = {
|
|
2830
|
+
terminalDecoration: ["windowControls", "cursor", "rowOrdinal"],
|
|
2831
|
+
figureKindLabel: ["text", "x", "y", "fontSize", "fontWeight", "letterSpacing"],
|
|
2832
|
+
figureInset: ["top", "right", "bottom", "left", "radius"],
|
|
2833
|
+
journeySize: ["plotPadding", "pointPadding"],
|
|
2834
|
+
chartBarSize: ["tickFontSize", "valueFontSize", "labelFontSize", "plotPadding", "barWidth", "cornerRadius"],
|
|
2835
|
+
chartLineSize: ["valueLabelOffset", "tickFontSize", "periodFontSize", "pointRadius", "plotPadding", "tickGap", "pointPadding", "periodY"],
|
|
2836
|
+
chartPieSize: ["tableWidth", "tableLabelFontSize", "tableValueFontSize", "tableRowGap", "tablePaddingInline", "outerRadius", "centerX"],
|
|
2837
|
+
chartWaffleSize: ["gridLeft", "cellPitch"],
|
|
2838
|
+
chartSlopeLabelTemplate: ["left", "right"],
|
|
2839
|
+
chartSlopeSize: ["pointRadius", "periodFontSize", "labelFontSize", "leftAxisX", "axisGap", "labelAxisGap", "plotPadding", "pointPadding"],
|
|
2840
|
+
chartStackedLayout: ["legendColumns", "legendColumnGap", "segmentGap", "barThickness", "barWidth", "legendMarkSize", "legendFontSize", "periodFontSize", "rateFontSize", "legendOffsetY"],
|
|
2841
|
+
chartGaugeSize: ["arcThickness", "outerRadius", "plotTop", "valueFontSize"],
|
|
2842
|
+
ganttSize: ["taskLabelFontSize", "taskLabelAlign", "taskLabelX", "tickFontSize", "tickCount", "barThickness", "milestoneFontSize", "milestoneRadius", "dependencyStrokeWidth", "taskColumnWidth", "timelinePaddingRight"],
|
|
2843
|
+
quadrantSize: ["plotOffset", "plotSize", "pointRadius", "pointFontSize", "pointLabelGap", "quadrantFontSize", "axisFontSize"],
|
|
2844
|
+
legendOffset: ["x", "y"],
|
|
2845
|
+
stageLayout: ["columnWidths", "columnGap", "backDetourTopGap", "backDetourSideGap", "backDetourTargetInset"],
|
|
2846
|
+
} as const;
|
|
2847
|
+
const objects = Object.fromEntries(Object.entries(specs).map(([name, keys]) => [name, closedObject(name as keyof typeof specs, keys)])) as Record<keyof typeof specs, Record<string, unknown> | undefined>;
|
|
2848
|
+
const positiveKeys: Partial<Record<keyof typeof specs, readonly string[]>> = {
|
|
2849
|
+
figureKindLabel: ["fontSize", "fontWeight"],
|
|
2850
|
+
chartBarSize: ["tickFontSize", "valueFontSize", "labelFontSize", "barWidth"],
|
|
2851
|
+
chartLineSize: ["tickFontSize", "periodFontSize", "pointRadius"],
|
|
2852
|
+
chartPieSize: ["tableWidth", "tableLabelFontSize", "tableValueFontSize", "outerRadius"],
|
|
2853
|
+
chartWaffleSize: ["cellPitch"],
|
|
2854
|
+
chartSlopeSize: ["pointRadius", "periodFontSize", "labelFontSize", "axisGap"],
|
|
2855
|
+
chartStackedLayout: ["barThickness", "barWidth", "legendMarkSize", "legendFontSize", "periodFontSize", "rateFontSize"],
|
|
2856
|
+
chartGaugeSize: ["arcThickness", "outerRadius", "valueFontSize"],
|
|
2857
|
+
ganttSize: ["taskLabelFontSize", "tickFontSize", "barThickness", "milestoneFontSize", "milestoneRadius", "dependencyStrokeWidth"],
|
|
2858
|
+
quadrantSize: ["plotSize", "pointRadius", "pointFontSize", "quadrantFontSize", "axisFontSize"],
|
|
2859
|
+
};
|
|
2860
|
+
const nonNegativeKeys: Partial<Record<keyof typeof specs, readonly string[]>> = {
|
|
2861
|
+
figureKindLabel: ["letterSpacing"], figureInset: ["top", "right", "bottom", "left", "radius"],
|
|
2862
|
+
chartBarSize: ["cornerRadius"], chartLineSize: ["tickGap", "periodY"], chartPieSize: ["tableRowGap", "tablePaddingInline", "centerX"], chartWaffleSize: ["gridLeft"], chartSlopeSize: ["leftAxisX", "labelAxisGap"],
|
|
2863
|
+
chartStackedLayout: ["legendColumnGap", "segmentGap"], chartGaugeSize: ["plotTop"],
|
|
2864
|
+
ganttSize: ["taskLabelX", "taskColumnWidth", "timelinePaddingRight"], quadrantSize: ["pointLabelGap"],
|
|
2865
|
+
stageLayout: ["columnGap", "backDetourTopGap", "backDetourSideGap", "backDetourTargetInset"],
|
|
2866
|
+
};
|
|
2867
|
+
for (const [name, object] of Object.entries(objects) as [keyof typeof specs, Record<string, unknown> | undefined][]) {
|
|
2868
|
+
if (object === undefined) continue;
|
|
2869
|
+
for (const key of positiveKeys[name] ?? []) if (object[key] !== undefined) positiveNumber(object[key], `$.${name}.${key}`);
|
|
2870
|
+
for (const key of nonNegativeKeys[name] ?? []) if (object[key] !== undefined) nonNegativeNumber(object[key], `$.${name}.${key}`);
|
|
2871
|
+
}
|
|
2872
|
+
const finite = (value: unknown, path: string): void => { if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value))) errors.push({ path, message: "must be a finite number" }); };
|
|
2873
|
+
if (objects.terminalDecoration) for (const key of specs.terminalDecoration) if (objects.terminalDecoration[key] !== undefined && typeof objects.terminalDecoration[key] !== "boolean") errors.push({ path: `$.terminalDecoration.${key}`, message: "must be boolean" });
|
|
2874
|
+
if (objects.figureKindLabel) {
|
|
2875
|
+
if (typeof objects.figureKindLabel.text !== "string" || objects.figureKindLabel.text.trim() === "") errors.push({ path: "$.figureKindLabel.text", message: "must be a non-empty string" });
|
|
2876
|
+
finite(objects.figureKindLabel.x, "$.figureKindLabel.x"); finite(objects.figureKindLabel.y, "$.figureKindLabel.y");
|
|
2877
|
+
}
|
|
2878
|
+
if (objects.chartSlopeLabelTemplate) for (const key of specs.chartSlopeLabelTemplate) if (objects.chartSlopeLabelTemplate[key] !== undefined && typeof objects.chartSlopeLabelTemplate[key] !== "string") errors.push({ path: `$.chartSlopeLabelTemplate.${key}`, message: "must be a string" });
|
|
2879
|
+
if (objects.chartStackedLayout) finite(objects.chartStackedLayout.legendOffsetY, "$.chartStackedLayout.legendOffsetY");
|
|
2880
|
+
if (objects.legendOffset) {
|
|
2881
|
+
finite(objects.legendOffset.x, "$.legendOffset.x");
|
|
2882
|
+
finite(objects.legendOffset.y, "$.legendOffset.y");
|
|
2883
|
+
}
|
|
2884
|
+
const nestedNumberObject = (parent: keyof typeof specs, key: string, keys: readonly string[], positive: boolean): void => {
|
|
2885
|
+
const value = objects[parent]?.[key]; if (value === undefined) return;
|
|
2886
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) { errors.push({ path: `$.${parent}.${key}`, message: "must be an object" }); return; }
|
|
2887
|
+
const object = value as Record<string, unknown>;
|
|
2888
|
+
for (const child of Object.keys(object)) if (!keys.includes(child)) errors.push({ path: `$.${parent}.${key}.${child}`, message: `unknown key "${child}"` });
|
|
2889
|
+
for (const child of keys) if (object[child] !== undefined) (positive ? positiveNumber : nonNegativeNumber)(object[child], `$.${parent}.${key}.${child}`);
|
|
2890
|
+
};
|
|
2891
|
+
nestedNumberObject("chartBarSize", "plotPadding", ["top", "right", "bottom", "left"], false);
|
|
2892
|
+
nestedNumberObject("chartLineSize", "valueLabelOffset", ["above", "below"], false);
|
|
2893
|
+
nestedNumberObject("chartLineSize", "plotPadding", ["top", "right", "bottom", "left"], false);
|
|
2894
|
+
nestedNumberObject("chartLineSize", "pointPadding", ["left", "right"], false);
|
|
2895
|
+
nestedNumberObject("chartSlopeSize", "plotPadding", ["top", "bottom"], false);
|
|
2896
|
+
nestedNumberObject("chartSlopeSize", "pointPadding", ["top", "bottom"], false);
|
|
2897
|
+
nestedNumberObject("journeySize", "plotPadding", ["left", "right"], false);
|
|
2898
|
+
nestedNumberObject("journeySize", "pointPadding", ["left", "right"], false);
|
|
2899
|
+
nestedNumberObject("quadrantSize", "plotOffset", ["x", "y"], false);
|
|
2900
|
+
const columnWidths = objects.stageLayout?.columnWidths;
|
|
2901
|
+
if (columnWidths !== undefined) {
|
|
2902
|
+
if (!columnWidths || typeof columnWidths !== "object" || Array.isArray(columnWidths)) errors.push({ path: "$.stageLayout.columnWidths", message: "must be an object" });
|
|
2903
|
+
else for (const [key, value] of Object.entries(columnWidths)) nonNegativeNumber(value, `$.stageLayout.columnWidths.${key}`);
|
|
2904
|
+
}
|
|
2905
|
+
const stackedColumns = objects.chartStackedLayout?.legendColumns;
|
|
2906
|
+
if (stackedColumns !== undefined && (!Number.isInteger(stackedColumns) || typeof stackedColumns !== "number" || stackedColumns <= 0)) errors.push({ path: "$.chartStackedLayout.legendColumns", message: "must be a positive integer" });
|
|
2907
|
+
if (
|
|
2908
|
+
objects.ganttSize?.taskLabelAlign !== undefined &&
|
|
2909
|
+
!(GANTT_TASK_LABEL_ALIGN_VALUES as readonly unknown[]).includes(objects.ganttSize.taskLabelAlign)
|
|
2910
|
+
) errors.push({ path: "$.ganttSize.taskLabelAlign", message: "must be start or end" });
|
|
2911
|
+
const tickCount = objects.ganttSize?.tickCount;
|
|
2912
|
+
if (tickCount !== undefined && (!Number.isInteger(tickCount) || typeof tickCount !== "number" || tickCount < 2)) errors.push({ path: "$.ganttSize.tickCount", message: "must be an integer greater than or equal to 2" });
|
|
2913
|
+
|
|
2914
|
+
if (Array.isArray(j.chartSlopePeriods) &&
|
|
2915
|
+
(j.chartSlopePeriods.length !== 2 || j.chartSlopePeriods.some((v) => typeof v !== "string" || v.trim() === ""))) {
|
|
2916
|
+
errors.push({ path: "$.chartSlopePeriods", message: "chartSlopePeriods must contain exactly two non-empty strings" });
|
|
2917
|
+
}
|
|
2918
|
+
if (Array.isArray(j.chartSlopeEmphasisIds) && j.chartSlopeEmphasisIds.length === 0) {
|
|
2919
|
+
errors.push({ path: "$.chartSlopeEmphasisIds", message: "chartSlopeEmphasisIds must contain at least one id" });
|
|
2920
|
+
}
|
|
2921
|
+
|
|
2922
|
+
const boundNumber = (value: unknown): boolean =>
|
|
2923
|
+
(typeof value === "number" && Number.isFinite(value)) ||
|
|
2924
|
+
(typeof value === "string" && value.trim() !== "");
|
|
2925
|
+
if (j.chartLineSeries !== undefined && !Array.isArray(j.chartLineSeries)) {
|
|
2926
|
+
errors.push({ path: "$.chartLineSeries", message: "chartLineSeries must be an array" });
|
|
2927
|
+
} else if (Array.isArray(j.chartLineSeries)) {
|
|
2928
|
+
if (j.chartLineSeries.length === 0) {
|
|
2929
|
+
errors.push({ path: "$.chartLineSeries", message: "chartLineSeries must contain at least one series" });
|
|
2930
|
+
}
|
|
2931
|
+
j.chartLineSeries.forEach((series, i) => {
|
|
2932
|
+
const path = `$.chartLineSeries[${i}]`;
|
|
2933
|
+
if (!series || typeof series !== "object" || Array.isArray(series)) {
|
|
2934
|
+
errors.push({ path, message: "chartLineSeries item must be an object" });
|
|
2935
|
+
return;
|
|
2936
|
+
}
|
|
2937
|
+
const value = series as Record<string, unknown>;
|
|
2938
|
+
for (const key of Object.keys(value)) {
|
|
2939
|
+
if (!["label", "points", "tone", "dash", "marker", "valueIndexes"].includes(key))
|
|
2940
|
+
errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
2941
|
+
}
|
|
2942
|
+
if (value.label !== undefined && typeof value.label !== "string")
|
|
2943
|
+
errors.push({ path: `${path}.label`, message: "label must be a string" });
|
|
2944
|
+
if (value.tone !== undefined && (typeof value.tone !== "string" || resolveTone(value.tone) === undefined))
|
|
2945
|
+
errors.push({ path: `${path}.tone`, message: "tone must be a known tone" });
|
|
2946
|
+
if (
|
|
2947
|
+
value.dash !== undefined &&
|
|
2948
|
+
(typeof value.dash !== "string" ||
|
|
2949
|
+
!(CHART_LINE_DASH_VALUES as readonly string[]).includes(value.dash))
|
|
2950
|
+
)
|
|
2951
|
+
errors.push({ path: `${path}.dash`, message: "dash must be solid or dotted" });
|
|
2952
|
+
if (
|
|
2953
|
+
value.marker !== undefined &&
|
|
2954
|
+
(typeof value.marker !== "string" ||
|
|
2955
|
+
!(CHART_LINE_MARKER_VALUES as readonly string[]).includes(value.marker))
|
|
2956
|
+
)
|
|
2957
|
+
errors.push({ path: `${path}.marker`, message: "marker must be dot, hollow, or none" });
|
|
2958
|
+
if (value.valueIndexes !== undefined &&
|
|
2959
|
+
(!Array.isArray(value.valueIndexes) || value.valueIndexes.some((v) => typeof v !== "number" || !Number.isInteger(v) || v < 0)))
|
|
2960
|
+
errors.push({ path: `${path}.valueIndexes`, message: "valueIndexes must contain non-negative integers" });
|
|
2961
|
+
if (!Array.isArray(value.points) || value.points.length === 0) {
|
|
2962
|
+
errors.push({ path: `${path}.points`, message: "points must be a non-empty array" });
|
|
2963
|
+
} else value.points.forEach((point, p) => {
|
|
2964
|
+
const pointPath = `${path}.points[${p}]`;
|
|
2965
|
+
if (!point || typeof point !== "object" || Array.isArray(point)) {
|
|
2966
|
+
errors.push({ path: pointPath, message: "point must be an object" });
|
|
2967
|
+
return;
|
|
2968
|
+
}
|
|
2969
|
+
const item = point as Record<string, unknown>;
|
|
2970
|
+
for (const key of Object.keys(item)) if (key !== "label" && key !== "value")
|
|
2971
|
+
errors.push({ path: `${pointPath}.${key}`, message: `unknown key "${key}"` });
|
|
2972
|
+
if (typeof item.label !== "string") errors.push({ path: `${pointPath}.label`, message: "label must be a string" });
|
|
2973
|
+
if (!boundNumber(item.value)) errors.push({ path: `${pointPath}.value`, message: "value must be a finite number or non-empty string" });
|
|
2974
|
+
});
|
|
2975
|
+
});
|
|
2976
|
+
}
|
|
2977
|
+
|
|
2978
|
+
if (j.chartStackedPeriods !== undefined && !Array.isArray(j.chartStackedPeriods)) {
|
|
2979
|
+
errors.push({ path: "$.chartStackedPeriods", message: "chartStackedPeriods must be an array" });
|
|
2980
|
+
} else if (Array.isArray(j.chartStackedPeriods)) {
|
|
2981
|
+
if (j.chartStackedPeriods.length === 0)
|
|
2982
|
+
errors.push({ path: "$.chartStackedPeriods", message: "chartStackedPeriods must contain at least one period" });
|
|
2983
|
+
j.chartStackedPeriods.forEach((period, i) => {
|
|
2984
|
+
const path = `$.chartStackedPeriods[${i}]`;
|
|
2985
|
+
if (!period || typeof period !== "object" || Array.isArray(period)) {
|
|
2986
|
+
errors.push({ path, message: "chartStackedPeriods item must be an object" });
|
|
2987
|
+
return;
|
|
2988
|
+
}
|
|
2989
|
+
const value = period as Record<string, unknown>;
|
|
2990
|
+
for (const key of Object.keys(value)) if (key !== "label" && key !== "values")
|
|
2991
|
+
errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
2992
|
+
if (typeof value.label !== "string") errors.push({ path: `${path}.label`, message: "label must be a string" });
|
|
2993
|
+
if (!Array.isArray(value.values) || value.values.some((entry) => !boundNumber(entry)))
|
|
2994
|
+
errors.push({ path: `${path}.values`, message: "values must be an array of finite numbers or non-empty strings" });
|
|
2995
|
+
});
|
|
2996
|
+
}
|
|
2997
|
+
|
|
2998
|
+
if (
|
|
2999
|
+
j.chartGaugeValue !== undefined &&
|
|
3000
|
+
(!j.chartGaugeValue || typeof j.chartGaugeValue !== "object" || Array.isArray(j.chartGaugeValue))
|
|
3001
|
+
) {
|
|
3002
|
+
errors.push({ path: "$.chartGaugeValue", message: "chartGaugeValue must be an object" });
|
|
3003
|
+
} else if (j.chartGaugeValue && typeof j.chartGaugeValue === "object") {
|
|
3004
|
+
const value = j.chartGaugeValue as Record<string, unknown>;
|
|
3005
|
+
for (const key of Object.keys(value)) if (!["max", "current", "target", "previous", "previousLabel"].includes(key))
|
|
3006
|
+
errors.push({ path: `$.chartGaugeValue.${key}`, message: `unknown key "${key}"` });
|
|
3007
|
+
const finite = (entry: unknown): boolean => typeof entry === "number" && Number.isFinite(entry);
|
|
3008
|
+
if (!finite(value.max) || Number(value.max) <= 0) errors.push({ path: "$.chartGaugeValue.max", message: "max must be a positive finite number" });
|
|
3009
|
+
if (!boundNumber(value.current)) errors.push({ path: "$.chartGaugeValue.current", message: "current must be a finite number or non-empty string" });
|
|
3010
|
+
for (const key of ["target", "previous"] as const) if (value[key] !== undefined && !boundNumber(value[key]))
|
|
3011
|
+
errors.push({ path: `$.chartGaugeValue.${key}`, message: `${key} must be a finite number or non-empty string` });
|
|
3012
|
+
if (value.previousLabel !== undefined && typeof value.previousLabel !== "string")
|
|
3013
|
+
errors.push({ path: "$.chartGaugeValue.previousLabel", message: "previousLabel must be a string" });
|
|
1853
3014
|
}
|
|
1854
3015
|
}
|
|
1855
3016
|
|
|
3017
|
+
/**
|
|
3018
|
+
* JSON の区画の名前を、図の側の綴りへ直す (#2667)。
|
|
3019
|
+
*
|
|
3020
|
+
* 記法と JSON はどちらも `左上` のような語で書き、図の側は `topLeft` で受ける。
|
|
3021
|
+
* 直す先の表は項目の欄と共有する (`区画`) = 同じ区画を 2 通りで呼ばない。
|
|
3022
|
+
*
|
|
3023
|
+
* 中身の無い形は「書かなかった」 と同じにする (`axes` と同じ扱い)。
|
|
3024
|
+
*/
|
|
3025
|
+
function 区画の名前を直す(r: JsonRegions | undefined): DslRegions | undefined {
|
|
3026
|
+
if (r === undefined) return undefined;
|
|
3027
|
+
const 出: DslRegions = {};
|
|
3028
|
+
for (const [語, key] of 区画) {
|
|
3029
|
+
const v = (r as Record<string, unknown>)[語];
|
|
3030
|
+
if (typeof v === "string" && v.trim() !== "") 出[key] = v;
|
|
3031
|
+
}
|
|
3032
|
+
return Object.keys(出).length > 0 ? 出 : undefined;
|
|
3033
|
+
}
|
|
3034
|
+
|
|
3035
|
+
/** 2 軸で仕分ける図の区画の名前を見る (#2667) */
|
|
3036
|
+
function validateRegions(v: unknown, errors: JsonDslError[]): void {
|
|
3037
|
+
if (v === undefined) return;
|
|
3038
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) {
|
|
3039
|
+
errors.push({
|
|
3040
|
+
path: "$.regions",
|
|
3041
|
+
message: 'regions must be a plain object like { "左上": "すぐやる" }',
|
|
3042
|
+
});
|
|
3043
|
+
return;
|
|
3044
|
+
}
|
|
3045
|
+
const o = v as Record<string, unknown>;
|
|
3046
|
+
checkUnknownKeys(o, "regions", "$.regions", errors);
|
|
3047
|
+
表で検査(o, "regions", "$.regions", "regions", errors);
|
|
3048
|
+
}
|
|
3049
|
+
|
|
1856
3050
|
/**
|
|
1857
3051
|
* 写しを作る時の入れ子の深さの上限。
|
|
1858
3052
|
*
|
|
@@ -2075,6 +3269,10 @@ export function 素のデータに写す(
|
|
|
2075
3269
|
}
|
|
2076
3270
|
}
|
|
2077
3271
|
|
|
3272
|
+
/**
|
|
3273
|
+
* shape validation。 layer 1 = 必須 field + 型 check、 layer 2 は compile 側の validation に委譲。
|
|
3274
|
+
* fail-fast ではなく全 error 収集して返す (LLM に一括で修正させるため)。
|
|
3275
|
+
*/
|
|
2078
3276
|
function validateJson(
|
|
2079
3277
|
json: unknown,
|
|
2080
3278
|
): { ok: true; data: DragonJson } | { ok: false; errors: JsonDslError[] } {
|
|
@@ -2115,6 +3313,8 @@ function validateJson(
|
|
|
2115
3313
|
// 値を見せる部品の中身を、記法と同じ表で見る (#1374)
|
|
2116
3314
|
validateReadouts(j.readouts, errors);
|
|
2117
3315
|
validateBands(j.bands, errors);
|
|
3316
|
+
validateLegend(j.legend, errors);
|
|
3317
|
+
validateStageHeader(j.stageHeader, errors);
|
|
2118
3318
|
// 読む人が動かすつまみの中身も、記法と同じ表で見る (#1389)
|
|
2119
3319
|
validateInputs(j.inputs, errors);
|
|
2120
3320
|
// 式は描画側の parser に通す (#1391)
|
|
@@ -2126,6 +3326,29 @@ function validateJson(
|
|
|
2126
3326
|
// 値そのものの型は表が見る (#1304)。 図表の箱の上の小見出し (#1247) の空文字は
|
|
2127
3327
|
// 「書かなかった」 と同じ扱いにするため通す (記法側の `eyebrow:` と揃える。 落とすのは `jsonToDoc`)
|
|
2128
3328
|
表で検査(j, "root", "$", "", errors);
|
|
3329
|
+
validateDiagramShape(j, errors);
|
|
3330
|
+
for (const 欄 of ["ticks", "目盛り"] as const) {
|
|
3331
|
+
if (Array.isArray(j[欄]) && j[欄].length === 0) {
|
|
3332
|
+
errors.push({ path: `$.${欄}`, message: `${欄} must contain at least one label` });
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
if (j.ganttToday !== undefined) {
|
|
3336
|
+
const today = j.ganttToday;
|
|
3337
|
+
if (!today || typeof today !== "object" || Array.isArray(today)) {
|
|
3338
|
+
errors.push({ path: "$.ganttToday", message: "ganttToday must be an object" });
|
|
3339
|
+
} else {
|
|
3340
|
+
const value = today as Record<string, unknown>;
|
|
3341
|
+
for (const key of Object.keys(value)) {
|
|
3342
|
+
if (key !== "index" && key !== "label") errors.push({ path: `$.ganttToday.${key}`, message: `unknown key "${key}"` });
|
|
3343
|
+
}
|
|
3344
|
+
if (typeof value.index !== "number" || !Number.isFinite(value.index)) {
|
|
3345
|
+
errors.push({ path: "$.ganttToday.index", message: "ganttToday.index must be a finite number" });
|
|
3346
|
+
}
|
|
3347
|
+
if (typeof value.label !== "string") {
|
|
3348
|
+
errors.push({ path: "$.ganttToday.label", message: "ganttToday.label must be a string" });
|
|
3349
|
+
}
|
|
3350
|
+
}
|
|
3351
|
+
}
|
|
2129
3352
|
|
|
2130
3353
|
if (!Array.isArray(j.actors) || j.actors.length === 0) {
|
|
2131
3354
|
errors.push({ path: "$.actors", message: "actors must be a non-empty array" });
|
|
@@ -2141,6 +3364,93 @@ function validateJson(
|
|
|
2141
3364
|
// 値そのものの型は表が見る (#1304)。 `kind` は見本 (parts) の名前も受けるため
|
|
2142
3365
|
// 非空の文字列までしか縛らない (CAR-1657 の unified syntax)
|
|
2143
3366
|
表で検査(ao, "actor", `$.actors[${i}]`, "actor", errors);
|
|
3367
|
+
const actorEnum = (key: string, allowed: readonly string[]): void => {
|
|
3368
|
+
const value = ao[key];
|
|
3369
|
+
if (value === undefined || typeof value !== "string") return;
|
|
3370
|
+
if (!allowed.includes(value)) errors.push({ path: `$.actors[${i}].${key}`, message: `${key} must be one of: ${allowed.join(", ")}` });
|
|
3371
|
+
};
|
|
3372
|
+
actorEnum("kindForm", KIND_FORM_VALUES);
|
|
3373
|
+
actorEnum("icon", CDL_ICON_VALUES);
|
|
3374
|
+
actorEnum("titleAlign", TITLE_ALIGN_VALUES);
|
|
3375
|
+
actorEnum("rowMarkForm", ROW_MARK_FORM_VALUES);
|
|
3376
|
+
actorEnum("rowDividerForm", ROW_DIVIDER_FORM_VALUES);
|
|
3377
|
+
if (ao.rowWell !== undefined) {
|
|
3378
|
+
const path = `$.actors[${i}].rowWell`;
|
|
3379
|
+
if (!ao.rowWell || typeof ao.rowWell !== "object" || Array.isArray(ao.rowWell)) {
|
|
3380
|
+
errors.push({ path, message: "rowWell must be an object" });
|
|
3381
|
+
} else {
|
|
3382
|
+
const well = ao.rowWell as Record<string, unknown>;
|
|
3383
|
+
for (const key of Object.keys(well)) {
|
|
3384
|
+
if (!(ROW_WELL_KEYS as readonly string[]).includes(key)) errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
3385
|
+
}
|
|
3386
|
+
for (const key of ROW_WELL_KEYS) {
|
|
3387
|
+
const value = well[key];
|
|
3388
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value) || value < 0)) errors.push({ path: `${path}.${key}`, message: "must be a non-negative finite number" });
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
if (ao.rowLayout !== undefined) {
|
|
3393
|
+
const path = `$.actors[${i}].rowLayout`;
|
|
3394
|
+
if (!ao.rowLayout || typeof ao.rowLayout !== "object" || Array.isArray(ao.rowLayout)) {
|
|
3395
|
+
errors.push({ path, message: "rowLayout must be an object" });
|
|
3396
|
+
} else {
|
|
3397
|
+
const layout = ao.rowLayout as Record<string, unknown>;
|
|
3398
|
+
for (const key of Object.keys(layout)) if (!["titleBaseline", "rowTop", "rowPitch"].includes(key)) errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
3399
|
+
for (const key of ["titleBaseline", "rowTop", "rowPitch"] as const) {
|
|
3400
|
+
const value = layout[key];
|
|
3401
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value) || (key === "rowPitch" ? value <= 0 : value < 0))) errors.push({ path: `${path}.${key}`, message: key === "rowPitch" ? "must be a positive finite number" : "must be a non-negative finite number" });
|
|
3402
|
+
}
|
|
3403
|
+
}
|
|
3404
|
+
}
|
|
3405
|
+
if (ao.figureCard !== undefined) {
|
|
3406
|
+
const path = `$.actors[${i}].figureCard`;
|
|
3407
|
+
if (!ao.figureCard || typeof ao.figureCard !== "object" || Array.isArray(ao.figureCard)) errors.push({ path, message: "figureCard must be an object" });
|
|
3408
|
+
else {
|
|
3409
|
+
const card = ao.figureCard as Record<string, unknown>;
|
|
3410
|
+
for (const key of Object.keys(card)) if (key !== "label" && key !== "note") errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
3411
|
+
if (typeof card.label !== "string") errors.push({ path: `${path}.label`, message: "label must be a string" });
|
|
3412
|
+
if (card.note !== undefined && typeof card.note !== "string") errors.push({ path: `${path}.note`, message: "note must be a string" });
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
if (ao.marks !== undefined && !Array.isArray(ao.marks)) {
|
|
3416
|
+
errors.push({ path: `$.actors[${i}].marks`, message: "marks must be an array" });
|
|
3417
|
+
} else if (Array.isArray(ao.marks)) {
|
|
3418
|
+
ao.marks.forEach((mark, markIndex) => {
|
|
3419
|
+
if (typeof mark === "string") return;
|
|
3420
|
+
const path = `$.actors[${i}].marks[${markIndex}]`;
|
|
3421
|
+
if (!mark || typeof mark !== "object" || Array.isArray(mark)) {
|
|
3422
|
+
errors.push({ path, message: "mark must be a string or object" });
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
const entry = mark as Record<string, unknown>;
|
|
3426
|
+
for (const key of Object.keys(entry)) if (key !== "mark" && key !== "tone") errors.push({ path: `${path}.${key}`, message: `unknown key "${key}"` });
|
|
3427
|
+
if (typeof entry.mark !== "string") errors.push({ path: `${path}.mark`, message: "mark must be a string" });
|
|
3428
|
+
if (entry.tone !== undefined && !(ROW_MARK_TONE_VALUES as readonly unknown[]).includes(entry.tone)) errors.push({ path: `${path}.tone`, message: `tone must be one of: ${ROW_MARK_TONE_VALUES.join(", ")}` });
|
|
3429
|
+
});
|
|
3430
|
+
}
|
|
3431
|
+
if (
|
|
3432
|
+
typeof ao.opportunityPosition === "string" &&
|
|
3433
|
+
!(JOURNEY_OPPORTUNITY_POSITION_VALUES as readonly string[]).includes(ao.opportunityPosition)
|
|
3434
|
+
) {
|
|
3435
|
+
errors.push({
|
|
3436
|
+
path: `$.actors[${i}].opportunityPosition`,
|
|
3437
|
+
message: `opportunityPosition must be one of: ${JOURNEY_OPPORTUNITY_POSITION_VALUES.join(", ")}`,
|
|
3438
|
+
});
|
|
3439
|
+
}
|
|
3440
|
+
for (const 欄 of ["at", "点の位置"] as const) {
|
|
3441
|
+
if (ao[欄] === undefined) continue;
|
|
3442
|
+
const at = ao[欄];
|
|
3443
|
+
if (
|
|
3444
|
+
!Array.isArray(at) ||
|
|
3445
|
+
at.length !== 2 ||
|
|
3446
|
+
at.some((v) => typeof v !== "number" && typeof v !== "string")
|
|
3447
|
+
) {
|
|
3448
|
+
errors.push({
|
|
3449
|
+
path: `$.actors[${i}].${欄}`,
|
|
3450
|
+
message: `actor.${欄} must be [x, y] with number or state reference values`,
|
|
3451
|
+
});
|
|
3452
|
+
}
|
|
3453
|
+
}
|
|
2144
3454
|
// 箱の中に描く図形の中身を、記法と同じ表で見る (#1374)
|
|
2145
3455
|
validateActorShape(ao.shape, `$.actors[${i}].shape`, errors);
|
|
2146
3456
|
// 見本 (parts) にしか効かない項目は、見本でない箱に書かれたら誤りにする (#1294)。
|
|
@@ -2150,6 +3460,7 @@ function validateJson(
|
|
|
2150
3460
|
const 代わりに使う欄: Record<(typeof 見本にしか効かない欄)[number], string> = {
|
|
2151
3461
|
state: "箱の見た目を変えるなら tone / color を使う",
|
|
2152
3462
|
scale: "大きさを変えるなら posW / posH を使う",
|
|
3463
|
+
phase: "普通の箱は自分の段を持たない。 動かす段は animation に書く",
|
|
2153
3464
|
};
|
|
2154
3465
|
if (!見本か) {
|
|
2155
3466
|
for (const 欄 of 見本にしか効かない欄) {
|
|
@@ -2173,8 +3484,8 @@ function validateJson(
|
|
|
2173
3484
|
});
|
|
2174
3485
|
}
|
|
2175
3486
|
}
|
|
2176
|
-
//
|
|
2177
|
-
// `{ v: {} }`
|
|
3487
|
+
// 値の上書き (`state`) は素の object に限り、中の値も数 / 文字 / 真偽のいずれかに限る。
|
|
3488
|
+
// `{ v: {} }` のような入れ子や null を通すと、値の初期値に扱えない型が入って組み立てが崩れる。
|
|
2178
3489
|
if (ao.state !== undefined) {
|
|
2179
3490
|
if (!ao.state || typeof ao.state !== "object" || Array.isArray(ao.state)) {
|
|
2180
3491
|
errors.push({
|
|
@@ -2195,7 +3506,11 @@ function validateJson(
|
|
|
2195
3506
|
}
|
|
2196
3507
|
// CAR-1693 Phase 1: actor DSL 表面 pos の validation
|
|
2197
3508
|
validateLayoutPos(ao.pos, `$.actors[${i}].pos`, errors);
|
|
3509
|
+
// 相対で置く指定 (#2039)
|
|
3510
|
+
validateRelativePos(ao.posRel, `$.actors[${i}].posRel`, errors);
|
|
2198
3511
|
});
|
|
3512
|
+
// 解けない相対の指定 (#2039)。 欄ごとの形を見た後に、図全体として解けるかを見る
|
|
3513
|
+
validateRelativeAnchors(j.actors, errors);
|
|
2199
3514
|
}
|
|
2200
3515
|
if (!Array.isArray(j.flow)) {
|
|
2201
3516
|
errors.push({ path: "$.flow", message: "flow must be an array" });
|
|
@@ -2209,6 +3524,44 @@ function validateJson(
|
|
|
2209
3524
|
checkUnknownKeys(so, "step", `$.flow[${i}]`, errors);
|
|
2210
3525
|
// 値そのものの型は表が見る (#1304)。 色と線種は engine の一覧と突き合わせる
|
|
2211
3526
|
表で検査(so, "step", `$.flow[${i}]`, "step", errors);
|
|
3527
|
+
if (so.labelCollision !== undefined && !(LABEL_COLLISION_VALUES as readonly unknown[]).includes(so.labelCollision)) {
|
|
3528
|
+
errors.push({ path: `$.flow[${i}].labelCollision`, message: `labelCollision must be one of: ${LABEL_COLLISION_VALUES.join(", ")}` });
|
|
3529
|
+
}
|
|
3530
|
+
if (
|
|
3531
|
+
so.labelSize !== undefined &&
|
|
3532
|
+
(!so.labelSize || typeof so.labelSize !== "object" || Array.isArray(so.labelSize))
|
|
3533
|
+
) {
|
|
3534
|
+
errors.push({ path: `$.flow[${i}].labelSize`, message: "labelSize must be an object" });
|
|
3535
|
+
} else if (so.labelSize !== undefined) {
|
|
3536
|
+
const size = so.labelSize as Record<string, unknown>;
|
|
3537
|
+
for (const key of Object.keys(size)) if (!["width", "height", "fontSize"].includes(key)) errors.push({ path: `$.flow[${i}].labelSize.${key}`, message: `unknown key "${key}"` });
|
|
3538
|
+
for (const key of ["width", "height", "fontSize"]) if (size[key] !== undefined && (typeof size[key] !== "number" || !Number.isFinite(size[key]) || size[key] <= 0)) errors.push({ path: `$.flow[${i}].labelSize.${key}`, message: "must be a positive finite number" });
|
|
3539
|
+
}
|
|
3540
|
+
if (so.waypoints !== undefined) {
|
|
3541
|
+
const path = `$.flow[${i}].waypoints`;
|
|
3542
|
+
if (!Array.isArray(so.waypoints)) errors.push({ path, message: "waypoints must be an array" });
|
|
3543
|
+
else {
|
|
3544
|
+
const points: Array<{ x: number; y: number }> = [];
|
|
3545
|
+
so.waypoints.forEach((point, pointIndex) => {
|
|
3546
|
+
const pointPath = `${path}[${pointIndex}]`;
|
|
3547
|
+
if (!point || typeof point !== "object" || Array.isArray(point)) {
|
|
3548
|
+
errors.push({ path: pointPath, message: "waypoint must be an object" });
|
|
3549
|
+
return;
|
|
3550
|
+
}
|
|
3551
|
+
const value = point as Record<string, unknown>;
|
|
3552
|
+
for (const key of Object.keys(value)) if (key !== "x" && key !== "y") errors.push({ path: `${pointPath}.${key}`, message: `unknown key "${key}"` });
|
|
3553
|
+
if (typeof value.x !== "number" || !Number.isFinite(value.x) || typeof value.y !== "number" || !Number.isFinite(value.y)) errors.push({ path: pointPath, message: "waypoint x and y must be finite numbers" });
|
|
3554
|
+
else points.push({ x: value.x, y: value.y });
|
|
3555
|
+
});
|
|
3556
|
+
const ends = [so.posX1, so.posY1, so.posX2, so.posY2];
|
|
3557
|
+
if (ends.every((value) => typeof value === "number" && Number.isFinite(value)) && points.length === so.waypoints.length) {
|
|
3558
|
+
const all = [{ x: so.posX1 as number, y: so.posY1 as number }, ...points, { x: so.posX2 as number, y: so.posY2 as number }];
|
|
3559
|
+
all.forEach((point, pointIndex) => {
|
|
3560
|
+
if (pointIndex > 0 && point.x !== all[pointIndex - 1]!.x && point.y !== all[pointIndex - 1]!.y) errors.push({ path: `${path}[${Math.min(pointIndex - 1, points.length - 1)}]`, message: "each waypoint segment must be orthogonal" });
|
|
3561
|
+
});
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
}
|
|
2212
3565
|
// CAR-1693 Phase 1: step DSL 表面 pos の validation
|
|
2213
3566
|
validateLayoutPos(so.pos, `$.flow[${i}].pos`, errors);
|
|
2214
3567
|
});
|
|
@@ -2245,11 +3598,38 @@ function validateJson(
|
|
|
2245
3598
|
}
|
|
2246
3599
|
validateStates(j.states, errors);
|
|
2247
3600
|
validateValues(j.values, errors);
|
|
3601
|
+
validateChartPresentationFields(j, errors);
|
|
2248
3602
|
validateAxes(j.axes, errors);
|
|
3603
|
+
validateRegions(j.regions, errors);
|
|
2249
3604
|
if (errors.length > 0) return { ok: false, errors };
|
|
2250
3605
|
return { ok: true, data: j as unknown as DragonJson };
|
|
2251
3606
|
}
|
|
2252
3607
|
|
|
3608
|
+
/** 最上位の `shape` を記法と同じ図種別の集合で検査する (#2797)。 */
|
|
3609
|
+
function validateDiagramShape(j: Record<string, unknown>, errors: JsonDslError[]): void {
|
|
3610
|
+
if (j.shape === undefined) return;
|
|
3611
|
+
if (typeof j.shape !== "string" || typeof j.type !== "string") return;
|
|
3612
|
+
const 別名 = TYPE_ALIASES.get(j.type);
|
|
3613
|
+
const type = 別名?.type ?? (PRESET_TYPES.has(j.type as PresetType) ? (j.type as PresetType) : null);
|
|
3614
|
+
if (type === null) return;
|
|
3615
|
+
const shape = resolveDiagramShape(j.shape);
|
|
3616
|
+
const 図種の語 = shapesForDiagramType(type);
|
|
3617
|
+
// 語そのものの誤りは `欄の型表` が同じ集合から伝える。 ここでは図種との組だけを見る。
|
|
3618
|
+
if (shape === null) return;
|
|
3619
|
+
if (shape !== null && (type !== "chart" || 図種の語.includes(shape))) return;
|
|
3620
|
+
const hint =
|
|
3621
|
+
図種の語.length > 0
|
|
3622
|
+
? `使える語 = ${図種の語.join(" / ")}`
|
|
3623
|
+
: `shape を書ける図種と語 = ${Object.entries(DIAGRAM_SHAPES)
|
|
3624
|
+
.map(([図種, 語]) => `type: ${図種} (${語.join(" / ")})`)
|
|
3625
|
+
.join(" / ")}`;
|
|
3626
|
+
errors.push({
|
|
3627
|
+
path: "$.shape",
|
|
3628
|
+
message: `shape is not valid for type: ${type}`,
|
|
3629
|
+
hint,
|
|
3630
|
+
});
|
|
3631
|
+
}
|
|
3632
|
+
|
|
2253
3633
|
/**
|
|
2254
3634
|
* 段の中で値を動かす指定を見る (#1186)。
|
|
2255
3635
|
*
|
|
@@ -2376,11 +3756,19 @@ function validateValues(v: unknown, errors: JsonDslError[]): void {
|
|
|
2376
3756
|
}
|
|
2377
3757
|
|
|
2378
3758
|
/**
|
|
2379
|
-
*
|
|
3759
|
+
* 段の説明を取り出す (#2621)。 受ける語は `PHASE_BODY_KEYS` が持つ。
|
|
2380
3760
|
*
|
|
2381
|
-
*
|
|
2382
|
-
*
|
|
3761
|
+
* **正の語 (`body`) を先に見る**。 表の先頭が正の語なので、並び順のまま最初に値を持つ語を
|
|
3762
|
+
* 採れば「どちらが効くか」 の規則を別に書かなくて済む。
|
|
2383
3763
|
*/
|
|
3764
|
+
function 段の説明(p: JsonPhase): string | undefined {
|
|
3765
|
+
for (const key of PHASE_BODY_KEYS) {
|
|
3766
|
+
const v = p[key];
|
|
3767
|
+
if (typeof v === "string") return v;
|
|
3768
|
+
}
|
|
3769
|
+
return undefined;
|
|
3770
|
+
}
|
|
3771
|
+
|
|
2384
3772
|
/**
|
|
2385
3773
|
* 図表の箱の上の小見出しを、 記法側と同じ形に整える (#1247)。
|
|
2386
3774
|
*
|
|
@@ -2393,11 +3781,40 @@ function 整えた小見出し(v: string | undefined): string | undefined {
|
|
|
2393
3781
|
return t.length > 0 ? t : undefined;
|
|
2394
3782
|
}
|
|
2395
3783
|
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
3784
|
+
/**
|
|
3785
|
+
* 見本の状態の上書きに、段を使うかどうか (`phase`) を混ぜる (#2125)。
|
|
3786
|
+
*
|
|
3787
|
+
* 組み立て側は段を外す指定を状態の上書きの中の `phase` として読む
|
|
3788
|
+
* (`compile/parts.ts` の `段を外す`)。 記法は中括弧に書いた名前をすべて上書きへ入れるため
|
|
3789
|
+
* `{ kind: wave-gauge, phase: false }` がそのまま届くが、JSON は欄ごとに型を宣言する形なので、
|
|
3790
|
+
* 欄として受けたものを同じ場所へ入れ直す。
|
|
3791
|
+
*
|
|
3792
|
+
* **書かなかった時は欄を作らない**。 `{ phase: undefined }` を混ぜると、上書きの有無で
|
|
3793
|
+
* 分岐する側から見て「書いた」 ことになる。
|
|
3794
|
+
*/
|
|
3795
|
+
function 段を含めた上書き(a: JsonActor): Record<string, number | string | boolean> | undefined {
|
|
3796
|
+
if (a.phase === undefined) return a.state;
|
|
3797
|
+
return { ...(a.state ?? {}), phase: a.phase };
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
/**
|
|
3801
|
+
* JSON DSL → DslDocument (AST) 変換。
|
|
3802
|
+
*
|
|
3803
|
+
* 行番号は `行の表` を渡した時だけ入る (#2117)。 JSON そのものは書いた場所を持たないため、
|
|
3804
|
+
* 本文の行が分かる入口 (編集画面の YAML 欄) が場所ごとの行を集めて渡す。 渡さない呼出は
|
|
3805
|
+
* 全ての要素が 0 行になる = 「行が分からない」 ことを 0 で表す。
|
|
3806
|
+
*
|
|
3807
|
+
* CAR-1693 Phase 1: DSL 表面 `pos: {x, y}` → 内部 AST `layoutPos:` の 2 層 mapping の実装 core。
|
|
3808
|
+
* test で mapping logic を実 execute するため export する (pos-field.test.ts の regression guard)。
|
|
3809
|
+
*/
|
|
3810
|
+
export function jsonToDoc(json: DragonJson, 行の表?: 書いた行の表): DslDocument {
|
|
3811
|
+
// 書いた場所から行を引く。 引けない場所は 0 行 = 表を渡さない呼出と同じ状態になる
|
|
3812
|
+
const 位置 = (...道: readonly (string | number)[]): { line: number } => ({
|
|
3813
|
+
line: 行の表?.get(書いた場所の鍵(...道)) ?? 0,
|
|
3814
|
+
});
|
|
3815
|
+
const actors: DslActor[] = json.actors.map((a, i) => {
|
|
2399
3816
|
if (typeof a === "string") {
|
|
2400
|
-
return { name: a, kind: "actor" as NodeKind, kindWritten: false, pos:
|
|
3817
|
+
return { name: a, kind: "actor" as NodeKind, kindWritten: false, pos: 位置("actors", i) };
|
|
2401
3818
|
}
|
|
2402
3819
|
// CAR-1657 = kind が既存 NodeKind に無い値なら parts identifier 候補、 partId に格納
|
|
2403
3820
|
const kindStr = a.kind ?? "actor";
|
|
@@ -2405,6 +3822,7 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2405
3822
|
// 色は記法と同じ振り分けを通す (#1294)。 `#` で始まれば色番号、それ以外は色の名前。
|
|
2406
3823
|
// 別々に書くと、同じ値が入口によって色番号にも色名にもなる
|
|
2407
3824
|
const 色 = a.color !== undefined ? splitColorValue(a.color) : {};
|
|
3825
|
+
const 書いた座標 = a.at ?? a.点の位置;
|
|
2408
3826
|
return {
|
|
2409
3827
|
name: a.name,
|
|
2410
3828
|
kind: isPart ? "actor" : resolveNodeKind(kindStr),
|
|
@@ -2412,12 +3830,33 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2412
3830
|
// 「書かなかった」 と同じ扱いにする (#1058)
|
|
2413
3831
|
kindWritten: a.kind !== undefined && !isPart,
|
|
2414
3832
|
subtitle: a.subtitle,
|
|
3833
|
+
kindForm: isPart ? undefined : a.kindForm,
|
|
3834
|
+
icon: isPart ? undefined : a.icon,
|
|
3835
|
+
emptyRowsNote: isPart ? undefined : a.emptyRowsNote,
|
|
3836
|
+
titleAlign: isPart ? undefined : a.titleAlign,
|
|
3837
|
+
rowRules: isPart ? undefined : a.rowRules,
|
|
3838
|
+
rowStripe: isPart ? undefined : a.rowStripe,
|
|
3839
|
+
rowLayout: isPart ? undefined : a.rowLayout,
|
|
3840
|
+
rowDividerForm: isPart ? undefined : a.rowDividerForm,
|
|
3841
|
+
rowWell: isPart ? undefined : a.rowWell,
|
|
3842
|
+
rowMarkForm: isPart ? undefined : a.rowMarkForm,
|
|
3843
|
+
nestIn: isPart ? undefined : a.nestIn,
|
|
3844
|
+
figureCard: isPart ? undefined : a.figureCard,
|
|
3845
|
+
stationNamePosition: a.stationNamePosition,
|
|
3846
|
+
subtitlePlacement: a.subtitlePlacement,
|
|
3847
|
+
titleFontSize: a.titleFontSize,
|
|
3848
|
+
markGap: a.markGap,
|
|
2415
3849
|
eyebrow: a.eyebrow,
|
|
2416
3850
|
value: a.value,
|
|
2417
3851
|
previous: a.previous,
|
|
2418
3852
|
rows: a.rows,
|
|
2419
|
-
// 行頭の印 (#1466)。
|
|
2420
|
-
marks: a.marks
|
|
3853
|
+
// 行頭の印 (#1466)。 行と対で読む。 古い語は記法と同じ関数で読み替える (#2782)
|
|
3854
|
+
marks: a.marks?.map((m) =>
|
|
3855
|
+
typeof m === "string"
|
|
3856
|
+
? 行頭の語へ読み替える(m)
|
|
3857
|
+
: { mark: 行頭の語へ読み替える(m.mark), ...(m.tone === undefined ? {} : { tone: m.tone }) },
|
|
3858
|
+
),
|
|
3859
|
+
stage: isPart ? undefined : a.stage,
|
|
2421
3860
|
lane: a.lane,
|
|
2422
3861
|
stack: a.stack,
|
|
2423
3862
|
initial: a.initial,
|
|
@@ -2441,8 +3880,17 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2441
3880
|
tone: resolveTone(a.tone) ?? 色.tone,
|
|
2442
3881
|
owner: a.owner,
|
|
2443
3882
|
end: a.end,
|
|
3883
|
+
startLabel: a.startLabel,
|
|
3884
|
+
milestone: a.milestone,
|
|
3885
|
+
emphasis: a.emphasis,
|
|
3886
|
+
at:
|
|
3887
|
+
書いた座標 === undefined
|
|
3888
|
+
? undefined
|
|
3889
|
+
: { x: 書いた座標[0], y: 書いた座標[1], raw: `[${書いた座標.join(", ")}]` },
|
|
3890
|
+
atPos: 書いた座標 === undefined ? undefined : 位置("actors", i, a.at ? "at" : "点の位置"),
|
|
2444
3891
|
touchpoint: a.touchpoint,
|
|
2445
3892
|
opportunity: a.opportunity,
|
|
3893
|
+
opportunityPosition: a.opportunityPosition,
|
|
2446
3894
|
}),
|
|
2447
3895
|
colorHex: 色.hex,
|
|
2448
3896
|
// 部品に書いた色の名前は効かない。 記法と同じく書いた名前として残し、組み立て側が知らせる (#1973)
|
|
@@ -2457,14 +3905,25 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2457
3905
|
scale: isPart ? a.scale : undefined,
|
|
2458
3906
|
scaleKeys: isPart && a.scale !== undefined ? ["scale"] : undefined,
|
|
2459
3907
|
partId: isPart ? kindStr : undefined,
|
|
2460
|
-
stateOverride: isPart ? a
|
|
3908
|
+
stateOverride: isPart ? 段を含めた上書き(a) : undefined,
|
|
2461
3909
|
// CAR-1693 Phase 1: DSL 表面 pos → 内部 AST layoutPos の 2 層 mapping (naming collision 回避)
|
|
2462
3910
|
layoutPos: a.pos,
|
|
2463
|
-
|
|
3911
|
+
// 相対で置く指定 (#2039)。 欄の名前が内部と同じなのでそのまま渡す
|
|
3912
|
+
posRel: a.posRel,
|
|
3913
|
+
pos: 位置("actors", i),
|
|
2464
3914
|
};
|
|
2465
3915
|
});
|
|
2466
|
-
//
|
|
2467
|
-
const
|
|
3916
|
+
// 図の意匠 (#1553 / #2790)。 正の語と別名を別々に解き、順番に依らず `theme` を優先する
|
|
3917
|
+
const 意匠 = json.theme === undefined ? null : resolveTheme(json.theme);
|
|
3918
|
+
const 別名の意匠 = json.palette === undefined ? null : resolveTheme(json.palette);
|
|
3919
|
+
const 選んだ意匠 = 意匠 ?? 別名の意匠;
|
|
3920
|
+
const 並び順 = json.order === undefined ? null : resolveOrder(json.order);
|
|
3921
|
+
const 形 = json.shape === undefined ? null : resolveDiagramShape(json.shape);
|
|
3922
|
+
const legend = json.legend?.flatMap((item) => {
|
|
3923
|
+
if (item.mark === undefined) return [item];
|
|
3924
|
+
const mark = resolveLegendMark(item.mark);
|
|
3925
|
+
return mark === null ? [] : [{ ...item, mark } as CdlLegendItem];
|
|
3926
|
+
});
|
|
2468
3927
|
|
|
2469
3928
|
const flow: DslStep[] = json.flow.map((s, i) => ({
|
|
2470
3929
|
no: i + 1,
|
|
@@ -2473,13 +3932,15 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2473
3932
|
label: s.label,
|
|
2474
3933
|
sub: s.sub,
|
|
2475
3934
|
side: s.side,
|
|
3935
|
+
fromSide: s.fromSide,
|
|
3936
|
+
toSide: s.toSide,
|
|
2476
3937
|
// 矢印の先の形 (#1462)。 読めない語は組み立てが落とす
|
|
2477
3938
|
head: s.head,
|
|
2478
3939
|
// 端の印の残り 3 欄と、関係の語 / 言づての種類 (#1466)
|
|
2479
3940
|
tailHead: s.tailHead,
|
|
2480
3941
|
headFill: s.headFill,
|
|
2481
3942
|
tailHeadFill: s.tailHeadFill,
|
|
2482
|
-
//
|
|
3943
|
+
// UML 関係の出どころ側の多重度 (#1771 / #2783)
|
|
2483
3944
|
tailSub: s.tailSub,
|
|
2484
3945
|
relation: s.relation,
|
|
2485
3946
|
// 辺の役目と名前の下地 (cdl#618)
|
|
@@ -2498,13 +3959,20 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2498
3959
|
dashOffsetBind: s.dashOffsetBind,
|
|
2499
3960
|
labelOffsetX: s.labelOffsetX,
|
|
2500
3961
|
labelOffsetY: s.labelOffsetY,
|
|
3962
|
+
posX1: s.posX1,
|
|
3963
|
+
posY1: s.posY1,
|
|
3964
|
+
posX2: s.posX2,
|
|
3965
|
+
posY2: s.posY2,
|
|
3966
|
+
waypoints: s.waypoints,
|
|
3967
|
+
labelCollision: s.labelCollision,
|
|
3968
|
+
labelSize: s.labelSize,
|
|
2501
3969
|
overlay: s.overlay,
|
|
2502
3970
|
// 部品の端で繋ぐ要素の id (#1979)。 書かない矢印には欄を足さない = 記法と同じ形になる
|
|
2503
3971
|
...(s.fromPartNode !== undefined ? { fromPartNode: s.fromPartNode } : {}),
|
|
2504
3972
|
...(s.toPartNode !== undefined ? { toPartNode: s.toPartNode } : {}),
|
|
2505
3973
|
// CAR-1693 Phase 1: DSL 表面 pos → 内部 AST layoutPos
|
|
2506
3974
|
layoutPos: s.pos,
|
|
2507
|
-
pos:
|
|
3975
|
+
pos: 位置("flow", i),
|
|
2508
3976
|
}));
|
|
2509
3977
|
// 状態は段が無くても図に載る (#1162 で組み立ての出口が載せる)。 **段の有無で分けない** =
|
|
2510
3978
|
// 分けると `states` だけを書いた JSON で値が 1 つも届かない (記法側で起きていた形、 #1181)
|
|
@@ -2517,7 +3985,7 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2517
3985
|
*
|
|
2518
3986
|
* 相手は名前のまま持ち、識別子への読み替えは組み立てが行う = 2 つの入口で同じ経路を通る。
|
|
2519
3987
|
*/
|
|
2520
|
-
const events: DslEventBinding[] | undefined = json.events?.map((e) => ({
|
|
3988
|
+
const events: DslEventBinding[] | undefined = json.events?.map((e, i) => ({
|
|
2521
3989
|
event: e.on as DslEventBinding["event"],
|
|
2522
3990
|
target:
|
|
2523
3991
|
e.diagram === true
|
|
@@ -2532,7 +4000,7 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2532
4000
|
? ({ kind: "lane" as const, name: e.lane } as const)
|
|
2533
4001
|
: ({ kind: "node" as const, name: e.box ?? "" } as const),
|
|
2534
4002
|
handlerId: e.handler.trim(),
|
|
2535
|
-
pos:
|
|
4003
|
+
pos: 位置("events", i),
|
|
2536
4004
|
}));
|
|
2537
4005
|
const scrolls: DslScrollTrigger[] | undefined = json.scrolls
|
|
2538
4006
|
? Object.entries(json.scrolls).map(([id, spec]) => ({ id, ...spec }))
|
|
@@ -2542,28 +4010,28 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2542
4010
|
const formulas: DslFormula[] | undefined = json.formulas
|
|
2543
4011
|
? Object.entries(json.formulas).map(([id, 値]) =>
|
|
2544
4012
|
typeof 値 === "string"
|
|
2545
|
-
? { id, expression: 値, pos:
|
|
4013
|
+
? { id, expression: 値, pos: 位置("formulas", id) }
|
|
2546
4014
|
: {
|
|
2547
4015
|
id,
|
|
2548
4016
|
expression: 値.expression,
|
|
2549
4017
|
...(値.label !== undefined ? { label: 値.label } : {}),
|
|
2550
|
-
pos:
|
|
4018
|
+
pos: 位置("formulas", id),
|
|
2551
4019
|
},
|
|
2552
4020
|
)
|
|
2553
4021
|
: undefined;
|
|
2554
4022
|
const states: DslState[] = Object.entries(json.states ?? {}).map(([name, initial]) => ({
|
|
2555
4023
|
name,
|
|
2556
4024
|
initial,
|
|
2557
|
-
pos:
|
|
4025
|
+
pos: 位置("states", name),
|
|
2558
4026
|
}));
|
|
2559
|
-
const phases: DslPhase[] = (json.animation ?? []).map((p) => ({
|
|
4027
|
+
const phases: DslPhase[] = (json.animation ?? []).map((p, i) => ({
|
|
2560
4028
|
name: p.step,
|
|
2561
4029
|
durationMs: Math.round((p.duration ?? 1.4) * 1000),
|
|
2562
4030
|
highlight: p.focus,
|
|
2563
|
-
body: p
|
|
4031
|
+
body: 段の説明(p),
|
|
2564
4032
|
badge: p.badge,
|
|
2565
4033
|
// 書いた段だけが欄を持つ。 空文字を置くと「書いた」 と「書いていない」 が同じ形になる
|
|
2566
|
-
...(p.draw !== undefined ? { draw: p.draw, drawPos:
|
|
4034
|
+
...(p.draw !== undefined ? { draw: p.draw, drawPos: 位置("animation", i, "draw") } : {}),
|
|
2567
4035
|
// 割合は `draw` がある段にだけ写す。 単独で書いても描く相手が決まらず何も起きないので、
|
|
2568
4036
|
// 記法側 (同じ行に書かせる形) と同じ状態に揃える
|
|
2569
4037
|
...(p.draw !== undefined && p.drawRatio !== undefined ? { drawRatio: p.drawRatio } : {}),
|
|
@@ -2575,47 +4043,142 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2575
4043
|
state,
|
|
2576
4044
|
from,
|
|
2577
4045
|
to,
|
|
2578
|
-
pos:
|
|
4046
|
+
pos: 位置("animation", i, "tween", state),
|
|
2579
4047
|
})),
|
|
2580
4048
|
}
|
|
2581
4049
|
: {}),
|
|
2582
4050
|
...(p.set && Object.keys(p.set).length > 0
|
|
2583
|
-
? {
|
|
4051
|
+
? {
|
|
4052
|
+
sets: Object.entries(p.set).map(([state, value]) => ({
|
|
4053
|
+
state,
|
|
4054
|
+
value,
|
|
4055
|
+
pos: 位置("animation", i, "set", state),
|
|
4056
|
+
})),
|
|
4057
|
+
}
|
|
2584
4058
|
: {}),
|
|
2585
|
-
pos:
|
|
4059
|
+
pos: 位置("animation", i),
|
|
2586
4060
|
}));
|
|
2587
4061
|
const animate: DslAnimate | undefined =
|
|
2588
|
-
states.length > 0 || phases.length > 0
|
|
4062
|
+
states.length > 0 || phases.length > 0
|
|
4063
|
+
? // 動きの塊そのものには書いた場所が無い。 段がある文書は `animation`、無い文書は
|
|
4064
|
+
// `states` を指す = どちらも「動きを書き始めた場所」 になる
|
|
4065
|
+
{ states, phases, pos: phases.length > 0 ? 位置("animation") : 位置("states") }
|
|
4066
|
+
: undefined;
|
|
4067
|
+
// **別名は記法と同じ表で開く** (#2655 / #2657)。 開いた後は古い綴りが残らない。
|
|
4068
|
+
//
|
|
4069
|
+
// **開く語を 1 つずつ書く** = 表の値をそのまま広げると、持たない語が `undefined` の
|
|
4070
|
+
// まま欄に載り、後ろの `...(形 !== null ? ...)` を打ち消す (実測 = `type: "pie"` の
|
|
4071
|
+
// JSON が形を持たず、書かない時の形 (棒) の図になった)
|
|
4072
|
+
const 別名 = TYPE_ALIASES.get(json.type);
|
|
4073
|
+
const form = json.form ?? json.見せ方;
|
|
4074
|
+
const ticks = json.ticks ?? json.目盛り;
|
|
2589
4075
|
return {
|
|
2590
4076
|
title: json.title,
|
|
2591
|
-
type: json.type,
|
|
4077
|
+
type: 別名?.type ?? (json.type as PresetType),
|
|
4078
|
+
...(form !== undefined
|
|
4079
|
+
? { form, formPos: 位置(json.form !== undefined ? "form" : "見せ方") }
|
|
4080
|
+
: {}),
|
|
4081
|
+
...(ticks !== undefined
|
|
4082
|
+
? { ticks: [...ticks], ticksPos: 位置(json.ticks !== undefined ? "ticks" : "目盛り") }
|
|
4083
|
+
: {}),
|
|
4084
|
+
...(json.ganttToday !== undefined ? { ganttToday: json.ganttToday } : {}),
|
|
4085
|
+
...(json.ganttTickLabels !== undefined ? { ganttTickLabels: [...json.ganttTickLabels] } : {}),
|
|
4086
|
+
...(json.ganttBarEnd !== undefined ? { ganttBarEnd: json.ganttBarEnd } : {}),
|
|
4087
|
+
...(json.ganttBarThickness !== undefined ? { ganttBarThickness: json.ganttBarThickness } : {}),
|
|
4088
|
+
...(json.chartSeriesSkipMuted !== undefined ? { chartSeriesSkipMuted: json.chartSeriesSkipMuted } : {}),
|
|
4089
|
+
...(json.chartLineSeries !== undefined ? { chartLineSeries: json.chartLineSeries } : {}),
|
|
4090
|
+
...(json.chartPieCenterLabel !== undefined ? { chartPieCenterLabel: json.chartPieCenterLabel } : {}),
|
|
4091
|
+
...(json.chartPieTableColumns !== undefined ? { chartPieTableColumns: json.chartPieTableColumns } : {}),
|
|
4092
|
+
...(json.chartPieRingWidth !== undefined ? { chartPieRingWidth: json.chartPieRingWidth } : {}),
|
|
4093
|
+
...(json.chartSlopePeriods !== undefined ? { chartSlopePeriods: json.chartSlopePeriods } : {}),
|
|
4094
|
+
...(json.chartSlopeEmphasisIds !== undefined ? { chartSlopeEmphasisIds: json.chartSlopeEmphasisIds } : {}),
|
|
4095
|
+
...(json.chartSlopeUnit !== undefined ? { chartSlopeUnit: json.chartSlopeUnit } : {}),
|
|
4096
|
+
...(json.chartStackedPeriods !== undefined ? { chartStackedPeriods: json.chartStackedPeriods } : {}),
|
|
4097
|
+
...(json.chartStackedRateId !== undefined ? { chartStackedRateId: json.chartStackedRateId } : {}),
|
|
4098
|
+
...(json.chartStackedLegendPosition !== undefined ? { chartStackedLegendPosition: json.chartStackedLegendPosition } : {}),
|
|
4099
|
+
...(json.chartGaugeValue !== undefined ? { chartGaugeValue: json.chartGaugeValue } : {}),
|
|
4100
|
+
...(json.chartWaffleLegendPosition !== undefined ? { chartWaffleLegendPosition: json.chartWaffleLegendPosition } : {}),
|
|
4101
|
+
...(json.funnelForm !== undefined ? { funnelForm: json.funnelForm } : {}),
|
|
4102
|
+
...(json.funnelRate !== undefined ? { funnelRate: json.funnelRate } : {}),
|
|
4103
|
+
...(json.quadrantPointLabelSide !== undefined ? { quadrantPointLabelSide: json.quadrantPointLabelSide } : {}),
|
|
4104
|
+
...(json.treeNodeForm !== undefined ? { treeNodeForm: json.treeNodeForm } : {}),
|
|
4105
|
+
...(json.treeSize !== undefined ? { treeSize: json.treeSize } : {}),
|
|
4106
|
+
...(json.treeEdgeTone !== undefined ? { treeEdgeTone: json.treeEdgeTone } : {}),
|
|
4107
|
+
...(json.treeEdgeHead !== undefined ? { treeEdgeHead: json.treeEdgeHead } : {}),
|
|
4108
|
+
...(json.mindForm !== undefined ? { mindForm: json.mindForm } : {}),
|
|
4109
|
+
...(json.mindSize !== undefined ? { mindSize: json.mindSize } : {}),
|
|
4110
|
+
...(json.edgeEndGap !== undefined ? { edgeEndGap: json.edgeEndGap } : {}),
|
|
4111
|
+
...(json.journeyForm !== undefined ? { journeyForm: json.journeyForm } : {}),
|
|
4112
|
+
...(json.journeyLineForm !== undefined ? { journeyLineForm: json.journeyLineForm } : {}),
|
|
4113
|
+
...(json.journeyLabels !== undefined ? { journeyLabels: json.journeyLabels } : {}),
|
|
4114
|
+
...(json.journeySize !== undefined ? { journeySize: json.journeySize } : {}),
|
|
4115
|
+
...(json.sequenceActorForm !== undefined ? { sequenceActorForm: json.sequenceActorForm } : {}),
|
|
4116
|
+
...(json.sequenceLabelForm !== undefined ? { sequenceLabelForm: json.sequenceLabelForm } : {}),
|
|
4117
|
+
...(json.sequenceReturnHead !== undefined ? { sequenceReturnHead: json.sequenceReturnHead } : {}),
|
|
4118
|
+
...(json.sequenceKindLabels !== undefined ? { sequenceKindLabels: json.sequenceKindLabels } : {}),
|
|
4119
|
+
...(json.sequenceRowGap !== undefined ? { sequenceRowGap: json.sequenceRowGap } : {}),
|
|
4120
|
+
...(json.sequenceGutter !== undefined ? { sequenceGutter: json.sequenceGutter } : {}),
|
|
4121
|
+
...(json.sequenceBandWidth !== undefined ? { sequenceBandWidth: json.sequenceBandWidth } : {}),
|
|
4122
|
+
...(json.sequenceSelfLoop !== undefined ? { sequenceSelfLoop: json.sequenceSelfLoop } : {}),
|
|
4123
|
+
...(json.figureCard !== undefined ? { figureCard: json.figureCard } : {}),
|
|
4124
|
+
...(json.figureSize !== undefined ? { figureSize: json.figureSize } : {}),
|
|
4125
|
+
...(json.terminalDecoration !== undefined ? { terminalDecoration: json.terminalDecoration } : {}),
|
|
4126
|
+
...(json.cornerForm !== undefined ? { cornerForm: json.cornerForm } : {}),
|
|
4127
|
+
...(json.figureKindLabel !== undefined ? { figureKindLabel: json.figureKindLabel } : {}),
|
|
4128
|
+
...(json.figureInset !== undefined ? { figureInset: json.figureInset } : {}),
|
|
4129
|
+
...(json.chartBarSize !== undefined ? { chartBarSize: json.chartBarSize } : {}),
|
|
4130
|
+
...(json.chartBarShape !== undefined ? { chartBarShape: json.chartBarShape } : {}),
|
|
4131
|
+
...(json.chartLineSize !== undefined ? { chartLineSize: json.chartLineSize } : {}),
|
|
4132
|
+
...(json.chartPieSize !== undefined ? { chartPieSize: json.chartPieSize } : {}),
|
|
4133
|
+
...(json.chartWaffleSize !== undefined ? { chartWaffleSize: json.chartWaffleSize } : {}),
|
|
4134
|
+
...(json.chartSlopeLabelTemplate !== undefined ? { chartSlopeLabelTemplate: json.chartSlopeLabelTemplate } : {}),
|
|
4135
|
+
...(json.chartSlopeSize !== undefined ? { chartSlopeSize: json.chartSlopeSize } : {}),
|
|
4136
|
+
...(json.chartStackedLayout !== undefined ? { chartStackedLayout: json.chartStackedLayout } : {}),
|
|
4137
|
+
...(json.chartGaugeSize !== undefined ? { chartGaugeSize: json.chartGaugeSize } : {}),
|
|
4138
|
+
...(json.ganttSize !== undefined ? { ganttSize: json.ganttSize } : {}),
|
|
4139
|
+
...(json.quadrantLabelPosition !== undefined ? { quadrantLabelPosition: json.quadrantLabelPosition } : {}),
|
|
4140
|
+
...(json.quadrantSize !== undefined ? { quadrantSize: json.quadrantSize } : {}),
|
|
4141
|
+
...(別名?.order !== undefined ? { order: 別名.order, orderPos: 位置("type") } : {}),
|
|
4142
|
+
...(別名?.shape !== undefined ? { shape: 別名.shape, shapePos: 位置("type") } : {}),
|
|
2592
4143
|
// 前後の空白を落としてから見る。 記法側 (`v05/parser.ts`) が `trim()` してから
|
|
2593
4144
|
// 空かどうかを判定するため、 揃えないと **空白だけの値で入口ごとに図が変わる**
|
|
2594
4145
|
// (記法は書かなかった扱い、 JSON は中身のない帯を描く。 Round 2 の指摘で実測)
|
|
2595
4146
|
...(整えた小見出し(json.eyebrow) !== undefined
|
|
2596
|
-
? { eyebrow: 整えた小見出し(json.eyebrow), eyebrowPos:
|
|
4147
|
+
? { eyebrow: 整えた小見出し(json.eyebrow), eyebrowPos: 位置("eyebrow") }
|
|
2597
4148
|
: {}),
|
|
2598
4149
|
// 2 軸で仕分ける図の軸の名前 (#1294)。 中身の無い形は「書かなかった」 と同じにする =
|
|
2599
4150
|
// 空の軸を渡すと、書いていない側の名前が空文字で描かれる (記法側と同じ扱い)
|
|
2600
4151
|
...(json.axes && (json.axes.x !== undefined || json.axes.y !== undefined)
|
|
2601
|
-
? { axes: json.axes, axesPos:
|
|
4152
|
+
? { axes: json.axes, axesPos: 位置("axes") }
|
|
2602
4153
|
: {}),
|
|
4154
|
+
// 区画の名前 (#2667)。 鍵は記法と同じ 4 語なので、描画側の綴りへここで直す
|
|
4155
|
+
...(区画の名前を直す(json.regions) === undefined
|
|
4156
|
+
? {}
|
|
4157
|
+
: { regions: 区画の名前を直す(json.regions) }),
|
|
4158
|
+
...(json.legendFontSize !== undefined ? { legendFontSize: json.legendFontSize } : {}),
|
|
4159
|
+
...(json.legendOffset !== undefined ? { legendOffset: json.legendOffset } : {}),
|
|
4160
|
+
...(json.stageHeader !== undefined ? { stageHeader: json.stageHeader } : {}),
|
|
4161
|
+
...(json.stageLayout !== undefined ? { stageLayout: json.stageLayout } : {}),
|
|
2603
4162
|
actors,
|
|
2604
4163
|
flow,
|
|
2605
4164
|
animate,
|
|
2606
4165
|
// 他の値から決まる値 (#1181)。 書いた順に並べる = 解く順は参照から決まるので順序に
|
|
2607
4166
|
// 意味は無いが、知らせの並びが書いた順になる
|
|
2608
4167
|
values: json.values
|
|
2609
|
-
? Object.entries(json.values).map(([name, expression]) => ({
|
|
4168
|
+
? Object.entries(json.values).map(([name, expression]) => ({
|
|
4169
|
+
name,
|
|
4170
|
+
expression,
|
|
4171
|
+
pos: 位置("values", name),
|
|
4172
|
+
}))
|
|
2610
4173
|
: undefined,
|
|
2611
|
-
viewport: json.viewport ? { ...json.viewport, pos:
|
|
4174
|
+
viewport: json.viewport ? { ...json.viewport, pos: 位置("viewport") } : undefined,
|
|
2612
4175
|
lanes: json.lanes
|
|
2613
4176
|
? Object.fromEntries(
|
|
2614
4177
|
Object.entries(json.lanes).map(([id, l]) => {
|
|
2615
4178
|
// CAR-1693 Phase 1: DSL 表面 pos → 内部 AST layoutPos の 2 層 mapping。
|
|
2616
4179
|
// JSON input の { pos, x, width, ... } を分離し、 pos のみ layoutPos に rename する。
|
|
2617
4180
|
const { pos: layoutPos, ...laneRest } = l;
|
|
2618
|
-
return [id, { id, ...laneRest, layoutPos, pos:
|
|
4181
|
+
return [id, { id, ...laneRest, layoutPos, pos: 位置("lanes", id) }];
|
|
2619
4182
|
}),
|
|
2620
4183
|
)
|
|
2621
4184
|
: undefined,
|
|
@@ -2623,7 +4186,7 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2623
4186
|
? Object.fromEntries(
|
|
2624
4187
|
Object.entries(json.groups).map(([id, g]) => [
|
|
2625
4188
|
id,
|
|
2626
|
-
{ id, label: g.label, lanes: g.lanes, pos:
|
|
4189
|
+
{ id, label: g.label, lanes: g.lanes, pos: 位置("groups", id) },
|
|
2627
4190
|
]),
|
|
2628
4191
|
)
|
|
2629
4192
|
: undefined,
|
|
@@ -2638,12 +4201,31 @@ export function jsonToDoc(json: DragonJson): DslDocument {
|
|
|
2638
4201
|
reveal: json.reveal,
|
|
2639
4202
|
// 箱に触れると関係する線だけを光らせるか (#1757)
|
|
2640
4203
|
relations: json.relations,
|
|
2641
|
-
// 図の並ぶ向き (#1494)。 JSON
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
|
|
4204
|
+
// 図の並ぶ向き (#1494)。 JSON は英語で書くので、記法と同じ語に直してから渡す。
|
|
4205
|
+
// 書いた行も記法と同じく持つ = 持たないと、向きの知らせが文書の行 (1 行目) を指す
|
|
4206
|
+
...(json.direction !== undefined
|
|
4207
|
+
? {
|
|
4208
|
+
direction: json.direction === "horizontal" ? ("横" as const) : ("縦" as const),
|
|
4209
|
+
directionPos: 位置("direction"),
|
|
4210
|
+
}
|
|
4211
|
+
: {}),
|
|
4212
|
+
// 箱を並べ替える軸 (#2655)。 向きと同じく英語で書くので、記法と同じ解決を通して日本語に直す。
|
|
4213
|
+
// 読めない語は渡さない = 公開 schema の語の一覧が絞っているので、ここに来るのは書き間違いだけ
|
|
4214
|
+
...(並び順 !== null ? { order: 並び順, orderPos: 位置("order") } : {}),
|
|
4215
|
+
// 数を描く図の形 (#2657)。 記法と同じ解決を通す = 受ける語がずれない
|
|
4216
|
+
...(形 !== null ? { shape: 形, shapePos: 位置("shape") } : {}),
|
|
4217
|
+
// 図の意匠 (#1553 / #2790)。 dragon の文書では `theme`、cdl の図へ載せる時は `palette` とする
|
|
4218
|
+
...(選んだ意匠 !== null ? { theme: 選んだ意匠 } : {}),
|
|
4219
|
+
...(legend && legend.length > 0 ? { legend } : {}),
|
|
4220
|
+
...(意匠 !== null && 別名の意匠 !== null
|
|
4221
|
+
? {
|
|
4222
|
+
themeAlsoPalettePos: {
|
|
4223
|
+
themeLine: 位置("theme").line,
|
|
4224
|
+
paletteLine: 位置("palette").line,
|
|
4225
|
+
},
|
|
4226
|
+
}
|
|
4227
|
+
: {}),
|
|
4228
|
+
pos: 位置(),
|
|
2647
4229
|
};
|
|
2648
4230
|
}
|
|
2649
4231
|
|
|
@@ -2673,7 +4255,17 @@ export function jsonToDiagram(
|
|
|
2673
4255
|
// **`onNotice` も通す**。 記法経路だけに通知を付けていたため、 同じ型を受ける JSON / YAML
|
|
2674
4256
|
// 経路では読めない値や捨てた矢印が利用者へ届かなかった (review 指摘)。 エディタの YAML タブは
|
|
2675
4257
|
// ここを通る
|
|
2676
|
-
opts?: {
|
|
4258
|
+
opts?: {
|
|
4259
|
+
partsCatalog?: Record<string, CdlDiagram>;
|
|
4260
|
+
onNotice?: CompileToCdlOpts["onNotice"];
|
|
4261
|
+
/**
|
|
4262
|
+
* 本文の場所から書いた行を引く表 (#2117)。 渡すと知らせが書いた行を指す。
|
|
4263
|
+
*
|
|
4264
|
+
* JSON そのものは書いた場所を持たないため、本文の行が分かる入口 (編集画面の YAML 欄) が
|
|
4265
|
+
* 集めて渡す。 渡さない呼出は今までどおり全ての知らせが 0 行になる。
|
|
4266
|
+
*/
|
|
4267
|
+
行の表?: 書いた行の表;
|
|
4268
|
+
},
|
|
2677
4269
|
): CdlDiagram {
|
|
2678
4270
|
const v = validateJson(json);
|
|
2679
4271
|
if (!v.ok) {
|
|
@@ -2682,7 +4274,7 @@ export function jsonToDiagram(
|
|
|
2682
4274
|
.join("\n");
|
|
2683
4275
|
throw new Error(`Dragon JSON DSL validation error:\n${msg}`);
|
|
2684
4276
|
}
|
|
2685
|
-
const doc = jsonToDoc(v.data);
|
|
4277
|
+
const doc = jsonToDoc(v.data, opts?.行の表);
|
|
2686
4278
|
return compileToCdl(doc, opts);
|
|
2687
4279
|
}
|
|
2688
4280
|
|