@hirokisakabe/pom 8.3.0 → 8.4.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.
Files changed (120) hide show
  1. package/README.md +35 -23
  2. package/dist/autoFit/autoFit.js +1 -1
  3. package/dist/autoFit/autoFit.js.map +1 -1
  4. package/dist/autoFit/strategies/reduceFontSize.js +16 -14
  5. package/dist/autoFit/strategies/reduceFontSize.js.map +1 -1
  6. package/dist/autoFit/strategies/reduceGapAndPadding.js +13 -20
  7. package/dist/autoFit/strategies/reduceGapAndPadding.js.map +1 -1
  8. package/dist/autoFit/strategies/reduceTableRowHeight.js +8 -2
  9. package/dist/autoFit/strategies/reduceTableRowHeight.js.map +1 -1
  10. package/dist/autoFit/strategies/uniformScale.js +19 -20
  11. package/dist/autoFit/strategies/uniformScale.js.map +1 -1
  12. package/dist/autoFit/strategyResult.js +15 -0
  13. package/dist/autoFit/strategyResult.js.map +1 -0
  14. package/dist/buildPptx.js +1 -1
  15. package/dist/calcYogaLayout/calcYogaLayout.js +16 -27
  16. package/dist/calcYogaLayout/calcYogaLayout.js.map +1 -1
  17. package/dist/diagnostics.d.ts +1 -1
  18. package/dist/diagnostics.d.ts.map +1 -1
  19. package/dist/diagnostics.js.map +1 -1
  20. package/dist/parseXml/coercionRules.js +43 -8
  21. package/dist/parseXml/coercionRules.js.map +1 -1
  22. package/dist/parseXml/parseXml.d.ts +8 -3
  23. package/dist/parseXml/parseXml.d.ts.map +1 -1
  24. package/dist/parseXml/parseXml.js +192 -212
  25. package/dist/parseXml/parseXml.js.map +1 -1
  26. package/dist/parseXml/serializeXml.d.ts.map +1 -1
  27. package/dist/parseXml/serializeXml.js +12 -17
  28. package/dist/parseXml/serializeXml.js.map +1 -1
  29. package/dist/registry/definitions/arrow.js +2 -2
  30. package/dist/registry/definitions/arrow.js.map +1 -1
  31. package/dist/registry/definitions/chart.js +2 -2
  32. package/dist/registry/definitions/chart.js.map +1 -1
  33. package/dist/registry/definitions/compositeNodes.js +7 -12
  34. package/dist/registry/definitions/compositeNodes.js.map +1 -1
  35. package/dist/registry/definitions/icon.js +2 -2
  36. package/dist/registry/definitions/icon.js.map +1 -1
  37. package/dist/registry/definitions/image.js +2 -2
  38. package/dist/registry/definitions/image.js.map +1 -1
  39. package/dist/registry/definitions/layer.js +4 -5
  40. package/dist/registry/definitions/layer.js.map +1 -1
  41. package/dist/registry/definitions/line.js +2 -2
  42. package/dist/registry/definitions/line.js.map +1 -1
  43. package/dist/registry/definitions/list.js +3 -4
  44. package/dist/registry/definitions/list.js.map +1 -1
  45. package/dist/registry/definitions/shape.js +2 -2
  46. package/dist/registry/definitions/shape.js.map +1 -1
  47. package/dist/registry/definitions/stack.js +3 -4
  48. package/dist/registry/definitions/stack.js.map +1 -1
  49. package/dist/registry/definitions/svg.js +2 -2
  50. package/dist/registry/definitions/svg.js.map +1 -1
  51. package/dist/registry/definitions/table.js +2 -2
  52. package/dist/registry/definitions/table.js.map +1 -1
  53. package/dist/registry/definitions/text.js +2 -2
  54. package/dist/registry/definitions/text.js.map +1 -1
  55. package/dist/registry/index.js.map +1 -1
  56. package/dist/registry/nodeMetadata.js +208 -0
  57. package/dist/registry/nodeMetadata.js.map +1 -0
  58. package/dist/registry/nodeRegistry.js +3 -0
  59. package/dist/registry/nodeRegistry.js.map +1 -1
  60. package/dist/registry/xmlChildRules.js +55 -0
  61. package/dist/registry/xmlChildRules.js.map +1 -0
  62. package/dist/renderPptx/nodes/arrow.js +7 -28
  63. package/dist/renderPptx/nodes/arrow.js.map +1 -1
  64. package/dist/renderPptx/nodes/chart.js +2 -7
  65. package/dist/renderPptx/nodes/chart.js.map +1 -1
  66. package/dist/renderPptx/nodes/flow.js +6 -13
  67. package/dist/renderPptx/nodes/flow.js.map +1 -1
  68. package/dist/renderPptx/nodes/icon.js +4 -2
  69. package/dist/renderPptx/nodes/icon.js.map +1 -1
  70. package/dist/renderPptx/nodes/image.js +5 -13
  71. package/dist/renderPptx/nodes/image.js.map +1 -1
  72. package/dist/renderPptx/nodes/line.js +9 -33
  73. package/dist/renderPptx/nodes/line.js.map +1 -1
  74. package/dist/renderPptx/nodes/list.js +8 -20
  75. package/dist/renderPptx/nodes/list.js.map +1 -1
  76. package/dist/renderPptx/nodes/matrix.js +10 -11
  77. package/dist/renderPptx/nodes/matrix.js.map +1 -1
  78. package/dist/renderPptx/nodes/processArrow.js +9 -16
  79. package/dist/renderPptx/nodes/processArrow.js.map +1 -1
  80. package/dist/renderPptx/nodes/pyramid.js +5 -7
  81. package/dist/renderPptx/nodes/pyramid.js.map +1 -1
  82. package/dist/renderPptx/nodes/shape.js +7 -20
  83. package/dist/renderPptx/nodes/shape.js.map +1 -1
  84. package/dist/renderPptx/nodes/svg.js +2 -5
  85. package/dist/renderPptx/nodes/svg.js.map +1 -1
  86. package/dist/renderPptx/nodes/table.js +2 -5
  87. package/dist/renderPptx/nodes/table.js.map +1 -1
  88. package/dist/renderPptx/nodes/text.js +4 -1
  89. package/dist/renderPptx/nodes/text.js.map +1 -1
  90. package/dist/renderPptx/nodes/timeline.js +20 -22
  91. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  92. package/dist/renderPptx/nodes/tree.js +5 -5
  93. package/dist/renderPptx/nodes/tree.js.map +1 -1
  94. package/dist/renderPptx/renderPptx.js +13 -29
  95. package/dist/renderPptx/renderPptx.js.map +1 -1
  96. package/dist/renderPptx/textOptions.js +32 -8
  97. package/dist/renderPptx/textOptions.js.map +1 -1
  98. package/dist/renderPptx/units.js +11 -1
  99. package/dist/renderPptx/units.js.map +1 -1
  100. package/dist/renderPptx/utils/backgroundBorder.js +103 -57
  101. package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
  102. package/dist/renderPptx/utils/contentArea.js +26 -9
  103. package/dist/renderPptx/utils/contentArea.js.map +1 -1
  104. package/dist/renderPptx/utils/scaleToFit.js +17 -1
  105. package/dist/renderPptx/utils/scaleToFit.js.map +1 -1
  106. package/dist/renderPptx/utils/straightLine.js +41 -0
  107. package/dist/renderPptx/utils/straightLine.js.map +1 -0
  108. package/dist/renderPptx/utils/visualStyle.js +113 -0
  109. package/dist/renderPptx/utils/visualStyle.js.map +1 -0
  110. package/dist/shared/boxSpacing.js +63 -0
  111. package/dist/shared/boxSpacing.js.map +1 -0
  112. package/dist/shared/walkTree.js +1 -7
  113. package/dist/shared/walkTree.js.map +1 -1
  114. package/dist/toPositioned/toPositioned.js +1 -1
  115. package/dist/toPositioned/toPositioned.js.map +1 -1
  116. package/dist/types.d.ts +1127 -95
  117. package/dist/types.d.ts.map +1 -1
  118. package/dist/types.js +47 -17
  119. package/dist/types.js.map +1 -1
  120. package/package.json +4 -3
