@hirokisakabe/pom 8.2.1 → 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 (136) hide show
  1. package/README.md +37 -25
  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/buildContext.js +3 -1
  15. package/dist/buildContext.js.map +1 -1
  16. package/dist/buildPptx.d.ts.map +1 -1
  17. package/dist/buildPptx.js +5 -1
  18. package/dist/buildPptx.js.map +1 -1
  19. package/dist/calcYogaLayout/calcYogaLayout.js +18 -28
  20. package/dist/calcYogaLayout/calcYogaLayout.js.map +1 -1
  21. package/dist/calcYogaLayout/fontLoader.js.map +1 -1
  22. package/dist/calcYogaLayout/measureText.d.ts.map +1 -1
  23. package/dist/calcYogaLayout/measureText.js +9 -2
  24. package/dist/calcYogaLayout/measureText.js.map +1 -1
  25. package/dist/diagnostics.d.ts +1 -1
  26. package/dist/diagnostics.d.ts.map +1 -1
  27. package/dist/diagnostics.js.map +1 -1
  28. package/dist/icons/renderIcon.js.map +1 -1
  29. package/dist/parseMasterPptx.js.map +1 -1
  30. package/dist/parseXml/coercionRules.js +48 -9
  31. package/dist/parseXml/coercionRules.js.map +1 -1
  32. package/dist/parseXml/parseXml.d.ts +8 -3
  33. package/dist/parseXml/parseXml.d.ts.map +1 -1
  34. package/dist/parseXml/parseXml.js +192 -209
  35. package/dist/parseXml/parseXml.js.map +1 -1
  36. package/dist/parseXml/serializeXml.d.ts.map +1 -1
  37. package/dist/parseXml/serializeXml.js +13 -17
  38. package/dist/parseXml/serializeXml.js.map +1 -1
  39. package/dist/registry/definitions/arrow.js +2 -2
  40. package/dist/registry/definitions/arrow.js.map +1 -1
  41. package/dist/registry/definitions/chart.js +2 -2
  42. package/dist/registry/definitions/chart.js.map +1 -1
  43. package/dist/registry/definitions/compositeNodes.js +7 -12
  44. package/dist/registry/definitions/compositeNodes.js.map +1 -1
  45. package/dist/registry/definitions/icon.js +2 -2
  46. package/dist/registry/definitions/icon.js.map +1 -1
  47. package/dist/registry/definitions/image.js +2 -2
  48. package/dist/registry/definitions/image.js.map +1 -1
  49. package/dist/registry/definitions/layer.js +4 -5
  50. package/dist/registry/definitions/layer.js.map +1 -1
  51. package/dist/registry/definitions/line.js +2 -2
  52. package/dist/registry/definitions/line.js.map +1 -1
  53. package/dist/registry/definitions/list.js +3 -4
  54. package/dist/registry/definitions/list.js.map +1 -1
  55. package/dist/registry/definitions/shape.js +2 -2
  56. package/dist/registry/definitions/shape.js.map +1 -1
  57. package/dist/registry/definitions/stack.js +3 -4
  58. package/dist/registry/definitions/stack.js.map +1 -1
  59. package/dist/registry/definitions/svg.js +2 -2
  60. package/dist/registry/definitions/svg.js.map +1 -1
  61. package/dist/registry/definitions/table.js +2 -2
  62. package/dist/registry/definitions/table.js.map +1 -1
  63. package/dist/registry/definitions/text.js +5 -3
  64. package/dist/registry/definitions/text.js.map +1 -1
  65. package/dist/registry/index.js.map +1 -1
  66. package/dist/registry/nodeMetadata.js +208 -0
  67. package/dist/registry/nodeMetadata.js.map +1 -0
  68. package/dist/registry/nodeRegistry.js +3 -0
  69. package/dist/registry/nodeRegistry.js.map +1 -1
  70. package/dist/registry/xmlChildRules.js +55 -0
  71. package/dist/registry/xmlChildRules.js.map +1 -0
  72. package/dist/renderPptx/gradientFills.js +139 -0
  73. package/dist/renderPptx/gradientFills.js.map +1 -0
  74. package/dist/renderPptx/nodes/arrow.js +7 -28
  75. package/dist/renderPptx/nodes/arrow.js.map +1 -1
  76. package/dist/renderPptx/nodes/chart.js +2 -7
  77. package/dist/renderPptx/nodes/chart.js.map +1 -1
  78. package/dist/renderPptx/nodes/flow.js +6 -13
  79. package/dist/renderPptx/nodes/flow.js.map +1 -1
  80. package/dist/renderPptx/nodes/icon.js +4 -2
  81. package/dist/renderPptx/nodes/icon.js.map +1 -1
  82. package/dist/renderPptx/nodes/image.js +5 -13
  83. package/dist/renderPptx/nodes/image.js.map +1 -1
  84. package/dist/renderPptx/nodes/line.js +9 -33
  85. package/dist/renderPptx/nodes/line.js.map +1 -1
  86. package/dist/renderPptx/nodes/list.js +8 -20
  87. package/dist/renderPptx/nodes/list.js.map +1 -1
  88. package/dist/renderPptx/nodes/matrix.js +10 -11
  89. package/dist/renderPptx/nodes/matrix.js.map +1 -1
  90. package/dist/renderPptx/nodes/processArrow.js +9 -16
  91. package/dist/renderPptx/nodes/processArrow.js.map +1 -1
  92. package/dist/renderPptx/nodes/pyramid.js +5 -7
  93. package/dist/renderPptx/nodes/pyramid.js.map +1 -1
  94. package/dist/renderPptx/nodes/shape.js +7 -20
  95. package/dist/renderPptx/nodes/shape.js.map +1 -1
  96. package/dist/renderPptx/nodes/svg.js +2 -5
  97. package/dist/renderPptx/nodes/svg.js.map +1 -1
  98. package/dist/renderPptx/nodes/table.js +2 -5
  99. package/dist/renderPptx/nodes/table.js.map +1 -1
  100. package/dist/renderPptx/nodes/text.js +22 -15
  101. package/dist/renderPptx/nodes/text.js.map +1 -1
  102. package/dist/renderPptx/nodes/timeline.js +20 -22
  103. package/dist/renderPptx/nodes/timeline.js.map +1 -1
  104. package/dist/renderPptx/nodes/tree.js +5 -5
  105. package/dist/renderPptx/nodes/tree.js.map +1 -1
  106. package/dist/renderPptx/renderPptx.js +18 -30
  107. package/dist/renderPptx/renderPptx.js.map +1 -1
  108. package/dist/renderPptx/textOptions.js +34 -9
  109. package/dist/renderPptx/textOptions.js.map +1 -1
  110. package/dist/renderPptx/units.js +11 -1
  111. package/dist/renderPptx/units.js.map +1 -1
  112. package/dist/renderPptx/utils/backgroundBorder.js +107 -59
  113. package/dist/renderPptx/utils/backgroundBorder.js.map +1 -1
  114. package/dist/renderPptx/utils/contentArea.js +26 -9
  115. package/dist/renderPptx/utils/contentArea.js.map +1 -1
  116. package/dist/renderPptx/utils/scaleToFit.js +17 -1
  117. package/dist/renderPptx/utils/scaleToFit.js.map +1 -1
  118. package/dist/renderPptx/utils/straightLine.js +41 -0
  119. package/dist/renderPptx/utils/straightLine.js.map +1 -0
  120. package/dist/renderPptx/utils/visualStyle.js +113 -0
  121. package/dist/renderPptx/utils/visualStyle.js.map +1 -0
  122. package/dist/shared/boxSpacing.js +63 -0
  123. package/dist/shared/boxSpacing.js.map +1 -0
  124. package/dist/shared/gradient.js +103 -0
  125. package/dist/shared/gradient.js.map +1 -0
  126. package/dist/shared/measureImage.js.map +1 -1
  127. package/dist/shared/tableUtils.js.map +1 -1
  128. package/dist/shared/walkTree.js +1 -7
  129. package/dist/shared/walkTree.js.map +1 -1
  130. package/dist/toPositioned/toPositioned.js +1 -1
  131. package/dist/toPositioned/toPositioned.js.map +1 -1
  132. package/dist/types.d.ts +1166 -93
  133. package/dist/types.d.ts.map +1 -1
  134. package/dist/types.js +54 -18
  135. package/dist/types.js.map +1 -1
  136. package/package.json +10 -9
