UNPKG

docxml

Version:

TypeScript (component) library for building and parsing a DOCX file

304 lines (302 loc) 13.3 kB
"use strict"; var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _Image_meta; Object.defineProperty(exports, "__esModule", { value: true }); exports.Image = void 0; const BinaryFile_js_1 = require("../classes/BinaryFile.js"); const Component_js_1 = require("../classes/Component.js"); const enums_js_1 = require("../enums.js"); const components_js_1 = require("../utilities/components.js"); const dom_js_1 = require("../utilities/dom.js"); const drawingml_extensions_js_1 = require("../utilities/drawingml-extensions.js"); const identifiers_js_1 = require("../utilities/identifiers.js"); const length_js_1 = require("../utilities/length.js"); const mime_types_js_1 = require("../utilities/mime-types.js"); const namespaces_js_1 = require("../utilities/namespaces.js"); const xquery_js_1 = require("../utilities/xquery.js"); /** * A component that represents an image in your DOCX document. You can create a new image by * passing any promise to an `Uint8Array` into the `data` prop, eg. get it from your file system * or from a web request. */ class Image extends Component_js_1.Component { constructor(props, ...children) { super(props, ...children); _Image_meta.set(this, void 0); __classPrivateFieldSet(this, _Image_meta, { location: `word/media/${(0, identifiers_js_1.createRandomId)('img')}`, mime: props.mime ? Promise.resolve(props.mime) : null, relationshipId: null, extensions: {}, }, "f"); if (props.dataExtensions) { const { svg } = props.dataExtensions; if (svg !== undefined) { __classPrivateFieldGet(this, _Image_meta, "f").extensions.svg = { location: `word/media/${(0, identifiers_js_1.createRandomId)('svg')}`, relationshipId: null, }; } } } get meta() { const embedMeta = __classPrivateFieldGet(this, _Image_meta, "f"); const props = this.props; return { get location() { return embedMeta.location; }, get mime() { if (embedMeta.mime === null) { embedMeta.mime = new Promise((resolve) => { props.data.then((data) => { resolve((0, mime_types_js_1.getMimeTypeForUint8Array)(data)); }); }); } return embedMeta.mime; }, get relationshipId() { return embedMeta.relationshipId; }, get extensions() { return { get svg() { const { svg } = embedMeta.extensions; if (!svg) { return undefined; } return { get location() { return svg.location; }, get relationshipId() { return svg.relationshipId; }, }; }, }; }, }; } /** * An event hook with which this component can ensure that the correct relationship type is * recorded to the relationship XML. */ async ensureRelationship(relationships) { const { location, mime, extensions } = this.meta; __classPrivateFieldGet(this, _Image_meta, "f").relationshipId = relationships.add(enums_js_1.RelationshipType.image, BinaryFile_js_1.BinaryFile.fromData(this.props.data, location, await mime)); const { svg } = extensions; if (__classPrivateFieldGet(this, _Image_meta, "f").extensions.svg && svg && this.props.dataExtensions?.svg) { __classPrivateFieldGet(this, _Image_meta, "f").extensions.svg.relationshipId = relationships.add(enums_js_1.RelationshipType.image, BinaryFile_js_1.BinaryFile.fromData(new TextEncoder().encode(await this.props.dataExtensions.svg), svg.location, enums_js_1.FileMime.svg)); } } /** * Creates an XML DOM node for this component instance. */ toNode(_ancestry) { if (!__classPrivateFieldGet(this, _Image_meta, "f").relationshipId) { throw new Error('Cannot serialize an image outside the context of an Document'); } let extensionList = null; const { svg } = this.meta.extensions; if (svg) { extensionList = (0, dom_js_1.create)(` element ${namespaces_js_1.QNS.a}extLst { element ${namespaces_js_1.QNS.a}ext { attribute uri { $extLstUseLocalDpi }, element ${namespaces_js_1.QNS.a14}useLocalDpi { attribute val { "0" } } }, element ${namespaces_js_1.QNS.a}ext { attribute uri { $extLstSvg }, element ${namespaces_js_1.QNS.asvg}svgBlip { attribute ${namespaces_js_1.QNS.r}embed { $relationshipId } } } } `, { relationshipId: svg.relationshipId, extLstUseLocalDpi: drawingml_extensions_js_1.extensionListUris.useLocalDpi, extLstSvg: drawingml_extensions_js_1.extensionListUris.svg, }); } return (0, dom_js_1.create)(` element ${namespaces_js_1.QNS.w}drawing { element ${namespaces_js_1.QNS.wp}inline { element ${namespaces_js_1.QNS.wp}extent { attribute cx { $width }, attribute cy { $height } }, element ${namespaces_js_1.QNS.wp}docPr { attribute id { $identifier }, attribute name { $name }, attribute descr { $desc } }, element ${namespaces_js_1.QNS.wp}cNvGraphicFramePr { element ${namespaces_js_1.QNS.a}graphicFrameLocks { attribute noChangeAspect { "1" } } }, (: nb: _Must_ be prefixed with "a" or MS Word will refuse to open :) element ${namespaces_js_1.QNS.a}graphic { element ${namespaces_js_1.QNS.a}graphicData { attribute uri { "${namespaces_js_1.NamespaceUri.pic}"}, element ${namespaces_js_1.QNS.pic}pic { element ${namespaces_js_1.QNS.pic}nvPicPr { element ${namespaces_js_1.QNS.pic}cNvPr { attribute id { $identifier }, attribute name { $name }, attribute descr { $desc } }, element ${namespaces_js_1.QNS.pic}cNvPicPr {} }, element ${namespaces_js_1.QNS.pic}blipFill { element ${namespaces_js_1.QNS.a}blip { attribute ${namespaces_js_1.QNS.r}embed { $relationshipId }, attribute cstate { "print" }, $extensionList }, element ${namespaces_js_1.QNS.a}stretch { element ${namespaces_js_1.QNS.a}fillRect {} } }, element ${namespaces_js_1.QNS.pic}spPr { element ${namespaces_js_1.QNS.a}xfrm { element ${namespaces_js_1.QNS.a}off { attribute x { "0" }, attribute y { "0" } }, element ${namespaces_js_1.QNS.a}ext { attribute cx { $width }, attribute cy { $height } } }, element ${namespaces_js_1.QNS.a}prstGeom { attribute prst { "rect" }, element ${namespaces_js_1.QNS.a}avLst {} } } } } } } } `, { identifier: (0, identifiers_js_1.createUniqueNumericIdentifier)(), relationshipId: __classPrivateFieldGet(this, _Image_meta, "f").relationshipId, width: Math.round(this.props.width.emu), height: Math.round(this.props.height.emu), name: this.props.title || '', desc: this.props.alt || '', extensionList, }); } /** * Asserts whether or not a given XML node correlates with this component. */ static matchesNode(node) { return node.nodeName === 'w:drawing'; } /** * Instantiate this component from the XML in an existing DOCX file. */ static fromNode(node, { archive, relationships }) { // Important nodes const inlineNode = (0, xquery_js_1.evaluateXPathToFirstNode)(`./${namespaces_js_1.QNS.wp}inline`, node); const picNode = (0, xquery_js_1.evaluateXPathToFirstNode)(`./${namespaces_js_1.QNS.a}graphic/${namespaces_js_1.QNS.a}graphicData/${namespaces_js_1.QNS.pic}pic`, inlineNode); const title = (0, xquery_js_1.evaluateXPathToString)(`./${namespaces_js_1.QNS.wp}docPr/@name/string()`, inlineNode); const width = (0, length_js_1.emu)((0, xquery_js_1.evaluateXPathToNumber)(`./${namespaces_js_1.QNS.wp}extent/@cx/number()`, inlineNode)); const height = (0, length_js_1.emu)((0, xquery_js_1.evaluateXPathToNumber)(`./${namespaces_js_1.QNS.wp}extent/@cy/number()`, inlineNode)); if (relationships === null) { // Our simplified images are always expected to reference a relationship ID throw new Error('Failed to load image. The image is referencing a relationship ID but RelationhipsXml is null in the context.'); } const blipNode = (0, xquery_js_1.evaluateXPathToFirstNode)(`${namespaces_js_1.QNS.pic}blipFill/${namespaces_js_1.QNS.a}blip`, picNode); if (blipNode === null) { throw new Error('Failed to load image. No blip found inside a blipFill.'); } const { main, svg } = extractDataFromBlipNode(archive, relationships, blipNode); const dataExtensions = {}; if (svg) { dataExtensions.svg = svg.data; } const image = new Image({ data: main.data, dataExtensions, title, width, height, }); __classPrivateFieldGet(image, _Image_meta, "f").location = main.location; if (svg) { const { svg: svgMeta } = __classPrivateFieldGet(image, _Image_meta, "f").extensions; // We are certain that if we pass `dataExtensions` with `svg` in it // `Image` construtor makes it so image.#meta.extensions has `svg` too. // eslint-disable-next-line @typescript-eslint/no-non-null-assertion svgMeta.location = svg.location; } return image; } } exports.Image = Image; _Image_meta = new WeakMap(); Object.defineProperty(Image, "children", { enumerable: true, configurable: true, writable: true, value: [] }); Object.defineProperty(Image, "mixed", { enumerable: true, configurable: true, writable: true, value: false }); (0, components_js_1.registerComponent)(Image); function extractDataFromBlipNode(archive, relationships, blipNode) { const blipEmbedRel = (0, xquery_js_1.evaluateXPathToString)(`@${namespaces_js_1.QNS.r}embed/string()`, blipNode); const location = relationships.getTarget(blipEmbedRel); const data = archive.readBinary(location); const allLocationsAndData = { main: { data, location, }, }; const blipextLst = (0, xquery_js_1.evaluateXPathToNodes)(`./extLst/*`, blipNode); blipextLst.forEach((node) => { if (node.nodeType !== 1) { return; } const element = node; const extensionUri = element.getAttribute('uri'); if (extensionUri === drawingml_extensions_js_1.extensionListUris.svg) { const extensionRel = element.children[0].getAttributeNS(namespaces_js_1.NamespaceUri.r, 'embed'); if (extensionRel === null) { throw new Error('Failed to load image SVG extension. SVG extension URI found in extLst but its node does not follow the known format.'); } const location = relationships.getTarget(extensionRel); const data = archive.readText(location); allLocationsAndData.svg = { location, data, }; return; } // Implement other similar blip extensions here // if (extensionUri === "some other rui") { } }); return allLocationsAndData; }