@@ -1,4 +1,5 @@
1
- import { arrowNodeSchema, chartNodeSchema, flowNodeSchema, iconNodeSchema, imageNodeSchema, lineNodeSchema, matrixNodeSchema, olNodeSchema, processArrowNodeSchema, pyramidNodeSchema, shapeNodeSchema, tableNodeSchema, textNodeSchema, timelineNodeSchema, treeNodeSchema, ulNodeSchema } from "../types.js";
1
+ import { NODE_METADATA, getNodeMetadata, getNodeMetadataByTag } from "../registry/nodeMetadata.js";
2
+ import { INLINE_BOOLEAN_FORMATS, INLINE_FORMAT_TAGS, INLINE_FORMAT_TAG_LIST, MARK_DEFAULT_HIGHLIGHT_COLOR, formatExpectedTags } from "../registry/xmlChildRules.js";
2
3
  import { CHILD_ELEMENT_COERCION_MAP, NODE_COERCION_MAP, coerceFallback, coerceWithRule, getObjectShapeFromRule, resolveMixedNotationShorthand } from "./coercionRules.js";
3
4
  import { XMLBuilder, XMLParser } from "fast-xml-parser";
4
5
  //#region src/parseXml/parseXml.ts
@@ -11,35 +12,7 @@ var ParseXmlError = class extends Error {
11
12
  this.errors = errors;
12
13
  }
13
14
  };
14
- const TAG_TO_TYPE = {
15
- Text: "text",
16
- Image: "image",
17
- Table: "table",
18
- Shape: "shape",
19
- Chart: "chart",
20
- Timeline: "timeline",
21
- Matrix: "matrix",
22
- Tree: "tree",
23
- Flow: "flow",
24
- ProcessArrow: "processArrow",
25
- Pyramid: "pyramid",
26
- Ul: "ul",
27
- Ol: "ol",
28
- Line: "line",
29
- Arrow: "arrow",
30
- VStack: "vstack",
31
- HStack: "hstack",
32
- Layer: "layer",
33
- Icon: "icon",
34
- Svg: "svg"
35
- };
36
- const TYPE_TO_TAG = Object.fromEntries(Object.entries(TAG_TO_TYPE).map(([tag, type]) => [type, tag]));
37
- const CONTAINER_TYPES = new Set([
38
- "vstack",
39
- "hstack",
40
- "layer"
41
- ]);
42
- const TEXT_CONTENT_NODES = new Set(["text", "shape"]);
15
+ Object.fromEntries(NODE_METADATA.map((def) => [def.tagName, def.type]));
43
16
  const UNIVERSAL_ATTRS = new Set(["x", "y"]);