@@ -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,52 +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) {
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
- runs.push(run);
250
- } else {
168
+ for (const child of children) {
169
+ if (isTextNode(child)) {
170
+ runs.push({
171
+ text: child["#text"],
172
+ ...inherited
173
+ });
174
+ continue;
175
+ }
251
176
  const tag = getTagName(child);
252
177
  const innerChildren = getRawChildren(child);
253
- if (tag === "B") runs.push(...extractTextRuns(innerChildren, true, inheritItalic, inheritHref, inheritUnderline, inheritStrike, inheritHighlight, inheritColor, inheritFontFamily));
254
- else if (tag === "I") runs.push(...extractTextRuns(innerChildren, inheritBold, true, inheritHref, inheritUnderline, inheritStrike, inheritHighlight, inheritColor, inheritFontFamily));
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
+ }));
255
183
  else if (tag === "A") {
256
- const href = getAttributes(child).href ?? "";
257
- runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, href, inheritUnderline, inheritStrike, inheritHighlight, inheritColor, inheritFontFamily));
258
- } else if (tag === "U") runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, inheritHref, true, inheritStrike, inheritHighlight, inheritColor, inheritFontFamily));
259
- else if (tag === "S") runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, inheritHref, inheritUnderline, true, inheritHighlight, inheritColor, inheritFontFamily));
260
- 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") {
261
190
  const rawColor = getAttributes(child).color;
