feat: release Office Tools 0.1.0
This commit is contained in:
@@ -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";
|
||||
}
|
||||
Reference in New Issue
Block a user