-
Notifications
You must be signed in to change notification settings - Fork 765
Open
Description
Support custom property

/**
* 生成 custom.xml 用于 Office Open XML 的自定义属性
* @param customProps 形如 { key1: value1, key2: value2 }
* @returns custom.xml 内容字符串
*/
export function makeXmlCustom (customProps: Record<string, string> = {}): string {
let xml = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/custom-properties" xmlns:vt="http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes">
`
let pid = 2
for (const [name, value] of Object.entries(customProps)) {
xml += `<property fmtid="{D5CDD505-2E9C-101B-9397-08002B2CF9AE}" pid="${pid++}" name="${encodeXmlEntities(name)}"><vt:lpwstr>${encodeXmlEntities(value)}</vt:lpwstr></property>
`
}
xml += `</Properties>
`
return xml
}
export function makeXmlContTypes (slides: PresSlide[], slideLayouts: SlideLayout[], masterSlide?: PresSlide): string {
let strXml = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>' + CRLF
strXml += '<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">'
strXml += '<Default Extension="xml" ContentType="application/xml"/>'
strXml += '<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>'
strXml += '<Default Extension="jpeg" ContentType="image/jpeg"/>'
strXml += '<Default Extension="jpg" ContentType="image/jpg"/>'
strXml += '<Default Extension="svg" ContentType="image/svg+xml"/>'
// STEP 1: Add standard/any media types used in Presentation
strXml += '<Default Extension="png" ContentType="image/png"/>'
strXml += '<Default Extension="gif" ContentType="image/gif"/>'
strXml += '<Default Extension="m4v" ContentType="video/mp4"/>' // NOTE: Hard-Code this extension as it wont be created in loop below (as extn !== type)
strXml += '<Default Extension="mp4" ContentType="video/mp4"/>' // NOTE: Hard-Code this extension as it wont be created in loop below (as extn !== type)
slides.forEach(slide => {
(slide._relsMedia || []).forEach(rel => {
if (rel.type !== 'image' && rel.type !== 'online' && rel.type !== 'chart' && rel.extn !== 'm4v' && !strXml.includes(rel.type)) {
strXml += '<Default Extension="' + rel.extn + '" ContentType="' + rel.type + '"/>'
}
})
})
strXml += '<Default Extension="vml" ContentType="application/vnd.openxmlformats-officedocument.vmlDrawing"/>'
strXml += '<Default Extension="xlsx" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"/>'
// STEP 2: Add presentation and slide master(s)/slide(s)
strXml += '<Override PartName="/ppt/presentation.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"/>'
strXml += '<Override PartName="/ppt/notesMasters/notesMaster1.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml"/>'
slides.forEach((slide, idx) => {
strXml += `<Override PartName="/ppt/slideMasters/slideMaster${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"/>`
strXml += `<Override PartName="/ppt/slides/slide${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slide+xml"/>`
// Add charts if any
slide._relsChart.forEach(rel => {
strXml += `<Override PartName="${rel.Target}" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>`
})
})
// STEP 3: Core PPT
strXml += '<Override PartName="/ppt/presProps.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.presProps+xml"/>'
strXml += '<Override PartName="/ppt/viewProps.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml"/>'
strXml += '<Override PartName="/ppt/theme/theme1.xml" ContentType="application/vnd.openxmlformats-officedocument.theme+xml"/>'
strXml += '<Override PartName="/ppt/tableStyles.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml"/>'
// STEP 4: Add Slide Layouts
slideLayouts.forEach((layout, idx) => {
strXml += `<Override PartName="/ppt/slideLayouts/slideLayout${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"/>`
; (layout._relsChart || []).forEach(rel => {
strXml += ' <Override PartName="' + rel.Target + '" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>'
})
})
// STEP 5: Add notes slide(s)
slides.forEach((_slide, idx) => {
strXml += `<Override PartName="/ppt/notesSlides/notesSlide${idx + 1}.xml" ContentType="application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"/>`
})
// STEP 6: Add rels
masterSlide._relsChart.forEach(rel => {
strXml += ' <Override PartName="' + rel.Target + '" ContentType="application/vnd.openxmlformats-officedocument.drawingml.chart+xml"/>'
})
masterSlide._relsMedia.forEach(rel => {
if (rel.type !== 'image' && rel.type !== 'online' && rel.type !== 'chart' && rel.extn !== 'm4v' && !strXml.includes(rel.type)) { strXml += ' <Default Extension="' + rel.extn + '" ContentType="' + rel.type + '"/>' }
})
// LAST: Finish XML (Resume core)
strXml += ' <Override PartName="/docProps/core.xml" ContentType="application/vnd.openxmlformats-package.core-properties+xml"/>'
strXml += ' <Override PartName="/docProps/app.xml" ContentType="application/vnd.openxmlformats-officedocument.extended-properties+xml"/>'
strXml += ' <Override PartName="/docProps/custom.xml" ContentType="application/vnd.openxmlformats-officedocument.custom-properties+xml"/>';
strXml += '</Types>'
return strXml
}
src/pptxgen.ts
/**
* @depricated custom Property
*/
private _customProps: Record<string, string> = {}
public set customProps (value: Record<string, string>) {
this._customProps = value
}
public get customProps (): Record<string, string> {
return this._customProps
}
private readonly exportPresentation = async (props: WriteProps): Promise<string | ArrayBuffer | Blob | Buffer | Uint8Array> => {
const arrChartPromises: Promise<string>[] = []
let arrMediaPromises: Promise<string>[] = []
const zip = new JSZip()
// STEP 1: Read/Encode all Media before zip as base64 content, etc. is required
this.slides.forEach(slide => {
arrMediaPromises = arrMediaPromises.concat(genMedia.encodeSlideMediaRels(slide))
})
this.slideLayouts.forEach(layout => {
arrMediaPromises = arrMediaPromises.concat(genMedia.encodeSlideMediaRels(layout))
})
arrMediaPromises = arrMediaPromises.concat(genMedia.encodeSlideMediaRels(this.masterSlide))
// STEP 2: Wait for Promises (if any) then generate the PPTX file
return await Promise.all(arrMediaPromises).then(async () => {
// A: Add empty placeholder objects to slides that don't already have them
this.slides.forEach(slide => {
if (slide._slideLayout) genObj.addPlaceholdersToSlideLayouts(slide)
})
// B: Add all required folders and files
zip.folder('_rels')
zip.folder('docProps')
zip.folder('ppt').folder('_rels')
zip.folder('ppt/charts').folder('_rels')
zip.folder('ppt/embeddings')
zip.folder('ppt/media')
zip.folder('ppt/slideLayouts').folder('_rels')
zip.folder('ppt/slideMasters').folder('_rels')
zip.folder('ppt/slides').folder('_rels')
zip.folder('ppt/theme')
zip.folder('ppt/notesMasters').folder('_rels')
zip.folder('ppt/notesSlides').folder('_rels')
zip.file('[Content_Types].xml', genXml.makeXmlContTypes(this.slides, this.slideLayouts, this.masterSlide)) // TODO: pass only `this` like below! 20200206
zip.file('_rels/.rels', genXml.makeXmlRootRels())
zip.file('docProps/app.xml', genXml.makeXmlApp(this.slides, this.company)) // TODO: pass only `this` like below! 20200206
zip.file('docProps/core.xml', genXml.makeXmlCore(this.title, this.subject, this.author, this.revision)) // TODO: pass only `this` like below! 20200206
zip.file('docProps/custom.xml', genXml.makeXmlCustom(this.customProps || {}))
zip.file('ppt/_rels/presentation.xml.rels', genXml.makeXmlPresentationRels(this.slides))
zip.file('ppt/theme/theme1.xml', genXml.makeXmlTheme(this))
zip.file('ppt/presentation.xml', genXml.makeXmlPresentation(this))
zip.file('ppt/presProps.xml', genXml.makeXmlPresProps())
zip.file('ppt/tableStyles.xml', genXml.makeXmlTableStyles())
zip.file('ppt/viewProps.xml', genXml.makeXmlViewProps())
// C: Create a Layout/Master/Rel/Slide file for each SlideLayout and Slide
this.slideLayouts.forEach((layout, idx) => {
zip.file(`ppt/slideLayouts/slideLayout${idx + 1}.xml`, genXml.makeXmlLayout(layout))
zip.file(`ppt/slideLayouts/_rels/slideLayout${idx + 1}.xml.rels`, genXml.makeXmlSlideLayoutRel(idx + 1, this.slideLayouts))
})
this.slides.forEach((slide, idx) => {
zip.file(`ppt/slides/slide${idx + 1}.xml`, genXml.makeXmlSlide(slide))
zip.file(`ppt/slides/_rels/slide${idx + 1}.xml.rels`, genXml.makeXmlSlideRel(this.slides, this.slideLayouts, idx + 1))
// Create all slide notes related items. Notes of empty strings are created for slides which do not have notes specified, to keep track of _rels.
zip.file(`ppt/notesSlides/notesSlide${idx + 1}.xml`, genXml.makeXmlNotesSlide(slide))
zip.file(`ppt/notesSlides/_rels/notesSlide${idx + 1}.xml.rels`, genXml.makeXmlNotesSlideRel(idx + 1))
})
zip.file('ppt/slideMasters/slideMaster1.xml', genXml.makeXmlMaster(this.masterSlide, this.slideLayouts))
zip.file('ppt/slideMasters/_rels/slideMaster1.xml.rels', genXml.makeXmlMasterRel(this.masterSlide, this.slideLayouts))
zip.file('ppt/notesMasters/notesMaster1.xml', genXml.makeXmlNotesMaster())
zip.file('ppt/notesMasters/_rels/notesMaster1.xml.rels', genXml.makeXmlNotesMasterRel())
// D: Create all Rels (images, media, chart data)
this.slideLayouts.forEach(layout => {
this.createChartMediaRels(layout, zip, arrChartPromises)
})
this.slides.forEach(slide => {
this.createChartMediaRels(slide, zip, arrChartPromises)
})
this.createChartMediaRels(this.masterSlide, zip, arrChartPromises)
// E: Wait for Promises (if any) then generate the PPTX file
return await Promise.all(arrChartPromises).then(async () => {
if (props.outputType === 'STREAM') {
// A: stream file
return await zip.generateAsync({ type: 'nodebuffer', compression: props.compression ? 'DEFLATE' : 'STORE' })
} else if (props.outputType) {
// B: Node [fs]: Output type user option or default
return await zip.generateAsync({ type: props.outputType })
} else {
// C: Browser: Output blob as app/ms-pptx
return await zip.generateAsync({ type: 'blob', compression: props.compression ? 'DEFLATE' : 'STORE' })
}
})
})
}
types/index.d.ts
/**
* Presentation custom properties
* @type {Record<string, string>}
*/
customProps: Record<string, string>
use
pptx.customProps = { AIGC: AIGC GENTERATE
};
Metadata
Metadata
Assignees
Labels
No labels