@hirokisakabe/pom 1.4.0 → 2.0.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 (52) hide show
  1. package/README.md +22 -177
  2. package/dist/buildPptx.d.ts +2 -2
  3. package/dist/buildPptx.d.ts.map +1 -1
  4. package/dist/buildPptx.js +3 -1
  5. package/dist/calcYogaLayout/fontLoader.d.ts +0 -7
  6. package/dist/calcYogaLayout/fontLoader.d.ts.map +1 -1
  7. package/dist/calcYogaLayout/fontLoader.js +1 -1
  8. package/dist/calcYogaLayout/measureText.d.ts +0 -4
  9. package/dist/calcYogaLayout/measureText.d.ts.map +1 -1
  10. package/dist/calcYogaLayout/measureText.js +0 -6
  11. package/dist/index.d.ts +2 -5
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +1 -4
  14. package/dist/inputSchema.d.ts +3 -438
  15. package/dist/inputSchema.d.ts.map +1 -1
  16. package/dist/inputSchema.js +6 -138
  17. package/dist/parseXml.d.ts +5 -1
  18. package/dist/parseXml.d.ts.map +1 -1
  19. package/dist/parseXml.js +502 -39
  20. package/dist/renderPptx/renderPptx.d.ts +1 -2
  21. package/dist/renderPptx/renderPptx.d.ts.map +1 -1
  22. package/dist/renderPptx/renderPptx.js +0 -2
  23. package/dist/renderPptx/textOptions.d.ts +0 -1
  24. package/dist/renderPptx/textOptions.d.ts.map +1 -1
  25. package/dist/renderPptx/textOptions.js +1 -1
  26. package/dist/renderPptx/types.d.ts +0 -2
  27. package/dist/renderPptx/types.d.ts.map +1 -1
  28. package/dist/table/utils.d.ts +0 -2
  29. package/dist/table/utils.d.ts.map +1 -1
  30. package/dist/table/utils.js +2 -2
  31. package/dist/types.d.ts +13 -121
  32. package/dist/types.d.ts.map +1 -1
  33. package/dist/types.js +21 -27
  34. package/package.json +3 -6
  35. package/dist/component.d.ts +0 -90
  36. package/dist/component.d.ts.map +0 -1
  37. package/dist/component.js +0 -123
  38. package/dist/renderPptx/nodes/box.d.ts +0 -2
  39. package/dist/renderPptx/nodes/box.d.ts.map +0 -1
  40. package/dist/renderPptx/nodes/box.js +0 -3
  41. package/dist/renderPptx/utils/index.d.ts +0 -6
  42. package/dist/renderPptx/utils/index.d.ts.map +0 -1
  43. package/dist/renderPptx/utils/index.js +0 -3
  44. package/dist/renderPptx/utils/shapeDrawing.d.ts +0 -27
  45. package/dist/renderPptx/utils/shapeDrawing.d.ts.map +0 -1
  46. package/dist/renderPptx/utils/shapeDrawing.js +0 -36
  47. package/dist/renderPptx/utils/textDrawing.d.ts +0 -25
  48. package/dist/renderPptx/utils/textDrawing.d.ts.map +0 -1
  49. package/dist/renderPptx/utils/textDrawing.js +0 -25
  50. package/dist/schema.d.ts +0 -23
  51. package/dist/schema.d.ts.map +0 -1
  52. package/dist/schema.js +0 -24
package/dist/parseXml.js CHANGED
@@ -1,7 +1,17 @@
1
1
  import { XMLParser } from "fast-xml-parser";
2
2
  import { z } from "zod";
3
3
  import { inputTextNodeSchema, inputImageNodeSchema, inputTableNodeSchema, inputShapeNodeSchema, inputChartNodeSchema, inputTimelineNodeSchema, inputMatrixNodeSchema, inputTreeNodeSchema, inputFlowNodeSchema, inputProcessArrowNodeSchema, inputLineNodeSchema, inputBaseNodeSchema, } from "./inputSchema.js";
