@diagrammo/dgmo 0.47.0 → 0.48.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/dist/auto.js +1 -1
- package/dist/auto.mjs +1 -1
- package/dist/element.js +1 -1
- package/dist/element.mjs +1 -1
- package/package.json +19 -70
- package/dist/advanced.cjs +0 -71648
- package/dist/advanced.d.cts +0 -5719
- package/dist/auto.cjs +0 -70648
- package/dist/auto.d.cts +0 -54
- package/dist/block.cjs +0 -70208
- package/dist/block.d.cts +0 -115
- package/dist/editor.cjs +0 -526
- package/dist/editor.d.cts +0 -26
- package/dist/element.cjs +0 -70530
- package/dist/element.d.cts +0 -62
- package/dist/highlight.cjs +0 -852
- package/dist/highlight.d.cts +0 -49
- package/dist/index.cjs +0 -70491
- package/dist/index.d.cts +0 -160
- package/dist/pert.cjs +0 -325
- package/dist/pert.d.cts +0 -559
- package/dist/themes-1-CuKpeH.d.cts +0 -908
- package/src/advanced.ts +0 -873
- package/src/arc/parser.ts +0 -15
- package/src/arc/renderer.ts +0 -633
- package/src/auto/index.ts +0 -621
- package/src/auto/shared.ts +0 -370
- package/src/auto/styles.ts +0 -73
- package/src/block/layout.ts +0 -198
- package/src/block/parser.ts +0 -427
- package/src/block/renderer.ts +0 -484
- package/src/block/types.ts +0 -67
- package/src/boxes-and-lines/collapse.ts +0 -96
- package/src/boxes-and-lines/focus.ts +0 -257
- package/src/boxes-and-lines/label-placement.ts +0 -339
- package/src/boxes-and-lines/layout-grouped.ts +0 -912
- package/src/boxes-and-lines/layout-layered.ts +0 -722
- package/src/boxes-and-lines/layout-search.ts +0 -1678
- package/src/boxes-and-lines/layout.ts +0 -398
- package/src/boxes-and-lines/parser.ts +0 -1181
- package/src/boxes-and-lines/renderer.ts +0 -1465
- package/src/boxes-and-lines/types.ts +0 -66
- package/src/c4/layout.ts +0 -2441
- package/src/c4/parser.ts +0 -1158
- package/src/c4/renderer.ts +0 -2008
- package/src/c4/types.ts +0 -89
- package/src/chart-type-registry.ts +0 -529
- package/src/chart-types.ts +0 -225
- package/src/chart.ts +0 -685
- package/src/charts-d3/bar.ts +0 -338
- package/src/charts-d3/chord.ts +0 -140
- package/src/charts-d3/function.ts +0 -147
- package/src/charts-d3/funnel.ts +0 -104
- package/src/charts-d3/heatmap.ts +0 -145
- package/src/charts-d3/index.ts +0 -337
- package/src/charts-d3/interactions.ts +0 -477
- package/src/charts-d3/line.ts +0 -344
- package/src/charts-d3/mount.ts +0 -151
- package/src/charts-d3/pie.ts +0 -117
- package/src/charts-d3/polar.ts +0 -89
- package/src/charts-d3/radar.ts +0 -0
- package/src/charts-d3/sankey.ts +0 -0
- package/src/charts-d3/scatter.ts +0 -185
- package/src/charts-d3/shared.ts +0 -208
- package/src/class/layout.ts +0 -326
- package/src/class/parser.ts +0 -635
- package/src/class/renderer.ts +0 -793
- package/src/class/types.ts +0 -58
- package/src/cli-banner.ts +0 -106
- package/src/cli.ts +0 -1080
- package/src/colors-diagnostics.ts +0 -35
- package/src/colors.ts +0 -379
- package/src/completion-types.ts +0 -27
- package/src/completion.ts +0 -2668
- package/src/cycle/layout.ts +0 -1026
- package/src/cycle/parser.ts +0 -387
- package/src/cycle/renderer.ts +0 -653
- package/src/cycle/types.ts +0 -101
- package/src/d3.ts +0 -1434
- package/src/data-chart-parser.ts +0 -1007
- package/src/dgmo-router.ts +0 -296
- package/src/diagnostics-registry.ts +0 -108
- package/src/diagnostics.ts +0 -0
- package/src/dimensions.ts +0 -20
- package/src/directives-registry.ts +0 -396
- package/src/editor/dgmo.grammar +0 -76
- package/src/editor/dgmo.grammar.d.ts +0 -2
- package/src/editor/dgmo.grammar.js +0 -18
- package/src/editor/dgmo.grammar.terms.d.ts +0 -5
- package/src/editor/dgmo.grammar.terms.js +0 -35
- package/src/editor/highlight-api.ts +0 -596
- package/src/editor/highlight.ts +0 -36
- package/src/editor/index.ts +0 -27
- package/src/editor/keywords.ts +0 -128
- package/src/editor/tokens.ts +0 -30
- package/src/element/index.ts +0 -432
- package/src/embed/css.ts +0 -312
- package/src/embed/escape.ts +0 -15
- package/src/embed/index.ts +0 -280
- package/src/er/classify.ts +0 -228
- package/src/er/layout.ts +0 -530
- package/src/er/parser.ts +0 -750
- package/src/er/renderer.ts +0 -725
- package/src/er/types.ts +0 -52
- package/src/error-card.ts +0 -269
- package/src/event-line/diagnostics.ts +0 -117
- package/src/event-line/parser.ts +0 -643
- package/src/event-line/renderer.ts +0 -1742
- package/src/event-line/types.ts +0 -74
- package/src/fonts.ts +0 -3
- package/src/gantt/calculator.ts +0 -979
- package/src/gantt/parser.ts +0 -1641
- package/src/gantt/renderer.ts +0 -3787
- package/src/gantt/resolver.ts +0 -172
- package/src/gantt/types.ts +0 -206
- package/src/graph/diagnostics.ts +0 -64
- package/src/graph/edge-spline.ts +0 -29
- package/src/graph/flowchart-parser.ts +0 -759
- package/src/graph/flowchart-renderer.ts +0 -817
- package/src/graph/layout.ts +0 -472
- package/src/graph/notes.ts +0 -21
- package/src/graph/state-collapse.ts +0 -78
- package/src/graph/state-parser.ts +0 -574
- package/src/graph/state-renderer.ts +0 -653
- package/src/graph/types.ts +0 -65
- package/src/index.ts +0 -261
- package/src/infra/compute.ts +0 -1385
- package/src/infra/layout.ts +0 -888
- package/src/infra/parser.ts +0 -1234
- package/src/infra/renderer.ts +0 -2534
- package/src/infra/roles.ts +0 -75
- package/src/infra/types.ts +0 -216
- package/src/infra/validation.ts +0 -201
- package/src/journey-map/layout.ts +0 -490
- package/src/journey-map/parser.ts +0 -557
- package/src/journey-map/renderer.ts +0 -1580
- package/src/journey-map/types.ts +0 -47
- package/src/kanban/mutations.ts +0 -175
- package/src/kanban/parser.ts +0 -509
- package/src/kanban/renderer.ts +0 -1449
- package/src/kanban/types.ts +0 -39
- package/src/label-layout.ts +0 -368
- package/src/map/colorize.ts +0 -54
- package/src/map/completion.ts +0 -316
- package/src/map/context-labels.ts +0 -609
- package/src/map/data/PROVENANCE.json +0 -1
- package/src/map/data/README.md +0 -40
- package/src/map/data/airport-collisions.json +0 -1
- package/src/map/data/airports.json +0 -1
- package/src/map/data/gazetteer.json +0 -1
- package/src/map/data/lakes.json +0 -1
- package/src/map/data/mountain-ranges.json +0 -1
- package/src/map/data/na-lakes.json +0 -1
- package/src/map/data/na-land.json +0 -1
- package/src/map/data/region-names.json +0 -1
- package/src/map/data/rivers.json +0 -1
- package/src/map/data/types.ts +0 -121
- package/src/map/data/us-states.json +0 -1
- package/src/map/data/water-bodies.json +0 -1
- package/src/map/data/world-coarse.json +0 -1
- package/src/map/data/world-detail.json +0 -1
- package/src/map/diagnostics.ts +0 -134
- package/src/map/dimensions.ts +0 -150
- package/src/map/geo-query.ts +0 -295
- package/src/map/geo.ts +0 -438
- package/src/map/invert.ts +0 -111
- package/src/map/layout.ts +0 -5069
- package/src/map/legend-band.ts +0 -127
- package/src/map/load-data.ts +0 -186
- package/src/map/parser.ts +0 -783
- package/src/map/renderer.ts +0 -1445
- package/src/map/resolved-types.ts +0 -160
- package/src/map/resolver.ts +0 -1146
- package/src/map/types.ts +0 -194
- package/src/mindmap/collapse.ts +0 -67
- package/src/mindmap/layout.ts +0 -649
- package/src/mindmap/parser.ts +0 -430
- package/src/mindmap/renderer.ts +0 -637
- package/src/mindmap/text-wrap.ts +0 -218
- package/src/mindmap/types.ts +0 -56
- package/src/org/collapse.ts +0 -151
- package/src/org/layout.ts +0 -1262
- package/src/org/parser.ts +0 -519
- package/src/org/renderer.ts +0 -866
- package/src/org/resolver.ts +0 -636
- package/src/palettes/atlas.ts +0 -77
- package/src/palettes/blueprint.ts +0 -73
- package/src/palettes/catppuccin.ts +0 -84
- package/src/palettes/color-utils.ts +0 -443
- package/src/palettes/index.ts +0 -67
- package/src/palettes/nord.ts +0 -67
- package/src/palettes/registry.ts +0 -114
- package/src/palettes/slate.ts +0 -73
- package/src/palettes/tidewater.ts +0 -73
- package/src/palettes/tokyo-night.ts +0 -86
- package/src/palettes/types.ts +0 -88
- package/src/pert/analyzer.ts +0 -1183
- package/src/pert/diagnostics.ts +0 -110
- package/src/pert/index.ts +0 -43
- package/src/pert/internal.ts +0 -252
- package/src/pert/layout.ts +0 -971
- package/src/pert/monte-carlo.ts +0 -446
- package/src/pert/parser.ts +0 -1606
- package/src/pert/renderer.ts +0 -4009
- package/src/pert/share-normalize.ts +0 -62
- package/src/pert/types.ts +0 -604
- package/src/pyramid/parser.ts +0 -185
- package/src/pyramid/renderer.ts +0 -768
- package/src/pyramid/types.ts +0 -28
- package/src/quadrant/parser.ts +0 -18
- package/src/quadrant/renderer.ts +0 -638
- package/src/raci/diagnostics.ts +0 -213
- package/src/raci/index.ts +0 -23
- package/src/raci/mutations.ts +0 -321
- package/src/raci/parser.ts +0 -699
- package/src/raci/renderer.ts +0 -1627
- package/src/raci/types.ts +0 -82
- package/src/raci/variants.ts +0 -320
- package/src/render.ts +0 -268
- package/src/ring/parser.ts +0 -212
- package/src/ring/renderer.ts +0 -490
- package/src/ring/types.ts +0 -27
- package/src/sequence/collapse.ts +0 -174
- package/src/sequence/parser.ts +0 -1596
- package/src/sequence/participant-inference.ts +0 -152
- package/src/sequence/renderer.ts +0 -3168
- package/src/sequence/tag-resolution.ts +0 -164
- package/src/sharing.ts +0 -211
- package/src/sitemap/collapse.ts +0 -100
- package/src/sitemap/layout.ts +0 -925
- package/src/sitemap/parser.ts +0 -755
- package/src/sitemap/renderer.ts +0 -828
- package/src/sitemap/types.ts +0 -42
- package/src/slope/parser.ts +0 -18
- package/src/slope/renderer.ts +0 -378
- package/src/swimlane/diagnostics.ts +0 -94
- package/src/swimlane/layout.ts +0 -597
- package/src/swimlane/parser.ts +0 -613
- package/src/swimlane/renderer.ts +0 -681
- package/src/swimlane/types.ts +0 -151
- package/src/tech-radar/interactive.ts +0 -1129
- package/src/tech-radar/layout.ts +0 -197
- package/src/tech-radar/parser.ts +0 -436
- package/src/tech-radar/renderer.ts +0 -1239
- package/src/tech-radar/shared.ts +0 -151
- package/src/tech-radar/types.ts +0 -86
- package/src/themes.ts +0 -22
- package/src/timeline/parser.ts +0 -472
- package/src/timeline/renderer.ts +0 -3024
- package/src/timeline/types.ts +0 -33
- package/src/timeline/viz-parser.ts +0 -21
- package/src/treemap/diagnostics.ts +0 -62
- package/src/treemap/layout-radial.ts +0 -233
- package/src/treemap/layout.ts +0 -189
- package/src/treemap/parser.ts +0 -463
- package/src/treemap/renderer-radial.ts +0 -438
- package/src/treemap/renderer.ts +0 -572
- package/src/treemap/treemap-shared.ts +0 -309
- package/src/treemap/types.ts +0 -60
- package/src/utils/arrow-markers.ts +0 -51
- package/src/utils/arrows-diagnostics.ts +0 -62
- package/src/utils/arrows.ts +0 -227
- package/src/utils/brand.ts +0 -61
- package/src/utils/card.ts +0 -183
- package/src/utils/collapse-engine/tree-edges.ts +0 -115
- package/src/utils/collapse-engine/tree.ts +0 -117
- package/src/utils/d3-helpers.ts +0 -219
- package/src/utils/d3-types.ts +0 -9
- package/src/utils/description-helpers.ts +0 -38
- package/src/utils/duration.ts +0 -312
- package/src/utils/export-container.ts +0 -41
- package/src/utils/expr-eval.ts +0 -27
- package/src/utils/fit-canvas.ts +0 -64
- package/src/utils/hover-styles.ts +0 -673
- package/src/utils/inline-markdown.ts +0 -96
- package/src/utils/legend-constants.ts +0 -48
- package/src/utils/legend-d3.ts +0 -618
- package/src/utils/legend-integration.ts +0 -107
- package/src/utils/legend-layout.ts +0 -776
- package/src/utils/legend-svg.ts +0 -214
- package/src/utils/legend-types.ts +0 -272
- package/src/utils/name-normalize.ts +0 -163
- package/src/utils/note-box/constants.ts +0 -25
- package/src/utils/note-box/index.ts +0 -11
- package/src/utils/note-box/metrics.ts +0 -90
- package/src/utils/note-box/svg.ts +0 -331
- package/src/utils/notes/bounds.ts +0 -30
- package/src/utils/notes/build.ts +0 -131
- package/src/utils/notes/index.ts +0 -18
- package/src/utils/notes/model.ts +0 -19
- package/src/utils/notes/parse.ts +0 -131
- package/src/utils/notes/place.ts +0 -177
- package/src/utils/notes/resolve.ts +0 -88
- package/src/utils/number-format.ts +0 -36
- package/src/utils/parsing.ts +0 -953
- package/src/utils/reserved-key-registry.ts +0 -239
- package/src/utils/safe-href.ts +0 -38
- package/src/utils/scaling.ts +0 -97
- package/src/utils/svg-embed.ts +0 -251
- package/src/utils/tag-groups.ts +0 -607
- package/src/utils/text-measure.ts +0 -122
- package/src/utils/time-ticks.ts +0 -253
- package/src/utils/title-constants.ts +0 -24
- package/src/utils/visual-conventions.ts +0 -61
- package/src/utils/wrapped-desc.ts +0 -87
- package/src/venn/parser.ts +0 -18
- package/src/venn/renderer.ts +0 -1047
- package/src/version-control/diagnostics.ts +0 -67
- package/src/version-control/parser.ts +0 -544
- package/src/version-control/renderer.ts +0 -724
- package/src/version-control/types.ts +0 -100
- package/src/visualizations/parse.ts +0 -1560
- package/src/visualizations/types.ts +0 -262
- package/src/wireframe/layout.ts +0 -479
- package/src/wireframe/parser.ts +0 -1114
- package/src/wireframe/renderer.ts +0 -1318
- package/src/wireframe/types.ts +0 -110
- package/src/wordcloud/parser.ts +0 -18
- package/src/wordcloud/renderer.ts +0 -357
package/src/sitemap/parser.ts
DELETED
|
@@ -1,755 +0,0 @@
|
|
|
1
|
-
// ============================================================
|
|
2
|
-
// Sitemap Diagram Parser
|
|
3
|
-
// ============================================================
|
|
4
|
-
|
|
5
|
-
import type { PaletteColors } from '../palettes';
|
|
6
|
-
import type { DgmoError } from '../diagnostics';
|
|
7
|
-
import {
|
|
8
|
-
formatDgmoError,
|
|
9
|
-
makeDgmoError,
|
|
10
|
-
makeFail,
|
|
11
|
-
suggest,
|
|
12
|
-
} from '../diagnostics';
|
|
13
|
-
import {
|
|
14
|
-
SITEMAP_REGISTRY,
|
|
15
|
-
withTagAliases,
|
|
16
|
-
} from '../utils/reserved-key-registry';
|
|
17
|
-
import { normalizeName } from '../utils/name-normalize';
|
|
18
|
-
import type { TagGroup } from '../utils/tag-groups';
|
|
19
|
-
import type { Writable } from '../utils/brand';
|
|
20
|
-
import {
|
|
21
|
-
isTagBlockHeading,
|
|
22
|
-
matchTagBlockHeading,
|
|
23
|
-
validateTagValues,
|
|
24
|
-
validateTagGroupNames,
|
|
25
|
-
stripDefaultModifier,
|
|
26
|
-
finalizeAutoTagColors,
|
|
27
|
-
AUTO_TAG_COLOR_SENTINEL,
|
|
28
|
-
tagAttrKey,
|
|
29
|
-
} from '../utils/tag-groups';
|
|
30
|
-
import {
|
|
31
|
-
measureIndent,
|
|
32
|
-
extractColor,
|
|
33
|
-
splitNameAndMeta,
|
|
34
|
-
parseFirstLine,
|
|
35
|
-
OPTION_NOCOLON_RE,
|
|
36
|
-
ALL_CHART_TYPES,
|
|
37
|
-
tryParseSharedOption,
|
|
38
|
-
warnUnknownMetaKeys,
|
|
39
|
-
} from '../utils/parsing';
|
|
40
|
-
import type { SitemapNode, ParsedSitemap } from './types';
|
|
41
|
-
import { tryStripDescriptionKeyword } from '../utils/description-helpers';
|
|
42
|
-
|
|
43
|
-
// ============================================================
|
|
44
|
-
// Regexes
|
|
45
|
-
// ============================================================
|
|
46
|
-
|
|
47
|
-
// Captures: [1]=name, [2]=trailing content (same-line `key: value` metadata).
|
|
48
|
-
const CONTAINER_RE = /^\[([^\]]+)\]\s*(.*)$/;
|
|
49
|
-
/** Metadata on content nodes: `key: value` (colon-separated, used in content phase) */
|
|
50
|
-
const METADATA_RE = /^([^:]+):\s*(.+)$/;
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Arrow line: `-label->` or `->` followed by target label.
|
|
54
|
-
* Edges have no color slot (spec §1.7).
|
|
55
|
-
* Captures: [1] label, [2] target
|
|
56
|
-
*/
|
|
57
|
-
const ARROW_RE = /^-([^>][^>]*?)?\s*->\s*(.+)$/;
|
|
58
|
-
const BARE_ARROW_RE = /^->\s*(.+)$/;
|
|
59
|
-
|
|
60
|
-
// ============================================================
|
|
61
|
-
// Helpers
|
|
62
|
-
// ============================================================
|
|
63
|
-
|
|
64
|
-
function parseArrowLine(
|
|
65
|
-
trimmed: string,
|
|
66
|
-
_palette: PaletteColors | undefined,
|
|
67
|
-
_lineNumber: number,
|
|
68
|
-
_diagnostics: DgmoError[]
|
|
69
|
-
): {
|
|
70
|
-
label?: string;
|
|
71
|
-
target: string;
|
|
72
|
-
targetIsGroup: boolean;
|
|
73
|
-
} | null {
|
|
74
|
-
// Bare arrow: -> Target
|
|
75
|
-
const bareMatch = trimmed.match(BARE_ARROW_RE);
|
|
76
|
-
if (bareMatch) {
|
|
77
|
-
// Capture group 1 present by regex shape.
|
|
78
|
-
const rawTarget = bareMatch[1]!.trim();
|
|
79
|
-
const groupMatch = rawTarget.match(/^\[(.+)\]$/);
|
|
80
|
-
return {
|
|
81
|
-
target: groupMatch ? groupMatch[1]!.trim() : rawTarget,
|
|
82
|
-
targetIsGroup: !!groupMatch,
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// Labeled arrow: -label-> Target
|
|
87
|
-
const arrowMatch = trimmed.match(ARROW_RE);
|
|
88
|
-
if (arrowMatch) {
|
|
89
|
-
const label = arrowMatch[1]?.trim() || undefined;
|
|
90
|
-
// Capture group 2 present by regex shape.
|
|
91
|
-
const rawTarget = arrowMatch[2]!.trim();
|
|
92
|
-
const groupMatch = rawTarget.match(/^\[(.+)\]$/);
|
|
93
|
-
return {
|
|
94
|
-
...(label !== undefined && { label }),
|
|
95
|
-
target: groupMatch ? groupMatch[1]!.trim() : rawTarget,
|
|
96
|
-
targetIsGroup: !!groupMatch,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ============================================================
|
|
104
|
-
// Inference
|
|
105
|
-
// ============================================================
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Returns true if content looks like a sitemap diagram.
|
|
109
|
-
* Heuristic: has `->` arrows AND `[Group]` containers but does NOT have
|
|
110
|
-
* flowchart shape delimiters ((...), <...>, /.../) adjacent to arrows.
|
|
111
|
-
*/
|
|
112
|
-
export function looksLikeSitemap(content: string): boolean {
|
|
113
|
-
const lines = content.split('\n');
|
|
114
|
-
let hasArrow = false;
|
|
115
|
-
let hasContainer = false;
|
|
116
|
-
|
|
117
|
-
for (const line of lines) {
|
|
118
|
-
const trimmed = line.trim();
|
|
119
|
-
if (!trimmed || trimmed.startsWith('//')) continue;
|
|
120
|
-
|
|
121
|
-
// Skip header lines
|
|
122
|
-
if (parseFirstLine(trimmed)) continue;
|
|
123
|
-
if (isTagBlockHeading(trimmed)) continue;
|
|
124
|
-
|
|
125
|
-
if (/^-.*->\s*.+/.test(trimmed) || /^->\s*.+/.test(trimmed)) {
|
|
126
|
-
hasArrow = true;
|
|
127
|
-
}
|
|
128
|
-
if (CONTAINER_RE.test(trimmed)) {
|
|
129
|
-
hasContainer = true;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
if (!hasArrow || !hasContainer) return false;
|
|
134
|
-
|
|
135
|
-
// Exclude flowchart: flowchart arrows connect shaped nodes like (X) -> [Y]
|
|
136
|
-
// Sitemap arrows are indented under a parent node, target is plain text
|
|
137
|
-
const hasFlowchartShapes =
|
|
138
|
-
/[\])][ \t]*-.*->/.test(content) || /->[ \t]*[[(</]/.test(content);
|
|
139
|
-
|
|
140
|
-
return !hasFlowchartShapes;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// ============================================================
|
|
144
|
-
// Parser
|
|
145
|
-
// ============================================================
|
|
146
|
-
|
|
147
|
-
export function parseSitemap(
|
|
148
|
-
content: string,
|
|
149
|
-
palette?: PaletteColors
|
|
150
|
-
): ParsedSitemap {
|
|
151
|
-
const options: Record<string, string> = {};
|
|
152
|
-
const result: Writable<ParsedSitemap> = {
|
|
153
|
-
title: null,
|
|
154
|
-
titleLineNumber: null,
|
|
155
|
-
direction: 'LR',
|
|
156
|
-
roots: [],
|
|
157
|
-
edges: [],
|
|
158
|
-
tagGroups: [],
|
|
159
|
-
options,
|
|
160
|
-
diagnostics: [],
|
|
161
|
-
error: null,
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const fail = makeFail(result);
|
|
165
|
-
|
|
166
|
-
const pushError = (line: number, message: string): void => {
|
|
167
|
-
const diag = makeDgmoError(line, message);
|
|
168
|
-
result.diagnostics.push(diag);
|
|
169
|
-
if (!result.error) result.error = formatDgmoError(diag);
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
const pushWarning = (line: number, message: string): void => {
|
|
173
|
-
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
if (!content?.trim()) {
|
|
177
|
-
return fail(0, 'No content provided');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const lines = content.split('\n');
|
|
181
|
-
let contentStarted = false;
|
|
182
|
-
let nodeCounter = 0;
|
|
183
|
-
let containerCounter = 0;
|
|
184
|
-
let firstLineParsed = false;
|
|
185
|
-
|
|
186
|
-
// Tag group parsing state
|
|
187
|
-
let currentTagGroup: Writable<TagGroup> | null = null;
|
|
188
|
-
|
|
189
|
-
// metaAliasMap: tag-group metadata-key aliases (per A1 convention).
|
|
190
|
-
const metaAliasMap = new Map<string, string>();
|
|
191
|
-
// nameAliasMap: TD-18 entity-name aliases (`a` → `node-7`). Per C8.
|
|
192
|
-
const nameAliasMap = new Map<string, string>();
|
|
193
|
-
|
|
194
|
-
// Indent stack for hierarchy tracking
|
|
195
|
-
const indentStack: { node: Writable<SitemapNode>; indent: number }[] = [];
|
|
196
|
-
|
|
197
|
-
// Map label (lowercased) -> node for arrow target resolution
|
|
198
|
-
const labelToNode = new Map<string, Writable<SitemapNode>>();
|
|
199
|
-
|
|
200
|
-
// Map label (lowercased) -> container for group-targeted arrow resolution
|
|
201
|
-
const labelToContainer = new Map<string, Writable<SitemapNode>>();
|
|
202
|
-
|
|
203
|
-
// Deferred arrows: { sourceNode, arrow info, lineNumber }
|
|
204
|
-
const deferredArrows: {
|
|
205
|
-
sourceNode: Writable<SitemapNode>;
|
|
206
|
-
targetLabel: string;
|
|
207
|
-
targetIsGroup: boolean;
|
|
208
|
-
label?: string;
|
|
209
|
-
lineNumber: number;
|
|
210
|
-
}[] = [];
|
|
211
|
-
|
|
212
|
-
for (let i = 0; i < lines.length; i++) {
|
|
213
|
-
// In-bounds by loop guard.
|
|
214
|
-
const line = lines[i]!;
|
|
215
|
-
const lineNumber = i + 1;
|
|
216
|
-
const trimmed = line.trim();
|
|
217
|
-
|
|
218
|
-
// Skip empty lines
|
|
219
|
-
if (!trimmed) {
|
|
220
|
-
if (currentTagGroup) {
|
|
221
|
-
currentTagGroup = null;
|
|
222
|
-
}
|
|
223
|
-
continue;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
// Skip comments
|
|
227
|
-
if (trimmed.startsWith('//')) continue;
|
|
228
|
-
|
|
229
|
-
// --- Header phase ---
|
|
230
|
-
|
|
231
|
-
// First line: try parseFirstLine for `sitemap [Title]`
|
|
232
|
-
if (!firstLineParsed && !contentStarted) {
|
|
233
|
-
const firstLineResult = parseFirstLine(trimmed);
|
|
234
|
-
if (firstLineResult) {
|
|
235
|
-
firstLineParsed = true;
|
|
236
|
-
if (firstLineResult.chartType !== 'sitemap') {
|
|
237
|
-
const allTypes = Array.from(ALL_CHART_TYPES);
|
|
238
|
-
let msg = `Expected chart type "sitemap", got "${firstLineResult.chartType}"`;
|
|
239
|
-
const hint = suggest(firstLineResult.chartType, allTypes);
|
|
240
|
-
if (hint) msg += `. ${hint}`;
|
|
241
|
-
return fail(lineNumber, msg);
|
|
242
|
-
}
|
|
243
|
-
if (firstLineResult.title) {
|
|
244
|
-
result.title = firstLineResult.title;
|
|
245
|
-
result.titleLineNumber = lineNumber;
|
|
246
|
-
}
|
|
247
|
-
continue;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Tag group heading
|
|
252
|
-
const tagBlockMatch = matchTagBlockHeading(trimmed);
|
|
253
|
-
if (tagBlockMatch) {
|
|
254
|
-
if (contentStarted) {
|
|
255
|
-
pushError(lineNumber, 'Tag groups must appear before sitemap content');
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
const newTagGroup: Writable<TagGroup> = {
|
|
259
|
-
name: tagBlockMatch.name,
|
|
260
|
-
...(tagBlockMatch.alias !== undefined && {
|
|
261
|
-
alias: tagBlockMatch.alias,
|
|
262
|
-
}),
|
|
263
|
-
entries: [],
|
|
264
|
-
lineNumber,
|
|
265
|
-
};
|
|
266
|
-
currentTagGroup = newTagGroup;
|
|
267
|
-
if (tagBlockMatch.alias) {
|
|
268
|
-
metaAliasMap.set(
|
|
269
|
-
normalizeName(tagBlockMatch.alias),
|
|
270
|
-
tagAttrKey(tagBlockMatch.name)
|
|
271
|
-
);
|
|
272
|
-
}
|
|
273
|
-
metaAliasMap.set(
|
|
274
|
-
normalizeName(tagBlockMatch.name),
|
|
275
|
-
tagAttrKey(tagBlockMatch.name)
|
|
276
|
-
);
|
|
277
|
-
result.tagGroups.push(newTagGroup);
|
|
278
|
-
continue;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
// Generic header options (space-separated, before content/tag groups)
|
|
282
|
-
// Skip lines with `|` (pipe metadata), `->` (arrows), or `:` (page
|
|
283
|
-
// with same-line metadata per §1.4) — those are content, not options.
|
|
284
|
-
if (
|
|
285
|
-
!contentStarted &&
|
|
286
|
-
!currentTagGroup &&
|
|
287
|
-
measureIndent(line) === 0 &&
|
|
288
|
-
!trimmed.includes('|') &&
|
|
289
|
-
!trimmed.includes('->') &&
|
|
290
|
-
!trimmed.includes(':')
|
|
291
|
-
) {
|
|
292
|
-
// Bare boolean: direction-tb
|
|
293
|
-
if (/^direction-tb$/i.test(trimmed)) {
|
|
294
|
-
result.direction = 'TB';
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (tryParseSharedOption(trimmed, options)) {
|
|
299
|
-
continue;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
const optMatch = trimmed.match(OPTION_NOCOLON_RE);
|
|
303
|
-
if (optMatch) {
|
|
304
|
-
// Capture groups 1 and 2 present by regex shape.
|
|
305
|
-
const key = optMatch[1]!.trim().toLowerCase();
|
|
306
|
-
options[key] = optMatch[2]!.trim();
|
|
307
|
-
continue;
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// Tag group entries (indented `Value color` under tag heading; §1.5)
|
|
312
|
-
// First entry is the default unless another is marked `default`
|
|
313
|
-
if (currentTagGroup && !contentStarted) {
|
|
314
|
-
const indent = measureIndent(line);
|
|
315
|
-
if (indent > 0) {
|
|
316
|
-
const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
|
|
317
|
-
const { label, color } = extractColor(
|
|
318
|
-
cleanEntry,
|
|
319
|
-
palette,
|
|
320
|
-
result.diagnostics,
|
|
321
|
-
lineNumber
|
|
322
|
-
);
|
|
323
|
-
// Bare value (no explicit color) → keep it; finalized below.
|
|
324
|
-
currentTagGroup.entries.push({
|
|
325
|
-
value: label,
|
|
326
|
-
color: color ?? AUTO_TAG_COLOR_SENTINEL,
|
|
327
|
-
lineNumber,
|
|
328
|
-
});
|
|
329
|
-
if (isDefault) {
|
|
330
|
-
currentTagGroup.defaultValue = label;
|
|
331
|
-
} else if (currentTagGroup.entries.length === 1) {
|
|
332
|
-
currentTagGroup.defaultValue = label;
|
|
333
|
-
}
|
|
334
|
-
continue;
|
|
335
|
-
}
|
|
336
|
-
// Non-indented line after tag group — fall through to content
|
|
337
|
-
currentTagGroup = null; // eslint-disable-line no-useless-assignment
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// --- Content phase ---
|
|
341
|
-
contentStarted = true;
|
|
342
|
-
currentTagGroup = null;
|
|
343
|
-
|
|
344
|
-
const indent = measureIndent(line);
|
|
345
|
-
|
|
346
|
-
// Check for arrow syntax (must check before metadata — arrows contain `:` in labels
|
|
347
|
-
// but also start with `-`)
|
|
348
|
-
const arrowInfo = parseArrowLine(
|
|
349
|
-
trimmed,
|
|
350
|
-
palette,
|
|
351
|
-
lineNumber,
|
|
352
|
-
result.diagnostics
|
|
353
|
-
);
|
|
354
|
-
if (arrowInfo) {
|
|
355
|
-
// Find the source node: the most recent node on the indent stack
|
|
356
|
-
// at a shallower indent (same pattern as metadata attachment)
|
|
357
|
-
const source = findParentNode(indent, indentStack);
|
|
358
|
-
if (!source) {
|
|
359
|
-
pushError(
|
|
360
|
-
lineNumber,
|
|
361
|
-
"Arrow '-> target' must be indented under its source page — no page precedes it."
|
|
362
|
-
);
|
|
363
|
-
} else {
|
|
364
|
-
deferredArrows.push({
|
|
365
|
-
sourceNode: source,
|
|
366
|
-
targetLabel: arrowInfo.target,
|
|
367
|
-
targetIsGroup: arrowInfo.targetIsGroup,
|
|
368
|
-
...(arrowInfo.label !== undefined && { label: arrowInfo.label }),
|
|
369
|
-
lineNumber,
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Check for container syntax: [Group Name]
|
|
376
|
-
const containerMatch = trimmed.match(CONTAINER_RE);
|
|
377
|
-
|
|
378
|
-
// Check for metadata syntax: `key: value` (the indented form
|
|
379
|
-
// that attaches to the parent node, not a node line with same-line
|
|
380
|
-
// metadata). Require the key region to be a single identifier so
|
|
381
|
-
// `Checkout access: Public` parses as a node, not metadata for the
|
|
382
|
-
// container.
|
|
383
|
-
const metadataMatch = (() => {
|
|
384
|
-
if (trimmed.includes('|')) return null;
|
|
385
|
-
const m = trimmed.match(METADATA_RE);
|
|
386
|
-
if (!m) return null;
|
|
387
|
-
const keyRegion = m[1]!.trim();
|
|
388
|
-
if (/\s/.test(keyRegion)) return null;
|
|
389
|
-
return m;
|
|
390
|
-
})();
|
|
391
|
-
|
|
392
|
-
if (containerMatch) {
|
|
393
|
-
// TD-18: peel optional `as <alias>` from container label.
|
|
394
|
-
// Capture group 1 present by regex shape.
|
|
395
|
-
const rawLabel = containerMatch[1]!.trim();
|
|
396
|
-
const asMatch = rawLabel.match(
|
|
397
|
-
/^(.*?)\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*$/
|
|
398
|
-
);
|
|
399
|
-
// Capture groups 1 and 2 present by regex shape.
|
|
400
|
-
const label = asMatch ? asMatch[1]!.trim() : rawLabel;
|
|
401
|
-
|
|
402
|
-
// Parse the tail after `]`: optional same-line metadata per §1.4.
|
|
403
|
-
const tail = (containerMatch[2] ?? '').trim();
|
|
404
|
-
const containerMetadata: Record<string, string> = {};
|
|
405
|
-
if (tail.length > 0) {
|
|
406
|
-
Object.assign(
|
|
407
|
-
containerMetadata,
|
|
408
|
-
parseSitemapMetaTail(tail, metaAliasMap)
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
containerCounter++;
|
|
413
|
-
const containerId = `container-${containerCounter}`;
|
|
414
|
-
if (asMatch) nameAliasMap.set(asMatch[2]!, containerId);
|
|
415
|
-
const node: Writable<SitemapNode> = {
|
|
416
|
-
id: containerId,
|
|
417
|
-
label,
|
|
418
|
-
metadata: containerMetadata,
|
|
419
|
-
children: [],
|
|
420
|
-
parentId: null,
|
|
421
|
-
isContainer: true,
|
|
422
|
-
lineNumber,
|
|
423
|
-
};
|
|
424
|
-
|
|
425
|
-
attachNode(node, indent, indentStack, result);
|
|
426
|
-
// Register in labelToContainer for group-targeted arrows (-> [Group])
|
|
427
|
-
const key = normalizeName(label);
|
|
428
|
-
labelToContainer.set(key, node);
|
|
429
|
-
} else if (metadataMatch && indentStack.length > 0) {
|
|
430
|
-
// Metadata line — attach to parent
|
|
431
|
-
// Capture groups 1 and 2 present by regex shape.
|
|
432
|
-
const rawKey = metadataMatch[1]!.trim().toLowerCase();
|
|
433
|
-
const key = metaAliasMap.get(rawKey) ?? rawKey;
|
|
434
|
-
const value = metadataMatch[2]!.trim();
|
|
435
|
-
|
|
436
|
-
const parent = findParentNode(indent, indentStack);
|
|
437
|
-
if (!parent) {
|
|
438
|
-
pushError(
|
|
439
|
-
lineNumber,
|
|
440
|
-
"Metadata 'key: value' must be indented under a page — no page precedes it."
|
|
441
|
-
);
|
|
442
|
-
} else {
|
|
443
|
-
parent.metadata = { ...parent.metadata, [key]: value };
|
|
444
|
-
}
|
|
445
|
-
} else if (metadataMatch && indentStack.length === 0) {
|
|
446
|
-
// Could be a node label containing ':'
|
|
447
|
-
if (indent === 0) {
|
|
448
|
-
const node = parseNodeLabel(
|
|
449
|
-
trimmed,
|
|
450
|
-
lineNumber,
|
|
451
|
-
palette,
|
|
452
|
-
++nodeCounter,
|
|
453
|
-
metaAliasMap,
|
|
454
|
-
pushWarning,
|
|
455
|
-
result.diagnostics,
|
|
456
|
-
nameAliasMap
|
|
457
|
-
);
|
|
458
|
-
attachNode(node, indent, indentStack, result);
|
|
459
|
-
const key = normalizeName(node.label);
|
|
460
|
-
labelToNode.set(key, node);
|
|
461
|
-
} else {
|
|
462
|
-
pushError(
|
|
463
|
-
lineNumber,
|
|
464
|
-
"Metadata 'key: value' must be indented under a page — no page precedes it."
|
|
465
|
-
);
|
|
466
|
-
}
|
|
467
|
-
} else {
|
|
468
|
-
// Check if this is a description line for a parent node
|
|
469
|
-
const descResult = tryStripDescriptionKeyword(trimmed);
|
|
470
|
-
if (
|
|
471
|
-
descResult.isKeyword &&
|
|
472
|
-
!descResult.needsColon &&
|
|
473
|
-
indentStack.length > 0
|
|
474
|
-
) {
|
|
475
|
-
const parent = findParentNode(indent, indentStack);
|
|
476
|
-
if (parent) {
|
|
477
|
-
parent.description = [
|
|
478
|
-
...(parent.description ?? []),
|
|
479
|
-
descResult.text.trim(),
|
|
480
|
-
];
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
// §12: reject sub-pages nested under a page inside a container.
|
|
486
|
-
const enclosingContainer = nestedPageInsideContainer(indent, indentStack);
|
|
487
|
-
if (enclosingContainer) {
|
|
488
|
-
pushError(
|
|
489
|
-
lineNumber,
|
|
490
|
-
`Pages inside container '[${enclosingContainer}]' cannot have indented sub-pages. Containers hold a flat list of pages.`
|
|
491
|
-
);
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
// Node label — possibly with pipe-delimited metadata
|
|
496
|
-
const node = parseNodeLabel(
|
|
497
|
-
trimmed,
|
|
498
|
-
lineNumber,
|
|
499
|
-
palette,
|
|
500
|
-
++nodeCounter,
|
|
501
|
-
metaAliasMap,
|
|
502
|
-
pushWarning,
|
|
503
|
-
result.diagnostics,
|
|
504
|
-
nameAliasMap
|
|
505
|
-
);
|
|
506
|
-
attachNode(node, indent, indentStack, result);
|
|
507
|
-
const key = normalizeName(node.label);
|
|
508
|
-
labelToNode.set(key, node);
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// --- Post-parse: resolve arrow targets ---
|
|
513
|
-
for (const arrow of deferredArrows) {
|
|
514
|
-
// TD-18: resolve alias literal first; if hit, use the bound id directly.
|
|
515
|
-
const aliasHit = nameAliasMap.get(arrow.targetLabel.trim());
|
|
516
|
-
if (aliasHit !== undefined) {
|
|
517
|
-
result.edges.push({
|
|
518
|
-
sourceId: arrow.sourceNode.id,
|
|
519
|
-
targetId: aliasHit,
|
|
520
|
-
...(arrow.label !== undefined && { label: arrow.label }),
|
|
521
|
-
lineNumber: arrow.lineNumber,
|
|
522
|
-
});
|
|
523
|
-
continue;
|
|
524
|
-
}
|
|
525
|
-
const targetKey = normalizeName(arrow.targetLabel);
|
|
526
|
-
|
|
527
|
-
if (arrow.targetIsGroup) {
|
|
528
|
-
// Group target: look up in labelToContainer
|
|
529
|
-
const targetContainer = labelToContainer.get(targetKey);
|
|
530
|
-
if (!targetContainer) {
|
|
531
|
-
const allLabels = Array.from(labelToContainer.keys());
|
|
532
|
-
let msg = `Group '[${arrow.targetLabel}]' not found`;
|
|
533
|
-
const hint = suggest(targetKey, allLabels);
|
|
534
|
-
if (hint) msg += `. ${hint}`;
|
|
535
|
-
pushError(arrow.lineNumber, msg);
|
|
536
|
-
continue;
|
|
537
|
-
}
|
|
538
|
-
result.edges.push({
|
|
539
|
-
sourceId: arrow.sourceNode.id,
|
|
540
|
-
targetId: targetContainer.id,
|
|
541
|
-
...(arrow.label !== undefined && { label: arrow.label }),
|
|
542
|
-
lineNumber: arrow.lineNumber,
|
|
543
|
-
});
|
|
544
|
-
} else {
|
|
545
|
-
// Node target: look up in labelToNode (existing behavior)
|
|
546
|
-
const targetNode = labelToNode.get(targetKey);
|
|
547
|
-
if (!targetNode) {
|
|
548
|
-
const allLabels = Array.from(labelToNode.keys());
|
|
549
|
-
let msg = `Arrow target "${arrow.targetLabel}" not found`;
|
|
550
|
-
const hint = suggest(targetKey, allLabels);
|
|
551
|
-
if (hint) msg += `. ${hint}`;
|
|
552
|
-
pushError(arrow.lineNumber, msg);
|
|
553
|
-
continue;
|
|
554
|
-
}
|
|
555
|
-
result.edges.push({
|
|
556
|
-
sourceId: arrow.sourceNode.id,
|
|
557
|
-
targetId: targetNode.id,
|
|
558
|
-
...(arrow.label !== undefined && { label: arrow.label }),
|
|
559
|
-
lineNumber: arrow.lineNumber,
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
// Validate tag group values on all nodes
|
|
565
|
-
// Assign palette colors to bare (colorless) tag values.
|
|
566
|
-
finalizeAutoTagColors(result.tagGroups as Writable<TagGroup>[], palette);
|
|
567
|
-
|
|
568
|
-
if (result.tagGroups.length > 0) {
|
|
569
|
-
const allNodes: SitemapNode[] = [];
|
|
570
|
-
const collectAll = (nodes: readonly SitemapNode[]) => {
|
|
571
|
-
for (const node of nodes) {
|
|
572
|
-
allNodes.push(node);
|
|
573
|
-
collectAll(node.children);
|
|
574
|
-
}
|
|
575
|
-
};
|
|
576
|
-
collectAll(result.roots);
|
|
577
|
-
validateTagValues(allNodes, result.tagGroups, pushWarning, suggest);
|
|
578
|
-
validateTagGroupNames(result.tagGroups, pushWarning, pushError);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
if (
|
|
582
|
-
result.roots.length === 0 &&
|
|
583
|
-
result.tagGroups.length === 0 &&
|
|
584
|
-
!result.error
|
|
585
|
-
) {
|
|
586
|
-
const diag = makeDgmoError(1, 'No pages found in sitemap');
|
|
587
|
-
result.diagnostics.push(diag);
|
|
588
|
-
result.error = formatDgmoError(diag);
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
return result;
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// ============================================================
|
|
595
|
-
// Internal helpers
|
|
596
|
-
// ============================================================
|
|
597
|
-
|
|
598
|
-
function parseNodeLabel(
|
|
599
|
-
trimmed: string,
|
|
600
|
-
lineNumber: number,
|
|
601
|
-
_palette: PaletteColors | undefined,
|
|
602
|
-
counter: number,
|
|
603
|
-
metaAliasMap: Map<string, string> = new Map(),
|
|
604
|
-
warnFn?: (line: number, msg: string) => void,
|
|
605
|
-
_diagnostics?: DgmoError[],
|
|
606
|
-
nameAliasMap?: Map<string, string>
|
|
607
|
-
): Writable<SitemapNode> {
|
|
608
|
-
// §1.4 unified metadata grammar — same-line cut.
|
|
609
|
-
const registry = withTagAliases(
|
|
610
|
-
SITEMAP_REGISTRY,
|
|
611
|
-
new Set(metaAliasMap.keys())
|
|
612
|
-
);
|
|
613
|
-
const id = `node-${counter}`;
|
|
614
|
-
const split = splitNameAndMeta(
|
|
615
|
-
trimmed,
|
|
616
|
-
registry,
|
|
617
|
-
metaAliasMap,
|
|
618
|
-
undefined,
|
|
619
|
-
_diagnostics,
|
|
620
|
-
lineNumber
|
|
621
|
-
);
|
|
622
|
-
if (warnFn) {
|
|
623
|
-
warnUnknownMetaKeys(
|
|
624
|
-
split.meta,
|
|
625
|
-
registry,
|
|
626
|
-
(msg) => warnFn(lineNumber, msg),
|
|
627
|
-
split.name
|
|
628
|
-
);
|
|
629
|
-
}
|
|
630
|
-
const label = split.name;
|
|
631
|
-
if (split.alias) nameAliasMap?.set(normalizeName(split.alias), id);
|
|
632
|
-
const metadata: Record<string, string> = { ...split.meta };
|
|
633
|
-
if (split.color !== undefined) metadata['color'] = split.color;
|
|
634
|
-
|
|
635
|
-
// Extract description from pipe metadata into dedicated field
|
|
636
|
-
let description: string[] | undefined;
|
|
637
|
-
if ('description' in metadata) {
|
|
638
|
-
const descVal = metadata['description']!.trim();
|
|
639
|
-
if (descVal) {
|
|
640
|
-
description = [descVal];
|
|
641
|
-
}
|
|
642
|
-
delete metadata['description'];
|
|
643
|
-
}
|
|
644
|
-
|
|
645
|
-
return {
|
|
646
|
-
id,
|
|
647
|
-
label,
|
|
648
|
-
metadata,
|
|
649
|
-
...(description !== undefined && { description }),
|
|
650
|
-
children: [],
|
|
651
|
-
parentId: null,
|
|
652
|
-
isContainer: false,
|
|
653
|
-
lineNumber,
|
|
654
|
-
};
|
|
655
|
-
}
|
|
656
|
-
|
|
657
|
-
/**
|
|
658
|
-
* Parse a pure-metadata tail (`key: value, k2: v2`) following a
|
|
659
|
-
* `[Container]` heading via the §1.4 grammar. Leads with the
|
|
660
|
-
* always-reserved `color:` sentinel so the entire tail lands in the
|
|
661
|
-
* metadata region regardless of whether the first key is reserved.
|
|
662
|
-
*/
|
|
663
|
-
function parseSitemapMetaTail(
|
|
664
|
-
tail: string,
|
|
665
|
-
metaAliasMap: Map<string, string>
|
|
666
|
-
): Record<string, string> {
|
|
667
|
-
const trimmed = tail.trim();
|
|
668
|
-
if (!trimmed?.includes(':')) return {};
|
|
669
|
-
const registry = withTagAliases(
|
|
670
|
-
SITEMAP_REGISTRY,
|
|
671
|
-
new Set(metaAliasMap.keys())
|
|
672
|
-
);
|
|
673
|
-
const split = splitNameAndMeta(
|
|
674
|
-
`color: __smph, ${trimmed}`,
|
|
675
|
-
registry,
|
|
676
|
-
metaAliasMap
|
|
677
|
-
);
|
|
678
|
-
const meta = split.meta;
|
|
679
|
-
if (meta['color'] === '__smph') delete meta['color'];
|
|
680
|
-
return meta;
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
function attachNode(
|
|
684
|
-
node: Writable<SitemapNode>,
|
|
685
|
-
indent: number,
|
|
686
|
-
indentStack: { node: Writable<SitemapNode>; indent: number }[],
|
|
687
|
-
result: Writable<ParsedSitemap>
|
|
688
|
-
): void {
|
|
689
|
-
// Pop stack entries with indent >= current indent
|
|
690
|
-
while (indentStack.length > 0) {
|
|
691
|
-
// In-bounds by length check above.
|
|
692
|
-
const top = indentStack[indentStack.length - 1]!;
|
|
693
|
-
if (top.indent < indent) break;
|
|
694
|
-
indentStack.pop();
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
if (indentStack.length > 0) {
|
|
698
|
-
// In-bounds by length check above.
|
|
699
|
-
const parent = indentStack[indentStack.length - 1]!.node;
|
|
700
|
-
node.parentId = parent.id;
|
|
701
|
-
// Cascade container metadata to child nodes (child overrides on conflict)
|
|
702
|
-
if (
|
|
703
|
-
parent.isContainer &&
|
|
704
|
-
Object.keys(parent.metadata).length > 0 &&
|
|
705
|
-
!node.isContainer
|
|
706
|
-
) {
|
|
707
|
-
node.metadata = { ...parent.metadata, ...node.metadata };
|
|
708
|
-
}
|
|
709
|
-
parent.children.push(node);
|
|
710
|
-
} else {
|
|
711
|
-
result.roots.push(node);
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
indentStack.push({ node, indent });
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
/**
|
|
718
|
-
* §12: containers hold a flat list of pages — a page inside a `[Container]`
|
|
719
|
-
* may not have indented sub-pages. Returns the enclosing container's label
|
|
720
|
-
* when attaching a page at `indent` would nest it under a page that itself
|
|
721
|
-
* lives inside a container; otherwise null. Non-destructive (mirrors
|
|
722
|
-
* `attachNode`'s pop logic without mutating the stack).
|
|
723
|
-
*/
|
|
724
|
-
function nestedPageInsideContainer(
|
|
725
|
-
indent: number,
|
|
726
|
-
indentStack: { node: Writable<SitemapNode>; indent: number }[]
|
|
727
|
-
): string | null {
|
|
728
|
-
let idx = indentStack.length - 1;
|
|
729
|
-
while (idx >= 0 && indentStack[idx]!.indent >= indent) idx--;
|
|
730
|
-
if (idx < 0) return null;
|
|
731
|
-
// Immediate parent. A container parent → flat member, allowed.
|
|
732
|
-
if (indentStack[idx]!.node.isContainer) return null;
|
|
733
|
-
// Parent is a page; flag only when an ancestor is a container.
|
|
734
|
-
for (let k = idx - 1; k >= 0; k--) {
|
|
735
|
-
if (indentStack[k]!.node.isContainer) return indentStack[k]!.node.label;
|
|
736
|
-
}
|
|
737
|
-
return null;
|
|
738
|
-
}
|
|
739
|
-
|
|
740
|
-
function findParentNode(
|
|
741
|
-
indent: number,
|
|
742
|
-
indentStack: { node: Writable<SitemapNode>; indent: number }[]
|
|
743
|
-
): Writable<SitemapNode> | null {
|
|
744
|
-
for (let i = indentStack.length - 1; i >= 0; i--) {
|
|
745
|
-
// In-bounds by loop guard.
|
|
746
|
-
if (indentStack[i]!.indent < indent) {
|
|
747
|
-
return indentStack[i]!.node;
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
if (indentStack.length > 0) {
|
|
751
|
-
// In-bounds by length check above.
|
|
752
|
-
return indentStack[indentStack.length - 1]!.node;
|
|
753
|
-
}
|
|
754
|
-
return null;
|
|
755
|
-
}
|