262
- const color = rawColor && rawColor.trim() ? rawColor : "FFFF00";
263
- runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, inheritHref, inheritUnderline, inheritStrike, color, inheritColor, inheritFontFamily));
191
+ const highlight = rawColor && rawColor.trim() ? rawColor : MARK_DEFAULT_HIGHLIGHT_COLOR;
192
+ runs.push(...extractTextRuns(innerChildren, {
193
+ ...inherited,
194
+ highlight
195
+ }));
264
196
  } else if (tag === "Span") {
265
197
  const spanAttrs = getAttributes(child);
266
- const rawSpanColor = spanAttrs.color;
267
- const spanColor = rawSpanColor && rawSpanColor.trim() ? rawSpanColor : inheritColor;
268
- const rawSpanFontFamily = spanAttrs.fontFamily;
269
- const spanFontFamily = rawSpanFontFamily && rawSpanFontFamily.trim() ? rawSpanFontFamily : inheritFontFamily;
270
- runs.push(...extractTextRuns(innerChildren, inheritBold, inheritItalic, inheritHref, inheritUnderline, inheritStrike, inheritHighlight, spanColor, spanFontFamily));
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));
271
203
  }
272
204
  }
273
205
  return runs;
@@ -320,85 +252,78 @@ function coerceChildAttrs(parentTagName, tagName, attrs, errors) {
320
252
  }
321
253
  return result;
322
254
  }
