@hirokisakabe/pom 7.0.0 → 7.2.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 +3 -2
- package/dist/buildPptx.d.ts +1 -0
- package/dist/buildPptx.d.ts.map +1 -1
- package/dist/buildPptx.js +24 -1
- package/dist/diagnostics.d.ts +1 -1
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/parseMasterPptx.d.ts +16 -0
- package/dist/parseMasterPptx.d.ts.map +1 -0
- package/dist/parseMasterPptx.js +152 -0
- package/dist/parseXml/coercionRules.d.ts +18 -0
- package/dist/parseXml/coercionRules.d.ts.map +1 -1
- package/dist/parseXml/coercionRules.js +46 -0
- package/dist/parseXml/parseXml.d.ts.map +1 -1
- package/dist/parseXml/parseXml.js +25 -16
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -36,11 +36,12 @@
|
|
|
36
36
|
## Features
|
|
37
37
|
|
|
38
38
|
- **AI Friendly** — Simple XML structure designed for LLM code generation. Include [llm.txt](./website/public/llm.txt) in your system prompt for XML reference. Also available at `https://pom.pptx.app/llm.txt`.
|
|
39
|
-
- **Declarative** — Describe slides as XML. No imperative API calls needed.
|
|
39
|
+
- **Declarative** — Describe slides as XML. No imperative API calls needed — just data in, PPTX out.
|
|
40
40
|
- **Flexible Layout** — Flexbox-style layout with VStack / HStack, powered by yoga-layout.
|
|
41
|
+
- **Shorthand + Dot Notation** — Layout/style attributes (e.g. `padding`, `margin`, `border`, `fill`, `shadow`) can mix shorthand and dot notation on the same node. Shorthand sets defaults and dot notation overrides specific keys.
|
|
41
42
|
- **Rich Nodes** — 18 built-in node types: charts, flowcharts, tables, timelines, org trees, and more.
|
|
42
43
|
- **Schema-validated** — XML input is validated with Zod schemas at runtime with clear error messages.
|
|
43
|
-
- **PowerPoint Native** —
|
|
44
|
+
- **PowerPoint Native** — Generates real editable PowerPoint shapes — not images. Recipients can modify everything.
|
|
44
45
|
- **Pixel Units** — Intuitive pixel-based sizing (internally converted to inches at 96 DPI).
|
|
45
46
|
- **Master Slide** — Define headers, footers, and page numbers once — applied to all slides automatically.
|
|
46
47
|
- **Accurate Text Measurement** — Text width measured with opentype.js and bundled Noto Sans JP fonts for consistent layout.
|
package/dist/buildPptx.d.ts
CHANGED
package/dist/buildPptx.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"buildPptx.d.ts","sourceRoot":"","sources":["../src/buildPptx.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAG3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"buildPptx.d.ts","sourceRoot":"","sources":["../src/buildPptx.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAG3E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAOnD,OAAO,EAAkB,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEhE,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,OAAO,WAAW,EAAE,OAAO,CAAC;IAClC,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AAED,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,EACnC,OAAO,CAAC,EAAE;IACR,MAAM,CAAC,EAAE,kBAAkB,CAAC;IAC5B,UAAU,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;IACtC,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,GACA,OAAO,CAAC,eAAe,CAAC,CAoD1B"}
|
package/dist/buildPptx.js
CHANGED
|
@@ -3,6 +3,7 @@ import { createBuildContext } from "./buildContext.js";
|
|
|
3
3
|
import { calcYogaLayout } from "./calcYogaLayout/calcYogaLayout.js";
|
|
4
4
|
import { extractLayoutResults } from "./calcYogaLayout/types.js";
|
|
5
5
|
import { DiagnosticsError } from "./diagnostics.js";
|
|
6
|
+
import { parseMasterPptx } from "./parseMasterPptx.js";
|
|
6
7
|
import { parseXml } from "./parseXml/parseXml.js";
|
|
7
8
|
import { renderPptx } from "./renderPptx/renderPptx.js";
|
|
8
9
|
import { freeYogaTree } from "./shared/freeYogaTree.js";
|
|
@@ -29,7 +30,29 @@ export async function buildPptx(xml, slideSize, options) {
|
|
|
29
30
|
freeYogaTree(map);
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
+
// masterPptx から背景を抽出し、master オプションにマージ
|
|
34
|
+
let master = options?.master;
|
|
35
|
+
if (options?.masterPptx) {
|
|
36
|
+
try {
|
|
37
|
+
const bg = await parseMasterPptx(options.masterPptx);
|
|
38
|
+
if (bg) {
|
|
39
|
+
if (master) {
|
|
40
|
+
// 明示的に background が指定されていない場合のみ、masterPptx の背景を使用
|
|
41
|
+
if (!master.background) {
|
|
42
|
+
master = { ...master, background: bg };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
master = { background: bg };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
const message = e instanceof Error ? e.message : "Unknown error parsing masterPptx";
|
|
52
|
+
ctx.diagnostics.add("MASTER_PPTX_PARSE_FAILED", message);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const pptx = await renderPptx(positionedPages, slideSize, ctx, master);
|
|
33
56
|
const diagnostics = ctx.diagnostics.items;
|
|
34
57
|
if (options?.strict && diagnostics.length > 0) {
|
|
35
58
|
throw new DiagnosticsError(diagnostics);
|
package/dist/diagnostics.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type DiagnosticCode = "IMAGE_MEASURE_FAILED" | "IMAGE_NOT_PREFETCHED" | "AUTOFIT_OVERFLOW" | "SCALE_BELOW_THRESHOLD";
|
|
1
|
+
export type DiagnosticCode = "IMAGE_MEASURE_FAILED" | "IMAGE_NOT_PREFETCHED" | "AUTOFIT_OVERFLOW" | "SCALE_BELOW_THRESHOLD" | "MASTER_PPTX_PARSE_FAILED";
|
|
2
2
|
export interface Diagnostic {
|
|
3
3
|
code: DiagnosticCode;
|
|
4
4
|
message: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GACtB,sBAAsB,GACtB,sBAAsB,GACtB,kBAAkB,GAClB,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"diagnostics.d.ts","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GACtB,sBAAsB,GACtB,sBAAsB,GACtB,kBAAkB,GAClB,uBAAuB,GACvB,0BAA0B,CAAC;AAE/B,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBAAa,mBAAmB;IAC9B,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,CAAM;IAElC,GAAG,CAAC,IAAI,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI;CAGjD;AAED,qBAAa,gBAAiB,SAAQ,KAAK;aACb,WAAW,EAAE,UAAU,EAAE;gBAAzB,WAAW,EAAE,UAAU,EAAE;CAOtD"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SlideMasterBackground } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* マスター PPTX のバッファからスライドマスターの背景情報を抽出する。
|
|
4
|
+
*
|
|
5
|
+
* 探索順序:
|
|
6
|
+
* 1. `ppt/slideMasters/slideMaster1.xml` の `p:bg > p:bgPr`
|
|
7
|
+
* 2. 各スライドレイアウト (`ppt/slideLayouts/slideLayoutN.xml`) の `p:bg > p:bgPr`
|
|
8
|
+
*
|
|
9
|
+
* サポートする背景:
|
|
10
|
+
* - 単色塗りつぶし (`a:solidFill` / `a:srgbClr`)
|
|
11
|
+
* - 画像背景 (`a:blipFill` / `a:blip`)
|
|
12
|
+
*
|
|
13
|
+
* @returns 背景情報。背景が設定されていない場合は `undefined`。
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseMasterPptx(pptxBuffer: ArrayBuffer | Uint8Array): Promise<SlideMasterBackground | undefined>;
|
|
16
|
+
//# sourceMappingURL=parseMasterPptx.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parseMasterPptx.d.ts","sourceRoot":"","sources":["../src/parseMasterPptx.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAqHxD;;;;;;;;;;;;GAYG;AACH,wBAAsB,eAAe,CACnC,UAAU,EAAE,WAAW,GAAG,UAAU,GACnC,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC,CAqD5C"}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { XMLParser } from "fast-xml-parser";
|
|
2
|
+
// JSZip は CJS パッケージのため動的 import で読み込む
|
|
3
|
+
async function loadJSZip() {
|
|
4
|
+
const mod = await import("jszip");
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */
|
|
6
|
+
return mod.default ?? mod;
|
|
7
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return */
|
|
8
|
+
}
|
|
9
|
+
const xmlParser = new XMLParser({
|
|
10
|
+
ignoreAttributes: false,
|
|
11
|
+
attributeNamePrefix: "@_",
|
|
12
|
+
});
|
|
13
|
+
/**
|
|
14
|
+
* MIME タイプを拡張子から判定する
|
|
15
|
+
*/
|
|
16
|
+
function mimeTypeFromExt(filePath) {
|
|
17
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
18
|
+
switch (ext) {
|
|
19
|
+
case "png":
|
|
20
|
+
return "image/png";
|
|
21
|
+
case "jpg":
|
|
22
|
+
case "jpeg":
|
|
23
|
+
return "image/jpeg";
|
|
24
|
+
case "gif":
|
|
25
|
+
return "image/gif";
|
|
26
|
+
case "bmp":
|
|
27
|
+
return "image/bmp";
|
|
28
|
+
case "svg":
|
|
29
|
+
return "image/svg+xml";
|
|
30
|
+
case "tiff":
|
|
31
|
+
case "tif":
|
|
32
|
+
return "image/tiff";
|
|
33
|
+
case "webp":
|
|
34
|
+
return "image/webp";
|
|
35
|
+
default:
|
|
36
|
+
return "image/png";
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* rels XML から rId に対応するファイルパスを取得する
|
|
41
|
+
*/
|
|
42
|
+
function resolveRelId(relsXml, rId) {
|
|
43
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
|
|
44
|
+
const parsed = xmlParser.parse(relsXml);
|
|
45
|
+
const relationships = parsed?.Relationships?.Relationship;
|
|
46
|
+
if (!relationships)
|
|
47
|
+
return undefined;
|
|
48
|
+
const rels = Array.isArray(relationships) ? relationships : [relationships];
|
|
49
|
+
for (const rel of rels) {
|
|
50
|
+
if (rel["@_Id"] === rId) {
|
|
51
|
+
return rel["@_Target"];
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
|
|
58
|
+
/**
|
|
59
|
+
* bgPr 要素から背景情報を抽出する
|
|
60
|
+
*/
|
|
61
|
+
async function extractBackgroundFromBgPr(
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
63
|
+
bgPr, zip, relsPath, basePath) {
|
|
64
|
+
if (!bgPr)
|
|
65
|
+
return undefined;
|
|
66
|
+
// 単色塗りつぶし
|
|
67
|
+
const solidFill = bgPr["a:solidFill"];
|
|
68
|
+
if (solidFill) {
|
|
69
|
+
const srgbClr = solidFill["a:srgbClr"];
|
|
70
|
+
if (srgbClr) {
|
|
71
|
+
const color = srgbClr["@_val"] ?? undefined;
|
|
72
|
+
if (color)
|
|
73
|
+
return { color };
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// 画像背景
|
|
77
|
+
const blipFill = bgPr["a:blipFill"];
|
|
78
|
+
if (blipFill) {
|
|
79
|
+
const blip = blipFill["a:blip"];
|
|
80
|
+
const rId = blip?.["@_r:embed"];
|
|
81
|
+
if (!rId)
|
|
82
|
+
return undefined;
|
|
83
|
+
const relsFile = zip.file(relsPath);
|
|
84
|
+
if (!relsFile)
|
|
85
|
+
return undefined;
|
|
86
|
+
const relsXml = await relsFile.async("text");
|
|
87
|
+
const target = resolveRelId(relsXml, rId);
|
|
88
|
+
if (!target)
|
|
89
|
+
return undefined;
|
|
90
|
+
// target は相対パスなので basePath からの相対パスとして解決
|
|
91
|
+
const imagePath = new URL(target, `file:///${basePath}dummy`).pathname.slice(1);
|
|
92
|
+
const imageFile = zip.file(imagePath);
|
|
93
|
+
if (!imageFile)
|
|
94
|
+
return undefined;
|
|
95
|
+
const imageData = await imageFile.async("base64");
|
|
96
|
+
const mimeType = mimeTypeFromExt(imagePath);
|
|
97
|
+
return { data: `data:${mimeType};base64,${imageData}` };
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
|
|
102
|
+
/**
|
|
103
|
+
* マスター PPTX のバッファからスライドマスターの背景情報を抽出する。
|
|
104
|
+
*
|
|
105
|
+
* 探索順序:
|
|
106
|
+
* 1. `ppt/slideMasters/slideMaster1.xml` の `p:bg > p:bgPr`
|
|
107
|
+
* 2. 各スライドレイアウト (`ppt/slideLayouts/slideLayoutN.xml`) の `p:bg > p:bgPr`
|
|
108
|
+
*
|
|
109
|
+
* サポートする背景:
|
|
110
|
+
* - 単色塗りつぶし (`a:solidFill` / `a:srgbClr`)
|
|
111
|
+
* - 画像背景 (`a:blipFill` / `a:blip`)
|
|
112
|
+
*
|
|
113
|
+
* @returns 背景情報。背景が設定されていない場合は `undefined`。
|
|
114
|
+
*/
|
|
115
|
+
export async function parseMasterPptx(pptxBuffer) {
|
|
116
|
+
const JSZip = await loadJSZip();
|
|
117
|
+
const zip = await JSZip.loadAsync(pptxBuffer);
|
|
118
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
|
|
119
|
+
// 1. スライドマスター本体を探索
|
|
120
|
+
const masterFile = zip.file("ppt/slideMasters/slideMaster1.xml");
|
|
121
|
+
if (masterFile) {
|
|
122
|
+
const masterXml = await masterFile.async("text");
|
|
123
|
+
const parsed = xmlParser.parse(masterXml);
|
|
124
|
+
const bgPr = parsed?.["p:sldMaster"]?.["p:cSld"]?.["p:bg"]?.["p:bgPr"];
|
|
125
|
+
const result = await extractBackgroundFromBgPr(bgPr, zip, "ppt/slideMasters/_rels/slideMaster1.xml.rels", "ppt/slideMasters/");
|
|
126
|
+
if (result)
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
// 2. スライドレイアウトを探索
|
|
130
|
+
const layoutFiles = Object.keys(zip.files).filter((f) => f.startsWith("ppt/slideLayouts/slideLayout") && f.endsWith(".xml"));
|
|
131
|
+
// 番号順にソート(数値ソートで slideLayout2 < slideLayout10 の順序を保証)
|
|
132
|
+
layoutFiles.sort((a, b) => {
|
|
133
|
+
const numA = parseInt(a.match(/slideLayout(\d+)\.xml$/)?.[1] ?? "0", 10);
|
|
134
|
+
const numB = parseInt(b.match(/slideLayout(\d+)\.xml$/)?.[1] ?? "0", 10);
|
|
135
|
+
return numA - numB;
|
|
136
|
+
});
|
|
137
|
+
for (const layoutPath of layoutFiles) {
|
|
138
|
+
const layoutFile = zip.file(layoutPath);
|
|
139
|
+
if (!layoutFile)
|
|
140
|
+
continue;
|
|
141
|
+
const layoutXml = await layoutFile.async("text");
|
|
142
|
+
const parsed = xmlParser.parse(layoutXml);
|
|
143
|
+
const bgPr = parsed?.["p:sldLayout"]?.["p:cSld"]?.["p:bg"]?.["p:bgPr"];
|
|
144
|
+
const fileName = layoutPath.split("/").pop();
|
|
145
|
+
const relsPath = `ppt/slideLayouts/_rels/${fileName}.rels`;
|
|
146
|
+
const result = await extractBackgroundFromBgPr(bgPr, zip, relsPath, "ppt/slideLayouts/");
|
|
147
|
+
if (result)
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
150
|
+
/* eslint-enable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access */
|
|
151
|
+
return undefined;
|
|
152
|
+
}
|
|
@@ -30,6 +30,24 @@ export declare function getObjectShapeFromRule(rule: CoercionRule): Record<strin
|
|
|
30
30
|
* endArrow="true" と endArrow.type="triangle" の共存を許可するために使用。
|
|
31
31
|
*/
|
|
32
32
|
export declare function isBooleanObjectUnionRule(rule: CoercionRule): boolean;
|
|
33
|
+
type ResolvedMixedNotationShorthand = {
|
|
34
|
+
mode: "merge";
|
|
35
|
+
value: Record<string, unknown>;
|
|
36
|
+
} | {
|
|
37
|
+
mode: "ignore";
|
|
38
|
+
} | {
|
|
39
|
+
mode: "conflict";
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* 同一属性で shorthand と dot notation を併用したときに、
|
|
43
|
+
* shorthand 側をどのように扱うかを解決する。
|
|
44
|
+
*
|
|
45
|
+
* - merge: shorthand をオブジェクト化して dot notation 側で上書き
|
|
46
|
+
* - ignore: boolean shorthand を無視して dot notation を優先
|
|
47
|
+
* - conflict: 併用不可(従来どおりエラー)
|
|
48
|
+
*/
|
|
49
|
+
export declare function resolveMixedNotationShorthand(value: string, rule: CoercionRule): ResolvedMixedNotationShorthand;
|
|
33
50
|
export declare const NODE_COERCION_MAP: Record<string, Record<string, CoercionRule>>;
|
|
34
51
|
export declare const CHILD_ELEMENT_COERCION_MAP: Record<string, Record<string, CoercionRule>>;
|
|
52
|
+
export {};
|
|
35
53
|
//# sourceMappingURL=coercionRules.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"coercionRules.d.ts","sourceRoot":"","sources":["../../src/parseXml/coercionRules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,MAAM,GACN;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CAAE,CAAC;AAI5D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,GACjB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAyD1C;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,EAAE,GACtB,OAAO,CAuCT;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAarD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,GACjB,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS,CAY1C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAQpE;AA8HD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CA2I1E,CAAC;AAGF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAC7C,MAAM,EACN,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAgF7B,CAAC"}
|
|
1
|
+
{"version":3,"file":"coercionRules.d.ts","sourceRoot":"","sources":["../../src/parseXml/coercionRules.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,MAAM,YAAY,GACpB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,MAAM,GACN;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAA;CAAE,GACrD;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,YAAY,EAAE,CAAA;CAAE,GAC1C;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CAAE,CAAC;AAI5D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,GACjB;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAyD1C;AAED,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,YAAY,EAAE,GACtB,OAAO,CAuCT;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAarD;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,YAAY,GACjB,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,GAAG,SAAS,CAY1C;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,YAAY,GAAG,OAAO,CAQpE;AAED,KAAK,8BAA8B,GAC/B;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,GAClB;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC;AAiBzB;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,YAAY,GACjB,8BAA8B,CA+BhC;AA8HD,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CA2I1E,CAAC;AAGF,eAAO,MAAM,0BAA0B,EAAE,MAAM,CAC7C,MAAM,EACN,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAgF7B,CAAC"}
|
|
@@ -143,6 +143,52 @@ export function isBooleanObjectUnionRule(rule) {
|
|
|
143
143
|
const hasObject = rule.options.some((opt) => typeof opt === "object" && opt.type === "object");
|
|
144
144
|
return hasBoolean && hasObject;
|
|
145
145
|
}
|
|
146
|
+
function isPlainObject(value) {
|
|
147
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
148
|
+
}
|
|
149
|
+
function isDirectionalBoxShape(shape) {
|
|
150
|
+
const keys = Object.keys(shape).sort();
|
|
151
|
+
return (keys.length === 4 &&
|
|
152
|
+
keys[0] === "bottom" &&
|
|
153
|
+
keys[1] === "left" &&
|
|
154
|
+
keys[2] === "right" &&
|
|
155
|
+
keys[3] === "top");
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 同一属性で shorthand と dot notation を併用したときに、
|
|
159
|
+
* shorthand 側をどのように扱うかを解決する。
|
|
160
|
+
*
|
|
161
|
+
* - merge: shorthand をオブジェクト化して dot notation 側で上書き
|
|
162
|
+
* - ignore: boolean shorthand を無視して dot notation を優先
|
|
163
|
+
* - conflict: 併用不可(従来どおりエラー)
|
|
164
|
+
*/
|
|
165
|
+
export function resolveMixedNotationShorthand(value, rule) {
|
|
166
|
+
const objectShape = getObjectShapeFromRule(rule);
|
|
167
|
+
if (!objectShape)
|
|
168
|
+
return { mode: "conflict" };
|
|
169
|
+
if (isBooleanObjectUnionRule(rule) &&
|
|
170
|
+
(value === "true" || value === "false")) {
|
|
171
|
+
return { mode: "ignore" };
|
|
172
|
+
}
|
|
173
|
+
const coerced = coerceWithRule(value, rule);
|
|
174
|
+
if (coerced.error !== null)
|
|
175
|
+
return { mode: "conflict" };
|
|
176
|
+
if (isPlainObject(coerced.value)) {
|
|
177
|
+
return { mode: "merge", value: coerced.value };
|
|
178
|
+
}
|
|
179
|
+
if (typeof coerced.value === "number" && isDirectionalBoxShape(objectShape)) {
|
|
180
|
+
return {
|
|
181
|
+
mode: "merge",
|
|
182
|
+
value: {
|
|
183
|
+
top: coerced.value,
|
|
184
|
+
right: coerced.value,
|
|
185
|
+
bottom: coerced.value,
|
|
186
|
+
left: coerced.value,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
return { mode: "conflict" };
|
|
191
|
+
}
|
|
146
192
|
// ===== 共通変換ルール =====
|
|
147
193
|
const LENGTH_RULE = {
|
|
148
194
|
type: "union",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"parseXml.d.ts","sourceRoot":"","sources":["../../src/parseXml/parseXml.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAgBb,MAAM,aAAa,CAAC;AAYrB,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAgB,MAAM,EAAE,MAAM,EAAE,CAAC;gBACrB,MAAM,EAAE,MAAM,EAAE;CAM7B;AAGD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmB9C,CAAC;
|
|
1
|
+
{"version":3,"file":"parseXml.d.ts","sourceRoot":"","sources":["../../src/parseXml/parseXml.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,KAAK,OAAO,EAgBb,MAAM,aAAa,CAAC;AAYrB,qBAAa,aAAc,SAAQ,KAAK;IACtC,SAAgB,MAAM,EAAE,MAAM,EAAE,CAAC;gBACrB,MAAM,EAAE,MAAM,EAAE;CAM7B;AAGD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAmB9C,CAAC;AAgpCF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,EAAE,CAiCrD"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { XMLBuilder, XMLParser } from "fast-xml-parser";
|
|
2
2
|
import { textNodeSchema, ulNodeSchema, olNodeSchema, imageNodeSchema, tableNodeSchema, shapeNodeSchema, chartNodeSchema, timelineNodeSchema, matrixNodeSchema, treeNodeSchema, flowNodeSchema, processArrowNodeSchema, pyramidNodeSchema, lineNodeSchema, iconNodeSchema, } from "../types.js";
|
|
3
|
-
import { NODE_COERCION_MAP, CHILD_ELEMENT_COERCION_MAP, coerceWithRule, coerceFallback, getObjectShapeFromRule,
|
|
3
|
+
import { NODE_COERCION_MAP, CHILD_ELEMENT_COERCION_MAP, coerceWithRule, coerceFallback, getObjectShapeFromRule, resolveMixedNotationShorthand, } from "./coercionRules.js";
|
|
4
4
|
// ===== ParseXmlError =====
|
|
5
5
|
export class ParseXmlError extends Error {
|
|
6
6
|
errors;
|
|
@@ -381,14 +381,18 @@ function coerceChildAttrs(parentTagName, tagName, attrs, errors) {
|
|
|
381
381
|
// Process regular attributes
|
|
382
382
|
for (const [key, value] of Object.entries(regularAttrs)) {
|
|
383
383
|
if (key in dotGroups) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
(
|
|
390
|
-
|
|
391
|
-
|
|
384
|
+
if (rules && rules[key]) {
|
|
385
|
+
const resolved = resolveMixedNotationShorthand(value, rules[key]);
|
|
386
|
+
if (resolved.mode === "ignore") {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
if (resolved.mode === "merge") {
|
|
390
|
+
result[key] = {
|
|
391
|
+
...resolved.value,
|
|
392
|
+
...result[key],
|
|
393
|
+
};
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
392
396
|
}
|
|
393
397
|
errors.push(`<${parentTagName}>.<${tagName}>: Attribute "${key}" conflicts with dot-notation attributes. Use one or the other, not both`);
|
|
394
398
|
continue;
|
|
@@ -765,14 +769,19 @@ function convertPomNode(nodeType, tagName, attrs, childElements, textContent, er
|
|
|
765
769
|
continue;
|
|
766
770
|
// Conflict check: dot-notation and regular attribute for the same key
|
|
767
771
|
if (key in dotGroups) {
|
|
768
|
-
// When the rule is a union of boolean and object (e.g., endArrow),
|
|
769
|
-
// allow boolean shorthand to coexist with dot-notation by ignoring the boolean value.
|
|
770
772
|
const ruleForConflict = getCoercionRule(nodeType, key);
|
|
771
|
-
if (ruleForConflict
|
|
772
|
-
|
|
773
|
-
(
|
|
774
|
-
|
|
775
|
-
|
|
773
|
+
if (ruleForConflict) {
|
|
774
|
+
const resolved = resolveMixedNotationShorthand(value, ruleForConflict);
|
|
775
|
+
if (resolved.mode === "ignore") {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
if (resolved.mode === "merge") {
|
|
779
|
+
result[key] = {
|
|
780
|
+
...resolved.value,
|
|
781
|
+
...result[key],
|
|
782
|
+
};
|
|
783
|
+
continue;
|
|
784
|
+
}
|
|
776
785
|
}
|
|
777
786
|
errors.push(`<${tagName}>: Attribute "${key}" conflicts with dot-notation attributes (e.g., "${key}.xxx"). Use one or the other, not both`);
|
|
778
787
|
continue;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hirokisakabe/pom",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.2.0",
|
|
4
4
|
"description": "AI-friendly PowerPoint generation with a Flexbox layout engine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -46,22 +46,23 @@
|
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@size-limit/file": "^12.0.1",
|
|
49
|
-
"@types/node": "^25.5.
|
|
49
|
+
"@types/node": "^25.5.2",
|
|
50
50
|
"@types/opentype.js": "^1.3.8",
|
|
51
51
|
"@types/pngjs": "6.0.5",
|
|
52
|
-
"@vitest/coverage-v8": "^4.1.
|
|
53
|
-
"@vitest/ui": "^4.1.
|
|
54
|
-
"lucide-static": "^
|
|
52
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
53
|
+
"@vitest/ui": "^4.1.2",
|
|
54
|
+
"lucide-static": "^1.7.0",
|
|
55
55
|
"pixelmatch": "7.1.0",
|
|
56
56
|
"pngjs": "7.0.0",
|
|
57
57
|
"size-limit": "^12.0.1",
|
|
58
58
|
"tsx": "4.21.0",
|
|
59
|
-
"typescript-eslint": "^8.
|
|
59
|
+
"typescript-eslint": "^8.58.0"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@resvg/resvg-wasm": "^2.6.2",
|
|
63
63
|
"fast-xml-parser": "^5.3.7",
|
|
64
64
|
"image-size": "2.0.2",
|
|
65
|
+
"jszip": "^3.10.1",
|
|
65
66
|
"opentype.js": "^1.3.4",
|
|
66
67
|
"pptxgenjs": "4.0.1",
|
|
67
68
|
"yoga-layout": "3.2.1",
|