@bendyline/squisq-formats 1.4.1 → 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 +48 -2
- package/dist/{chunk-WWBIKRNP.js → chunk-2CSZJEAL.js} +13 -11
- package/dist/chunk-2CSZJEAL.js.map +1 -0
- package/dist/{chunk-RTS5XBZ7.js → chunk-2EFDWKC6.js} +262 -84
- package/dist/chunk-2EFDWKC6.js.map +1 -0
- package/dist/chunk-2PRVLAU3.js +740 -0
- package/dist/chunk-2PRVLAU3.js.map +1 -0
- package/dist/{chunk-SHLPSADL.js → chunk-4HJ6AK2Q.js} +54 -193
- package/dist/chunk-4HJ6AK2Q.js.map +1 -0
- package/dist/{chunk-5LSSWZPU.js → chunk-4VUWTSGM.js} +72 -25
- package/dist/chunk-4VUWTSGM.js.map +1 -0
- package/dist/{chunk-YESS7VY3.js → chunk-CVNVD7VH.js} +154 -39
- package/dist/chunk-CVNVD7VH.js.map +1 -0
- package/dist/chunk-CX3LH47J.js +99 -0
- package/dist/chunk-CX3LH47J.js.map +1 -0
- package/dist/{chunk-D7IFPWNZ.js → chunk-GVS2XXV6.js} +27 -26
- package/dist/chunk-GVS2XXV6.js.map +1 -0
- package/dist/chunk-H7T5DXUU.js +479 -0
- package/dist/chunk-H7T5DXUU.js.map +1 -0
- package/dist/{chunk-EHLGMSTE.js → chunk-HBSCRS5P.js} +10 -8
- package/dist/chunk-HBSCRS5P.js.map +1 -0
- package/dist/{chunk-46FMDOWZ.js → chunk-LXYLOOST.js} +49 -17
- package/dist/chunk-LXYLOOST.js.map +1 -0
- package/dist/{chunk-MJGRI6XR.js → chunk-MLX2BOJC.js} +12 -7
- package/dist/chunk-MLX2BOJC.js.map +1 -0
- package/dist/chunk-NKAJPJ4G.js +106 -0
- package/dist/chunk-NKAJPJ4G.js.map +1 -0
- package/dist/{chunk-DQAZR57U.js → chunk-O3GVVND4.js} +1 -1
- package/dist/chunk-ODL3SSPT.js +18 -0
- package/dist/chunk-ODL3SSPT.js.map +1 -0
- package/dist/chunk-X4USWOQ2.js +131 -0
- package/dist/chunk-X4USWOQ2.js.map +1 -0
- package/dist/chunk-XTLQXR4U.js +195 -0
- package/dist/chunk-XTLQXR4U.js.map +1 -0
- package/dist/chunk-YRT7GQ5Y.js +28 -0
- package/dist/chunk-YRT7GQ5Y.js.map +1 -0
- package/dist/chunk-ZZ7D4DN6.js +97 -0
- package/dist/chunk-ZZ7D4DN6.js.map +1 -0
- package/dist/container/index.d.ts +6 -2
- package/dist/container/index.js +15 -17
- package/dist/container/index.js.map +1 -1
- package/dist/docx/index.d.ts +6 -2
- package/dist/docx/index.js +5 -3
- package/dist/epub/index.d.ts +3 -1
- package/dist/epub/index.js +3 -3
- package/dist/export-Djev9BLU.d.ts +69 -0
- package/dist/extract-LNOA7DBK.js +15 -0
- package/dist/extract-LNOA7DBK.js.map +1 -0
- package/dist/html/index.d.ts +15 -85
- package/dist/html/index.js +3 -3
- package/dist/images-7FBWPKE3.js +7 -0
- package/dist/images-7FBWPKE3.js.map +1 -0
- package/dist/import-DTkDxHmZ.d.ts +84 -0
- package/dist/import-Yivc3kZM.d.ts +136 -0
- package/dist/index.d.ts +13 -4
- package/dist/index.js +30 -11
- package/dist/infer/index.d.ts +138 -0
- package/dist/infer/index.js +28 -0
- package/dist/infer/index.js.map +1 -0
- package/dist/layouts-7OUUVAGU.js +12 -0
- package/dist/layouts-7OUUVAGU.js.map +1 -0
- package/dist/layouts-CZ0bdsNL.d.ts +150 -0
- package/dist/mapTheme-IR27S6IV.js +11 -0
- package/dist/mapTheme-IR27S6IV.js.map +1 -0
- package/dist/ooxml/index.d.ts +21 -127
- package/dist/ooxml/index.js +31 -9
- package/dist/pdf/index.d.ts +3 -1
- package/dist/pdf/index.js +1 -1
- package/dist/pptx/index.d.ts +8 -108
- package/dist/pptx/index.js +13 -4
- package/dist/reader-Gof6OrJX.d.ts +140 -0
- package/dist/registry/index.d.ts +46 -8
- package/dist/registry/index.js +5 -2
- package/dist/themeReader-DPw4J5Ss.d.ts +56 -0
- package/dist/xlsx/index.d.ts +6 -66
- package/dist/xlsx/index.js +4 -2
- package/dist/zipLimits-bGYMS9cI.d.ts +29 -0
- package/package.json +8 -2
- package/src/__tests__/container.test.ts +41 -0
- package/src/__tests__/convert.test.ts +167 -0
- package/src/__tests__/epub.test.ts +102 -1
- package/src/__tests__/html.test.ts +62 -0
- package/src/__tests__/inferTheme.test.ts +135 -0
- package/src/__tests__/lossyWarnings.test.ts +41 -0
- package/src/__tests__/ooxml.test.ts +59 -2
- package/src/__tests__/ooxmlThemeReader.test.ts +92 -0
- package/src/__tests__/plainHtml.test.ts +15 -1
- package/src/__tests__/plainHtmlBundle.test.ts +18 -0
- package/src/__tests__/pptxImport.test.ts +30 -0
- package/src/__tests__/pptxInferFixtures.ts +314 -0
- package/src/__tests__/pptxLayoutInfer.test.ts +395 -0
- package/src/__tests__/zipSafety.test.ts +317 -0
- package/src/container/index.ts +20 -29
- package/src/docx/export.ts +4 -2
- package/src/docx/import.ts +4 -3
- package/src/epub/export.ts +94 -25
- package/src/html/docsHtmlBundle.ts +6 -2
- package/src/html/htmlTemplate.ts +31 -6
- package/src/html/imageUtils.ts +14 -5
- package/src/html/index.ts +12 -1
- package/src/html/plainHtml.ts +7 -3
- package/src/html/plainHtmlBundle.ts +11 -9
- package/src/index.ts +20 -0
- package/src/infer/extract.ts +127 -0
- package/src/infer/index.ts +168 -0
- package/src/infer/mapTheme.ts +176 -0
- package/src/infer/types.ts +27 -0
- package/src/ooxml/index.ts +14 -0
- package/src/ooxml/readUtils.ts +44 -0
- package/src/ooxml/reader.ts +87 -28
- package/src/ooxml/themeReader.ts +197 -0
- package/src/ooxml/types.ts +8 -9
- package/src/ooxml/writer.ts +28 -10
- package/src/pdf/export.ts +4 -2
- package/src/pdf/import.ts +40 -34
- package/src/pptx/export.ts +4 -2
- package/src/pptx/import.ts +214 -32
- package/src/pptx/index.ts +14 -0
- package/src/pptx/layouts.ts +1168 -0
- package/src/registry/convert.ts +54 -23
- package/src/registry/defaultFormats.ts +131 -44
- package/src/registry/index.ts +3 -0
- package/src/registry/types.ts +52 -6
- package/src/shared/boundedZipArchive.ts +342 -0
- package/src/shared/fidelity.ts +130 -0
- package/src/shared/zipEntryCount.ts +97 -0
- package/src/shared/zipLimits.ts +286 -0
- package/src/shared/zipSafety.ts +19 -0
- package/src/xlsx/import.ts +3 -2
- package/dist/chunk-46FMDOWZ.js.map +0 -1
- package/dist/chunk-5LSSWZPU.js.map +0 -1
- package/dist/chunk-D7IFPWNZ.js.map +0 -1
- package/dist/chunk-EHLGMSTE.js.map +0 -1
- package/dist/chunk-MJGRI6XR.js.map +0 -1
- package/dist/chunk-RTS5XBZ7.js.map +0 -1
- package/dist/chunk-SHLPSADL.js.map +0 -1
- package/dist/chunk-WWBIKRNP.js.map +0 -1
- package/dist/chunk-YESS7VY3.js.map +0 -1
- /package/dist/{chunk-DQAZR57U.js.map → chunk-O3GVVND4.js.map} +0 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// src/infer/mapTheme.ts
|
|
2
|
+
import {
|
|
3
|
+
accentToColorScheme,
|
|
4
|
+
compileTheme,
|
|
5
|
+
contrastRatio,
|
|
6
|
+
isHex,
|
|
7
|
+
matchFontFamily,
|
|
8
|
+
relativeLuminance
|
|
9
|
+
} from "@bendyline/squisq/schemas";
|
|
10
|
+
var FALLBACK_PRIMARY = "#3182ce";
|
|
11
|
+
var MIN_ACCENT_CONTRAST = 1.3;
|
|
12
|
+
var ACCENT_SLOTS = ["accent1", "accent2", "accent3", "accent4", "accent5", "accent6"];
|
|
13
|
+
var COMPANION_SLOT = { lt1: "lt2", dk1: "dk2", lt2: "lt1", dk2: "dk1" };
|
|
14
|
+
function slugify(name) {
|
|
15
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
16
|
+
return slug || "imported";
|
|
17
|
+
}
|
|
18
|
+
function extractedThemeToPartial(extraction, opts = {}) {
|
|
19
|
+
const warnings = [];
|
|
20
|
+
const { colors, colorMap, fonts } = extraction;
|
|
21
|
+
const name = opts.nameHint?.trim() || extraction.themeName || "Imported Theme";
|
|
22
|
+
const partial = {
|
|
23
|
+
id: `custom-${slugify(name)}`,
|
|
24
|
+
name,
|
|
25
|
+
description: `Imported from a ${extraction.sourceFormat} file${extraction.themeName ? ` (theme "${extraction.themeName}")` : ""}.`
|
|
26
|
+
};
|
|
27
|
+
if (colors) {
|
|
28
|
+
const primary = colors.accent1 ?? colors.accent2;
|
|
29
|
+
if (!primary) {
|
|
30
|
+
warnings.push("theme: no usable accent colors; primary defaults to Squisq blue");
|
|
31
|
+
}
|
|
32
|
+
let background = colors[colorMap.bg1];
|
|
33
|
+
let text = colors[colorMap.tx1];
|
|
34
|
+
if (background && text && Math.abs(relativeLuminance(background) - relativeLuminance(text)) < 0.2) {
|
|
35
|
+
warnings.push("theme: background and text colors are too close; deriving surfaces instead");
|
|
36
|
+
background = void 0;
|
|
37
|
+
text = void 0;
|
|
38
|
+
}
|
|
39
|
+
const seeds = {
|
|
40
|
+
primary: primary ?? FALLBACK_PRIMARY,
|
|
41
|
+
...colors.accent2 ? { secondary: colors.accent2 } : {},
|
|
42
|
+
...colors.accent3 ? { accent: colors.accent3 } : {},
|
|
43
|
+
...background ? { background } : {},
|
|
44
|
+
...text ? { text } : {}
|
|
45
|
+
};
|
|
46
|
+
partial.seedColors = seeds;
|
|
47
|
+
const explicitColors = {};
|
|
48
|
+
const companion = background ? colors[COMPANION_SLOT[colorMap.bg1]] : void 0;
|
|
49
|
+
if (background && companion && companion !== background) {
|
|
50
|
+
explicitColors.backgroundLight = companion;
|
|
51
|
+
}
|
|
52
|
+
if (colors.hlink && isHex(colors.hlink)) {
|
|
53
|
+
explicitColors.highlight = colors.hlink;
|
|
54
|
+
}
|
|
55
|
+
if (Object.keys(explicitColors).length > 0) partial.colors = explicitColors;
|
|
56
|
+
const schemes = {};
|
|
57
|
+
const seenHexes = /* @__PURE__ */ new Set();
|
|
58
|
+
const schemeBackground = background ?? colors[colorMap.bg1];
|
|
59
|
+
for (const slot of ACCENT_SLOTS) {
|
|
60
|
+
const hex = colors[slot];
|
|
61
|
+
if (!hex || seenHexes.has(hex)) continue;
|
|
62
|
+
if (schemeBackground && contrastRatio(hex, schemeBackground) < MIN_ACCENT_CONTRAST) {
|
|
63
|
+
warnings.push(`theme: ${slot} is too close to the background; dropped from accents`);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
seenHexes.add(hex);
|
|
67
|
+
schemes[slot] = accentToColorScheme(hex);
|
|
68
|
+
}
|
|
69
|
+
if (Object.keys(schemes).length > 0) partial.colorSchemes = schemes;
|
|
70
|
+
}
|
|
71
|
+
if (fonts?.majorLatin || fonts?.minorLatin) {
|
|
72
|
+
partial.typography = {
|
|
73
|
+
...fonts.majorLatin ? { titleFont: matchFontFamily(fonts.majorLatin) } : {},
|
|
74
|
+
...fonts.minorLatin ? { bodyFont: matchFontFamily(fonts.minorLatin) } : {}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
return { partial, warnings };
|
|
78
|
+
}
|
|
79
|
+
function compileExtractedTheme(extraction, opts = {}) {
|
|
80
|
+
const { partial, warnings } = extractedThemeToPartial(extraction, opts);
|
|
81
|
+
return { theme: compileTheme(partial), warnings };
|
|
82
|
+
}
|
|
83
|
+
function colorHintsFromExtraction(extraction) {
|
|
84
|
+
const { colors, colorMap } = extraction;
|
|
85
|
+
if (!colors) return {};
|
|
86
|
+
const schemeColors = {};
|
|
87
|
+
for (const [slot, hex] of Object.entries(colors)) {
|
|
88
|
+
if (typeof hex === "string") schemeColors[slot] = hex;
|
|
89
|
+
}
|
|
90
|
+
const background = colors[colorMap.bg1];
|
|
91
|
+
const text = colors[colorMap.tx1];
|
|
92
|
+
if (background) schemeColors["bg1"] = background;
|
|
93
|
+
if (text) schemeColors["tx1"] = text;
|
|
94
|
+
return {
|
|
95
|
+
...background ? { background } : {},
|
|
96
|
+
...text ? { text } : {},
|
|
97
|
+
schemeColors
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export {
|
|
102
|
+
extractedThemeToPartial,
|
|
103
|
+
compileExtractedTheme,
|
|
104
|
+
colorHintsFromExtraction
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=chunk-NKAJPJ4G.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/infer/mapTheme.ts"],"sourcesContent":["/**\n * Map a raw OOXML theme extraction onto a compiled Squisq `Theme`.\n *\n * Only a handful of seeds are taken from the file (lead accents, background,\n * text, major/minor fonts); `compileTheme` derives the rest of the palette\n * via OKLCh so the result is always a complete, validated theme that stays\n * re-editable in the customizer (seeds are recorded on `Theme.seedColors`).\n */\n\nimport type {\n DeepPartial,\n Theme,\n ThemeColorScheme,\n ThemeSeedColors,\n} from '@bendyline/squisq/schemas';\nimport {\n accentToColorScheme,\n compileTheme,\n contrastRatio,\n isHex,\n matchFontFamily,\n relativeLuminance,\n} from '@bendyline/squisq/schemas';\nimport type { PptxColorHints } from '../pptx/layouts.js';\nimport type { ExtractedFileTheme } from './types.js';\n\nexport interface CompileExtractedOptions {\n /** Preferred display name for the compiled theme (e.g. the file's basename). */\n nameHint?: string;\n}\n\nexport interface MappedThemePartial {\n partial: DeepPartial<Theme>;\n warnings: string[];\n}\n\n/** Neutral primary used when a theme resolves no usable accent slots. */\nconst FALLBACK_PRIMARY = '#3182ce';\n\n/** Accents closer to the background than this contrast are dropped (near-invisible). */\nconst MIN_ACCENT_CONTRAST = 1.3;\n\nconst ACCENT_SLOTS = ['accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6'] as const;\n\n/** Companion slot providing `backgroundLight` for each background slot. */\nconst COMPANION_SLOT = { lt1: 'lt2', dk1: 'dk2', lt2: 'lt1', dk2: 'dk1' } as const;\n\nfunction slugify(name: string): string {\n const slug = name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '');\n return slug || 'imported';\n}\n\n/**\n * Build the `DeepPartial<Theme>` for an extraction. Exported for tests; most\n * callers want {@link compileExtractedTheme}.\n */\nexport function extractedThemeToPartial(\n extraction: ExtractedFileTheme,\n opts: CompileExtractedOptions = {},\n): MappedThemePartial {\n const warnings: string[] = [];\n const { colors, colorMap, fonts } = extraction;\n\n const name = opts.nameHint?.trim() || extraction.themeName || 'Imported Theme';\n const partial: DeepPartial<Theme> = {\n id: `custom-${slugify(name)}`,\n name,\n description: `Imported from a ${extraction.sourceFormat} file${\n extraction.themeName ? ` (theme \"${extraction.themeName}\")` : ''\n }.`,\n };\n\n if (colors) {\n const primary = colors.accent1 ?? colors.accent2;\n if (!primary) {\n warnings.push('theme: no usable accent colors; primary defaults to Squisq blue');\n }\n\n let background: string | undefined = colors[colorMap.bg1];\n let text: string | undefined = colors[colorMap.tx1];\n // Pathological color maps can land background and text on similar\n // surfaces; when they don't contrast, let the compiler derive both\n // from the primary instead of shipping unreadable seeds.\n if (\n background &&\n text &&\n Math.abs(relativeLuminance(background) - relativeLuminance(text)) < 0.2\n ) {\n warnings.push('theme: background and text colors are too close; deriving surfaces instead');\n background = undefined;\n text = undefined;\n }\n\n const seeds: ThemeSeedColors = {\n primary: primary ?? FALLBACK_PRIMARY,\n ...(colors.accent2 ? { secondary: colors.accent2 } : {}),\n ...(colors.accent3 ? { accent: colors.accent3 } : {}),\n ...(background ? { background } : {}),\n ...(text ? { text } : {}),\n };\n partial.seedColors = seeds;\n\n const explicitColors: DeepPartial<Theme>['colors'] = {};\n const companion = background ? colors[COMPANION_SLOT[colorMap.bg1]] : undefined;\n if (background && companion && companion !== background) {\n explicitColors.backgroundLight = companion;\n }\n if (colors.hlink && isHex(colors.hlink)) {\n explicitColors.highlight = colors.hlink;\n }\n if (Object.keys(explicitColors).length > 0) partial.colors = explicitColors;\n\n const schemes: Record<string, ThemeColorScheme> = {};\n const seenHexes = new Set<string>();\n const schemeBackground = background ?? colors[colorMap.bg1];\n for (const slot of ACCENT_SLOTS) {\n const hex = colors[slot];\n if (!hex || seenHexes.has(hex)) continue;\n if (schemeBackground && contrastRatio(hex, schemeBackground) < MIN_ACCENT_CONTRAST) {\n warnings.push(`theme: ${slot} is too close to the background; dropped from accents`);\n continue;\n }\n seenHexes.add(hex);\n schemes[slot] = accentToColorScheme(hex);\n }\n if (Object.keys(schemes).length > 0) partial.colorSchemes = schemes;\n }\n\n if (fonts?.majorLatin || fonts?.minorLatin) {\n partial.typography = {\n ...(fonts.majorLatin ? { titleFont: matchFontFamily(fonts.majorLatin) } : {}),\n ...(fonts.minorLatin ? { bodyFont: matchFontFamily(fonts.minorLatin) } : {}),\n };\n }\n\n return { partial, warnings };\n}\n\n/**\n * Compile an extraction into a full validated Squisq theme. Mapping warnings\n * (dropped accents, low-contrast surfaces) are returned alongside; extraction\n * warnings stay on `extraction.warnings`.\n */\nexport function compileExtractedTheme(\n extraction: ExtractedFileTheme,\n opts: CompileExtractedOptions = {},\n): { theme: Theme; warnings: string[] } {\n const { partial, warnings } = extractedThemeToPartial(extraction, opts);\n return { theme: compileTheme(partial), warnings };\n}\n\n/**\n * Build the color hints the PPTX layout generator uses to resolve scheme\n * color references (`a:schemeClr val=\"accent1\"`, `bg1`, `tx1`, …) on\n * decorative shapes.\n */\nexport function colorHintsFromExtraction(extraction: ExtractedFileTheme): PptxColorHints {\n const { colors, colorMap } = extraction;\n if (!colors) return {};\n const schemeColors: Record<string, string> = {};\n for (const [slot, hex] of Object.entries(colors)) {\n if (typeof hex === 'string') schemeColors[slot] = hex;\n }\n const background = colors[colorMap.bg1];\n const text = colors[colorMap.tx1];\n if (background) schemeColors['bg1'] = background;\n if (text) schemeColors['tx1'] = text;\n return {\n ...(background ? { background } : {}),\n ...(text ? { text } : {}),\n schemeColors,\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/registry/errors.ts
|
|
2
|
+
var ConversionError = class extends Error {
|
|
3
|
+
constructor(code, message, opts = {}) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.name = "ConversionError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.format = opts.format;
|
|
8
|
+
this.hint = opts.hint;
|
|
9
|
+
if (opts.cause !== void 0) {
|
|
10
|
+
this.cause = opts.cause;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
ConversionError
|
|
17
|
+
};
|
|
18
|
+
//# sourceMappingURL=chunk-ODL3SSPT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/registry/errors.ts"],"sourcesContent":["/**\n * Structured error for the format registry / `convert()` pipeline.\n *\n * Every failure path in the registry throws a `ConversionError` with a stable\n * machine-readable `code`, so callers can branch on the failure kind rather\n * than string-matching messages.\n */\n\nimport type { FormatId } from './types.js';\n\n/** Machine-readable failure kinds surfaced by the registry / `convert()`. */\nexport type ConversionErrorCode =\n | 'unknown-format'\n | 'unsupported-input'\n | 'unsupported-output'\n | 'invalid-input'\n | 'missing-dependency'\n | 'conversion-failed';\n\n/** Options for constructing a {@link ConversionError}. */\nexport interface ConversionErrorOptions {\n /** The format id the error relates to, if any. */\n format?: FormatId;\n /** A human-oriented remediation hint. */\n hint?: string;\n /** The underlying error, preserved on `error.cause`. */\n cause?: unknown;\n}\n\n/** An error raised by the format registry or `convert()`. */\nexport class ConversionError extends Error {\n readonly code: ConversionErrorCode;\n readonly format?: FormatId;\n readonly hint?: string;\n\n constructor(code: ConversionErrorCode, message: string, opts: ConversionErrorOptions = {}) {\n super(message);\n this.name = 'ConversionError';\n this.code = code;\n this.format = opts.format;\n this.hint = opts.hint;\n if (opts.cause !== undefined) {\n (this as Error & { cause?: unknown }).cause = opts.cause;\n }\n }\n}\n"],"mappings":";AA8BO,IAAM,kBAAN,cAA8B,MAAM;AAAA,EAKzC,YAAY,MAA2B,SAAiB,OAA+B,CAAC,GAAG;AACzF,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,OAAO;AACZ,SAAK,SAAS,KAAK;AACnB,SAAK,OAAO,KAAK;AACjB,QAAI,KAAK,UAAU,QAAW;AAC5B,MAAC,KAAqC,QAAQ,KAAK;AAAA,IACrD;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import {
|
|
2
|
+
baseDirOf,
|
|
3
|
+
findRelByType,
|
|
4
|
+
resolveTarget
|
|
5
|
+
} from "./chunk-YRT7GQ5Y.js";
|
|
6
|
+
import {
|
|
7
|
+
NS_DRAWINGML,
|
|
8
|
+
REL_THEME,
|
|
9
|
+
getPartRelationships,
|
|
10
|
+
getPartXml
|
|
11
|
+
} from "./chunk-4HJ6AK2Q.js";
|
|
12
|
+
|
|
13
|
+
// src/ooxml/themeReader.ts
|
|
14
|
+
var HEX6_RE = /^[0-9a-fA-F]{6}$/;
|
|
15
|
+
var SYS_COLOR_DEFAULTS = {
|
|
16
|
+
windowText: "000000",
|
|
17
|
+
window: "ffffff"
|
|
18
|
+
};
|
|
19
|
+
function resolveSchemeColor(slot) {
|
|
20
|
+
for (let i = 0; i < slot.childNodes.length; i++) {
|
|
21
|
+
const child = slot.childNodes[i];
|
|
22
|
+
if (!child || child.nodeType !== 1) continue;
|
|
23
|
+
const el = child;
|
|
24
|
+
if (el.localName === "srgbClr") {
|
|
25
|
+
const val = el.getAttribute("val");
|
|
26
|
+
return val && HEX6_RE.test(val) ? `#${val.toLowerCase()}` : null;
|
|
27
|
+
}
|
|
28
|
+
if (el.localName === "sysClr") {
|
|
29
|
+
const lastClr = el.getAttribute("lastClr");
|
|
30
|
+
if (lastClr && HEX6_RE.test(lastClr)) return `#${lastClr.toLowerCase()}`;
|
|
31
|
+
const val = el.getAttribute("val");
|
|
32
|
+
const known = val ? SYS_COLOR_DEFAULTS[val] : void 0;
|
|
33
|
+
return known ? `#${known}` : null;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function firstNS(parent, local) {
|
|
40
|
+
const els = parent.getElementsByTagNameNS(NS_DRAWINGML, local);
|
|
41
|
+
return els.length > 0 ? els[0] : null;
|
|
42
|
+
}
|
|
43
|
+
function parseColorScheme(clrScheme, warnings) {
|
|
44
|
+
const resolve = (slotName) => {
|
|
45
|
+
const slot = firstNS(clrScheme, slotName);
|
|
46
|
+
if (!slot) return null;
|
|
47
|
+
return resolveSchemeColor(slot);
|
|
48
|
+
};
|
|
49
|
+
const dk1 = resolve("dk1");
|
|
50
|
+
const lt1 = resolve("lt1");
|
|
51
|
+
if (!dk1) warnings.push("theme: dk1 color could not be resolved; using #000000");
|
|
52
|
+
if (!lt1) warnings.push("theme: lt1 color could not be resolved; using #ffffff");
|
|
53
|
+
const dk2 = resolve("dk2");
|
|
54
|
+
const lt2 = resolve("lt2");
|
|
55
|
+
const colors = {
|
|
56
|
+
dk1: dk1 ?? "#000000",
|
|
57
|
+
lt1: lt1 ?? "#ffffff",
|
|
58
|
+
dk2: dk2 ?? dk1 ?? "#000000",
|
|
59
|
+
lt2: lt2 ?? lt1 ?? "#ffffff"
|
|
60
|
+
};
|
|
61
|
+
for (const slot of ["accent1", "accent2", "accent3", "accent4", "accent5", "accent6"]) {
|
|
62
|
+
const hex = resolve(slot);
|
|
63
|
+
if (hex) colors[slot] = hex;
|
|
64
|
+
else warnings.push(`theme: ${slot} color could not be resolved; dropped`);
|
|
65
|
+
}
|
|
66
|
+
for (const slot of ["hlink", "folHlink"]) {
|
|
67
|
+
const hex = resolve(slot);
|
|
68
|
+
if (hex) colors[slot] = hex;
|
|
69
|
+
}
|
|
70
|
+
return colors;
|
|
71
|
+
}
|
|
72
|
+
function parseFontScheme(fontScheme, warnings) {
|
|
73
|
+
const readLatin = (fontName) => {
|
|
74
|
+
const font = firstNS(fontScheme, fontName);
|
|
75
|
+
if (!font) return void 0;
|
|
76
|
+
const latin = firstNS(font, "latin");
|
|
77
|
+
const typeface = latin?.getAttribute("typeface")?.trim();
|
|
78
|
+
if (!typeface) {
|
|
79
|
+
warnings.push(`theme: ${fontName} has no latin typeface`);
|
|
80
|
+
return void 0;
|
|
81
|
+
}
|
|
82
|
+
return typeface;
|
|
83
|
+
};
|
|
84
|
+
const majorLatin = readLatin("majorFont");
|
|
85
|
+
const minorLatin = readLatin("minorFont");
|
|
86
|
+
if (!majorLatin && !minorLatin) return void 0;
|
|
87
|
+
return {
|
|
88
|
+
...majorLatin ? { majorLatin } : {},
|
|
89
|
+
...minorLatin ? { minorLatin } : {}
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function parseThemeXml(themeDoc) {
|
|
93
|
+
const warnings = [];
|
|
94
|
+
const root = firstNS(themeDoc, "theme");
|
|
95
|
+
const name = root?.getAttribute("name")?.trim() || void 0;
|
|
96
|
+
const clrScheme = firstNS(themeDoc, "clrScheme");
|
|
97
|
+
let colors;
|
|
98
|
+
if (clrScheme) {
|
|
99
|
+
colors = parseColorScheme(clrScheme, warnings);
|
|
100
|
+
} else {
|
|
101
|
+
warnings.push("theme: no clrScheme found");
|
|
102
|
+
}
|
|
103
|
+
const fontScheme = firstNS(themeDoc, "fontScheme");
|
|
104
|
+
let fonts;
|
|
105
|
+
if (fontScheme) {
|
|
106
|
+
fonts = parseFontScheme(fontScheme, warnings);
|
|
107
|
+
} else {
|
|
108
|
+
warnings.push("theme: no fontScheme found");
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
...name ? { name } : {},
|
|
112
|
+
...colors ? { colors } : {},
|
|
113
|
+
...fonts ? { fonts } : {},
|
|
114
|
+
warnings
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
async function readThemePart(pkg, fromPartPath) {
|
|
118
|
+
const rels = await getPartRelationships(pkg, fromPartPath);
|
|
119
|
+
const rel = findRelByType(rels, REL_THEME);
|
|
120
|
+
if (!rel) return null;
|
|
121
|
+
const themePath = resolveTarget(baseDirOf(fromPartPath), rel.target);
|
|
122
|
+
const doc = await getPartXml(pkg, themePath);
|
|
123
|
+
if (!doc) return null;
|
|
124
|
+
return parseThemeXml(doc);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export {
|
|
128
|
+
parseThemeXml,
|
|
129
|
+
readThemePart
|
|
130
|
+
};
|
|
131
|
+
//# sourceMappingURL=chunk-X4USWOQ2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ooxml/themeReader.ts"],"sourcesContent":["/**\n * OOXML theme reader — parses a `theme1.xml` part (DrawingML `a:theme`)\n * into normalized color and font schemes.\n *\n * Shared by DOCX/PPTX/XLSX theme inference: all three formats carry the same\n * theme part shape, differing only in where the part hangs (document/master/\n * workbook relationships). This module never fails hard — unreadable slots\n * degrade to fallbacks or absence, with a warning per degradation.\n */\n\nimport type { OoxmlPackage } from './types.js';\nimport { getPartRelationships, getPartXml } from './reader.js';\nimport { NS_DRAWINGML, REL_THEME } from './namespaces.js';\nimport { baseDirOf, findRelByType, resolveTarget } from './readUtils.js';\n\n/**\n * The clrScheme slots as normalized `#rrggbb` strings. The four surface/text\n * slots always resolve (falling back to black/white when degraded); accent and\n * hyperlink slots are omitted when unresolvable.\n */\nexport interface OoxmlColorScheme {\n dk1: string;\n lt1: string;\n dk2: string;\n lt2: string;\n accent1?: string;\n accent2?: string;\n accent3?: string;\n accent4?: string;\n accent5?: string;\n accent6?: string;\n hlink?: string;\n folHlink?: string;\n}\n\nexport interface OoxmlFontScheme {\n /** `a:fontScheme > a:majorFont > a:latin@typeface` (headings). */\n majorLatin?: string;\n /** `a:fontScheme > a:minorFont > a:latin@typeface` (body). */\n minorLatin?: string;\n}\n\nexport interface OoxmlTheme {\n /** `a:theme@name` (e.g. \"Office Theme\", \"Ion\"). */\n name?: string;\n /** Absent when the theme carries no parseable `a:clrScheme`. */\n colors?: OoxmlColorScheme;\n /** Absent when the theme carries no `a:fontScheme` latin typefaces. */\n fonts?: OoxmlFontScheme;\n warnings: string[];\n}\n\nconst HEX6_RE = /^[0-9a-fA-F]{6}$/;\n\n/** Well-known sysClr values that resolve without a `lastClr` hint. */\nconst SYS_COLOR_DEFAULTS: Record<string, string> = {\n windowText: '000000',\n window: 'ffffff',\n};\n\n/**\n * Resolve one clrScheme slot element to a `#rrggbb` string, or null.\n * Only the base color matters for theming — trailing transform children\n * (`a:lumMod`, `a:tint`, …) are ignored.\n */\nfunction resolveSchemeColor(slot: Element): string | null {\n for (let i = 0; i < slot.childNodes.length; i++) {\n const child = slot.childNodes[i];\n if (!child || child.nodeType !== 1) continue;\n const el = child as Element;\n if (el.localName === 'srgbClr') {\n const val = el.getAttribute('val');\n return val && HEX6_RE.test(val) ? `#${val.toLowerCase()}` : null;\n }\n if (el.localName === 'sysClr') {\n const lastClr = el.getAttribute('lastClr');\n if (lastClr && HEX6_RE.test(lastClr)) return `#${lastClr.toLowerCase()}`;\n const val = el.getAttribute('val');\n const known = val ? SYS_COLOR_DEFAULTS[val] : undefined;\n return known ? `#${known}` : null;\n }\n // Any other color form (schemeClr self-reference, prstClr, …) is\n // unresolvable without a full DrawingML color pipeline.\n return null;\n }\n return null;\n}\n\nfunction firstNS(parent: Element | Document, local: string): Element | null {\n const els = parent.getElementsByTagNameNS(NS_DRAWINGML, local);\n return els.length > 0 ? els[0]! : null;\n}\n\nfunction parseColorScheme(clrScheme: Element, warnings: string[]): OoxmlColorScheme {\n const resolve = (slotName: string): string | null => {\n const slot = firstNS(clrScheme, slotName);\n if (!slot) return null;\n return resolveSchemeColor(slot);\n };\n\n const dk1 = resolve('dk1');\n const lt1 = resolve('lt1');\n if (!dk1) warnings.push('theme: dk1 color could not be resolved; using #000000');\n if (!lt1) warnings.push('theme: lt1 color could not be resolved; using #ffffff');\n\n const dk2 = resolve('dk2');\n const lt2 = resolve('lt2');\n\n const colors: OoxmlColorScheme = {\n dk1: dk1 ?? '#000000',\n lt1: lt1 ?? '#ffffff',\n dk2: dk2 ?? dk1 ?? '#000000',\n lt2: lt2 ?? lt1 ?? '#ffffff',\n };\n\n for (const slot of ['accent1', 'accent2', 'accent3', 'accent4', 'accent5', 'accent6'] as const) {\n const hex = resolve(slot);\n if (hex) colors[slot] = hex;\n else warnings.push(`theme: ${slot} color could not be resolved; dropped`);\n }\n for (const slot of ['hlink', 'folHlink'] as const) {\n const hex = resolve(slot);\n if (hex) colors[slot] = hex;\n }\n return colors;\n}\n\nfunction parseFontScheme(fontScheme: Element, warnings: string[]): OoxmlFontScheme | undefined {\n const readLatin = (fontName: 'majorFont' | 'minorFont'): string | undefined => {\n const font = firstNS(fontScheme, fontName);\n if (!font) return undefined;\n const latin = firstNS(font, 'latin');\n const typeface = latin?.getAttribute('typeface')?.trim();\n if (!typeface) {\n warnings.push(`theme: ${fontName} has no latin typeface`);\n return undefined;\n }\n return typeface;\n };\n\n const majorLatin = readLatin('majorFont');\n const minorLatin = readLatin('minorFont');\n if (!majorLatin && !minorLatin) return undefined;\n return {\n ...(majorLatin ? { majorLatin } : {}),\n ...(minorLatin ? { minorLatin } : {}),\n };\n}\n\n/** Parse an already-loaded theme part Document. */\nexport function parseThemeXml(themeDoc: Document): OoxmlTheme {\n const warnings: string[] = [];\n\n const root = firstNS(themeDoc, 'theme');\n const name = root?.getAttribute('name')?.trim() || undefined;\n\n const clrScheme = firstNS(themeDoc, 'clrScheme');\n let colors: OoxmlColorScheme | undefined;\n if (clrScheme) {\n colors = parseColorScheme(clrScheme, warnings);\n } else {\n warnings.push('theme: no clrScheme found');\n }\n\n const fontScheme = firstNS(themeDoc, 'fontScheme');\n let fonts: OoxmlFontScheme | undefined;\n if (fontScheme) {\n fonts = parseFontScheme(fontScheme, warnings);\n } else {\n warnings.push('theme: no fontScheme found');\n }\n\n return {\n ...(name ? { name } : {}),\n ...(colors ? { colors } : {}),\n ...(fonts ? { fonts } : {}),\n warnings,\n };\n}\n\n/**\n * Resolve the theme part related from `fromPartPath` (via `REL_THEME`) and\n * parse it. Returns null when the part has no theme relationship or the\n * target part is missing — callers decide whether that's an error.\n */\nexport async function readThemePart(\n pkg: OoxmlPackage,\n fromPartPath: string,\n): Promise<OoxmlTheme | null> {\n const rels = await getPartRelationships(pkg, fromPartPath);\n const rel = findRelByType(rels, REL_THEME);\n if (!rel) return null;\n const themePath = resolveTarget(baseDirOf(fromPartPath), rel.target);\n const doc = await getPartXml(pkg, themePath);\n if (!doc) return null;\n return parseThemeXml(doc);\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeXml,
|
|
3
|
+
xmlDeclaration
|
|
4
|
+
} from "./chunk-U4MRIFKL.js";
|
|
5
|
+
import {
|
|
6
|
+
CONTENT_TYPE_CORE_PROPERTIES,
|
|
7
|
+
CONTENT_TYPE_RELATIONSHIPS,
|
|
8
|
+
NS_CONTENT_TYPES,
|
|
9
|
+
NS_CORE_PROPERTIES,
|
|
10
|
+
NS_DC,
|
|
11
|
+
NS_DCTERMS,
|
|
12
|
+
NS_RELATIONSHIPS,
|
|
13
|
+
NS_XSI,
|
|
14
|
+
REL_CORE_PROPERTIES
|
|
15
|
+
} from "./chunk-4HJ6AK2Q.js";
|
|
16
|
+
import {
|
|
17
|
+
assertSafeZipPath
|
|
18
|
+
} from "./chunk-H7T5DXUU.js";
|
|
19
|
+
|
|
20
|
+
// src/ooxml/writer.ts
|
|
21
|
+
import JSZip from "jszip";
|
|
22
|
+
function createPackage() {
|
|
23
|
+
const parts = [];
|
|
24
|
+
const relationships = [];
|
|
25
|
+
let coreProps;
|
|
26
|
+
return {
|
|
27
|
+
addPart(path, content, contentType) {
|
|
28
|
+
assertSafeZipPath(path);
|
|
29
|
+
parts.push({ path, content, contentType });
|
|
30
|
+
},
|
|
31
|
+
addBinaryPart(path, data, contentType) {
|
|
32
|
+
assertSafeZipPath(path);
|
|
33
|
+
parts.push({ path, binaryContent: data, contentType });
|
|
34
|
+
},
|
|
35
|
+
addRelationship(sourcePart, rel) {
|
|
36
|
+
if (sourcePart) assertSafeZipPath(sourcePart);
|
|
37
|
+
relationships.push({ sourcePart, relationship: rel });
|
|
38
|
+
},
|
|
39
|
+
setCoreProperties(props) {
|
|
40
|
+
coreProps = props;
|
|
41
|
+
},
|
|
42
|
+
async toBlob() {
|
|
43
|
+
const zip = assemble(parts, relationships, coreProps);
|
|
44
|
+
return zip.generateAsync({ type: "blob" });
|
|
45
|
+
},
|
|
46
|
+
async toArrayBuffer() {
|
|
47
|
+
const zip = assemble(parts, relationships, coreProps);
|
|
48
|
+
return zip.generateAsync({ type: "arraybuffer" });
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
function assemble(parts, relationships, coreProps) {
|
|
53
|
+
const zip = new JSZip();
|
|
54
|
+
const assembledRelationships = relationships.slice();
|
|
55
|
+
for (const part of parts) {
|
|
56
|
+
if (part.content !== void 0) {
|
|
57
|
+
zip.file(part.path, part.content);
|
|
58
|
+
} else if (part.binaryContent !== void 0) {
|
|
59
|
+
zip.file(part.path, part.binaryContent);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (coreProps) {
|
|
63
|
+
const coreXml = buildCorePropertiesXml(coreProps);
|
|
64
|
+
zip.file("docProps/core.xml", coreXml);
|
|
65
|
+
if (!assembledRelationships.some(
|
|
66
|
+
({ sourcePart, relationship }) => sourcePart === "" && relationship.type === REL_CORE_PROPERTIES
|
|
67
|
+
)) {
|
|
68
|
+
const ids = new Set(assembledRelationships.map(({ relationship }) => relationship.id));
|
|
69
|
+
let nextId = 100;
|
|
70
|
+
while (ids.has(`rId${nextId}`)) nextId++;
|
|
71
|
+
assembledRelationships.push({
|
|
72
|
+
sourcePart: "",
|
|
73
|
+
relationship: {
|
|
74
|
+
id: `rId${nextId}`,
|
|
75
|
+
type: REL_CORE_PROPERTIES,
|
|
76
|
+
target: "docProps/core.xml"
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
zip.file("[Content_Types].xml", buildContentTypesXml(parts, coreProps));
|
|
82
|
+
const relsBySource = groupRelationshipsBySource(assembledRelationships);
|
|
83
|
+
for (const [sourcePart, rels] of relsBySource) {
|
|
84
|
+
const relsPath = sourcePart === "" ? "_rels/.rels" : buildRelsPath(sourcePart);
|
|
85
|
+
zip.file(relsPath, buildRelationshipsXml(rels));
|
|
86
|
+
}
|
|
87
|
+
return zip;
|
|
88
|
+
}
|
|
89
|
+
function buildContentTypesXml(parts, coreProps) {
|
|
90
|
+
const lines = [xmlDeclaration()];
|
|
91
|
+
lines.push(`<Types xmlns="${NS_CONTENT_TYPES}">`);
|
|
92
|
+
const extensionTypes = /* @__PURE__ */ new Map();
|
|
93
|
+
extensionTypes.set("rels", CONTENT_TYPE_RELATIONSHIPS);
|
|
94
|
+
extensionTypes.set("xml", "application/xml");
|
|
95
|
+
for (const part of parts) {
|
|
96
|
+
if (part.binaryContent !== void 0) {
|
|
97
|
+
const ext = getExtension(part.path);
|
|
98
|
+
if (ext && !extensionTypes.has(ext)) {
|
|
99
|
+
extensionTypes.set(ext, part.contentType);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
for (const [ext, ct] of extensionTypes) {
|
|
104
|
+
lines.push(` <Default Extension="${escapeXml(ext)}" ContentType="${escapeXml(ct)}"/>`);
|
|
105
|
+
}
|
|
106
|
+
for (const part of parts) {
|
|
107
|
+
if (part.content !== void 0) {
|
|
108
|
+
lines.push(
|
|
109
|
+
` <Override PartName="/${escapeXml(part.path)}" ContentType="${escapeXml(part.contentType)}"/>`
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (coreProps) {
|
|
114
|
+
lines.push(
|
|
115
|
+
` <Override PartName="/docProps/core.xml" ContentType="${CONTENT_TYPE_CORE_PROPERTIES}"/>`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
lines.push("</Types>");
|
|
119
|
+
return lines.join("\n");
|
|
120
|
+
}
|
|
121
|
+
function buildRelationshipsXml(rels) {
|
|
122
|
+
const lines = [xmlDeclaration()];
|
|
123
|
+
lines.push(`<Relationships xmlns="${NS_RELATIONSHIPS}">`);
|
|
124
|
+
for (const rel of rels) {
|
|
125
|
+
const attrs = [
|
|
126
|
+
`Id="${escapeXml(rel.id)}"`,
|
|
127
|
+
`Type="${escapeXml(rel.type)}"`,
|
|
128
|
+
`Target="${escapeXml(rel.target)}"`
|
|
129
|
+
];
|
|
130
|
+
if (rel.targetMode) {
|
|
131
|
+
attrs.push(`TargetMode="${escapeXml(rel.targetMode)}"`);
|
|
132
|
+
}
|
|
133
|
+
lines.push(` <Relationship ${attrs.join(" ")}/>`);
|
|
134
|
+
}
|
|
135
|
+
lines.push("</Relationships>");
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
function buildCorePropertiesXml(props) {
|
|
139
|
+
const lines = [xmlDeclaration()];
|
|
140
|
+
lines.push(
|
|
141
|
+
`<cp:coreProperties xmlns:cp="${NS_CORE_PROPERTIES}" xmlns:dc="${NS_DC}" xmlns:dcterms="${NS_DCTERMS}" xmlns:xsi="${NS_XSI}">`
|
|
142
|
+
);
|
|
143
|
+
if (props.title) lines.push(` <dc:title>${escapeXml(props.title)}</dc:title>`);
|
|
144
|
+
if (props.subject) lines.push(` <dc:subject>${escapeXml(props.subject)}</dc:subject>`);
|
|
145
|
+
if (props.creator) lines.push(` <dc:creator>${escapeXml(props.creator)}</dc:creator>`);
|
|
146
|
+
if (props.description)
|
|
147
|
+
lines.push(` <dc:description>${escapeXml(props.description)}</dc:description>`);
|
|
148
|
+
if (props.keywords) lines.push(` <cp:keywords>${escapeXml(props.keywords)}</cp:keywords>`);
|
|
149
|
+
if (props.lastModifiedBy)
|
|
150
|
+
lines.push(` <cp:lastModifiedBy>${escapeXml(props.lastModifiedBy)}</cp:lastModifiedBy>`);
|
|
151
|
+
if (props.revision) lines.push(` <cp:revision>${escapeXml(props.revision)}</cp:revision>`);
|
|
152
|
+
if (props.created) {
|
|
153
|
+
lines.push(
|
|
154
|
+
` <dcterms:created xsi:type="dcterms:W3CDTF">${escapeXml(props.created)}</dcterms:created>`
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
if (props.modified) {
|
|
158
|
+
lines.push(
|
|
159
|
+
` <dcterms:modified xsi:type="dcterms:W3CDTF">${escapeXml(props.modified)}</dcterms:modified>`
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
lines.push("</cp:coreProperties>");
|
|
163
|
+
return lines.join("\n");
|
|
164
|
+
}
|
|
165
|
+
function getExtension(path) {
|
|
166
|
+
const dot = path.lastIndexOf(".");
|
|
167
|
+
if (dot === -1) return void 0;
|
|
168
|
+
return path.substring(dot + 1).toLowerCase();
|
|
169
|
+
}
|
|
170
|
+
function buildRelsPath(partPath) {
|
|
171
|
+
const lastSlash = partPath.lastIndexOf("/");
|
|
172
|
+
if (lastSlash === -1) {
|
|
173
|
+
return `_rels/${partPath}.rels`;
|
|
174
|
+
}
|
|
175
|
+
const dir = partPath.substring(0, lastSlash);
|
|
176
|
+
const file = partPath.substring(lastSlash + 1);
|
|
177
|
+
return `${dir}/_rels/${file}.rels`;
|
|
178
|
+
}
|
|
179
|
+
function groupRelationshipsBySource(pending) {
|
|
180
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
181
|
+
for (const { sourcePart, relationship } of pending) {
|
|
182
|
+
let list = grouped.get(sourcePart);
|
|
183
|
+
if (!list) {
|
|
184
|
+
list = [];
|
|
185
|
+
grouped.set(sourcePart, list);
|
|
186
|
+
}
|
|
187
|
+
list.push(relationship);
|
|
188
|
+
}
|
|
189
|
+
return grouped;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export {
|
|
193
|
+
createPackage
|
|
194
|
+
};
|
|
195
|
+
//# sourceMappingURL=chunk-XTLQXR4U.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ooxml/writer.ts"],"sourcesContent":["/**\n * OOXML Package Writer\n *\n * Builds OOXML archives (.docx, .pptx, .xlsx) from parts.\n * Handles automatic generation of [Content_Types].xml and\n * _rels/*.rels files from the registered parts and relationships.\n */\n\nimport JSZip from 'jszip';\nimport type { CoreProperties, PackagePart, PendingRelationship, Relationship } from './types.js';\nimport {\n NS_CONTENT_TYPES,\n NS_RELATIONSHIPS,\n NS_CORE_PROPERTIES,\n NS_DC,\n NS_DCTERMS,\n NS_XSI,\n CONTENT_TYPE_RELATIONSHIPS,\n CONTENT_TYPE_CORE_PROPERTIES,\n REL_CORE_PROPERTIES,\n} from './namespaces.js';\nimport { xmlDeclaration, escapeXml } from './xmlUtils.js';\nimport { assertSafeZipPath } from '../shared/zipSafety.js';\n\n// ============================================\n// Package Builder\n// ============================================\n\n/**\n * Mutable builder for constructing an OOXML package.\n *\n * Add parts, relationships, and core properties, then call `toBlob()`\n * or `toArrayBuffer()` to produce the final ZIP archive.\n *\n * @example\n * ```ts\n * const builder = createPackage();\n * builder.addPart('word/document.xml', documentXml, CONTENT_TYPE_DOCX_DOCUMENT);\n * builder.addRelationship('', {\n * id: 'rId1',\n * type: REL_OFFICE_DOCUMENT,\n * target: 'word/document.xml',\n * });\n * const blob = await builder.toBlob();\n * ```\n */\nexport interface OoxmlPackageBuilder {\n /**\n * Add an XML or text part to the package.\n *\n * @param path - Path within the archive (e.g., \"word/document.xml\")\n * @param content - XML string content\n * @param contentType - MIME content type for [Content_Types].xml\n */\n addPart(path: string, content: string, contentType: string): void;\n\n /**\n * Add a binary part to the package (e.g., an image).\n *\n * @param path - Path within the archive (e.g., \"word/media/image1.png\")\n * @param data - Binary content\n * @param contentType - MIME content type (e.g., \"image/png\")\n */\n addBinaryPart(path: string, data: ArrayBuffer | Uint8Array, contentType: string): void;\n\n /**\n * Register a relationship.\n *\n * @param sourcePart - The part this relationship belongs to (e.g., \"word/document.xml\").\n * Use \"\" (empty string) for root-level relationships (_rels/.rels).\n * @param rel - The relationship entry\n */\n addRelationship(sourcePart: string, rel: Relationship): void;\n\n /**\n * Set core document properties (docProps/core.xml).\n * Calling this multiple times overwrites previous values.\n */\n setCoreProperties(props: CoreProperties): void;\n\n /**\n * Assemble the final OOXML package as a Blob.\n * Generates [Content_Types].xml and all _rels/*.rels files automatically.\n */\n toBlob(): Promise<Blob>;\n\n /**\n * Assemble the final OOXML package as an ArrayBuffer.\n */\n toArrayBuffer(): Promise<ArrayBuffer>;\n}\n\n/**\n * Create a new OOXML package builder.\n */\nexport function createPackage(): OoxmlPackageBuilder {\n const parts: PackagePart[] = [];\n const relationships: PendingRelationship[] = [];\n let coreProps: CoreProperties | undefined;\n\n return {\n addPart(path, content, contentType) {\n assertSafeZipPath(path);\n parts.push({ path, content, contentType });\n },\n\n addBinaryPart(path, data, contentType) {\n assertSafeZipPath(path);\n parts.push({ path, binaryContent: data, contentType });\n },\n\n addRelationship(sourcePart, rel) {\n if (sourcePart) assertSafeZipPath(sourcePart);\n relationships.push({ sourcePart, relationship: rel });\n },\n\n setCoreProperties(props) {\n coreProps = props;\n },\n\n async toBlob() {\n const zip = assemble(parts, relationships, coreProps);\n return zip.generateAsync({ type: 'blob' });\n },\n\n async toArrayBuffer() {\n const zip = assemble(parts, relationships, coreProps);\n return zip.generateAsync({ type: 'arraybuffer' });\n },\n };\n}\n\n// ============================================\n// Assembly\n// ============================================\n\n/**\n * Assemble a JSZip archive from parts, relationships, and core properties.\n */\nfunction assemble(\n parts: PackagePart[],\n relationships: PendingRelationship[],\n coreProps?: CoreProperties,\n): JSZip {\n const zip = new JSZip();\n // Assembly must be idempotent: callers may request both Blob and\n // ArrayBuffer representations from one builder. Never append generated\n // relationships to the builder-owned array.\n const assembledRelationships = relationships.slice();\n\n // Write content parts\n for (const part of parts) {\n if (part.content !== undefined) {\n zip.file(part.path, part.content);\n } else if (part.binaryContent !== undefined) {\n zip.file(part.path, part.binaryContent);\n }\n }\n\n // Add core properties if set\n if (coreProps) {\n const coreXml = buildCorePropertiesXml(coreProps);\n zip.file('docProps/core.xml', coreXml);\n\n // Add the generated relationship unless the caller already supplied it.\n if (\n !assembledRelationships.some(\n ({ sourcePart, relationship }) =>\n sourcePart === '' && relationship.type === REL_CORE_PROPERTIES,\n )\n ) {\n const ids = new Set(assembledRelationships.map(({ relationship }) => relationship.id));\n let nextId = 100;\n while (ids.has(`rId${nextId}`)) nextId++;\n assembledRelationships.push({\n sourcePart: '',\n relationship: {\n id: `rId${nextId}`,\n type: REL_CORE_PROPERTIES,\n target: 'docProps/core.xml',\n },\n });\n }\n }\n\n // Build and write [Content_Types].xml\n zip.file('[Content_Types].xml', buildContentTypesXml(parts, coreProps));\n\n // Build and write _rels/*.rels files\n const relsBySource = groupRelationshipsBySource(assembledRelationships);\n for (const [sourcePart, rels] of relsBySource) {\n const relsPath = sourcePart === '' ? '_rels/.rels' : buildRelsPath(sourcePart);\n zip.file(relsPath, buildRelationshipsXml(rels));\n }\n\n return zip;\n}\n\n// ============================================\n// [Content_Types].xml\n// ============================================\n\nfunction buildContentTypesXml(parts: PackagePart[], coreProps?: CoreProperties): string {\n const lines: string[] = [xmlDeclaration()];\n lines.push(`<Types xmlns=\"${NS_CONTENT_TYPES}\">`);\n\n // Default extension types\n const extensionTypes = new Map<string, string>();\n extensionTypes.set('rels', CONTENT_TYPE_RELATIONSHIPS);\n extensionTypes.set('xml', 'application/xml');\n\n // Collect extensions from binary parts (e.g., png, jpeg)\n for (const part of parts) {\n if (part.binaryContent !== undefined) {\n const ext = getExtension(part.path);\n if (ext && !extensionTypes.has(ext)) {\n extensionTypes.set(ext, part.contentType);\n }\n }\n }\n\n for (const [ext, ct] of extensionTypes) {\n lines.push(` <Default Extension=\"${escapeXml(ext)}\" ContentType=\"${escapeXml(ct)}\"/>`);\n }\n\n // Override entries for each XML part\n for (const part of parts) {\n if (part.content !== undefined) {\n lines.push(\n ` <Override PartName=\"/${escapeXml(part.path)}\" ContentType=\"${escapeXml(part.contentType)}\"/>`,\n );\n }\n }\n\n // Core properties override\n if (coreProps) {\n lines.push(\n ` <Override PartName=\"/docProps/core.xml\" ContentType=\"${CONTENT_TYPE_CORE_PROPERTIES}\"/>`,\n );\n }\n\n lines.push('</Types>');\n return lines.join('\\n');\n}\n\n// ============================================\n// Relationships XML\n// ============================================\n\nfunction buildRelationshipsXml(rels: Relationship[]): string {\n const lines: string[] = [xmlDeclaration()];\n lines.push(`<Relationships xmlns=\"${NS_RELATIONSHIPS}\">`);\n\n for (const rel of rels) {\n const attrs: string[] = [\n `Id=\"${escapeXml(rel.id)}\"`,\n `Type=\"${escapeXml(rel.type)}\"`,\n `Target=\"${escapeXml(rel.target)}\"`,\n ];\n if (rel.targetMode) {\n attrs.push(`TargetMode=\"${escapeXml(rel.targetMode)}\"`);\n }\n lines.push(` <Relationship ${attrs.join(' ')}/>`);\n }\n\n lines.push('</Relationships>');\n return lines.join('\\n');\n}\n\n// ============================================\n// Core Properties XML\n// ============================================\n\nfunction buildCorePropertiesXml(props: CoreProperties): string {\n const lines: string[] = [xmlDeclaration()];\n lines.push(\n `<cp:coreProperties` +\n ` xmlns:cp=\"${NS_CORE_PROPERTIES}\"` +\n ` xmlns:dc=\"${NS_DC}\"` +\n ` xmlns:dcterms=\"${NS_DCTERMS}\"` +\n ` xmlns:xsi=\"${NS_XSI}\">`,\n );\n\n if (props.title) lines.push(` <dc:title>${escapeXml(props.title)}</dc:title>`);\n if (props.subject) lines.push(` <dc:subject>${escapeXml(props.subject)}</dc:subject>`);\n if (props.creator) lines.push(` <dc:creator>${escapeXml(props.creator)}</dc:creator>`);\n if (props.description)\n lines.push(` <dc:description>${escapeXml(props.description)}</dc:description>`);\n if (props.keywords) lines.push(` <cp:keywords>${escapeXml(props.keywords)}</cp:keywords>`);\n if (props.lastModifiedBy)\n lines.push(` <cp:lastModifiedBy>${escapeXml(props.lastModifiedBy)}</cp:lastModifiedBy>`);\n if (props.revision) lines.push(` <cp:revision>${escapeXml(props.revision)}</cp:revision>`);\n if (props.created) {\n lines.push(\n ` <dcterms:created xsi:type=\"dcterms:W3CDTF\">${escapeXml(props.created)}</dcterms:created>`,\n );\n }\n if (props.modified) {\n lines.push(\n ` <dcterms:modified xsi:type=\"dcterms:W3CDTF\">${escapeXml(props.modified)}</dcterms:modified>`,\n );\n }\n\n lines.push('</cp:coreProperties>');\n return lines.join('\\n');\n}\n\n// ============================================\n// Helpers\n// ============================================\n\nfunction getExtension(path: string): string | undefined {\n const dot = path.lastIndexOf('.');\n if (dot === -1) return undefined;\n return path.substring(dot + 1).toLowerCase();\n}\n\nfunction buildRelsPath(partPath: string): string {\n const lastSlash = partPath.lastIndexOf('/');\n if (lastSlash === -1) {\n return `_rels/${partPath}.rels`;\n }\n const dir = partPath.substring(0, lastSlash);\n const file = partPath.substring(lastSlash + 1);\n return `${dir}/_rels/${file}.rels`;\n}\n\nfunction groupRelationshipsBySource(pending: PendingRelationship[]): Map<string, Relationship[]> {\n const grouped = new Map<string, Relationship[]>();\n for (const { sourcePart, relationship } of pending) {\n let list = grouped.get(sourcePart);\n if (!list) {\n list = [];\n grouped.set(sourcePart, list);\n }\n list.push(relationship);\n }\n return grouped;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/ooxml/readUtils.ts
|
|
2
|
+
function attrNS(el, ns, local, qualified) {
|
|
3
|
+
return el.getAttributeNS(ns, local) ?? el.getAttribute(qualified);
|
|
4
|
+
}
|
|
5
|
+
function baseDirOf(path) {
|
|
6
|
+
const slash = path.lastIndexOf("/");
|
|
7
|
+
return slash === -1 ? "" : path.slice(0, slash);
|
|
8
|
+
}
|
|
9
|
+
function resolveTarget(baseDir, target) {
|
|
10
|
+
if (target.startsWith("/")) return target.replace(/^\//, "");
|
|
11
|
+
const stack = baseDir ? baseDir.split("/") : [];
|
|
12
|
+
for (const seg of target.split("/")) {
|
|
13
|
+
if (seg === "..") stack.pop();
|
|
14
|
+
else if (seg !== ".") stack.push(seg);
|
|
15
|
+
}
|
|
16
|
+
return stack.join("/");
|
|
17
|
+
}
|
|
18
|
+
function findRelByType(rels, type) {
|
|
19
|
+
return rels.find((r) => r.type === type);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
attrNS,
|
|
24
|
+
baseDirOf,
|
|
25
|
+
resolveTarget,
|
|
26
|
+
findRelByType
|
|
27
|
+
};
|
|
28
|
+
//# sourceMappingURL=chunk-YRT7GQ5Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/ooxml/readUtils.ts"],"sourcesContent":["/**\n * Shared DOM-read helpers for OOXML importers.\n *\n * Every importer walks parts with `getElementsByTagNameNS` and resolves\n * relationship targets relative to the referencing part; these helpers hold\n * the common pieces so new readers (theme, slide layouts) don't re-implement\n * them per format.\n */\n\nimport type { Relationship } from './types.js';\n\n/**\n * Read a namespaced attribute with a qualified-name fallback for documents\n * whose producer omitted the namespace declaration (e.g. `r:id`).\n */\nexport function attrNS(el: Element, ns: string, local: string, qualified: string): string | null {\n return el.getAttributeNS(ns, local) ?? el.getAttribute(qualified);\n}\n\n/** Directory portion of a part path, e.g. `ppt/slides/slide1.xml` → `ppt/slides`. */\nexport function baseDirOf(path: string): string {\n const slash = path.lastIndexOf('/');\n return slash === -1 ? '' : path.slice(0, slash);\n}\n\n/**\n * Resolve a relationship target against the referencing part's directory.\n * Absolute targets (leading `/`) are package-rooted; relative targets may\n * climb with `..` segments (e.g. `../slideLayouts/slideLayout1.xml`).\n */\nexport function resolveTarget(baseDir: string, target: string): string {\n if (target.startsWith('/')) return target.replace(/^\\//, '');\n const stack = baseDir ? baseDir.split('/') : [];\n for (const seg of target.split('/')) {\n if (seg === '..') stack.pop();\n else if (seg !== '.') stack.push(seg);\n }\n return stack.join('/');\n}\n\n/** First relationship of the given type, if any. */\nexport function findRelByType(rels: Relationship[], type: string): Relationship | undefined {\n return rels.find((r) => r.type === type);\n}\n"],"mappings":";AAeO,SAAS,OAAO,IAAa,IAAY,OAAe,WAAkC;AAC/F,SAAO,GAAG,eAAe,IAAI,KAAK,KAAK,GAAG,aAAa,SAAS;AAClE;AAGO,SAAS,UAAU,MAAsB;AAC9C,QAAM,QAAQ,KAAK,YAAY,GAAG;AAClC,SAAO,UAAU,KAAK,KAAK,KAAK,MAAM,GAAG,KAAK;AAChD;AAOO,SAAS,cAAc,SAAiB,QAAwB;AACrE,MAAI,OAAO,WAAW,GAAG,EAAG,QAAO,OAAO,QAAQ,OAAO,EAAE;AAC3D,QAAM,QAAQ,UAAU,QAAQ,MAAM,GAAG,IAAI,CAAC;AAC9C,aAAW,OAAO,OAAO,MAAM,GAAG,GAAG;AACnC,QAAI,QAAQ,KAAM,OAAM,IAAI;AAAA,aACnB,QAAQ,IAAK,OAAM,KAAK,GAAG;AAAA,EACtC;AACA,SAAO,MAAM,KAAK,GAAG;AACvB;AAGO,SAAS,cAAc,MAAsB,MAAwC;AAC1F,SAAO,KAAK,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACzC;","names":[]}
|