@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.
- package/README.md +22 -177
- package/dist/buildPptx.d.ts +2 -2
- package/dist/buildPptx.d.ts.map +1 -1
- package/dist/buildPptx.js +3 -1
- package/dist/calcYogaLayout/fontLoader.d.ts +0 -7
- package/dist/calcYogaLayout/fontLoader.d.ts.map +1 -1
- package/dist/calcYogaLayout/fontLoader.js +1 -1
- package/dist/calcYogaLayout/measureText.d.ts +0 -4
- package/dist/calcYogaLayout/measureText.d.ts.map +1 -1
- package/dist/calcYogaLayout/measureText.js +0 -6
- package/dist/index.d.ts +2 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -4
- package/dist/inputSchema.d.ts +3 -438
- package/dist/inputSchema.d.ts.map +1 -1
- package/dist/inputSchema.js +6 -138
- package/dist/parseXml.d.ts +5 -1
- package/dist/parseXml.d.ts.map +1 -1
- package/dist/parseXml.js +502 -39
- package/dist/renderPptx/renderPptx.d.ts +1 -2
- package/dist/renderPptx/renderPptx.d.ts.map +1 -1
- package/dist/renderPptx/renderPptx.js +0 -2
- package/dist/renderPptx/textOptions.d.ts +0 -1
- package/dist/renderPptx/textOptions.d.ts.map +1 -1
- package/dist/renderPptx/textOptions.js +1 -1
- package/dist/renderPptx/types.d.ts +0 -2
- package/dist/renderPptx/types.d.ts.map +1 -1
- package/dist/table/utils.d.ts +0 -2
- package/dist/table/utils.d.ts.map +1 -1
- package/dist/table/utils.js +2 -2
- package/dist/types.d.ts +13 -121
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +21 -27
- package/package.json +3 -6
- package/dist/component.d.ts +0 -90
- package/dist/component.d.ts.map +0 -1
- package/dist/component.js +0 -123
- package/dist/renderPptx/nodes/box.d.ts +0 -2
- package/dist/renderPptx/nodes/box.d.ts.map +0 -1
- package/dist/renderPptx/nodes/box.js +0 -3
- package/dist/renderPptx/utils/index.d.ts +0 -6
- package/dist/renderPptx/utils/index.d.ts.map +0 -1
- package/dist/renderPptx/utils/index.js +0 -3
- package/dist/renderPptx/utils/shapeDrawing.d.ts +0 -27
- package/dist/renderPptx/utils/shapeDrawing.d.ts.map +0 -1
- package/dist/renderPptx/utils/shapeDrawing.js +0 -36
- package/dist/renderPptx/utils/textDrawing.d.ts +0 -25
- package/dist/renderPptx/utils/textDrawing.d.ts.map +0 -1
- package/dist/renderPptx/utils/textDrawing.js +0 -25
- package/dist/schema.d.ts +0 -23
- package/dist/schema.d.ts.map +0 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
280
|
-
|
|
742
|
+
// Zod validation for leaf nodes
|
|
743
|
+
if (!CONTAINER_TYPES.has(nodeType)) {
|
|
744
|
+
validateLeafNode(nodeType, result, errors);
|
|
281
745
|
}
|
|
282
|
-
|
|
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
|
-
|
|
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;
|
|
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;
|