4
- import { alignItemsSchema, justifyContentSchema, shadowStyleSchema, } from "./types.js";
4
+ import { alignItemsSchema, justifyContentSchema, shadowStyleSchema, processArrowStepSchema, timelineItemSchema, matrixAxisSchema, matrixQuadrantsSchema, matrixItemSchema, flowNodeItemSchema, flowConnectionSchema, chartDataSchema, tableColumnSchema, tableCellSchema, } from "./types.js";
5
+ // ===== ParseXmlError =====
6
+ export class ParseXmlError extends Error {
7
+ errors;
8
+ constructor(errors) {
9
+ const message = `XML validation failed (${errors.length} error${errors.length > 1 ? "s" : ""}):\n${errors.map((e) => ` - ${e}`).join("\n")}`;
10
+ super(message);
11
+ this.name = "ParseXmlError";
12
+ this.errors = errors;
13
+ }
14
+ }
5
15
  // ===== Tag name → POM node type mapping =====
6
16
  const TAG_TO_TYPE = {
7
17
  Text: "text",
@@ -20,6 +30,8 @@ const TAG_TO_TYPE = {
20
30
  HStack: "hstack",
21
31
  Layer: "layer",
22
32
  };
33
+ // Reverse mapping: node type → tag name
34
+ const TYPE_TO_TAG = Object.fromEntries(Object.entries(TAG_TO_TYPE).map(([tag, type]) => [type, tag]));
23
35
  function extractShape(schema) {
24
36
  return schema.shape;
25
37
  }
@@ -52,6 +64,154 @@ const containerShapes = {
52
64
  };
53
65
  const CONTAINER_TYPES = new Set(["box", "vstack", "hstack", "layer"]);
54
66
  const TEXT_CONTENT_NODES = new Set(["text", "shape"]);
67
+ // Attributes allowed on any node (e.g., x/y for Layer children positioning)
68
+ const UNIVERSAL_ATTRS = new Set(["x", "y"]);
69
+ // ===== Validation helpers =====
70
+ function getKnownAttributes(nodeType) {
71
+ const shape = leafNodeShapes[nodeType] ?? containerShapes[nodeType];
72
+ if (!shape)
73
+ return [];
74
+ return Object.keys(shape).filter((k) => k !== "type");
75
+ }
76
+ function levenshteinDistance(a, b) {
77
+ const m = a.length;
78
+ const n = b.length;
79
+ const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
80
+ for (let i = 0; i <= m; i++)
81
+ dp[i][0] = i;
82
+ for (let j = 0; j <= n; j++)
83
+ dp[0][j] = j;
84
+ for (let i = 1; i <= m; i++) {
85
+ for (let j = 1; j <= n; j++) {
86
+ dp[i][j] =
87
+ a[i - 1] === b[j - 1]
88
+ ? dp[i - 1][j - 1]
89
+ : 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
90
+ }
91
+ }
92
+ return dp[m][n];
93
+ }
94
+ function findClosestMatch(input, candidates) {
95
+ const threshold = Math.max(2, Math.floor(input.length / 2));
96
+ let bestMatch;
97
+ let bestDistance = Infinity;
98
+ for (const candidate of candidates) {
99
+ const dist = levenshteinDistance(input.toLowerCase(), candidate.toLowerCase());
100
+ if (dist < bestDistance && dist <= threshold) {
101
+ bestDistance = dist;
102
+ bestMatch = candidate;
103
+ }
104
+ }
105
+ return bestMatch;
106
+ }
107
+ function getKnownChildAttributes(tagName) {
108
+ const shape = childElementShapes[tagName];
109
+ if (!shape)
110
+ return [];
111
+ return Object.keys(shape);
112
+ }
113
+ // ===== Leaf node Zod validation schemas =====
114
+ const leafNodeValidationSchemas = {
115
+ text: inputTextNodeSchema,
116
+ image: inputImageNodeSchema,
117
+ table: inputTableNodeSchema,
118
+ shape: inputShapeNodeSchema,
119
+ chart: inputChartNodeSchema,
120
+ timeline: inputTimelineNodeSchema,
121
+ matrix: inputMatrixNodeSchema,
122
+ tree: inputTreeNodeSchema,
123
+ flow: inputFlowNodeSchema,
124
+ processArrow: inputProcessArrowNodeSchema,
125
+ line: inputLineNodeSchema,
126
+ };
127
+ function formatZodIssue(issue, tagName) {
128
+ const path = issue.path;
129
+ // Skip children-related issues (validated recursively)
130
+ if (path.length > 0 && path[0] === "children")
131
+ return null;
132
+ // Skip "type" field issues (set internally)
133
+ if (path.length === 1 && path[0] === "type")
134
+ return null;
135
+ const attrName = path.length > 0 ? String(path[0]) : undefined;
136
+ const code = issue.code;
137
+ if (code === "invalid_type") {
138
+ // Missing required attribute
139
+ if (issue.input === undefined) {
140
+ if (attrName) {
141
+ return `<${tagName}>: Missing required attribute "${attrName}"`;
142
+ }
143
+ return `<${tagName}>: ${issue.message}`;
144
+ }
145
+ // Type mismatch
146
+ if (attrName) {
147
+ return `<${tagName}>: Invalid type for attribute "${attrName}". ${issue.message}`;
148
+ }
149
+ return `<${tagName}>: ${issue.message}`;
150
+ }
151
+ if (code === "invalid_value") {
152
+ if (attrName) {
153
+ const values = issue.values;
154
+ if (values) {
155
+ return `<${tagName}>: Invalid value for attribute "${attrName}". Expected: ${values.map((v) => `"${v}"`).join(", ")}`;
156
+ }
157
+ return `<${tagName}>: Invalid value for attribute "${attrName}". ${issue.message}`;
158
+ }
159
+ return `<${tagName}>: ${issue.message}`;
160
+ }
161
+ if (code === "too_small" || code === "too_big") {
162
+ if (attrName) {
163
+ return `<${tagName}>: Invalid value for attribute "${attrName}". ${issue.message}`;
164
+ }
165
+ return `<${tagName}>: ${issue.message}`;
166
+ }
167
+ // Generic fallback
168
+ if (attrName) {
169
+ return `<${tagName}>: Attribute "${attrName}": ${issue.message}`;
170
+ }
171
+ return `<${tagName}>: ${issue.message}`;
172
+ }
173
+ // Properties that may be legitimately absent when using child element notation
174
+ // or when the property is optional in practice (even if required in schema).
175
+ const CHILD_ELEMENT_PROPS = {
176
+ flow: new Set(["nodes", "connections"]),
177
+ table: new Set(["columns", "rows"]),
178
+ chart: new Set(["data"]),
179
+ timeline: new Set(["items"]),
180
+ matrix: new Set(["axes", "items", "quadrants"]),
181
+ processArrow: new Set(["steps"]),
182
+ tree: new Set(["data"]),
183
+ };
184
+ function validateLeafNode(nodeType, result, errors) {
185
+ const schema = leafNodeValidationSchemas[nodeType];
186
+ if (!schema)
187
+ return;
188
+ const tagName = TYPE_TO_TAG[nodeType] ?? nodeType;
189
+ const childProps = CHILD_ELEMENT_PROPS[nodeType];
190
+ const parseResult = schema.safeParse(result);
191
+ if (!parseResult.success) {
192
+ const seen = new Set();
193
+ for (const issue of parseResult.error.issues) {
194
+ // Skip only top-level missing child-element properties (path.length === 1)
195
+ // Nested issues (e.g., data.children[0].label) must still be reported
196
+ if (childProps &&
197
+ issue.path.length === 1 &&
198
+ childProps.has(String(issue.path[0])) &&
199
+ issue.code === "invalid_type" &&
200
+ issue.input === undefined) {
201
+ continue;
202
+ }
203
+ // Skip issues for universal attributes (x, y)
204
+ if (issue.path.length > 0 && UNIVERSAL_ATTRS.has(String(issue.path[0]))) {
205
+ continue;
206
+ }
207
+ const msg = formatZodIssue(issue, tagName);
208
+ if (msg && !seen.has(msg)) {
209
+ seen.add(msg);
210
+ errors.push(msg);
211
+ }
212
+ }
213
+ }
214
+ }
55
215
  function getDef(schema) {
56
216
  return schema._def;
57
217
  }
@@ -85,6 +245,7 @@ function resolveZodTypeName(schema) {
85
245
  return getZodType(unwrapSchema(schema));
86
246
  }
87
247
  // ===== Value coercion =====
248
+ // Returns { value, error } — if error is non-null, coercion failed.
88
249
  function coerceValue(value, schema) {
89
250
  const unwrapped = unwrapSchema(schema);
90
251
  const typeName = getZodType(unwrapped);
@@ -93,34 +254,48 @@ function coerceValue(value, schema) {
93
254
  case "number": {
94
255
  const num = Number(value);
95
256
  if (isNaN(num)) {
96
- throw new Error(`Cannot convert "${value}" to number`);
257
+ return {
258
+ value: undefined,
259
+ error: `Cannot convert "${value}" to number`,
260
+ };
97
261
  }
98
- return num;
262
+ return { value: num, error: null };
99
263
  }
100
264
  case "boolean":
101
265
  if (value !== "true" && value !== "false") {
102
- throw new Error(`Cannot convert "${value}" to boolean (expected "true" or "false")`);
266
+ return {
267
+ value: undefined,
268
+ error: `Cannot convert "${value}" to boolean (expected "true" or "false")`,
269
+ };
103
270
  }
104
- return value === "true";
271
+ return { value: value === "true", error: null };
105
272
  case "string":
106
273
  case "enum":
107
- return value;
274
+ return { value, error: null };
108
275
  case "literal": {
109
276
  const values = def.values;
110
277
  const singleValue = def.value;
111
- return values?.[0] ?? singleValue;
278
+ return { value: values?.[0] ?? singleValue, error: null };
112
279
  }
113
280
  case "array":
114
281
  case "object":
115
282
  case "record":
116
283
  case "tuple":
117
- return JSON.parse(value);
284
+ try {
285
+ return { value: JSON.parse(value), error: null };
286
+ }
287
+ catch {
288
+ return {
289
+ value: undefined,
290
+ error: `Cannot parse JSON value: "${value}"`,
291
+ };
292
+ }
118
293
  case "union": {
119
294
  const options = def.options;
120
- return coerceUnionValue(value, options);
295
+ return { value: coerceUnionValue(value, options), error: null };
121
296
  }
122
297
  default:
123
- return coerceFallback(value);
298
+ return { value: coerceFallback(value), error: null };
124
299
  }
125
300
  }
126
301
  function coerceUnionValue(value, options) {
@@ -223,32 +398,312 @@ function getTextContent(node) {
223
398
  }
224
399
  return textParts.length > 0 ? textParts.join("") : undefined;
225
400
  }
401
+ // ===== Child element schemas for type coercion =====
402
+ const childElementShapes = {
403
+ Step: extractShape(processArrowStepSchema),
404
+ TimelineItem: extractShape(timelineItemSchema),
405
+ Axes: extractShape(matrixAxisSchema),
406
+ Quadrants: extractShape(matrixQuadrantsSchema),
407
+ MatrixItem: extractShape(matrixItemSchema),
408
+ FlowNode: extractShape(flowNodeItemSchema),
409
+ Connection: extractShape(flowConnectionSchema),
410
+ Column: extractShape(tableColumnSchema),
411
+ Cell: extractShape(tableCellSchema),
412
+ };
413
+ function coerceChildAttrs(parentTagName, tagName, attrs, errors) {
414
+ const shape = childElementShapes[tagName];
415
+ const result = {};
416
+ for (const [key, value] of Object.entries(attrs)) {
417
+ if (shape && shape[key]) {
418
+ const coerced = coerceValue(value, shape[key]);
419
+ if (coerced.error !== null) {
420
+ errors.push(`<${parentTagName}>.<${tagName}>: ${coerced.error}`);
421
+ }
422
+ else {
423
+ result[key] = coerced.value;
424
+ }
425
+ }
426
+ else if (shape) {
427
+ // Unknown attribute on child element
428
+ const knownAttrs = getKnownChildAttributes(tagName);
429
+ const suggestion = findClosestMatch(key, knownAttrs);
430
+ errors.push(`<${parentTagName}>.<${tagName}>: Unknown attribute "${key}"${suggestion ? `. Did you mean "${suggestion}"?` : ""}`);
431
+ }
432
+ else {
433
+ result[key] = coerceFallback(value);
434
+ }
435
+ }
436
+ return result;
437
+ }
438
+ function convertProcessArrowChildren(childElements, result, errors) {
439
+ const steps = [];
440
+ for (const child of childElements) {
441
+ const tag = getTagName(child);
442
+ if (tag !== "Step") {
443
+ errors.push(`Unknown child element <${tag}> inside <ProcessArrow>. Expected: <Step>`);
444
+ continue;
445
+ }
446
+ steps.push(coerceChildAttrs("ProcessArrow", tag, getAttributes(child), errors));
447
+ }
448
+ result.steps = steps;
449
+ }
450
+ function convertTimelineChildren(childElements, result, errors) {
451
+ const items = [];
452
+ for (const child of childElements) {
453
+ const tag = getTagName(child);
454
+ if (tag !== "TimelineItem") {
455
+ errors.push(`Unknown child element <${tag}> inside <Timeline>. Expected: <TimelineItem>`);
456
+ continue;
457
+ }
458
+ items.push(coerceChildAttrs("Timeline", tag, getAttributes(child), errors));
459
+ }
460
+ result.items = items;
461
+ }
462
+ function convertMatrixChildren(childElements, result, errors) {
463
+ const items = [];
464
+ for (const child of childElements) {
465
+ const tag = getTagName(child);
466
+ switch (tag) {
467
+ case "Axes":
468
+ result.axes = coerceChildAttrs("Matrix", tag, getAttributes(child), errors);
469
+ break;
470
+ case "Quadrants":
471
+ result.quadrants = coerceChildAttrs("Matrix", tag, getAttributes(child), errors);
472
+ break;
473
+ case "MatrixItem":
474
+ items.push(coerceChildAttrs("Matrix", tag, getAttributes(child), errors));
475
+ break;
476
+ default:
477
+ errors.push(`Unknown child element <${tag}> inside <Matrix>. Expected: <Axes>, <Quadrants>, or <MatrixItem>`);
478
+ }
479
+ }
480
+ if (items.length > 0) {
481
+ result.items = items;
482
+ }
483
+ }
484
+ function convertFlowChildren(childElements, result, errors) {
485
+ const nodes = [];
486
+ const connections = [];
487
+ for (const child of childElements) {
488
+ const tag = getTagName(child);
489
+ switch (tag) {
490
+ case "FlowNode":
491
+ nodes.push(coerceChildAttrs("Flow", tag, getAttributes(child), errors));
492
+ break;
493
+ case "Connection":
494
+ connections.push(coerceChildAttrs("Flow", tag, getAttributes(child), errors));
495
+ break;
496
+ default:
497
+ errors.push(`Unknown child element <${tag}> inside <Flow>. Expected: <FlowNode> or <Connection>`);
498
+ }
499
+ }
500
+ if (nodes.length > 0) {
501
+ result.nodes = nodes;
502
+ }
503
+ if (connections.length > 0) {
504
+ result.connections = connections;
505
+ }
506
+ }
507
+ function convertChartChildren(childElements, result, errors) {
508
+ const dataShape = extractShape(chartDataSchema);
509
+ const data = [];
510
+ for (const child of childElements) {
511
+ const tag = getTagName(child);
512
+ if (tag !== "Series") {
513
+ errors.push(`Unknown child element <${tag}> inside <Chart>. Expected: <Series>`);
514
+ continue;
515
+ }
516
+ const attrs = getAttributes(child);
517
+ const series = {
518
+ labels: [],
519
+ values: [],
520
+ };
521
+ if (attrs.name !== undefined) {
522
+ const nameSchema = dataShape.name;
523
+ if (nameSchema) {
524
+ const coerced = coerceValue(attrs.name, nameSchema);
525
+ if (coerced.error !== null) {
526
+ errors.push(`<Chart>.<Series>: ${coerced.error}`);
527
+ }
528
+ else {
529
+ series.name = coerced.value;
530
+ }
531
+ }
532
+ else {
533
+ series.name = attrs.name;
534
+ }
535
+ }
536
+ for (const dp of getChildElements(child)) {
537
+ const dpTag = getTagName(dp);
538
+ if (dpTag !== "DataPoint") {
539
+ errors.push(`Unknown child element <${dpTag}> inside <Series>. Expected: <DataPoint>`);
540
+ continue;
541
+ }
542
+ const dpAttrs = getAttributes(dp);
543
+ if (dpAttrs.label === undefined) {
544
+ errors.push('<DataPoint> requires a "label" attribute');
545
+ }
546
+ if (dpAttrs.value === undefined) {
547
+ errors.push('<DataPoint> requires a "value" attribute');
548
+ }
549
+ if (dpAttrs.label === undefined || dpAttrs.value === undefined) {
550
+ continue;
551
+ }
552
+ const numValue = Number(dpAttrs.value);
553
+ if (isNaN(numValue)) {
554
+ errors.push(`Cannot convert "${dpAttrs.value}" to number in <DataPoint> "value" attribute`);
555
+ continue;
556
+ }
557
+ series.labels.push(dpAttrs.label);
558
+ series.values.push(numValue);
559
+ }
560
+ data.push(series);
561
+ }
562
+ result.data = data;
563
+ }
564
+ function convertTableChildren(childElements, result, errors) {
565
+ const columns = [];
566
+ const rows = [];
567
+ for (const child of childElements) {
568
+ const tag = getTagName(child);
569
+ switch (tag) {
570
+ case "Column":
571
+ columns.push(coerceChildAttrs("Table", tag, getAttributes(child), errors));
572
+ break;
573
+ case "Row": {
574
+ const rowAttrs = getAttributes(child);
575
+ const cells = [];
576
+ for (const cellEl of getChildElements(child)) {
577
+ const cellTag = getTagName(cellEl);
578
+ if (cellTag !== "Cell") {
579
+ errors.push(`Unknown child element <${cellTag}> inside <Row>. Expected: <Cell>`);
580
+ continue;
581
+ }
582
+ const cellAttrs = coerceChildAttrs("Row", cellTag, getAttributes(cellEl), errors);
583
+ const cellText = getTextContent(cellEl);
584
+ if (cellText !== undefined && !("text" in cellAttrs)) {
585
+ cellAttrs.text = cellText;
586
+ }
587
+ cells.push(cellAttrs);
588
+ }
589
+ const row = { cells };
590
+ if (rowAttrs.height !== undefined) {
591
+ const h = Number(rowAttrs.height);
592
+ if (isNaN(h)) {
593
+ errors.push(`Cannot convert "${rowAttrs.height}" to number in <Row> "height" attribute`);
594
+ }
595
+ else {
596
+ row.height = h;
597
+ }
598
+ }
599
+ rows.push(row);
600
+ break;
601
+ }
602
+ default:
603
+ errors.push(`Unknown child element <${tag}> inside <Table>. Expected: <Column> or <Row>`);
604
+ }
605
+ }
606
+ if (columns.length > 0) {
607
+ result.columns = columns;
608
+ }
609
+ if (rows.length > 0) {
610
+ result.rows = rows;
611
+ }
612
+ }
613
+ function convertTreeItem(element, errors) {
614
+ const attrs = getAttributes(element);
615
+ if (attrs.label === undefined) {
616
+ errors.push('<TreeItem> requires a "label" attribute');
617
+ }
618
+ const item = {};
619
+ if (attrs.label !== undefined) {
620
+ item.label = attrs.label;
621
+ }
622
+ if (attrs.color !== undefined) {
623
+ item.color = attrs.color;
624
+ }
625
+ const children = getChildElements(element);
626
+ if (children.length > 0) {
627
+ item.children = children
628
+ .map((child) => {
629
+ const tag = getTagName(child);
630
+ if (tag !== "TreeItem") {
631
+ errors.push(`Unknown child element <${tag}> inside <TreeItem>. Expected: <TreeItem>`);
632
+ return null;
633
+ }
634
+ return convertTreeItem(child, errors);
635
+ })
636
+ .filter((item) => item !== null);
637
+ }
638
+ return item;
639
+ }
640
+ function convertTreeChildren(childElements, result, errors) {
641
+ if (childElements.length !== 1) {
642
+ errors.push(`<Tree> must have exactly 1 <TreeItem> child element, but got ${childElements.length}`);
643
+ return;
644
+ }
645
+ const child = childElements[0];
646
+ const tag = getTagName(child);
647
+ if (tag !== "TreeItem") {
648
+ errors.push(`Unknown child element <${tag}> inside <Tree>. Expected: <TreeItem>`);
649
+ return;
650
+ }
651
+ result.data = convertTreeItem(child, errors);
652
+ }
653
+ const CHILD_ELEMENT_CONVERTERS = {
654
+ processArrow: convertProcessArrowChildren,
655
+ timeline: convertTimelineChildren,
656
+ matrix: convertMatrixChildren,
657
+ flow: convertFlowChildren,
658
+ chart: convertChartChildren,
659
+ table: convertTableChildren,
660
+ tree: convertTreeChildren,
661
+ };
226
662
  // ===== Node conversion =====
227
- function convertElement(node) {
663
+ function convertElement(node, errors) {
228
664
  const tagName = getTagName(node);
229
665
  const nodeType = TAG_TO_TYPE[tagName];
230
666
  const attrs = getAttributes(node);
231
667
  const childElements = getChildElements(node);
232
668
  const textContent = getTextContent(node);
233
669
  if (nodeType) {
234
- return convertPomNode(nodeType, attrs, childElements, textContent);
670
+ return convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors);
235
671
  }
236
672
  else {
237
- return convertComponentNode(tagName, attrs, childElements, textContent);
673
+ errors.push(`Unknown tag: <${tagName}>`);
674
+ return null;
238
675
  }
239
676
  }
240
- function convertPomNode(nodeType, attrs, childElements, textContent) {
677
+ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, errors) {
241
678
  const result = { type: nodeType };
242
679
  for (const [key, value] of Object.entries(attrs)) {
243
680
  if (key === "type")
244
681
  continue;
245
682
  const propSchema = getPropertySchema(nodeType, key);
246
683
  if (propSchema) {
247
- result[key] = coerceValue(value, propSchema);
684
+ const coerced = coerceValue(value, propSchema);
685
+ if (coerced.error !== null) {
686
+ errors.push(`<${tagName}>: ${coerced.error}`);
687
+ }
688
+ else {
689
+ result[key] = coerced.value;
690
+ }
248
691
  }
249
- else {
692
+ else if (UNIVERSAL_ATTRS.has(key)) {
693
+ // Allow universal attributes (e.g., x/y for Layer children)
250
694
  result[key] = coerceFallback(value);
251
695
  }
696
+ else {
697
+ // Unknown attribute
698
+ const knownAttrs = getKnownAttributes(nodeType);
699
+ const suggestion = findClosestMatch(key, knownAttrs);
700
+ if (suggestion) {
701
+ errors.push(`<${tagName}>: Unknown attribute "${key}". Did you mean "${suggestion}"?`);
702
+ }
703
+ else {
704
+ errors.push(`<${tagName}>: Unknown attribute "${key}"`);
705
+ }
706
+ }
252
707
  }
253
708
  // Text content → text property for nodes that support it
254
709
  if (textContent !== undefined && TEXT_CONTENT_NODES.has(nodeType)) {
@@ -256,44 +711,46 @@ function convertPomNode(nodeType, attrs, childElements, textContent) {
256
711
  result.text = textContent;
257
712
  }
258
713
  }
714
+ // Child element notation for complex properties
715
+ const childConverter = CHILD_ELEMENT_CONVERTERS[nodeType];
716
+ if (childConverter && childElements.length > 0) {
717
+ childConverter(childElements, result, errors);
718
+ }
259
719
  // Children for container nodes
260
- if (CONTAINER_TYPES.has(nodeType) && childElements.length > 0) {
261
- const convertedChildren = childElements.map(convertElement);
720
+ else if (CONTAINER_TYPES.has(nodeType) && childElements.length > 0) {
721
+ const convertedChildren = childElements
722
+ .map((child) => convertElement(child, errors))
723
+ .filter((child) => child !== null);
262
724
  if (nodeType === "box") {
263
725
  if (childElements.length !== 1) {
264
- throw new Error(`<Box> must have exactly 1 child element, but got ${childElements.length}`);
726
+ errors.push(`<Box> must have exactly 1 child element, but got ${childElements.length}`);
727
+ }
728
+ if (convertedChildren.length > 0) {
729
+ result.children = convertedChildren[0];
265
730
  }
266
- result.children = convertedChildren[0];
267
731
  }
268
732
  else {
269
733
  result.children = convertedChildren;
270
734
  }
271
735
  }
272
- return result;
273
- }
274
- function convertComponentNode(tagName, attrs, childElements, textContent) {
275
- const props = {};
276
- for (const [key, value] of Object.entries(attrs)) {
277
- props[key] = coerceFallback(value);
736
+ // Leaf nodes that shouldn't have child elements
737
+ else if (!CONTAINER_TYPES.has(nodeType) &&
738
+ !childConverter &&
739
+ childElements.length > 0) {
740
+ errors.push(`<${tagName}>: Unexpected child elements. <${tagName}> does not accept child elements`);
278
741
  }
279
- if (childElements.length > 0) {
280
- props.children = childElements.map(convertElement);
742
+ // Zod validation for leaf nodes
743
+ if (!CONTAINER_TYPES.has(nodeType)) {
744
+ validateLeafNode(nodeType, result, errors);
281
745
  }
282
- else if (textContent !== undefined) {
283
- props.children = textContent;
284
- }
285
- return {
286
- type: "component",
287
- name: tagName,
288
- props,
289
- };
746
+ return result;
290
747
  }
291
748
  /**
292
749
  * XML 文字列を POMNode 配列に変換する。
293
750
  *
294
751
  * XML タグは POM ノードタイプにマッピングされ、属性値は Zod スキーマを参照して
295
752
  * 適切な型(number, boolean, array, object)に変換される。
296
- * 組み込みノード以外のタグ名はカスタムコンポーネントとして扱われる。
753
+ * 未知のタグ名が指定された場合はエラーがスローされる。
297
754
  *
298
755
  * @example
299
756
  * ```typescript
@@ -326,7 +783,13 @@ export function parseXml(xmlString) {
326
783
  return [];
327
784
  const rootElement = parsed[0];
328
785
  const rootChildren = (rootElement["__root__"] ?? []);
329
- return rootChildren
786
+ const errors = [];
787
+ const nodes = rootChildren
330
788
  .filter((child) => !isTextNode(child))
331
- .map((child) => convertElement(child));
789
+ .map((child) => convertElement(child, errors))
790
+ .filter((child) => child !== null);
791
+ if (errors.length > 0) {
792
+ throw new ParseXmlError(errors);
793
+ }
794
+ return nodes;
332
795
  }
@@ -3,8 +3,6 @@ type SlidePx = {
3
3
  w: number;
4
4
  h: number;
5
5
  };
6
- export { createTextOptions, convertUnderline, convertStrike, } from "./textOptions.ts";
7
- export { PX_PER_IN, pxToIn, pxToPt } from "./units.ts";
8
6
  /**
9
7
  * PositionedNode ツリーを PptxGenJS スライドに変換する
10
8
  * @param pages PositionedNode ツリーの配列(各要素が1ページ)
@@ -13,4 +11,5 @@ export { PX_PER_IN, pxToIn, pxToPt } from "./units.ts";
13
11
  * @returns PptxGenJS インスタンス
14
12
  */
15
13
  export declare function renderPptx(pages: PositionedNode[], slidePx: SlidePx, master?: SlideMasterOptions): import("pptxgenjs").default;
14
+ export {};
16
15
  //# sourceMappingURL=renderPptx.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"renderPptx.d.ts","sourceRoot":"","sources":["../../src/renderPptx/renderPptx.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAoBrB,KAAK,OAAO,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AACxC,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAqJvD;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,kBAAkB,+BAqK5B"}
1
+ {"version":3,"file":"renderPptx.d.ts","sourceRoot":"","sources":["../../src/renderPptx/renderPptx.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,cAAc,EACd,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAoBrB,KAAK,OAAO,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAqJxC;;;;;;GAMG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,cAAc,EAAE,EACvB,OAAO,EAAE,OAAO,EAChB,MAAM,CAAC,EAAE,kBAAkB,+BAqK5B"}
@@ -10,8 +10,6 @@ import { convertUnderline, convertStrike } from "./textOptions.js";
10
10
  import { getImageData } from "../calcYogaLayout/measureImage.js";
11
11
  import { renderBackgroundAndBorder } from "./utils/backgroundBorder.js";
12
12
  import { renderTextNode, renderImageNode, renderTableNode, renderShapeNode, renderChartNode, renderTimelineNode, renderMatrixNode, renderTreeNode, renderFlowNode, renderProcessArrowNode, renderLineNode, } from "./nodes/index.js";
13
- export { createTextOptions, convertUnderline, convertStrike, } from "./textOptions.js";
14
- export { PX_PER_IN, pxToIn, pxToPt } from "./units.js";
15
13
  const DEFAULT_MASTER_NAME = "POM_MASTER";
16
14
  /**
17
15
  * MasterObject を pptxgenjs の objects 形式に変換する
@@ -19,7 +19,6 @@ type PptxBulletOptions = {
19
19
  numberType?: BulletOptions["numberType"];
20
20
  numberStartAt?: number;
21
21
  };
22
- export declare function createBulletOptions(bullet: boolean | BulletOptions): PptxBulletOptions | boolean;
23
22
  export declare function createTextOptions(node: TextNode): {
24
23
  x: number;
25
24
  y: number;