323
- function convertProcessArrowChildren(childElements, result, errors) {
324
- const steps = [];
325
- for (const child of childElements) {
326
- const tag = getTagName(child);
327
- if (tag !== "ProcessArrowStep") {
328
- errors.push(`Unknown child element <${tag}> inside <ProcessArrow>. Expected: <ProcessArrowStep>`);
329
- continue;
330
- }
331
- steps.push(coerceChildAttrs("ProcessArrow", tag, getAttributes(child), errors));
332
- }
333
- result.steps = steps;
255
+ function unknownChildError(childTag, parentTag, expectedTags) {
256
+ return `Unknown child element <${childTag}> inside <${parentTag}>. Expected: ${formatExpectedTags(expectedTags)}`;
334
257
  }
335
- function convertPyramidChildren(childElements, result, errors) {
336
- const levels = [];
337
- for (const child of childElements) {
338
- const tag = getTagName(child);
339
- if (tag !== "PyramidLevel") {
340
- errors.push(`Unknown child element <${tag}> inside <Pyramid>. Expected: <PyramidLevel>`);
341
- continue;
342
- }
343
- 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;
344
267
  }
345
- result.levels = levels;
346
268
  }
347
- function convertTimelineChildren(childElements, result, errors) {
269
+ /** RepeatedChildRule (単一種類の child tag の繰り返し → 配列 property) の汎用 converter */
270
+ function convertRepeatedChildren(rule, parentTag, childElements, result, errors) {
348
271
  const items = [];
349
272
  for (const child of childElements) {
350
273
  const tag = getTagName(child);
351
- if (tag !== "TimelineItem") {
352
- errors.push(`Unknown child element <${tag}> inside <Timeline>. Expected: <TimelineItem>`);
274
+ if (tag !== rule.childTag) {
275
+ errors.push(unknownChildError(tag, parentTag, [rule.childTag]));
353
276
  continue;
354
277
  }
355
- 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);
356
281
  }
357
- result.items = items;
282
+ result[rule.property] = items;
358
283
  }
359
- function convertMatrixChildren(childElements, result, errors) {
284
+ function convertMatrixChildren(rule, tagName, childElements, result, errors) {
360
285
  const items = [];
361
286
  for (const child of childElements) {
362
287
  const tag = getTagName(child);
363
288
  switch (tag) {
364
289
  case "MatrixAxes":
365
- result.axes = coerceChildAttrs("Matrix", tag, getAttributes(child), errors);
290
+ result.axes = coerceChildAttrs(tagName, tag, getAttributes(child), errors);
366
291
  break;
367
292
  case "MatrixQuadrants":
368
- result.quadrants = coerceChildAttrs("Matrix", tag, getAttributes(child), errors);
293
+ result.quadrants = coerceChildAttrs(tagName, tag, getAttributes(child), errors);
369
294
  break;
370
295
  case "MatrixItem":
371
- items.push(coerceChildAttrs("Matrix", tag, getAttributes(child), errors));
296
+ items.push(coerceChildAttrs(tagName, tag, getAttributes(child), errors));
372
297
  break;
373
- default: errors.push(`Unknown child element <${tag}> inside <Matrix>. Expected: <MatrixAxes>, <MatrixQuadrants>, or <MatrixItem>`);
298
+ default: errors.push(unknownChildError(tag, tagName, rule.expectedTags));
374
299
  }
375
300
  }
376
301
  if (items.length > 0) result.items = items;
377
302
  }
378
- function convertFlowChildren(childElements, result, errors) {
303
+ function convertFlowChildren(rule, tagName, childElements, result, errors) {
379
304
  const nodes = [];
380
305
  const connections = [];
381
306
  for (const child of childElements) {
382
307
  const tag = getTagName(child);
383
308
  switch (tag) {
384
309
  case "FlowNode":
385
- nodes.push(coerceChildAttrs("Flow", tag, getAttributes(child), errors));
310
+ nodes.push(coerceChildAttrs(tagName, tag, getAttributes(child), errors));
386
311
  break;
387
312
  case "FlowConnection":
388
- connections.push(coerceChildAttrs("Flow", tag, getAttributes(child), errors));
313
+ connections.push(coerceChildAttrs(tagName, tag, getAttributes(child), errors));
389
314
  break;
390
- default: errors.push(`Unknown child element <${tag}> inside <Flow>. Expected: <FlowNode> or <FlowConnection>`);
315
+ default: errors.push(unknownChildError(tag, tagName, rule.expectedTags));
391
316
  }
392
317
  }
393
318
  if (nodes.length > 0) result.nodes = nodes;
394
319
  if (connections.length > 0) result.connections = connections;
395
320
  }
396
- function convertChartChildren(childElements, result, errors) {
321
+ function convertChartChildren(rule, tagName, childElements, result, errors) {
397
322
  const data = [];
398
323
  for (const child of childElements) {
399
324
  const tag = getTagName(child);
400
325
  if (tag !== "ChartSeries") {
401
- errors.push(`Unknown child element <${tag}> inside <Chart>. Expected: <ChartSeries>`);
326
+ errors.push(unknownChildError(tag, tagName, rule.expectedTags));
402
327
  continue;
403
328
  }
404
329
  const attrs = getAttributes(child);
@@ -410,7 +335,7 @@ function convertChartChildren(childElements, result, errors) {
410
335
  for (const dp of getChildElements(child)) {
411
336
  const dpTag = getTagName(dp);
412
337
  if (dpTag !== "ChartDataPoint") {
413
- errors.push(`Unknown child element <${dpTag}> inside <ChartSeries>. Expected: <ChartDataPoint>`);
338
+ errors.push(unknownChildError(dpTag, "ChartSeries", ["ChartDataPoint"]));
414
339
  continue;
415
340
  }
416
341
  const dpAttrs = getAttributes(dp);
@@ -429,14 +354,14 @@ function convertChartChildren(childElements, result, errors) {
429
354
  }
430
355
  result.data = data;
431
356
  }
432
- function convertTableChildren(childElements, result, errors) {
357
+ function convertTableChildren(rule, tagName, childElements, result, errors) {
433
358
  const columns = [];
434
359
  const rows = [];
435
360
  for (const child of childElements) {
436
361
  const tag = getTagName(child);
437
362
  switch (tag) {
438
363
  case "Col":
439
- columns.push(coerceChildAttrs("Table", tag, getAttributes(child), errors));
364
+ columns.push(coerceChildAttrs(tagName, tag, getAttributes(child), errors));
440
365
  break;
441
366
  case "Tr": {
442
367
  const rowAttrs = getAttributes(child);
@@ -444,18 +369,11 @@ function convertTableChildren(childElements, result, errors) {
444
369
  for (const cellEl of getChildElements(child)) {
445
370
  const cellTag = getTagName(cellEl);
446
371
  if (cellTag !== "Td") {
447
- errors.push(`Unknown child element <${cellTag}> inside <Tr>. Expected: <Td>`);
372
+ errors.push(unknownChildError(cellTag, "Tr", ["Td"]));
448
373
  continue;
449
374
  }
450
375
  const cellAttrs = coerceChildAttrs("Tr", cellTag, getAttributes(cellEl), errors);
451
- const runsResult = buildRunsAndText(cellEl);
452
- if (runsResult) {
453
- cellAttrs.runs = runsResult.runs;
454
- cellAttrs.text = runsResult.text;
455
- } else {
456
- const cellText = getTextContent(cellEl);
457
- if (cellText !== void 0 && !("text" in cellAttrs)) cellAttrs.text = cellText;
458
- }
376
+ applyTextAndRuns(cellEl, cellAttrs);
459
377
  cells.push(cellAttrs);
460
378
  }
461
379
  const row = { cells };
@@ -467,7 +385,7 @@ function convertTableChildren(childElements, result, errors) {
467
385
  rows.push(row);
468
386
  break;
469
387
  }
470
- default: errors.push(`Unknown child element <${tag}> inside <Table>. Expected: <Col> or <Tr>`);
388
+ default: errors.push(unknownChildError(tag, tagName, rule.expectedTags));
471
389
  }
472
390
  }
473
391
  if (columns.length > 0) result.columns = columns;
@@ -483,18 +401,19 @@ function convertTreeItem(element, errors) {
483
401
  const item = {};
484
402
  if (attrs.label !== void 0) item.label = attrs.label;
485
403
  if (attrs.color !== void 0) item.color = attrs.color;
404
+ if (attrs.textColor !== void 0) item.textColor = attrs.textColor;
486
405
  const children = getChildElements(element);
487
406
  if (children.length > 0) item.children = children.map((child) => {
488
407
  const tag = getTagName(child);
489
408
  if (tag !== "TreeItem") {
490
- errors.push(`Unknown child element <${tag}> inside <TreeItem>. Expected: <TreeItem>`);
409
+ errors.push(unknownChildError(tag, "TreeItem", ["TreeItem"]));
491
410
  return null;
492
411
  }
493
412
  return convertTreeItem(child, errors);
494
413
  }).filter((item) => item !== null);
495
414
  return item;
496
415
  }
497
- function convertTreeChildren(childElements, result, errors) {
416
+ function convertTreeChildren(rule, tagName, childElements, result, errors) {
498
417
  if (childElements.length !== 1) {
499
418
  errors.push(`<Tree> must have exactly 1 <TreeItem> child element, but got ${childElements.length}`);
500
419
  return;
@@ -502,32 +421,11 @@ function convertTreeChildren(childElements, result, errors) {
502
421
  const child = childElements[0];
503
422
  const tag = getTagName(child);
504
423
  if (tag !== "TreeItem") {
505
- errors.push(`Unknown child element <${tag}> inside <Tree>. Expected: <TreeItem>`);
424
+ errors.push(unknownChildError(tag, tagName, rule.expectedTags));
506
425
  return;
507
426
  }
508
427
  result.data = convertTreeItem(child, errors);
509
428
  }
510
- function convertListChildren(parentTag, childElements, result, errors) {
511
- const items = [];
512
- for (const child of childElements) {
513
- const tag = getTagName(child);
514
- if (tag !== "Li") {
515
- errors.push(`Unknown child element <${tag}> inside <${parentTag}>. Expected: <Li>`);
516
- continue;
517
- }
518
- const attrs = coerceChildAttrs(parentTag, tag, getAttributes(child), errors);
519
- const runsResult = buildRunsAndText(child);
520
- if (runsResult) {
521
- attrs.runs = runsResult.runs;
522
- attrs.text = runsResult.text;
523
- } else {
524
- const textContent = getTextContent(child);
525
- if (textContent !== void 0 && !("text" in attrs)) attrs.text = textContent;
526
- }
527
- items.push(attrs);
528
- }
529
- result.items = items;
530
- }
531
429
  const svgBuilder = new XMLBuilder({
532
430
  preserveOrder: true,
533
431
  ignoreAttributes: false,
@@ -536,7 +434,7 @@ const svgBuilder = new XMLBuilder({
536
434
  function serializeSvgElement(svgElement) {
537
435
  return String(svgBuilder.build([svgElement]));
538
436
  }
539
- function convertSvgChildren(childElements, result, errors) {
437
+ function convertSvgChildren(_rule, _tagName, childElements, result, errors) {
540
438
  if (childElements.length !== 1) {
541
439
  errors.push(`<Svg>: Expected exactly one <svg> child element, but found ${childElements.length} child element(s)`);
542
440
  return;
@@ -549,11 +447,13 @@ function convertSvgChildren(childElements, result, errors) {
549
447
  }
550
448
  result.svgContent = serializeSvgElement(child);
551
449
  }
552
- function convertTextInlineChildren(childElements, result, errors, node) {
450
+ function convertInlineRunsChildren(tagName, childElements, result, errors, node) {
553
451
  for (const el of childElements) {
554
452
  const tag = getTagName(el);
555
453
  if (!INLINE_FORMAT_TAGS.has(tag)) {
556
- 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}>`);
557
457
  return;
558
458
  }
559
459
  }
@@ -564,13 +464,8 @@ function convertTextInlineChildren(childElements, result, errors, node) {
564
464
  result.text = runsResult.text;
565
465
  }
566
466
  }
567
- const CHILD_ELEMENT_CONVERTERS = {
568
- text: convertTextInlineChildren,
569
- ul: (childElements, result, errors) => convertListChildren("Ul", childElements, result, errors),
570
- ol: (childElements, result, errors) => convertListChildren("Ol", childElements, result, errors),
571
- processArrow: convertProcessArrowChildren,
572
- pyramid: convertPyramidChildren,
573
- timeline: convertTimelineChildren,
467
+ /** NodeSpecificChildRule を持つノードの専用 converter テーブル */
468
+ const NODE_SPECIFIC_CHILD_CONVERTERS = {
574
469
  matrix: convertMatrixChildren,
575
470
  flow: convertFlowChildren,
576
471
  chart: convertChartChildren,
@@ -578,20 +473,85 @@ const CHILD_ELEMENT_CONVERTERS = {
578
473
  tree: convertTreeChildren,
579
474
  svg: convertSvgChildren
580
475
  };
581
- 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) {
582
541
  const tagName = getTagName(node);
583
- const nodeType = TAG_TO_TYPE[tagName];
542
+ const def = getNodeMetadataByTag(tagName);
584
543
  const attrs = getAttributes(node);
585
544
  const childElements = getChildElements(node);
586
545
  const textContent = getTextContent(node);
587
- 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);
588
547
  else {
589
548
  errors.push(`Unknown tag: <${tagName}>`);
590
549
  return null;
591
550
  }
592
551
  }
593
- function convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors, xmlNode) {
552
+ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors, theme, xmlNode) {
594
553
  const result = { type: nodeType };
554
+ const def = getNodeMetadata(nodeType);
595
555
  const { regular: regularAttrs, dotGroups } = expandDotNotation(attrs);
596
556
  for (const [prefix, subAttrs] of Object.entries(dotGroups)) {
597
557
  if (prefix === "type") continue;
@@ -633,14 +593,18 @@ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, er
633
593
  else errors.push(`<${tagName}>: Unknown attribute "${key}"`);
634
594
  }
635
595
  }
636
- if (textContent !== void 0 && TEXT_CONTENT_NODES.has(nodeType)) {
637
- if (!("text" in result)) result.text = textContent;
596
+ if (textContent !== void 0 && def.textContentProperty) {
597
+ if (!(def.textContentProperty in result)) result[def.textContentProperty] = textContent;
638
598
  }
639
- const childConverter = CHILD_ELEMENT_CONVERTERS[nodeType];
640
- if (childConverter && childElements.length > 0) childConverter(childElements, result, errors, xmlNode);
641
- else if (CONTAINER_TYPES.has(nodeType) && childElements.length > 0) result.children = childElements.map((child) => convertElement(child, errors)).filter((child) => child !== null);
642
- else if (!CONTAINER_TYPES.has(nodeType) && !childConverter && childElements.length > 0) errors.push(`<${tagName}>: Unexpected child elements. <${tagName}> does not accept child elements`);
643
- 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);
644
608
  if (nodeType === "icon") {
645
609
  if (typeof result.color === "string" && !result.color.startsWith("#")) result.color = `#${result.color}`;
646
610
  if (typeof result.bgColor === "string" && !result.bgColor.startsWith("#")) result.bgColor = `#${result.bgColor}`;
@@ -654,9 +618,14 @@ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, er
654
618
  /**
655
619
  * XML 文字列を POMNode 配列に変換する。
656
620
  *
657
- * 最上位は `<Slide>` 要素のみが許容される。各 `<Slide>` が 1 つのスライドに
658
- * 対応し、その子要素がスライドのルート POMNode となる。子要素が複数ある場合は
659
- * 暗黙的に VStack でラップされる。
621
+ * 最上位は `<Slide>` 要素と `<Theme>` 要素のみが許容される。各 `<Slide>` が
622
+ * 1 つのスライドに対応し、その子要素がスライドのルート POMNode となる。
623
+ * 子要素が複数ある場合は暗黙的に VStack でラップされる。
624
+ *
625
+ * `<Theme>` は文書全体に適用されるデザイントークン(配色)の宣言で、最大 1 つ
626
+ * 置ける。属性名がトークン名、属性値が 6 桁 hex の色値となり、各ノードの色属性
627
+ * から `$トークン名` で参照できる。参照は parse 時に解決されるため、返される
628
+ * POMNode には解決済みの hex 値が入る(`<Theme>` 自体はノードにならない)。
660
629
  *
661
630
  * XML タグは POM ノードタイプにマッピングされ、属性値は Zod スキーマを参照して
662
631
  * 適切な型(number, boolean, array, object)に変換される。
@@ -693,12 +662,26 @@ function parseXml(xmlString) {
693
662
  if (!parsed || parsed.length === 0) return [];
694
663
  const rootChildren = parsed[0]["__root__"] ?? [];
695
664
  const errors = [];
696
- 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");
697
680
  const nodes = [];
698
681
  for (const slideEl of slideElements) {
699
682
  const tagName = getTagName(slideEl);
700
683
  if (tagName !== "Slide") {
701
- 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>.`);
702
685
  continue;
703
686
  }
704
687
  if (Object.keys(getAttributes(slideEl)).length > 0) errors.push(`<Slide>: Attributes are not supported`);
@@ -707,7 +690,7 @@ function parseXml(xmlString) {
707
690
  errors.push(`<Slide> must contain at least one child element`);
708
691
  continue;
709
692
  }
710
- 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);
711
694
  if (converted.length === 0) continue;
712
695
  if (converted.length === 1) nodes.push(converted[0]);
713
696
  else nodes.push({
@@ -719,6 +702,6 @@ function parseXml(xmlString) {
719
702
  return nodes;
720
703
  }
721
704
  //#endregion
722
- export { ParseXmlError, TAG_TO_TYPE, parseXml };
705
+ export { ParseXmlError, parseXml };
723
706
 
724
707
  //# sourceMappingURL=parseXml.js.map