feat: release Office Tools 0.1.0

This commit is contained in:
2026-08-31 22:37:01 +02:00
commit a5524e27e3
90 changed files with 16328 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
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,
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";
}