44
17
  function getKnownAttributes(nodeType) {
45
18
  const rules = NODE_COERCION_MAP[nodeType];
@@ -73,24 +46,6 @@ function getKnownChildAttributes(tagName) {
73
46
  if (!rules) return [];
74
47
  return Object.keys(rules);
75
48
  }
76
- const leafNodeValidationSchemas = {
77
- text: textNodeSchema,
78
- image: imageNodeSchema,
79
- table: tableNodeSchema,
80
- shape: shapeNodeSchema,
81
- chart: chartNodeSchema,
82
- timeline: timelineNodeSchema,
83
- matrix: matrixNodeSchema,
84
- tree: treeNodeSchema,
85
- flow: flowNodeSchema,
86
- processArrow: processArrowNodeSchema,
87
- pyramid: pyramidNodeSchema,
88
- line: lineNodeSchema,
89
- arrow: arrowNodeSchema,
90
- ul: ulNodeSchema,
91
- ol: olNodeSchema,
92
- icon: iconNodeSchema
93
- };
94
49
  function formatZodIssue(issue, tagName) {
95
50
  const path = issue.path;
96
51
  if (path.length > 0 && path[0] === "children") return null;
@@ -120,34 +75,17 @@ function formatZodIssue(issue, tagName) {
120
75
  if (attrName) return `<${tagName}>: Attribute "${attrName}": ${issue.message}`;
121
76
  return `<${tagName}>: ${issue.message}`;
122
77
  }
123
- const CHILD_ELEMENT_PROPS = {
124
- flow: new Set(["nodes", "connections"]),
125
- table: new Set(["columns", "rows"]),
126
- chart: new Set(["data"]),
127
- timeline: new Set(["items"]),
128
- matrix: new Set([
129
- "axes",
130
- "items",
131
- "quadrants"
132
- ]),
133
- processArrow: new Set(["steps"]),
134
- pyramid: new Set(["levels"]),
135
- tree: new Set(["data"]),
136
- ul: new Set(["items"]),
137
- ol: new Set(["items"]),
138
- icon: new Set(["name"]),
139
- svg: new Set(["svgContent"])
140
- };
141
78
  function validateLeafNode(nodeType, result, errors) {
142
- const schema = leafNodeValidationSchemas[nodeType];
143
- if (!schema) return;
144
- const tagName = TYPE_TO_TAG[nodeType] ?? nodeType;
145
- const childProps = CHILD_ELEMENT_PROPS[nodeType];
79
+ const def = getNodeMetadata(nodeType);
80
+ if (def.childPolicy.kind === "pom-children") return;
81
+ const schema = def.schema;
82
+ const tagName = def.tagName;
83
+ const optionalChildProps = new Set(def.childPolicy.kind === "custom" ? def.childPolicy.optionalProperties ?? [] : []);
146
84
  const parseResult = schema.safeParse(result);
147
85
  if (!parseResult.success) {
148
86
  const seen = /* @__PURE__ */ new Set();
149
87
  for (const issue of parseResult.error.issues) {
150
- if (childProps && issue.path.length === 1 && childProps.has(String(issue.path[0])) && issue.code === "invalid_type" && issue.input === void 0) continue;
88
+ if (optionalChildProps.size > 0 && issue.path.length === 1 && optionalChildProps.has(String(issue.path[0])) && issue.code === "invalid_type" && issue.input === void 0) continue;
151
89
  if (issue.path.length > 0 && UNIVERSAL_ATTRS.has(String(issue.path[0]))) continue;
152
90
  const msg = formatZodIssue(issue, tagName);
153
91
  if (msg && !seen.has(msg)) {
@@ -222,55 +160,46 @@ function getTextContent(node) {
222
160
  function getRawChildren(node) {
223
161
  return node[getTagName(node)] ?? [];
224
162
  }
225
- const INLINE_FORMAT_TAGS = new Set([
226
- "B",
227
- "I",
228
- "A",
229
- "U",
230
- "S",
231
- "Mark",
232
- "Span"
233
- ]);
234
163
  function hasInlineFormatChildren(childElements) {
235
164
  return childElements.length > 0 && childElements.every((el) => INLINE_FORMAT_TAGS.has(getTagName(el)));
236
165
  }
237
- function extractTextRuns(children, inheritBold, inheritItalic, inheritHref, inheritUnderline, inheritStrike, inheritHighlight, inheritColor, inheritFontFamily, inheritLetterSpacing) {
166
+ function extractTextRuns(children, inherited = {}) {
238
167
  const runs = [];
239
- for (const child of children) if (isTextNode(child)) {
240
- const run = { text: child["#text"] };
241
- if (inheritBold) run.bold = true;
242
- if (inheritItalic) run.italic = true;
243
- if (inheritUnderline) run.underline = true;
244
- if (inheritStrike) run.strike = true;
245
- if (inheritHighlight) run.highlight = inheritHighlight;
246
- if (inheritColor) run.color = inheritColor;
247
- if (inheritHref) run.href = inheritHref;
248
- if (inheritFontFamily) run.fontFamily = inheritFontFamily;
249
- if (inheritLetterSpacing !== void 0) run.letterSpacing = inheritLetterSpacing;
250
- runs.push(run);
251
- } else {
168
+ for (const child of children) {
169
+ if (isTextNode(child)) {
170
+ runs.push({
171
+ text: child["#text"],
172
+ ...inherited
173
+ });
174
+ continue;
175
+ }
252
176
  const tag = getTagName(child);
253
177
  const innerChildren = getRawChildren(child);
254
- if (tag === "B") runs.push(...extractTextRuns(innerChildren, true, inheritItalic, inheritHref, inheritUnderline, inheritStrike, inheritHighlight, inheritColor, inheritFontFamily, inheritLetterSpacing));
255
- else if (tag === "I") runs.push(...extractTextRuns(innerChildren, inheritBold, true, inheritHref, inheritUnderline, inheritStrike, inheritHighlight, inheritColor, inheritFontFamily, inheritLetterSpacing));
178
+ const booleanFormat = INLINE_BOOLEAN_FORMATS.find((format) => format.tag === tag);
179
+ if (booleanFormat) runs.push(...extractTextRuns(innerChildren, {
180
+ ...inherited,
181
+ [booleanFormat.property]: true
182
+ }));
256
183
  else if (tag === "A") {
257
- const href = getAttributes(child).href ?? "";
258
- runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, href, inheritUnderline, inheritStrike, inheritHighlight, inheritColor, inheritFontFamily, inheritLetterSpacing));
259
- } else if (tag === "U") runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, inheritHref, true, inheritStrike, inheritHighlight, inheritColor, inheritFontFamily, inheritLetterSpacing));
260
- else if (tag === "S") runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, inheritHref, inheritUnderline, true, inheritHighlight, inheritColor, inheritFontFamily, inheritLetterSpacing));
261
- else if (tag === "Mark") {
184
+ const next = { ...inherited };
185
+ const href = getAttributes(child).href;
186
+ if (href) next.href = href;
187
+ else delete next.href;
188
+ runs.push(...extractTextRuns(innerChildren, next));
189
+ } else if (tag === "Mark") {
262
190
  const rawColor = getAttributes(child).color;
263
- const color = rawColor && rawColor.trim() ? rawColor : "FFFF00";
264
- runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, inheritHref, inheritUnderline, inheritStrike, color, inheritColor, inheritFontFamily, inheritLetterSpacing));
191
+ const highlight = rawColor && rawColor.trim() ? rawColor : MARK_DEFAULT_HIGHLIGHT_COLOR;
192
+ runs.push(...extractTextRuns(innerChildren, {
193
+ ...inherited,
194
+ highlight
195
+ }));
265
196
  } else if (tag === "Span") {
266
197
  const spanAttrs = getAttributes(child);
267
- const rawSpanColor = spanAttrs.color;
268
- const spanColor = rawSpanColor && rawSpanColor.trim() ? rawSpanColor : inheritColor;
269
- const rawSpanFontFamily = spanAttrs.fontFamily;
270
- const spanFontFamily = rawSpanFontFamily && rawSpanFontFamily.trim() ? rawSpanFontFamily : inheritFontFamily;
271
- const rawSpanLetterSpacing = spanAttrs.letterSpacing;
272
- const spanLetterSpacing = rawSpanLetterSpacing && rawSpanLetterSpacing.trim() ? Number(rawSpanLetterSpacing) : inheritLetterSpacing;
273
- runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, inheritHref, inheritUnderline, inheritStrike, inheritHighlight, spanColor, spanFontFamily, spanLetterSpacing));
198
+ const next = { ...inherited };
199
+ if (spanAttrs.color && spanAttrs.color.trim()) next.color = spanAttrs.color;
200
+ if (spanAttrs.fontFamily && spanAttrs.fontFamily.trim()) next.fontFamily = spanAttrs.fontFamily;
201
+ if (spanAttrs.letterSpacing && spanAttrs.letterSpacing.trim()) next.letterSpacing = Number(spanAttrs.letterSpacing);
202
+ runs.push(...extractTextRuns(innerChildren, next));
274
203
  }
275
204
  }
