@segnavia/idml 0.28.0 → 0.30.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/dist/frame-preferences.js +45 -0
- package/dist/import.d.ts +2 -2
- package/dist/import.js +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/map-book.js +56 -19
- package/dist/margins.js +21 -0
- package/dist/masters.js +200 -0
- package/dist/model.d.ts +9 -32
- package/dist/model.js +150 -118
- package/dist/parse-parts.js +61 -0
- package/dist/report.d.ts +24 -0
- package/dist/report.js +78 -0
- package/dist/stories.js +218 -0
- package/dist/story-model.js +53 -0
- package/dist/style-mappings.js +52 -0
- package/dist/style-property-report.js +236 -0
- package/dist/styles.js +285 -0
- package/dist/types.d.ts +90 -0
- package/dist/unsupported-elements.js +51 -0
- package/dist/variables.js +21 -0
- package/dist/xml.js +18 -0
- package/package.json +3 -3
package/dist/model.js
CHANGED
|
@@ -1,21 +1,15 @@
|
|
|
1
1
|
import { refusal } from "./refusal.js";
|
|
2
|
+
import { localName, numberAttribute } from "./xml.js";
|
|
3
|
+
import { readMargin } from "./margins.js";
|
|
4
|
+
import { ReportCollector } from "./report.js";
|
|
5
|
+
import { parseStyles, parseSwatches } from "./styles.js";
|
|
6
|
+
import { FramePreferenceIndex, framePreference } from "./frame-preferences.js";
|
|
2
7
|
import { IDML_LIMITS } from "./limits.js";
|
|
8
|
+
import { parseParts } from "./parse-parts.js";
|
|
9
|
+
import { parseStories } from "./story-model.js";
|
|
10
|
+
import { collectUnsupportedElements } from "./unsupported-elements.js";
|
|
3
11
|
import { isTag } from "domhandler";
|
|
4
|
-
import { Parser, parseDocument } from "htmlparser2";
|
|
5
12
|
//#region src/model.ts
|
|
6
|
-
const MAPPED_ELEMENTS = /* @__PURE__ */ new Set([
|
|
7
|
-
"Document",
|
|
8
|
-
"DocumentPreference",
|
|
9
|
-
"MarginPreference",
|
|
10
|
-
"MasterSpread",
|
|
11
|
-
"Spread",
|
|
12
|
-
"Page",
|
|
13
|
-
"Story",
|
|
14
|
-
"TextFrame"
|
|
15
|
-
]);
|
|
16
|
-
function localName(name) {
|
|
17
|
-
return name.includes(":") ? name.split(":").at(-1) ?? name : name;
|
|
18
|
-
}
|
|
19
13
|
function elements(root, name) {
|
|
20
14
|
const found = [];
|
|
21
15
|
const pending = [root];
|
|
@@ -35,12 +29,6 @@ function attribute(element, ...names) {
|
|
|
35
29
|
if (value !== void 0) return value;
|
|
36
30
|
}
|
|
37
31
|
}
|
|
38
|
-
function numberAttribute(element, name, fallback) {
|
|
39
|
-
const raw = element && attribute(element, name);
|
|
40
|
-
const value = raw === void 0 ? fallback : Number(raw);
|
|
41
|
-
if (value === void 0 || !Number.isFinite(value) || value < 0) throw refusal(`IDML ${name} must be a non-negative number`, `set ${name} to a non-negative numeric value`);
|
|
42
|
-
return value;
|
|
43
|
-
}
|
|
44
32
|
function boolAttribute(element, name, fallback) {
|
|
45
33
|
const raw = attribute(element, name);
|
|
46
34
|
if (raw === void 0) return fallback;
|
|
@@ -48,57 +36,14 @@ function boolAttribute(element, name, fallback) {
|
|
|
48
36
|
if (raw === "false") return false;
|
|
49
37
|
throw refusal(`IDML ${name} must be true or false`, `set ${name} to true or false`);
|
|
50
38
|
}
|
|
51
|
-
function checkXmlDepth(source, part, limit) {
|
|
52
|
-
let depth = 0;
|
|
53
|
-
new Parser({
|
|
54
|
-
onopentag() {
|
|
55
|
-
depth += 1;
|
|
56
|
-
if (depth > limit) throw refusal(`IDML XML part ${part} exceeds the ${limit} level XML depth limit`, `reduce its nesting to ${limit} levels or fewer`);
|
|
57
|
-
},
|
|
58
|
-
onclosetag() {
|
|
59
|
-
depth -= 1;
|
|
60
|
-
}
|
|
61
|
-
}, {
|
|
62
|
-
xmlMode: true,
|
|
63
|
-
recognizeSelfClosing: true
|
|
64
|
-
}).end(source);
|
|
65
|
-
}
|
|
66
|
-
function parseParts(entries, limits) {
|
|
67
|
-
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
68
|
-
return entries.filter((entry) => entry.name.toLowerCase().endsWith(".xml")).map((entry) => {
|
|
69
|
-
let source;
|
|
70
|
-
try {
|
|
71
|
-
source = decoder.decode(entry.bytes);
|
|
72
|
-
} catch {
|
|
73
|
-
throw refusal(`IDML XML part ${entry.name} is not valid UTF-8`, "export that XML part as UTF-8");
|
|
74
|
-
}
|
|
75
|
-
let document;
|
|
76
|
-
try {
|
|
77
|
-
checkXmlDepth(source, entry.name, limits.xmlDepth);
|
|
78
|
-
document = parseDocument(source, {
|
|
79
|
-
xmlMode: true,
|
|
80
|
-
recognizeSelfClosing: true
|
|
81
|
-
});
|
|
82
|
-
} catch (error) {
|
|
83
|
-
if (error instanceof Error && error.message.includes("XML depth limit")) throw error;
|
|
84
|
-
throw refusal(`IDML XML part ${entry.name} could not be parsed: ${String(error)}`, "export well-formed XML");
|
|
85
|
-
}
|
|
86
|
-
const all = elements(document);
|
|
87
|
-
if (all.length === 0) throw refusal(`IDML XML part ${entry.name} has no root element`, "export a non-empty well-formed XML part");
|
|
88
|
-
return {
|
|
89
|
-
name: entry.name,
|
|
90
|
-
document,
|
|
91
|
-
all
|
|
92
|
-
};
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
39
|
function page(element) {
|
|
96
40
|
const id = attribute(element, "Self");
|
|
97
41
|
if (!id) throw refusal("an IDML Page has no Self identifier", "give every Page a Self identifier");
|
|
98
42
|
return {
|
|
99
43
|
id,
|
|
100
44
|
appliedMaster: attribute(element, "AppliedMaster"),
|
|
101
|
-
stories: []
|
|
45
|
+
stories: [],
|
|
46
|
+
side: "single"
|
|
102
47
|
};
|
|
103
48
|
}
|
|
104
49
|
const IDENTITY = [
|
|
@@ -140,12 +85,19 @@ function pageArea(element) {
|
|
|
140
85
|
apply(matrix, [left, bottom])
|
|
141
86
|
];
|
|
142
87
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
88
|
+
function bounds(points) {
|
|
89
|
+
if (points.length === 0) return void 0;
|
|
90
|
+
const xs = points.map(([x]) => x);
|
|
91
|
+
const ys = points.map(([, y]) => y);
|
|
92
|
+
return {
|
|
93
|
+
left: Math.min(...xs),
|
|
94
|
+
top: Math.min(...ys),
|
|
95
|
+
right: Math.max(...xs),
|
|
96
|
+
bottom: Math.max(...ys)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
function frameBounds(element, matrix) {
|
|
100
|
+
return bounds(elements(element).map((candidate) => attribute(candidate, "Anchor")).filter((value) => value !== void 0).map((value) => value.trim().split(/\s+/).map(Number)).filter((value) => value.length === 2 && value.every(Number.isFinite)).map((point) => apply(matrix, point)));
|
|
149
101
|
}
|
|
150
102
|
function contains(area, [x, y]) {
|
|
151
103
|
let inside = false;
|
|
@@ -157,7 +109,22 @@ function contains(area, [x, y]) {
|
|
|
157
109
|
}
|
|
158
110
|
return inside;
|
|
159
111
|
}
|
|
160
|
-
function
|
|
112
|
+
function framePlacement(element, matrix, location) {
|
|
113
|
+
const story = attribute(element, "ParentStory");
|
|
114
|
+
if (!story) return void 0;
|
|
115
|
+
const preference = framePreference(element, location);
|
|
116
|
+
const box = frameBounds(element, matrix);
|
|
117
|
+
return {
|
|
118
|
+
id: attribute(element, "Self") ?? story,
|
|
119
|
+
story,
|
|
120
|
+
...box ? {
|
|
121
|
+
bounds: box,
|
|
122
|
+
center: [(box.left + box.right) / 2, (box.top + box.bottom) / 2]
|
|
123
|
+
} : {},
|
|
124
|
+
...preference ? { preference } : {}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
function spreadFrames(spread, location) {
|
|
161
128
|
const result = [];
|
|
162
129
|
const pending = [...spread.children].reverse().map((node) => ({
|
|
163
130
|
node,
|
|
@@ -170,11 +137,8 @@ function spreadFrames(spread) {
|
|
|
170
137
|
if (name === "Page" || name === "Spread" || name === "MasterSpread") continue;
|
|
171
138
|
const matrix = name === "Group" || name === "TextFrame" ? compose(current.matrix, transform(current.node)) : current.matrix;
|
|
172
139
|
if (name === "TextFrame") {
|
|
173
|
-
const
|
|
174
|
-
if (
|
|
175
|
-
story,
|
|
176
|
-
center: frameCenter(current.node, matrix)
|
|
177
|
-
});
|
|
140
|
+
const placement = framePlacement(current.node, matrix, `${location}#TextFrame[${result.length + 1}]`);
|
|
141
|
+
if (placement) result.push(placement);
|
|
178
142
|
continue;
|
|
179
143
|
}
|
|
180
144
|
for (let index = current.node.children.length - 1; index >= 0; index -= 1) pending.push({
|
|
@@ -184,25 +148,99 @@ function spreadFrames(spread) {
|
|
|
184
148
|
}
|
|
185
149
|
return result;
|
|
186
150
|
}
|
|
187
|
-
function
|
|
151
|
+
function assignSides(pageElements, pages) {
|
|
152
|
+
if (pages.length < 2) return;
|
|
153
|
+
const order = pageElements.map((element) => {
|
|
154
|
+
const area = pageArea(element);
|
|
155
|
+
return area ? bounds(area) : void 0;
|
|
156
|
+
}).map((area, index) => ({
|
|
157
|
+
index,
|
|
158
|
+
x: area ? (area.left + area.right) / 2 : index
|
|
159
|
+
})).sort((a, b) => a.x - b.x);
|
|
160
|
+
const left = order[0];
|
|
161
|
+
const right = order.at(-1);
|
|
162
|
+
const leftPage = left ? pages[left.index] : void 0;
|
|
163
|
+
const rightPage = right ? pages[right.index] : void 0;
|
|
164
|
+
if (leftPage) leftPage.side = "left";
|
|
165
|
+
if (rightPage && right?.index !== left?.index) rightPage.side = "right";
|
|
166
|
+
}
|
|
167
|
+
function masterFrames(frames, pageElements, pages) {
|
|
168
|
+
const result = pages.map(() => []);
|
|
169
|
+
const areas = pageElements.map((candidate) => {
|
|
170
|
+
const area = pageArea(candidate);
|
|
171
|
+
return area ? bounds(area) : void 0;
|
|
172
|
+
});
|
|
173
|
+
for (const frame of frames) {
|
|
174
|
+
const absolute = frame.bounds;
|
|
175
|
+
if (!absolute || !frame.center) continue;
|
|
176
|
+
const index = pageElements.findIndex((candidate) => {
|
|
177
|
+
const area = pageArea(candidate);
|
|
178
|
+
return area ? contains(area, frame.center) : false;
|
|
179
|
+
});
|
|
180
|
+
const area = areas[index];
|
|
181
|
+
if (index < 0 || !area) continue;
|
|
182
|
+
result[index]?.push({
|
|
183
|
+
id: frame.id,
|
|
184
|
+
story: frame.story,
|
|
185
|
+
bounds: {
|
|
186
|
+
left: absolute.left - area.left,
|
|
187
|
+
top: absolute.top - area.top,
|
|
188
|
+
width: absolute.right - absolute.left,
|
|
189
|
+
height: absolute.bottom - absolute.top
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return result;
|
|
194
|
+
}
|
|
195
|
+
function masterSpread(element, pageElements, pages, placements) {
|
|
196
|
+
const frames = masterFrames(placements, pageElements, pages);
|
|
197
|
+
const prefix = attribute(element, "NamePrefix") ?? "";
|
|
198
|
+
const base = attribute(element, "BaseName") ?? attribute(element, "Name") ?? attribute(element, "Self") ?? "master";
|
|
199
|
+
const name = prefix ? `${prefix}-${base}` : base;
|
|
200
|
+
const frameIds = new Set(frames.flat().map((frame) => frame.id));
|
|
201
|
+
const unmappedItems = element.children.flatMap((child) => {
|
|
202
|
+
if (!isTag(child)) return [];
|
|
203
|
+
const type = localName(child.name);
|
|
204
|
+
if (type === "Page" || type === "Properties") return [];
|
|
205
|
+
const id = attribute(child, "Self") ?? type;
|
|
206
|
+
if (type === "TextFrame" && frameIds.has(id)) return [];
|
|
207
|
+
return [{
|
|
208
|
+
type,
|
|
209
|
+
id
|
|
210
|
+
}];
|
|
211
|
+
});
|
|
212
|
+
return {
|
|
213
|
+
id: attribute(element, "Self") ?? base,
|
|
214
|
+
name,
|
|
215
|
+
...attribute(element, "BasedOn") ? { basedOn: attribute(element, "BasedOn") } : {},
|
|
216
|
+
pages: pages.map((value, index) => ({
|
|
217
|
+
...value,
|
|
218
|
+
margin: readMargin(elements(pageElements[index], "MarginPreference")[0]),
|
|
219
|
+
frames: frames[index] ?? []
|
|
220
|
+
})),
|
|
221
|
+
unmappedItems
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function placeStories(frames, pageElements, pages, report, framePreferences) {
|
|
188
225
|
const areas = pageElements.map(pageArea);
|
|
189
226
|
const frameStories = /* @__PURE__ */ new Set();
|
|
190
|
-
for (const frame of
|
|
227
|
+
for (const frame of frames) {
|
|
191
228
|
frameStories.add(frame.story);
|
|
229
|
+
framePreferences.add(frame.story, frame.preference);
|
|
192
230
|
const pageIndex = frame.center ? areas.findIndex((area) => area !== void 0 && contains(area, frame.center)) : -1;
|
|
193
231
|
const target = pages[pageIndex];
|
|
194
232
|
if (target && !target.stories.includes(frame.story)) target.stories.push(frame.story);
|
|
195
|
-
else if (pageIndex < 0)
|
|
233
|
+
else if (pageIndex < 0) report.add(report.unsupportedElements, "TextFrame off page", frame.story);
|
|
196
234
|
}
|
|
197
235
|
return frameStories;
|
|
198
236
|
}
|
|
199
|
-
function spreads(parts, kind, orderedNames,
|
|
237
|
+
function spreads(parts, kind, orderedNames, report, framePreferences) {
|
|
200
238
|
const result = [];
|
|
201
239
|
const frameStories = /* @__PURE__ */ new Set();
|
|
202
240
|
const byName = new Map(parts.map((part) => [part.name, part]));
|
|
203
241
|
const named = new Set(orderedNames);
|
|
204
242
|
const unlisted = parts.filter((part) => !named.has(part.name) && part.all.some((element) => localName(element.name) === kind && attribute(element, "Self"))).length;
|
|
205
|
-
if (unlisted > 0)
|
|
243
|
+
if (unlisted > 0) report.add(report.unsupportedElements, "unlisted spread part", kind, unlisted);
|
|
206
244
|
for (const name of orderedNames) {
|
|
207
245
|
const part = byName.get(name);
|
|
208
246
|
if (!part) throw refusal(`designmap.xml ${kind} src ${name} has no matching ZIP entry`, `add the named ${kind} part to the package`);
|
|
@@ -211,8 +249,10 @@ function spreads(parts, kind, orderedNames, unsupportedCounts) {
|
|
|
211
249
|
if (!id) continue;
|
|
212
250
|
const pageElements = elements(element, "Page");
|
|
213
251
|
const pages = pageElements.map(page);
|
|
214
|
-
|
|
215
|
-
|
|
252
|
+
assignSides(pageElements, pages);
|
|
253
|
+
const frames = spreadFrames(element, name);
|
|
254
|
+
for (const story of placeStories(frames, pageElements, pages, report, framePreferences)) frameStories.add(story);
|
|
255
|
+
result.push(kind === "MasterSpread" ? masterSpread(element, pageElements, pages, frames) : {
|
|
216
256
|
id,
|
|
217
257
|
pages
|
|
218
258
|
});
|
|
@@ -223,31 +263,23 @@ function spreads(parts, kind, orderedNames, unsupportedCounts) {
|
|
|
223
263
|
frameStories
|
|
224
264
|
};
|
|
225
265
|
}
|
|
226
|
-
function
|
|
227
|
-
return
|
|
228
|
-
}
|
|
229
|
-
function stories(parts) {
|
|
230
|
-
const result = [];
|
|
231
|
-
const seen = /* @__PURE__ */ new Set();
|
|
232
|
-
for (const part of parts) for (const element of part.all.filter((candidate) => localName(candidate.name) === "Story")) {
|
|
266
|
+
function textVariables(designmap) {
|
|
267
|
+
return designmap.all.filter((element) => localName(element.name) === "TextVariable").flatMap((element) => {
|
|
233
268
|
const id = attribute(element, "Self");
|
|
234
|
-
if (!id
|
|
235
|
-
|
|
236
|
-
|
|
269
|
+
if (!id) return [];
|
|
270
|
+
const preference = element.children.find((child) => isTag(child) && localName(child.name).endsWith("Preference"));
|
|
271
|
+
return [{
|
|
237
272
|
id,
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
273
|
+
name: attribute(element, "Name") ?? id,
|
|
274
|
+
type: attribute(element, "VariableType") ?? "unknown",
|
|
275
|
+
appliedCharacterStyle: preference ? attribute(preference, "AppliedCharacterStyle") : void 0,
|
|
276
|
+
searchStrategy: preference ? attribute(preference, "SearchStrategy") : void 0,
|
|
277
|
+
location: `designmap.xml#${id}`
|
|
278
|
+
}];
|
|
279
|
+
});
|
|
242
280
|
}
|
|
243
|
-
function
|
|
244
|
-
|
|
245
|
-
for (const part of parts) for (const element of part.all) {
|
|
246
|
-
const name = localName(element.name);
|
|
247
|
-
if (MAPPED_ELEMENTS.has(name)) continue;
|
|
248
|
-
counts.set(name, (counts.get(name) ?? 0) + 1);
|
|
249
|
-
}
|
|
250
|
-
return counts;
|
|
281
|
+
function spreadSources(document, kind) {
|
|
282
|
+
return document.all.filter((element) => localName(element.name) === kind).map((element) => attribute(element, "src")).filter((source) => source !== void 0);
|
|
251
283
|
}
|
|
252
284
|
function idmlModel(entries, limits = IDML_LIMITS) {
|
|
253
285
|
const parts = parseParts(entries, limits);
|
|
@@ -260,11 +292,12 @@ function idmlModel(entries, limits = IDML_LIMITS) {
|
|
|
260
292
|
const margin = parts.flatMap((part) => part.all).find((element) => localName(element.name) === "MarginPreference");
|
|
261
293
|
const binding = attribute(preference, "PageBinding") ?? attribute(document, "PageBinding") ?? "LeftToRight";
|
|
262
294
|
if (binding !== "LeftToRight" && binding !== "RightToLeft") throw refusal(`IDML PageBinding ${binding} is not supported`, "use LeftToRight or RightToLeft page binding");
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
const
|
|
295
|
+
const report = new ReportCollector();
|
|
296
|
+
collectUnsupportedElements(parts, report);
|
|
297
|
+
const framePreferences = new FramePreferenceIndex();
|
|
298
|
+
const spreadResult = spreads(parts, "Spread", spreadSources(designmap, "Spread"), report, framePreferences);
|
|
299
|
+
const masterResult = spreads(parts, "MasterSpread", spreadSources(designmap, "MasterSpread"), report, new FramePreferenceIndex());
|
|
266
300
|
const masterStoryIds = [...masterResult.frameStories].filter((story) => !spreadResult.frameStories.has(story));
|
|
267
|
-
if (masterStoryIds.length > 0) unsupportedCounts.set("master story", masterStoryIds.length);
|
|
268
301
|
return {
|
|
269
302
|
id: attribute(document, "Self") ?? "IDML document",
|
|
270
303
|
preferences: {
|
|
@@ -272,19 +305,18 @@ function idmlModel(entries, limits = IDML_LIMITS) {
|
|
|
272
305
|
pageHeight: numberAttribute(preference, "PageHeight"),
|
|
273
306
|
facingPages: boolAttribute(preference, "FacingPages", true),
|
|
274
307
|
binding: binding === "RightToLeft" ? "right-to-left" : "left-to-right",
|
|
275
|
-
margin:
|
|
276
|
-
top: numberAttribute(margin, "Top", 0),
|
|
277
|
-
bottom: numberAttribute(margin, "Bottom", 0),
|
|
278
|
-
left: numberAttribute(margin, "Left", 0),
|
|
279
|
-
right: numberAttribute(margin, "Right", 0)
|
|
280
|
-
}
|
|
308
|
+
margin: readMargin(margin)
|
|
281
309
|
},
|
|
282
310
|
spreads: spreadResult.values,
|
|
283
311
|
masterSpreads: masterResult.values,
|
|
284
|
-
stories:
|
|
312
|
+
stories: parseStories(parts, framePreferences, report),
|
|
313
|
+
bodyFramePreferences: framePreferences.values(),
|
|
314
|
+
textVariables: textVariables(designmap),
|
|
285
315
|
masterStoryIds,
|
|
286
316
|
partsRead: parts.map((part) => part.name),
|
|
287
|
-
|
|
317
|
+
styles: parts.flatMap((part) => part.name.endsWith("/Styles.xml") || part.name === "Resources/Styles.xml" ? parseStyles(part.all, part.name) : []),
|
|
318
|
+
swatches: parts.flatMap((part) => part.name.endsWith("/Graphic.xml") || part.name === "Resources/Graphic.xml" ? parseSwatches(part.all) : []),
|
|
319
|
+
report: report.data()
|
|
288
320
|
};
|
|
289
321
|
}
|
|
290
322
|
//#endregion
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { refusal } from "./refusal.js";
|
|
2
|
+
import { isTag } from "domhandler";
|
|
3
|
+
import { Parser, parseDocument } from "htmlparser2";
|
|
4
|
+
//#region src/parse-parts.ts
|
|
5
|
+
function allElements(root) {
|
|
6
|
+
const found = [];
|
|
7
|
+
const pending = [root];
|
|
8
|
+
while (pending.length > 0) {
|
|
9
|
+
const node = pending.pop();
|
|
10
|
+
if (!node) continue;
|
|
11
|
+
if (isTag(node)) found.push(node);
|
|
12
|
+
if ("children" in node) for (const child of [...node.children].reverse()) pending.push(child);
|
|
13
|
+
}
|
|
14
|
+
return found;
|
|
15
|
+
}
|
|
16
|
+
function checkDepth(source, part, limit) {
|
|
17
|
+
let depth = 0;
|
|
18
|
+
new Parser({
|
|
19
|
+
onopentag() {
|
|
20
|
+
depth += 1;
|
|
21
|
+
if (depth > limit) throw refusal(`IDML XML part ${part} exceeds the ${limit} level XML depth limit`, `reduce its nesting to ${limit} levels or fewer`);
|
|
22
|
+
},
|
|
23
|
+
onclosetag() {
|
|
24
|
+
depth -= 1;
|
|
25
|
+
}
|
|
26
|
+
}, {
|
|
27
|
+
xmlMode: true,
|
|
28
|
+
recognizeSelfClosing: true
|
|
29
|
+
}).end(source);
|
|
30
|
+
}
|
|
31
|
+
function parseParts(entries, limits) {
|
|
32
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
33
|
+
return entries.filter((entry) => entry.name.toLowerCase().endsWith(".xml")).map((entry) => {
|
|
34
|
+
let source;
|
|
35
|
+
try {
|
|
36
|
+
source = decoder.decode(entry.bytes);
|
|
37
|
+
} catch {
|
|
38
|
+
throw refusal(`IDML XML part ${entry.name} is not valid UTF-8`, "export that XML part as UTF-8");
|
|
39
|
+
}
|
|
40
|
+
let document;
|
|
41
|
+
try {
|
|
42
|
+
checkDepth(source, entry.name, limits.xmlDepth);
|
|
43
|
+
document = parseDocument(source, {
|
|
44
|
+
xmlMode: true,
|
|
45
|
+
recognizeSelfClosing: true
|
|
46
|
+
});
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error instanceof Error && error.message.includes("XML depth limit")) throw error;
|
|
49
|
+
throw refusal(`IDML XML part ${entry.name} could not be parsed: ${String(error)}`, "export well-formed XML");
|
|
50
|
+
}
|
|
51
|
+
const all = allElements(document);
|
|
52
|
+
if (all.length === 0) throw refusal(`IDML XML part ${entry.name} has no root element`, "export a non-empty well-formed XML part");
|
|
53
|
+
return {
|
|
54
|
+
name: entry.name,
|
|
55
|
+
document,
|
|
56
|
+
all
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
//#endregion
|
|
61
|
+
export { parseParts };
|
package/dist/report.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
//#region src/report.d.ts
|
|
2
|
+
export interface IdmlFontUse {
|
|
3
|
+
appliedFont: string;
|
|
4
|
+
fontStyle?: string;
|
|
5
|
+
locations: string[];
|
|
6
|
+
}
|
|
7
|
+
export interface IdmlMixedMasterChapter {
|
|
8
|
+
chapter: string;
|
|
9
|
+
side: 'recto' | 'verso';
|
|
10
|
+
masters: string[];
|
|
11
|
+
chosen: string;
|
|
12
|
+
}
|
|
13
|
+
export interface IdmlReportData {
|
|
14
|
+
unsupportedElements: Record<string, number>;
|
|
15
|
+
unsupportedStyleProperties: Record<string, number>;
|
|
16
|
+
unsupportedOverrideProperties: Record<string, number>;
|
|
17
|
+
unsupportedSpecialCharacters: Record<string, number>;
|
|
18
|
+
unsupportedLanguages: Record<string, number>;
|
|
19
|
+
spotColors: Record<string, number>;
|
|
20
|
+
locations: Record<string, string[]>;
|
|
21
|
+
fonts: IdmlFontUse[];
|
|
22
|
+
mixedMasterChapters: IdmlMixedMasterChapter[];
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
package/dist/report.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
//#region src/report.ts
|
|
2
|
+
const BUCKET_NAMES = {
|
|
3
|
+
unsupportedElements: "element",
|
|
4
|
+
unsupportedStyleProperties: "style",
|
|
5
|
+
unsupportedOverrideProperties: "override",
|
|
6
|
+
unsupportedSpecialCharacters: "special",
|
|
7
|
+
unsupportedLanguages: "language",
|
|
8
|
+
spotColors: "spot"
|
|
9
|
+
};
|
|
10
|
+
var ReportCollector = class {
|
|
11
|
+
locations = /* @__PURE__ */ new Map();
|
|
12
|
+
fonts = /* @__PURE__ */ new Map();
|
|
13
|
+
mixedMasterChapters;
|
|
14
|
+
buckets = Object.fromEntries(Object.keys(BUCKET_NAMES).map((name) => [name, /* @__PURE__ */ new Map()]));
|
|
15
|
+
get unsupportedElements() {
|
|
16
|
+
return this.buckets.unsupportedElements;
|
|
17
|
+
}
|
|
18
|
+
get unsupportedStyleProperties() {
|
|
19
|
+
return this.buckets.unsupportedStyleProperties;
|
|
20
|
+
}
|
|
21
|
+
get unsupportedOverrideProperties() {
|
|
22
|
+
return this.buckets.unsupportedOverrideProperties;
|
|
23
|
+
}
|
|
24
|
+
get unsupportedSpecialCharacters() {
|
|
25
|
+
return this.buckets.unsupportedSpecialCharacters;
|
|
26
|
+
}
|
|
27
|
+
get unsupportedLanguages() {
|
|
28
|
+
return this.buckets.unsupportedLanguages;
|
|
29
|
+
}
|
|
30
|
+
get spotColors() {
|
|
31
|
+
return this.buckets.spotColors;
|
|
32
|
+
}
|
|
33
|
+
constructor(seed) {
|
|
34
|
+
this.mixedMasterChapters = [...seed?.mixedMasterChapters ?? []];
|
|
35
|
+
for (const name of Object.keys(BUCKET_NAMES)) for (const [key, count] of Object.entries(seed?.[name] ?? {})) this.buckets[name].set(key, count);
|
|
36
|
+
for (const [key, locations] of Object.entries(seed?.locations ?? {})) this.locations.set(key, [...locations]);
|
|
37
|
+
for (const font of seed?.fonts ?? []) this.font(font.appliedFont, font.fontStyle, font.locations[0] ?? "Resources/Styles.xml");
|
|
38
|
+
}
|
|
39
|
+
add(bucket, name, location, count = 1) {
|
|
40
|
+
bucket.set(name, (bucket.get(name) ?? 0) + count);
|
|
41
|
+
const key = `${this.bucketName(bucket)}:${name}`;
|
|
42
|
+
const values = this.locations.get(key) ?? [];
|
|
43
|
+
if (values.length < 5 && !values.includes(location)) values.push(location);
|
|
44
|
+
this.locations.set(key, values);
|
|
45
|
+
}
|
|
46
|
+
addOnce(bucket, name, location) {
|
|
47
|
+
const key = `${this.bucketName(bucket)}:${name}`;
|
|
48
|
+
if (this.locations.get(key)?.includes(location)) return;
|
|
49
|
+
this.add(bucket, name, location);
|
|
50
|
+
}
|
|
51
|
+
font(appliedFont, fontStyle, location) {
|
|
52
|
+
if (!appliedFont) return;
|
|
53
|
+
const key = `${appliedFont}\u0000${fontStyle ?? ""}`;
|
|
54
|
+
const found = this.fonts.get(key) ?? {
|
|
55
|
+
appliedFont,
|
|
56
|
+
...fontStyle ? { fontStyle } : {},
|
|
57
|
+
locations: []
|
|
58
|
+
};
|
|
59
|
+
if (found.locations.length < 5 && !found.locations.includes(location)) found.locations.push(location);
|
|
60
|
+
this.fonts.set(key, found);
|
|
61
|
+
}
|
|
62
|
+
data() {
|
|
63
|
+
const record = (values) => Object.fromEntries([...values].sort(([a], [b]) => a.localeCompare(b)));
|
|
64
|
+
return {
|
|
65
|
+
...Object.fromEntries(Object.keys(BUCKET_NAMES).map((name) => [name, record(this.buckets[name])])),
|
|
66
|
+
locations: Object.fromEntries([...this.locations].sort(([a], [b]) => a.localeCompare(b))),
|
|
67
|
+
fonts: [...this.fonts.values()],
|
|
68
|
+
mixedMasterChapters: [...this.mixedMasterChapters]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
bucketName(bucket) {
|
|
72
|
+
const found = Object.keys(BUCKET_NAMES).find((name) => this.buckets[name] === bucket);
|
|
73
|
+
if (!found) throw new Error("Report bucket is not registered.");
|
|
74
|
+
return BUCKET_NAMES[found];
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
//#endregion
|
|
78
|
+
export { ReportCollector };
|