168 lines
4.9 KiB
TypeScript
168 lines
4.9 KiB
TypeScript
import { prepareOdfPackage, type PreparedOdfPackage } from "./package";
|
|
import { parseFrameImage, parseTextBlocks, parseTextTable } from "./text";
|
|
import type {
|
|
OdfParseOptions,
|
|
OdfPresentationDocument,
|
|
OdfPresentationShape,
|
|
OdfPresentationShapeType,
|
|
OdfPresentationSlide,
|
|
} from "./types";
|
|
import { OdfParseError } from "./types";
|
|
import {
|
|
NS,
|
|
attribute,
|
|
childElements,
|
|
childrenNamed,
|
|
firstChildNamed,
|
|
parseLength,
|
|
type XmlElement,
|
|
} from "./xml";
|
|
|
|
export function parseOdp(
|
|
input: ArrayBuffer,
|
|
options: Omit<OdfParseOptions, "expectedFormat"> = {},
|
|
): OdfPresentationDocument {
|
|
const prepared = prepareOdfPackage(input, {
|
|
...options,
|
|
expectedFormat: "odp",
|
|
});
|
|
return parsePreparedOdp(prepared);
|
|
}
|
|
|
|
export function parsePreparedOdp(
|
|
prepared: PreparedOdfPackage,
|
|
): OdfPresentationDocument {
|
|
if (prepared.format !== "odp") {
|
|
throw new OdfParseError(
|
|
"type-mismatch",
|
|
"Prepared package is not an ODP document",
|
|
);
|
|
}
|
|
const body = firstChildNamed(
|
|
prepared.content.documentElement!,
|
|
NS.office,
|
|
"body",
|
|
);
|
|
const presentation = body && firstChildNamed(body, NS.office, "presentation");
|
|
if (!presentation) {
|
|
throw new OdfParseError(
|
|
"type-mismatch",
|
|
"ODP content.xml does not contain an office:presentation document body",
|
|
);
|
|
}
|
|
const slides = childrenNamed(presentation, NS.draw, "page").map(
|
|
(page, index) => parseSlide(page, index, prepared.context),
|
|
);
|
|
return {
|
|
format: "odp",
|
|
mediaType: prepared.mediaType,
|
|
metadata: prepared.metadata,
|
|
styles: prepared.styles,
|
|
assets: prepared.context.assets,
|
|
warnings: prepared.context.warnings,
|
|
pageWidth: prepared.pageWidth,
|
|
pageHeight: prepared.pageHeight,
|
|
slides,
|
|
};
|
|
}
|
|
|
|
function parseSlide(
|
|
page: XmlElement,
|
|
index: number,
|
|
context: ReturnType<typeof prepareOdfPackage>["context"],
|
|
): OdfPresentationSlide {
|
|
const notesElement = firstChildNamed(page, NS.presentation, "notes");
|
|
const shapes: OdfPresentationShape[] = [];
|
|
let shapeIndex = 0;
|
|
for (const element of childElements(page)) {
|
|
if (element === notesElement || element.namespaceURI !== NS.draw) continue;
|
|
shapes.push(
|
|
parseShape(element, `slide-${index + 1}-shape-${++shapeIndex}`, context),
|
|
);
|
|
}
|
|
return {
|
|
name: attribute(page, NS.draw, "name") ?? `Slide ${index + 1}`,
|
|
styleName: attribute(page, NS.draw, "style-name"),
|
|
masterPageName: attribute(page, NS.draw, "master-page-name"),
|
|
layoutName: attribute(
|
|
page,
|
|
NS.presentation,
|
|
"presentation-page-layout-name",
|
|
),
|
|
shapes,
|
|
notes: notesElement ? parseTextBlocks(notesElement, context) : [],
|
|
};
|
|
}
|
|
|
|
function parseShape(
|
|
element: XmlElement,
|
|
fallbackId: string,
|
|
context: ReturnType<typeof prepareOdfPackage>["context"],
|
|
): OdfPresentationShape {
|
|
const image =
|
|
element.localName === "frame"
|
|
? parseFrameImage(element, context)
|
|
: undefined;
|
|
const textBox = firstChildNamed(element, NS.draw, "text-box");
|
|
const tableElement =
|
|
firstChildNamed(element, NS.table, "table") ??
|
|
(textBox ? firstChildNamed(textBox, NS.table, "table") : undefined);
|
|
const table = tableElement
|
|
? parseTextTable(tableElement, context)
|
|
: undefined;
|
|
const children: OdfPresentationShape[] = [];
|
|
if (element.localName === "g") {
|
|
let index = 0;
|
|
for (const child of childElements(element)) {
|
|
if (child.namespaceURI === NS.draw) {
|
|
children.push(parseShape(child, `${fallbackId}-${++index}`, context));
|
|
}
|
|
}
|
|
}
|
|
const blocks = textBox
|
|
? parseTextBlocks(textBox, context).filter(
|
|
(block) => block.kind !== "table",
|
|
)
|
|
: parseTextBlocks(element, context);
|
|
return {
|
|
id: attribute(element, NS.draw, "id") ?? fallbackId,
|
|
type: shapeType(
|
|
element,
|
|
image !== undefined,
|
|
table !== undefined,
|
|
textBox !== undefined,
|
|
),
|
|
name: attribute(element, NS.draw, "name"),
|
|
styleName: attribute(element, NS.draw, "style-name"),
|
|
textStyleName: attribute(element, NS.draw, "text-style-name"),
|
|
layer: attribute(element, NS.draw, "layer"),
|
|
x: parseLength(attribute(element, NS.svg, "x")),
|
|
y: parseLength(attribute(element, NS.svg, "y")),
|
|
width: parseLength(attribute(element, NS.svg, "width")),
|
|
height: parseLength(attribute(element, NS.svg, "height")),
|
|
transform: attribute(element, NS.draw, "transform"),
|
|
blocks,
|
|
image,
|
|
table,
|
|
children,
|
|
};
|
|
}
|
|
|
|
function shapeType(
|
|
element: XmlElement,
|
|
hasImage: boolean,
|
|
hasTable: boolean,
|
|
hasTextBox: boolean,
|
|
): OdfPresentationShapeType {
|
|
if (hasImage) return "image";
|
|
if (hasTable) return "table";
|
|
if (hasTextBox) return "text-box";
|
|
if (element.localName === "rect") return "rectangle";
|
|
if (element.localName === "ellipse" || element.localName === "circle")
|
|
return "ellipse";
|
|
if (element.localName === "line") return "line";
|
|
if (element.localName === "custom-shape") return "custom";
|
|
if (element.localName === "g") return "group";
|
|
return "unknown";
|
|
}
|