276
205
  return runs;
@@ -323,85 +252,78 @@ function coerceChildAttrs(parentTagName, tagName, attrs, errors) {
323
252
  }
324
253
  return result;
325
254
  }
326
- function convertProcessArrowChildren(childElements, result, errors) {
327
- const steps = [];
328
- for (const child of childElements) {
329
- const tag = getTagName(child);
330
- if (tag !== "ProcessArrowStep") {
331
- errors.push(`Unknown child element <${tag}> inside <ProcessArrow>. Expected: <ProcessArrowStep>`);
332
- continue;
333
- }
334
- steps.push(coerceChildAttrs("ProcessArrow", tag, getAttributes(child), errors));
335
- }
336
- result.steps = steps;
255
+ function unknownChildError(childTag, parentTag, expectedTags) {
256
+ return `Unknown child element <${childTag}> inside <${parentTag}>. Expected: ${formatExpectedTags(expectedTags)}`;
337
257
  }
338
- function convertPyramidChildren(childElements, result, errors) {
339
- const levels = [];
340
- for (const child of childElements) {
341
- const tag = getTagName(child);
342
- if (tag !== "PyramidLevel") {
343
- errors.push(`Unknown child element <${tag}> inside <Pyramid>. Expected: <PyramidLevel>`);
344
- continue;
345
- }
346
- levels.push(coerceChildAttrs("Pyramid", tag, getAttributes(child), errors));
258
+ /** element text content / インライン装飾を attrs の text / runs へ反映する */
259
+ function applyTextAndRuns(element, attrs) {
260
+ const runsResult = buildRunsAndText(element);
261
+ if (runsResult) {
262
+ attrs.runs = runsResult.runs;
263
+ attrs.text = runsResult.text;
264
+ } else {
265
+ const textContent = getTextContent(element);
266
+ if (textContent !== void 0 && !("text" in attrs)) attrs.text = textContent;
347
267
  }
348
- result.levels = levels;
349
268
  }
350
- function convertTimelineChildren(childElements, result, errors) {
269
+ /** RepeatedChildRule (単一種類の child tag の繰り返し → 配列 property) の汎用 converter */
270
+ function convertRepeatedChildren(rule, parentTag, childElements, result, errors) {
351
271
  const items = [];
352
272
  for (const child of childElements) {
353
273
  const tag = getTagName(child);
354
- if (tag !== "TimelineItem") {
355
- errors.push(`Unknown child element <${tag}> inside <Timeline>. Expected: <TimelineItem>`);
274
+ if (tag !== rule.childTag) {
275
+ errors.push(unknownChildError(tag, parentTag, [rule.childTag]));
356
276
  continue;
357
277
  }
358
- items.push(coerceChildAttrs("Timeline", tag, getAttributes(child), errors));
278
+ const attrs = coerceChildAttrs(parentTag, tag, getAttributes(child), errors);
279
+ if (rule.allowsItemText) applyTextAndRuns(child, attrs);
280
+ items.push(attrs);
359
281
  }
360
- result.items = items;
282
+ result[rule.property] = items;
361
283
  }
362
- function convertMatrixChildren(childElements, result, errors) {
284
+ function convertMatrixChildren(rule, tagName, childElements, result, errors) {
363
285
  const items = [];
364
286
  for (const child of childElements) {
365
287
  const tag = getTagName(child);
366
288
  switch (tag) {
367
289
  case "MatrixAxes":
368
- result.axes = coerceChildAttrs("Matrix", tag, getAttributes(child), errors);
290
+ result.axes = coerceChildAttrs(tagName, tag, getAttributes(child), errors);
369
291
  break;
370
292
  case "MatrixQuadrants":
371
- result.quadrants = coerceChildAttrs("Matrix", tag, getAttributes(child), errors);
293
+ result.quadrants = coerceChildAttrs(tagName, tag, getAttributes(child), errors);
372
294
  break;
373
295
  case "MatrixItem":
374
- items.push(coerceChildAttrs("Matrix", tag, getAttributes(child), errors));
296
+ items.push(coerceChildAttrs(tagName, tag, getAttributes(child), errors));
375
297
  break;
376
- default: errors.push(`Unknown child element <${tag}> inside <Matrix>. Expected: <MatrixAxes>, <MatrixQuadrants>, or <MatrixItem>`);
298
+ default: errors.push(unknownChildError(tag, tagName, rule.expectedTags));
377
299
  }
378
300
  }
379
301
  if (items.length > 0) result.items = items;
380
302
  }
381
- function convertFlowChildren(childElements, result, errors) {
303
+ function convertFlowChildren(rule, tagName, childElements, result, errors) {
382
304
  const nodes = [];
383
305
  const connections = [];
384
306
  for (const child of childElements) {
385
307
  const tag = getTagName(child);
386
308
  switch (tag) {
387
309
  case "FlowNode":
388
- nodes.push(coerceChildAttrs("Flow", tag, getAttributes(child), errors));
310
+ nodes.push(coerceChildAttrs(tagName, tag, getAttributes(child), errors));
389
311
  break;
390
312
  case "FlowConnection":
391
- connections.push(coerceChildAttrs("Flow", tag, getAttributes(child), errors));
313
+ connections.push(coerceChildAttrs(tagName, tag, getAttributes(child), errors));
392
314
  break;
393
- default: errors.push(`Unknown child element <${tag}> inside <Flow>. Expected: <FlowNode> or <FlowConnection>`);
315
+ default: errors.push(unknownChildError(tag, tagName, rule.expectedTags));
394
316
  }
395
317
  }
396
318
  if (nodes.length > 0) result.nodes = nodes;
397
319
  if (connections.length > 0) result.connections = connections;
398
320
  }
399
- function convertChartChildren(childElements, result, errors) {
321
+ function convertChartChildren(rule, tagName, childElements, result, errors) {
400
322
  const data = [];
401
323
  for (const child of childElements) {
402
324
  const tag = getTagName(child);
403
325
  if (tag !== "ChartSeries") {
404
- errors.push(`Unknown child element <${tag}> inside <Chart>. Expected: <ChartSeries>`);
326
+ errors.push(unknownChildError(tag, tagName, rule.expectedTags));
405
327
  continue;
406
328
  }
407
329
  const attrs = getAttributes(child);
@@ -413,7 +335,7 @@ function convertChartChildren(childElements, result, errors) {
413
335
  for (const dp of getChildElements(child)) {
414
336
  const dpTag = getTagName(dp);
415
337
  if (dpTag !== "ChartDataPoint") {
416
- errors.push(`Unknown child element <${dpTag}> inside <ChartSeries>. Expected: <ChartDataPoint>`);
338
+ errors.push(unknownChildError(dpTag, "ChartSeries", ["ChartDataPoint"]));
417
339
  continue;
418
340
  }
419
341
  const dpAttrs = getAttributes(dp);
@@ -432,14 +354,14 @@ function convertChartChildren(childElements, result, errors) {
432
354
  }
433
355
  result.data = data;
434
356
  }
435
- function convertTableChildren(childElements, result, errors) {
357
+ function convertTableChildren(rule, tagName, childElements, result, errors) {
436
358
  const columns = [];
437
359
  const rows = [];
438
360
  for (const child of childElements) {
439
361
  const tag = getTagName(child);
440
362
  switch (tag) {
441
363
  case "Col":
442
- columns.push(coerceChildAttrs("Table", tag, getAttributes(child), errors));
364
+ columns.push(coerceChildAttrs(tagName, tag, getAttributes(child), errors));
443
365
  break;
444
366
  case "Tr": {
445
367
  const rowAttrs = getAttributes(child);
@@ -447,18 +369,11 @@ function convertTableChildren(childElements, result, errors) {
447
369
  for (const cellEl of getChildElements(child)) {
448
370
  const cellTag = getTagName(cellEl);
449
371
  if (cellTag !== "Td") {
450
- errors.push(`Unknown child element <${cellTag}> inside <Tr>. Expected: <Td>`);
372
+ errors.push(unknownChildError(cellTag, "Tr", ["Td"]));
451
373
  continue;
452
374
  }
453
375
  const cellAttrs = coerceChildAttrs("Tr", cellTag, getAttributes(cellEl), errors);
454
- const runsResult = buildRunsAndText(cellEl);
455
- if (runsResult) {
456
- cellAttrs.runs = runsResult.runs;
457
- cellAttrs.text = runsResult.text;
458
- } else {
459
- const cellText = getTextContent(cellEl);
460
- if (cellText !== void 0 && !("text" in cellAttrs)) cellAttrs.text = cellText;
461
- }
376
+ applyTextAndRuns(cellEl, cellAttrs);
462
377
  cells.push(cellAttrs);
463
378
  }
464
379
  const row = { cells };
@@ -470,7 +385,7 @@ function convertTableChildren(childElements, result, errors) {
470
385
  rows.push(row);
471
386
  break;
472
387
  }
473
- default: errors.push(`Unknown child element <${tag}> inside <Table>. Expected: <Col> or <Tr>`);
388
+ default: errors.push(unknownChildError(tag, tagName, rule.expectedTags));
474
389
  }
475
390
  }
476
391
  if (columns.length > 0) result.columns = columns;
@@ -486,18 +401,19 @@ function convertTreeItem(element, errors) {
486
401
  const item = {};
487
402
  if (attrs.label !== void 0) item.label = attrs.label;
488
403
  if (attrs.color !== void 0) item.color = attrs.color;
404
+ if (attrs.textColor !== void 0) item.textColor = attrs.textColor;
489
405
  const children = getChildElements(element);
490
406
  if (children.length > 0) item.children = children.map((child) => {
491
407
  const tag = getTagName(child);
492
408
  if (tag !== "TreeItem") {
493
- errors.push(`Unknown child element <${tag}> inside <TreeItem>. Expected: <TreeItem>`);
409
+ errors.push(unknownChildError(tag, "TreeItem", ["TreeItem"]));
494
410
  return null;
495
411
  }
496
412
  return convertTreeItem(child, errors);
497
413
  }).filter((item) => item !== null);
498
414
  return item;
499
415
  }
500
- function convertTreeChildren(childElements, result, errors) {
416
+ function convertTreeChildren(rule, tagName, childElements, result, errors) {
501
417
  if (childElements.length !== 1) {
502
418
  errors.push(`<Tree> must have exactly 1 <TreeItem> child element, but got ${childElements.length}`);
503
419
  return;
@@ -505,32 +421,11 @@ function convertTreeChildren(childElements, result, errors) {
505
421
  const child = childElements[0];
506
422
  const tag = getTagName(child);
507
423
  if (tag !== "TreeItem") {
508
- errors.push(`Unknown child element <${tag}> inside <Tree>. Expected: <TreeItem>`);
424
+ errors.push(unknownChildError(tag, tagName, rule.expectedTags));
509
425
  return;
510
426
  }
511
427
  result.data = convertTreeItem(child, errors);
512
428
  }
513
- function convertListChildren(parentTag, childElements, result, errors) {
514
- const items = [];
515
- for (const child of childElements) {
516
- const tag = getTagName(child);
517
- if (tag !== "Li") {
518
- errors.push(`Unknown child element <${tag}> inside <${parentTag}>. Expected: <Li>`);
519
- continue;
520
- }
521
- const attrs = coerceChildAttrs(parentTag, tag, getAttributes(child), errors);
522
- const runsResult = buildRunsAndText(child);
523
- if (runsResult) {
524
- attrs.runs = runsResult.runs;
525
- attrs.text = runsResult.text;
526
- } else {
527
- const textContent = getTextContent(child);
528
- if (textContent !== void 0 && !("text" in attrs)) attrs.text = textContent;
529
- }
530
- items.push(attrs);
531
- }
532
- result.items = items;
533
- }
534
429
  const svgBuilder = new XMLBuilder({
535
430
  preserveOrder: true,
536
431
  ignoreAttributes: false,
@@ -539,7 +434,7 @@ const svgBuilder = new XMLBuilder({
539
434
  function serializeSvgElement(svgElement) {
540
435
  return String(svgBuilder.build([svgElement]));
541
436
  }
542
- function convertSvgChildren(childElements, result, errors) {
437
+ function convertSvgChildren(_rule, _tagName, childElements, result, errors) {
543
438
  if (childElements.length !== 1) {
544
439
  errors.push(`<Svg>: Expected exactly one <svg> child element, but found ${childElements.length} child element(s)`);
545
440
  return;
@@ -552,11 +447,13 @@ function convertSvgChildren(childElements, result, errors) {
552
447
  }
553
448
  result.svgContent = serializeSvgElement(child);
554
449
  }
555
- function convertTextInlineChildren(childElements, result, errors, node) {
450
+ function convertInlineRunsChildren(tagName, childElements, result, errors, node) {
556
451
  for (const el of childElements) {
557
452
  const tag = getTagName(el);
558
453
  if (!INLINE_FORMAT_TAGS.has(tag)) {
559
- errors.push(`<Text>: Unexpected child element <${tag}>. Only <B>, <I>, <A>, <U>, <S>, <Mark>, and <Span> are allowed inside <Text>`);
454
+ const allowedTags = INLINE_FORMAT_TAG_LIST.map((t) => `<${t}>`);
455
+ const allowedList = `${allowedTags.slice(0, -1).join(", ")}, and ${allowedTags[allowedTags.length - 1]}`;
456
+ errors.push(`<${tagName}>: Unexpected child element <${tag}>. Only ${allowedList} are allowed inside <${tagName}>`);
560
457
  return;
561
458
  }
562
459
  }
@@ -567,13 +464,8 @@ function convertTextInlineChildren(childElements, result, errors, node) {
567
464
  result.text = runsResult.text;
568
465
  }
569
466
  }
570
- const CHILD_ELEMENT_CONVERTERS = {
571
- text: convertTextInlineChildren,
572
- ul: (childElements, result, errors) => convertListChildren("Ul", childElements, result, errors),
573
- ol: (childElements, result, errors) => convertListChildren("Ol", childElements, result, errors),
574
- processArrow: convertProcessArrowChildren,
575
- pyramid: convertPyramidChildren,
576
- timeline: convertTimelineChildren,
467
+ /** NodeSpecificChildRule を持つノードの専用 converter テーブル */
468
+ const NODE_SPECIFIC_CHILD_CONVERTERS = {
577
469
  matrix: convertMatrixChildren,
578
470
  flow: convertFlowChildren,
579
471
  chart: convertChartChildren,
@@ -581,20 +473,85 @@ const CHILD_ELEMENT_CONVERTERS = {
581
473
  tree: convertTreeChildren,
582
474
  svg: convertSvgChildren
583
475
  };
584
- function convertElement(node, errors) {
476
+ /** NodeMetadata xmlChildRule に従って child element を変換する */
477
+ function applyXmlChildRule(rule, nodeType, tagName, childElements, result, errors, node) {
478
+ if (rule.kind === "inline-runs") {
479
+ convertInlineRunsChildren(tagName, childElements, result, errors, node);
480
+ return;
481
+ }
482
+ if (rule.kind === "repeated") {
483
+ convertRepeatedChildren(rule, tagName, childElements, result, errors);
484
+ return;
485
+ }
486
+ const converter = NODE_SPECIFIC_CHILD_CONVERTERS[nodeType];
487
+ if (!converter) throw new Error(`No node-specific child converter registered for node type: ${nodeType}`);
488
+ converter(rule, tagName, childElements, result, errors);
489
+ }
490
+ const THEME_TOKEN_NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
491
+ const THEME_TOKEN_VALUE_PATTERN = /^[0-9A-Fa-f]{6}$/;
492
+ const TOKEN_REF_PATTERN = /^(#?)\$([A-Za-z][A-Za-z0-9_-]*)$/;
493
+ const TOKEN_REF_IN_GRADIENT_PATTERN = /#?\$([A-Za-z][A-Za-z0-9_-]*)/g;
494
+ function parseThemeElement(element, tokens, errors) {
495
+ if (getChildElements(element).length > 0) errors.push(`<Theme>: Child elements are not supported. Declare tokens as attributes (e.g. <Theme accent="1D4ED8" />)`);
496
+ for (const [name, rawValue] of Object.entries(getAttributes(element))) {
497
+ if (!THEME_TOKEN_NAME_PATTERN.test(name)) {
498
+ errors.push(`<Theme>: Invalid token name "${name}". Token names must start with a letter and contain only letters, digits, "_", and "-"`);
499
+ continue;
500
+ }
501
+ const value = rawValue.replace(/^#/, "");
502
+ if (!THEME_TOKEN_VALUE_PATTERN.test(value)) {
503
+ errors.push(`<Theme>: Invalid color value "${rawValue}" for token "${name}". Expected 6-digit hex (e.g. "1D4ED8")`);
504
+ continue;
505
+ }
506
+ tokens[name] = value;
507
+ }
508
+ }
509
+ function isColorKey(key) {
510
+ return /colors?$/i.test(key) || key === "highlight";
511
+ }
512
+ function resolveThemeToken(name, hashPrefix, tokens, themeDeclared, errors) {
513
+ const value = tokens[name];
514
+ if (value !== void 0) return `${hashPrefix}${value}`;
515
+ if (!themeDeclared) errors.push(`Theme token "$${name}" is referenced, but no <Theme> is declared. Add a top-level <Theme ${name}="RRGGBB" /> element`);
516
+ else {
517
+ const suggestion = findClosestMatch(name, Object.keys(tokens));
518
+ errors.push(`Unknown theme token "$${name}"${suggestion ? `. Did you mean "$${suggestion}"?` : ""}`);
519
+ }
520
+ }
521
+ function resolveThemeTokensDeep(value, key, tokens, themeDeclared, errors) {
522
+ if (typeof value === "string") {
523
+ if (key === "backgroundGradient") return value.replace(TOKEN_REF_IN_GRADIENT_PATTERN, (matched, name) => {
524
+ return resolveThemeToken(name, "#", tokens, themeDeclared, errors) ?? matched;
525
+ });
526
+ if (key !== null && isColorKey(key)) {
527
+ const match = TOKEN_REF_PATTERN.exec(value.trim());
528
+ if (match) return resolveThemeToken(match[2], match[1], tokens, themeDeclared, errors) ?? value;
529
+ }
530
+ return value;
531
+ }
532
+ if (Array.isArray(value)) return value.map((item) => resolveThemeTokensDeep(item, key, tokens, themeDeclared, errors));
533
+ if (typeof value === "object" && value !== null) {
534
+ const result = {};
535
+ for (const [childKey, childValue] of Object.entries(value)) result[childKey] = resolveThemeTokensDeep(childValue, childKey, tokens, themeDeclared, errors);
536
+ return result;
537
+ }
538
+ return value;
539
+ }
540
+ function convertElement(node, errors, theme) {
585
541
  const tagName = getTagName(node);
586
- const nodeType = TAG_TO_TYPE[tagName];
542
+ const def = getNodeMetadataByTag(tagName);
587
543
  const attrs = getAttributes(node);
588
544
  const childElements = getChildElements(node);
589
545
  const textContent = getTextContent(node);
590
- if (nodeType) return convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors, node);
546
+ if (def) return convertPomNode(def.type, tagName, attrs, childElements, textContent, errors, theme, node);
591
547
  else {
592
548
  errors.push(`Unknown tag: <${tagName}>`);
593
549
  return null;
594
550
  }
595
551
  }
596
- function convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors, xmlNode) {
552
+ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors, theme, xmlNode) {
597
553
  const result = { type: nodeType };
554
+ const def = getNodeMetadata(nodeType);
598
555
  const { regular: regularAttrs, dotGroups } = expandDotNotation(attrs);
599
556
  for (const [prefix, subAttrs] of Object.entries(dotGroups)) {
600
557
  if (prefix === "type") continue;
@@ -636,14 +593,18 @@ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, er
636
593
  else errors.push(`<${tagName}>: Unknown attribute "${key}"`);
637
594
  }
638
595
  }
639
- if (textContent !== void 0 && TEXT_CONTENT_NODES.has(nodeType)) {
640
- if (!("text" in result)) result.text = textContent;
596
+ if (textContent !== void 0 && def.textContentProperty) {
597
+ if (!(def.textContentProperty in result)) result[def.textContentProperty] = textContent;
641
598
  }
642
- const childConverter = CHILD_ELEMENT_CONVERTERS[nodeType];
643
- if (childConverter && childElements.length > 0) childConverter(childElements, result, errors, xmlNode);
644
- else if (CONTAINER_TYPES.has(nodeType) && childElements.length > 0) result.children = childElements.map((child) => convertElement(child, errors)).filter((child) => child !== null);
645
- else if (!CONTAINER_TYPES.has(nodeType) && !childConverter && childElements.length > 0) errors.push(`<${tagName}>: Unexpected child elements. <${tagName}> does not accept child elements`);
646
- if (!CONTAINER_TYPES.has(nodeType)) validateLeafNode(nodeType, result, errors);
599
+ const childRule = def.xmlChildRule;
600
+ if (childRule && childElements.length > 0) applyXmlChildRule(childRule, nodeType, tagName, childElements, result, errors, xmlNode);
601
+ else if (def.childPolicy.kind === "pom-children" && childElements.length > 0) result.children = childElements.map((child) => convertElement(child, errors, theme)).filter((child) => child !== null);
602
+ else if (def.childPolicy.kind !== "pom-children" && !childRule && childElements.length > 0) errors.push(`<${tagName}>: Unexpected child elements. <${tagName}> does not accept child elements`);
603
+ for (const [key, value] of Object.entries(result)) {
604
+ if (key === "type" || key === "children") continue;
605
+ result[key] = resolveThemeTokensDeep(value, key, theme.tokens, theme.declared, errors);
606
+ }
607
+ if (def.childPolicy.kind !== "pom-children") validateLeafNode(nodeType, result, errors);
647
608
  if (nodeType === "icon") {
648
609
  if (typeof result.color === "string" && !result.color.startsWith("#")) result.color = `#${result.color}`;
649
610
  if (typeof result.bgColor === "string" && !result.bgColor.startsWith("#")) result.bgColor = `#${result.bgColor}`;
@@ -657,9 +618,14 @@ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, er
657
618
  /**
658
619
  * XML 文字列を POMNode 配列に変換する。
659
620
  *
660
- * 最上位は `<Slide>` 要素のみが許容される。各 `<Slide>` が 1 つのスライドに
661
- * 対応し、その子要素がスライドのルート POMNode となる。子要素が複数ある場合は
662
- * 暗黙的に VStack でラップされる。
621
+ * 最上位は `<Slide>` 要素と `<Theme>` 要素のみが許容される。各 `<Slide>` が
622
+ * 1 つのスライドに対応し、その子要素がスライドのルート POMNode となる。
623
+ * 子要素が複数ある場合は暗黙的に VStack でラップされる。
624
+ *
625
+ * `<Theme>` は文書全体に適用されるデザイントークン(配色)の宣言で、最大 1 つ
626
+ * 置ける。属性名がトークン名、属性値が 6 桁 hex の色値となり、各ノードの色属性
627
+ * から `$トークン名` で参照できる。参照は parse 時に解決されるため、返される
628
+ * POMNode には解決済みの hex 値が入る(`<Theme>` 自体はノードにならない)。
663
629
  *
664
630
  * XML タグは POM ノードタイプにマッピングされ、属性値は Zod スキーマを参照して
665
631
  * 適切な型(number, boolean, array, object)に変換される。
@@ -696,12 +662,26 @@ function parseXml(xmlString) {
696
662
  if (!parsed || parsed.length === 0) return [];
697
663
  const rootChildren = parsed[0]["__root__"] ?? [];
698
664
  const errors = [];
699
- const slideElements = rootChildren.filter((child) => !isTextNode(child));
665
+ const topLevelElements = rootChildren.filter((child) => !isTextNode(child));
666
+ const theme = {
667
+ tokens: {},
668
+ declared: false
669
+ };
670
+ for (const element of topLevelElements) {
671
+ if (getTagName(element) !== "Theme") continue;
672
+ if (theme.declared) {
673
+ errors.push(`Only one <Theme> element is allowed, but multiple were found`);
674
+ continue;
675
+ }
676
+ theme.declared = true;
677
+ parseThemeElement(element, theme.tokens, errors);
678
+ }
679
+ const slideElements = topLevelElements.filter((element) => getTagName(element) !== "Theme");
700
680
  const nodes = [];
701
681
  for (const slideEl of slideElements) {
702
682
  const tagName = getTagName(slideEl);
703
683
  if (tagName !== "Slide") {
704
- errors.push(`Top-level element must be <Slide>, but got <${tagName}>. Wrap your slide content in <Slide>...</Slide>.`);
684
+ errors.push(`Top-level element must be <Slide> or <Theme>, but got <${tagName}>. Wrap your slide content in <Slide>...</Slide>.`);
705
685
  continue;
706
686
  }
707
687
  if (Object.keys(getAttributes(slideEl)).length > 0) errors.push(`<Slide>: Attributes are not supported`);
@@ -710,7 +690,7 @@ function parseXml(xmlString) {
710
690
  errors.push(`<Slide> must contain at least one child element`);
711
691
  continue;
712
692
  }
713
- const converted = slideChildren.map((child) => convertElement(child, errors)).filter((c) => c !== null);
693
+ const converted = slideChildren.map((child) => convertElement(child, errors, theme)).filter((c) => c !== null);
714
694
  if (converted.length === 0) continue;
715
695
  if (converted.length === 1) nodes.push(converted[0]);
716
696
  else nodes.push({
@@ -722,6 +702,6 @@ function parseXml(xmlString) {
722
702
  return nodes;
723
703
  }
724
704
  //#endregion
725
- export { ParseXmlError, TAG_TO_TYPE, parseXml };
705
+ export { ParseXmlError, parseXml };
726
706
 
727
707
  //# sourceMappingURL=parseXml.js.map