Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@jsreport/jsreport-pptx

Package Overview
Dependencies
Maintainers
2
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jsreport/jsreport-pptx - npm Package Compare versions

Comparing version 3.2.1 to 3.3.0

126

lib/postprocess/image.js
const sizeOf = require('image-size')
const { nodeListToArray } = require('../utils')
const axios = require('axios')
const { nodeListToArray, pxToEMU, cmToEMU } = require('../utils')
module.exports = (files) => {
module.exports = async (files) => {
const contentTypesFile = files.find(f => f.path === '[Content_Types].xml')

@@ -30,6 +31,40 @@ const types = contentTypesFile.doc.getElementsByTagName('Types')[0]

const el = imagesEl[i]
const imageSrc = el.getAttribute('src')
const imageExtensions = imageSrc.split(';')[0].split('/')[1]
const imageBuffer = Buffer.from(imageSrc.split(';')[1].substring('base64,'.length), 'base64')
if (!el.textContent.includes('$pptxImage')) {
throw new Error('Invalid pptxImage element')
}
const match = el.textContent.match(/\$pptxImage([^$]*)\$/)
const imageConfig = JSON.parse(Buffer.from(match[1], 'base64').toString())
let imageExtension
let imageBuffer
if (imageConfig.src && imageConfig.src.startsWith('data:')) {
imageExtension = imageConfig.src.split(';')[0].split('/')[1]
imageBuffer = Buffer.from(imageConfig.src.split(';')[1].substring('base64,'.length), 'base64')
} else {
const response = await axios({
url: imageConfig.src,
responseType: 'arraybuffer',
method: 'GET'
})
const contentType = response.headers['content-type'] || response.headers['Content-Type']
if (!contentType) {
throw new Error(`Empty content-type for remote image at "${imageConfig.src}"`)
}
const extensionsParts = contentType.split(';')[0].split('/').filter((p) => p)
if (extensionsParts.length === 0 || extensionsParts.length > 2) {
throw new Error(`Invalid content-type "${contentType}" for remote image at "${imageConfig.src}"`)
}
// some servers returns the image content type without the "image/" prefix
imageExtension = extensionsParts.length === 1 ? extensionsParts[0] : extensionsParts[1]
imageBuffer = Buffer.from(response.data)
}
imagesStartId++

@@ -39,6 +74,6 @@ const relEl = relsDoc.createElement('Relationship')

relEl.setAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image')
relEl.setAttribute('Target', `../media/imageJsReport${imagesStartId}.${imageExtensions}`)
relEl.setAttribute('Target', `../media/imageJsReport${imagesStartId}.${imageExtension}`)
files.push({
path: `ppt/media/imageJsReport${imagesStartId}.${imageExtensions}`,
path: `ppt/media/imageJsReport${imagesStartId}.${imageExtension}`,
data: imageBuffer

@@ -49,11 +84,64 @@ })

const existsTypeForImageExtension = nodeListToArray(types.getElementsByTagName('Default')).find(
d => d.getAttribute('Extension') === imageExtension
) != null
if (!existsTypeForImageExtension) {
const newDefault = contentTypesFile.doc.createElement('Default')
newDefault.setAttribute('Extension', imageExtension)
newDefault.setAttribute('ContentType', `image/${imageExtension}`)
types.appendChild(newDefault)
}
const grpSp = el.parentNode.parentNode.parentNode.parentNode.parentNode.parentNode
const aExt = grpSp.getElementsByTagName('p:grpSpPr')[0].getElementsByTagName('a:xfrm')[0].getElementsByTagName('a:ext')[0]
const blip = grpSp.getElementsByTagName('a:blip')[0]
let imageWidthEMU
let imageHeightEMU
if (imageConfig.width != null || imageConfig.height != null) {
const imageDimension = sizeOf(imageBuffer)
const targetWidth = getDimension(imageConfig.width)
const targetHeight = getDimension(imageConfig.height)
if (targetWidth) {
imageWidthEMU =
targetWidth.unit === 'cm'
? cmToEMU(targetWidth.value)
: pxToEMU(targetWidth.value)
}
if (targetHeight) {
imageHeightEMU =
targetHeight.unit === 'cm'
? cmToEMU(targetHeight.value)
: pxToEMU(targetHeight.value)
}
if (imageWidthEMU != null && imageHeightEMU == null) {
// adjust height based on aspect ratio of image
imageHeightEMU = Math.round(
imageWidthEMU *
(pxToEMU(imageDimension.height) / pxToEMU(imageDimension.width))
)
} else if (imageHeightEMU != null && imageWidthEMU == null) {
// adjust width based on aspect ratio of image
imageWidthEMU = Math.round(
imageHeightEMU *
(pxToEMU(imageDimension.width) / pxToEMU(imageDimension.height))
)
}
} else if (imageConfig.usePlaceholderSize) {
// taking existing size defined in word
imageWidthEMU = parseFloat(aExt.getAttribute('cx'))
imageHeightEMU = parseFloat(aExt.getAttribute('cy'))
} else {
const imageDimension = sizeOf(imageBuffer)
imageWidthEMU = pxToEMU(imageDimension.width)
imageHeightEMU = pxToEMU(imageDimension.height)
}
blip.setAttribute('r:embed', `rId${imagesStartId}`)
const imageDimension = sizeOf(imageBuffer)
const imageWidthEMU = Math.round(imageDimension.width * 914400 / 96)
const imageHeightEMU = Math.round(imageDimension.height * 914400 / 96)
const aExt = grpSp.getElementsByTagName('a:ext')[0]
aExt.setAttribute('cx', imageWidthEMU)

@@ -66,1 +154,15 @@ aExt.setAttribute('cy', imageHeightEMU)

}
function getDimension (value) {
const regexp = /^(\d+(.\d+)?)(cm|px)$/
const match = regexp.exec(value)
if (match) {
return {
value: parseFloat(match[1]),
unit: match[3]
}
}
return null
}

4

lib/postprocess/postprocess.js
const slides = require('./slides')
const image = require('./image')
module.exports = (files) => {
module.exports = async (files) => {
slides(files)
image(files)
await image(files)
}

@@ -10,65 +10,80 @@ const { DOMParser, XMLSerializer } = require('@xmldom/xmldom')

const files = await decompress()(pptxTemplateContent)
try {
let files
for (const f of files) {
if (contentIsXML(f.data)) {
f.doc = new DOMParser().parseFromString(f.data.toString())
f.data = f.data.toString()
try {
files = await decompress()(pptxTemplateContent)
} catch (parseTemplateError) {
throw reporter.createError('Failed to parse pptx template input', {
original: parseTemplateError
})
}
}
await preprocess(files)
for (const f of files) {
if (contentIsXML(f.data)) {
f.doc = new DOMParser().parseFromString(f.data.toString())
f.data = f.data.toString()
}
}
const filesToRender = files.filter(f => contentIsXML(f.data))
await preprocess(files)
const contentToRender = (
filesToRender
.map(f => new XMLSerializer().serializeToString(f.doc).replace(/<pptxRemove>/g, '').replace(/<\/pptxRemove>/g, ''))
.join('$$$docxFile$$$')
)
const filesToRender = files.filter(f => contentIsXML(f.data))
reporter.logger.debug('Starting child request to render pptx dynamic parts', req)
const contentToRender = (
filesToRender
.map(f => new XMLSerializer().serializeToString(f.doc).replace(/<pptxRemove>/g, '').replace(/<\/pptxRemove>/g, ''))
.join('$$$docxFile$$$')
)
const { content: newContent } = await reporter.render({
template: {
content: contentToRender,
engine: req.template.engine,
recipe: 'html',
helpers: req.template.helpers
}
}, req)
reporter.logger.debug('Starting child request to render pptx dynamic parts', req)
const contents = newContent.toString().split('$$$docxFile$$$')
const { content: newContent } = await reporter.render({
template: {
content: contentToRender,
engine: req.template.engine,
recipe: 'html',
helpers: req.template.helpers
}
}, req)
for (let i = 0; i < filesToRender.length; i++) {
filesToRender[i].data = contents[i]
filesToRender[i].doc = new DOMParser().parseFromString(contents[i])
}
const contents = newContent.toString().split('$$$docxFile$$$')
await postprocess(files)
for (let i = 0; i < filesToRender.length; i++) {
filesToRender[i].data = contents[i]
filesToRender[i].doc = new DOMParser().parseFromString(contents[i])
}
for (const f of files) {
let isXML = false
await postprocess(files)
if (f.data == null) {
isXML = f.path.includes('.xml')
} else {
isXML = contentIsXML(f.data)
}
for (const f of files) {
let isXML = false
if (isXML) {
f.data = Buffer.from(new XMLSerializer().serializeToString(f.doc))
if (f.data == null) {
isXML = f.path.includes('.xml')
} else {
isXML = contentIsXML(f.data)
}
if (isXML) {
f.data = Buffer.from(new XMLSerializer().serializeToString(f.doc))
}
}
}
await saveXmlsToOfficeFile({
outputPath,
files
})
await saveXmlsToOfficeFile({
outputPath,
files
})
reporter.logger.debug('pptx successfully zipped', req)
reporter.logger.debug('pptx successfully zipped', req)
return {
pptxFilePath: outputPath
return {
pptxFilePath: outputPath
}
} catch (e) {
throw reporter.createError('Error while executing pptx recipe', {
original: e,
weak: true
})
}
}

@@ -0,14 +1,4 @@

const { nodeListToArray } = require('../utils')
const regexp = /{{#?pptxTable [^{}]{0,500}}}/
const regexp = /{{#pptxTable [^{}]{0,500}}}/
function processClosingTag (doc, el) {
el.textContent = el.textContent.replace('{{/pptxTable}}', '')
const wpElement = el.parentNode.parentNode.parentNode.parentNode
const fakeElement = doc.createElement('docxRemove')
fakeElement.textContent = '{{/pptxTable}}'
wpElement.parentNode.insertBefore(fakeElement, wpElement.nextSibling)
}
// the same idea as list, check the docs there

@@ -18,27 +8,208 @@ module.exports = (files) => {

const doc = f.doc
const elements = doc.getElementsByTagName('w:t')
const elements = doc.getElementsByTagName('a:t')
const openTags = []
let openedDocx = false
for (let i = 0; i < elements.length; i++) {
const el = elements[i]
if (el.textContent.includes('{{/pptxTable}}') && openedDocx) {
openedDocx = false
processClosingTag(doc, el)
if (el.textContent.includes('{{/pptxTable}}') && openTags.length > 0) {
const tag = openTags.pop()
processClosingTag(doc, el, tag.mode === 'column')
}
if (el.textContent.includes('{{#pptxTable')) {
if (
(
el.textContent.includes('{{pptxTable') ||
el.textContent.includes('{{#pptxTable')
) &&
el.textContent.includes('rows=') &&
el.textContent.includes('columns=')
) {
const isBlock = el.textContent.includes('{{#pptxTable')
// full table mode
let helperCall = el.textContent.match(regexp)[0]
if (!isBlock) {
// setting the cell text to be the value for the rows (before we clone)
el.textContent = el.textContent.replace(regexp, '{{this}}')
} else {
el.textContent = el.textContent.replace(regexp, '')
}
const paragraphNode = el.parentNode.parentNode
let newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{#if @placeholderCell}}'
paragraphNode.parentNode.parentNode.insertBefore(newElement, paragraphNode)
const emptyParagraphNode = paragraphNode.cloneNode(true)
while (emptyParagraphNode.firstChild) {
emptyParagraphNode.removeChild(emptyParagraphNode.firstChild)
emptyParagraphNode.removeAttribute('__block_helper_container__')
}
paragraphNode.parentNode.parentNode.insertBefore(emptyParagraphNode, paragraphNode)
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{else}}'
paragraphNode.parentNode.parentNode.insertBefore(newElement, paragraphNode)
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{/if}}'
paragraphNode.parentNode.parentNode.insertBefore(newElement, paragraphNode.nextSibling)
const cellNode = paragraphNode.parentNode.parentNode
const cellPropertiesNode = nodeListToArray(cellNode.childNodes).find((node) => node.nodeName === 'a:tcPr')
// insert conditional logic for colspan and rowspan
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{#pptxTable check="colspan"}}'
cellPropertiesNode.appendChild(newElement)
newElement = doc.createElement('a:gridSpan')
newElement.setAttribute('a:val', '{{this}}')
cellPropertiesNode.appendChild(newElement)
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{/pptxTable}}'
cellPropertiesNode.appendChild(newElement)
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{#pptxTable check="rowspan"}}'
cellPropertiesNode.appendChild(newElement)
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{#if @empty}}'
cellPropertiesNode.appendChild(newElement)
newElement = doc.createElement('a:vMerge')
cellPropertiesNode.appendChild(newElement)
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{else}}'
cellPropertiesNode.appendChild(newElement)
newElement = doc.createElement('a:vMerge')
newElement.setAttribute('a:val', 'restart')
cellPropertiesNode.appendChild(newElement)
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{/if}}'
cellPropertiesNode.appendChild(newElement)
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{/pptxTable}}'
cellPropertiesNode.appendChild(newElement)
const rowNode = cellNode.parentNode
const tableNode = rowNode.parentNode
const newRowNode = rowNode.cloneNode(true)
if (!isBlock) {
helperCall = helperCall.replace('{{pptxTable', '{{#pptxTable')
}
newElement = doc.createElement('pptxRemove')
newElement.textContent = helperCall.replace('{{#pptxTable', '{{#pptxTable wrapper="main"')
tableNode.parentNode.insertBefore(newElement, tableNode)
newElement = doc.createElement('pptxRemove')
newElement.textContent = '{{/pptxTable}}'
tableNode.parentNode.insertBefore(newElement, tableNode.nextSibling)
if (isBlock) {
openTags.push({ mode: 'column' })
}
processOpeningTag(doc, cellNode, helperCall.replace('rows=', 'ignore='))
if (!isBlock) {
processClosingTag(doc, cellNode)
} else {
if (el.textContent.includes('{{/pptxTable')) {
openTags.pop()
processClosingTag(doc, el, true)
}
const clonedTextNodes = nodeListToArray(newRowNode.getElementsByTagName('a:t'))
for (const tNode of clonedTextNodes) {
if (tNode.textContent.includes('{{/pptxTable')) {
tNode.textContent = tNode.textContent.replace('{{/pptxTable}}', '')
}
}
}
// row template, handling the cells for the data values
rowNode.parentNode.insertBefore(newRowNode, rowNode.nextSibling)
const cellInNewRowNode = nodeListToArray(newRowNode.childNodes).find((node) => node.nodeName === 'a:tc')
processOpeningTag(doc, cellInNewRowNode, helperCall.replace('rows=', 'ignore=').replace('columns=', 'ignore='))
processClosingTag(doc, cellInNewRowNode)
processOpeningTag(doc, newRowNode, helperCall)
processClosingTag(doc, newRowNode)
} else if (el.textContent.includes('{{#pptxTable')) {
const helperCall = el.textContent.match(regexp)[0]
const wpElement = el.parentNode.parentNode.parentNode.parentNode
const fakeElement = doc.createElement('docxRemove')
fakeElement.textContent = helperCall
const isVertical = el.textContent.includes('vertical=')
const isNormal = !isVertical
wpElement.parentNode.insertBefore(fakeElement, wpElement)
el.textContent = el.textContent.replace(regexp, '')
if (el.textContent.includes('{{/pptxTable')) {
processClosingTag(doc, el)
if (isNormal) {
openTags.push({ mode: 'row' })
}
if (isVertical) {
const cellNode = el.parentNode.parentNode.parentNode.parentNode
const cellIndex = getCellIndex(cellNode)
const [affectedRows, textNodeTableClose] = getNextRowsUntilTableClose(cellNode.parentNode)
if (textNodeTableClose) {
textNodeTableClose.textContent = textNodeTableClose.textContent.replace('{{/pptxTable}}', '')
}
const tableNode = cellNode.parentNode.parentNode
const tableGridNode = tableNode.getElementsByTagName('a:tblGrid')[0]
const tableGridColNodes = nodeListToArray(tableGridNode.getElementsByTagName('a:gridCol'))
// add loop for colum definitions (pptx table requires this to show the newly created columns)
processOpeningTag(doc, tableGridColNodes[cellIndex], helperCall, isVertical)
processClosingTag(doc, tableGridColNodes[cellIndex], isVertical)
processOpeningTag(doc, el, helperCall, isVertical)
processClosingTag(doc, el, isVertical)
for (const rowNode of affectedRows) {
const cellNodes = nodeListToArray(rowNode.childNodes).filter((node) => node.nodeName === 'a:tc')
const cellNode = cellNodes[cellIndex]
if (cellNode) {
processOpeningTag(doc, cellNode, helperCall, isVertical)
processClosingTag(doc, cellNode, isVertical)
}
}
} else {
openedDocx = true
processOpeningTag(doc, el, helperCall, isVertical)
}
if (isNormal && el.textContent.includes('{{/pptxTable')) {
openTags.pop()
processClosingTag(doc, el)
}
}

@@ -48,1 +219,124 @@ }

}
function processOpeningTag (doc, el, helperCall, useColumnRef = false) {
if (el.nodeName === 'a:t') {
el.textContent = el.textContent.replace(regexp, '')
}
const fakeElement = doc.createElement('pptxRemove')
fakeElement.textContent = helperCall
let refElement
if (el.nodeName !== 'a:t') {
refElement = el
} else {
if (useColumnRef) {
// ref is the column a:tc
refElement = el.parentNode.parentNode.parentNode.parentNode
} else {
// ref is the row a:tr
refElement = el.parentNode.parentNode.parentNode.parentNode.parentNode
}
}
refElement.parentNode.insertBefore(fakeElement, refElement)
}
function processClosingTag (doc, el, useColumnRef = false) {
if (el.nodeName === 'a:t') {
el.textContent = el.textContent.replace('{{/pptxTable}}', '')
}
const fakeElement = doc.createElement('pptxRemove')
fakeElement.textContent = '{{/pptxTable}}'
let refElement
if (el.nodeName !== 'a:t') {
refElement = el
} else {
if (useColumnRef) {
refElement = el.parentNode.parentNode.parentNode.parentNode
} else {
refElement = el.parentNode.parentNode.parentNode.parentNode.parentNode
}
}
refElement.parentNode.insertBefore(fakeElement, refElement.nextSibling)
}
function getCellIndex (cellEl) {
if (cellEl.nodeName !== 'a:tc') {
throw new Error('Expected a table cell element during the processing')
}
let prevElements = 0
let currentNode = cellEl.previousSibling
while (
currentNode != null &&
currentNode.nodeName === 'a:tc'
) {
prevElements += 1
currentNode = currentNode.previousSibling
}
return prevElements
}
function getNextRowsUntilTableClose (rowEl) {
if (rowEl.nodeName !== 'a:tr') {
throw new Error('Expected a table row element during the processing')
}
let currentNode = rowEl.nextSibling
let tableCloseNode
const rows = []
while (
currentNode != null &&
currentNode.nodeName === 'a:tr'
) {
rows.push(currentNode)
const cellNodes = nodeListToArray(currentNode.childNodes).filter((node) => node.nodeName === 'a:tc')
for (const cellNode of cellNodes) {
let textNodes = nodeListToArray(cellNode.getElementsByTagName('a:t'))
// get text nodes of the current cell, we don't want text
// nodes of nested tables
textNodes = textNodes.filter((tNode) => {
let current = tNode.parentNode
while (current.nodeName !== 'a:tc') {
current = current.parentNode
}
return current === cellNode
})
for (const tNode of textNodes) {
if (tNode.textContent.includes('{{/pptxTable')) {
currentNode = null
tableCloseNode = tNode
break
}
}
if (currentNode == null) {
break
}
}
if (currentNode != null) {
currentNode = currentNode.nextSibling
}
}
return [rows, tableCloseNode]
}

@@ -30,3 +30,3 @@ const fs = require('fs')

if (!Buffer.isBuffer(templateAsset.content)) {
templateAsset.content = Buffer.from(templateAsset.content, templateAsset.encoding || 'utf8')
templateAsset.content = Buffer.from(templateAsset.content, templateAsset.encoding || 'base64')
}

@@ -33,0 +33,0 @@ }

function pxToEMU (val) {
return Math.round(val * 914400 / 96)
}
function cmToEMU (val) {
// cm to dxa
// eslint-disable-next-line no-loss-of-precision
const dxa = val * 567.058823529411765
// dxa to EMU
return Math.round(dxa * 914400 / 72 / 20)
}
module.exports.contentIsXML = (content) => {

@@ -19,1 +31,4 @@ if (!Buffer.isBuffer(content) && typeof content !== 'string') {

}
module.exports.pxToEMU = pxToEMU
module.exports.cmToEMU = cmToEMU
{
"name": "@jsreport/jsreport-pptx",
"version": "3.2.1",
"version": "3.3.0",
"description": "jsreport recipe rendering pptx files",

@@ -37,12 +37,14 @@ "keywords": [

"@jsreport/office": "3.0.0",
"@xmldom/xmldom": "0.7.0",
"@xmldom/xmldom": "0.8.6",
"axios": "0.24.0",
"image-size": "0.7.4"
},
"devDependencies": {
"@jsreport/jsreport-assets": "3.4.2",
"@jsreport/jsreport-core": "3.6.0",
"@jsreport/jsreport-handlebars": "3.1.0",
"@jsreport/studio-dev": "3.1.0",
"@jsreport/jsreport-assets": "3.5.0",
"@jsreport/jsreport-core": "3.9.0",
"@jsreport/jsreport-handlebars": "3.2.1",
"@jsreport/studio-dev": "3.2.0",
"handlebars": "4.7.7",
"mocha": "6.1.4",
"mocha": "10.1.0",
"nock": "11.7.2",
"should": "13.2.3",

@@ -49,0 +51,0 @@ "standard": "16.0.4",

@@ -10,2 +10,8 @@ # @jsreport/jsreport-pptx

### 3.3.0
- fix support of pptxTable and add support for vertical tables
- pptxImage now support same options as `docxImage` (usePlaceholderSize, width, height options)
- accept buffer strings as base64 and throw better error when failed to parse office template input
### 3.2.1

@@ -12,0 +18,0 @@

@@ -11,3 +11,214 @@ /* eslint no-unused-vars: 0 */

const Handlebars = require('handlebars')
return Handlebars.helpers.each(data, options)
const optionsToUse = options == null ? data : options
let currentData
const getMatchedMergedCell = (rowIndex, columnIndex, activeMergedCellsItems) => {
let matchedRowspan
for (const item of activeMergedCellsItems) {
if (
rowIndex >= item.rowStart &&
rowIndex <= item.rowEnd &&
columnIndex >= item.colStart &&
columnIndex <= item.colEnd
) {
matchedRowspan = item
break
}
}
return matchedRowspan
}
if (
arguments.length === 1 &&
Object.prototype.hasOwnProperty.call(optionsToUse.hash, 'wrapper') &&
optionsToUse.hash.wrapper === 'main'
) {
const newData = Handlebars.createFrame({})
newData.rows = optionsToUse.hash.rows
newData.columns = optionsToUse.hash.columns
newData.activeMergedCellsItems = []
return optionsToUse.fn(this, { data: newData })
}
if (
arguments.length === 1 &&
Object.prototype.hasOwnProperty.call(optionsToUse.hash, 'check')
) {
if (
optionsToUse.hash.check === 'colspan' &&
optionsToUse.data.colspan > 1
) {
return optionsToUse.fn(optionsToUse.data.colspan)
}
if (
optionsToUse.hash.check === 'rowspan'
) {
const matchedMergedCell = getMatchedMergedCell(optionsToUse.data.rowIndex, optionsToUse.data.columnIndex, optionsToUse.data.activeMergedCellsItems)
if (matchedMergedCell != null && matchedMergedCell.rowStart !== matchedMergedCell.rowEnd) {
const data = Handlebars.createFrame({})
data.empty = matchedMergedCell.rowStart !== optionsToUse.data.rowIndex
return optionsToUse.fn({}, { data })
}
}
return new Handlebars.SafeString('')
}
if (
arguments.length === 1 &&
(
Object.prototype.hasOwnProperty.call(optionsToUse.hash, 'rows') ||
Object.prototype.hasOwnProperty.call(optionsToUse.hash, 'columns') ||
Object.prototype.hasOwnProperty.call(optionsToUse.hash, 'ignore')
)
) {
// full table mode
if (Object.prototype.hasOwnProperty.call(optionsToUse.hash, 'rows')) {
if (!Object.prototype.hasOwnProperty.call(optionsToUse.hash, 'columns')) {
throw new Error('pptxTable full table mode needs to have both rows and columns defined as params when processing row')
}
// rows block processing start here
currentData = optionsToUse.hash.rows
const newData = Handlebars.createFrame(optionsToUse.data)
optionsToUse.data = newData
const chunks = []
if (!currentData || !Array.isArray(currentData)) {
return new Handlebars.SafeString('')
}
for (let i = 0; i < currentData.length; i++) {
newData.index = i
chunks.push(optionsToUse.fn(this, { data: newData }))
}
return new Handlebars.SafeString(chunks.join(''))
} else {
// columns processing, when isInsideRowHelper is false it means
// that we are processing the first row based on columns info,
// when true it means we are processing columns inside the rows block
let isInsideRowHelper = false
if (optionsToUse.hash.columns) {
currentData = optionsToUse.hash.columns
} else if (optionsToUse.data && optionsToUse.data.columns) {
isInsideRowHelper = true
currentData = optionsToUse.data.columns
} else {
throw new Error('pptxTable full table mode needs to have columns defined when processing column')
}
const chunks = []
const newData = Handlebars.createFrame(optionsToUse.data)
const rowIndex = newData.index || 0
delete newData.index
delete newData.key
delete newData.first
delete newData.last
if (!currentData || !Array.isArray(currentData)) {
return new Handlebars.SafeString('')
}
const getCellInfo = (item) => {
const cellInfo = {}
if (item != null && typeof item === 'object' && !Array.isArray(item)) {
cellInfo.value = item.value
cellInfo.colspan = item.colspan
cellInfo.rowspan = item.rowspan
} else {
cellInfo.value = item
}
if (cellInfo.colspan == null) {
cellInfo.colspan = 1
}
if (cellInfo.rowspan == null) {
cellInfo.rowspan = 1
}
return cellInfo
}
// if all cells in current row have the same rowspan then
// assume there is no rowspan applied
const cellsInRow = isInsideRowHelper ? optionsToUse.data.rows[rowIndex] : currentData
for (const [idx, item] of cellsInRow.entries()) {
// rowIndex + 1 because this is technically the second row on table after the row of table headers
newData.rowIndex = isInsideRowHelper ? rowIndex + 1 : 0
newData.columnIndex = cellsInRow.reduce((acu, cell, cellIdx) => {
if (cellIdx >= idx) {
return acu
}
const matchedMergedCell = getMatchedMergedCell(newData.rowIndex, acu, newData.activeMergedCellsItems)
if (matchedMergedCell != null && matchedMergedCell.colStart !== matchedMergedCell.colEnd) {
return acu + (matchedMergedCell.colEnd - matchedMergedCell.colStart) + 1
}
const cellInfo = getCellInfo(cell)
return acu + cellInfo.colspan
}, 0)
const currentItem = isInsideRowHelper ? optionsToUse.data.rows[rowIndex][idx] : item
const cellInfo = getCellInfo(currentItem)
const allCellsInRowHaveSameRowspan = cellsInRow.every((cell) => {
const cellInfo = getCellInfo(cell)
return cellInfo.rowspan === getCellInfo(cellsInRow[0]).rowspan
})
if (allCellsInRowHaveSameRowspan) {
cellInfo.rowspan = 1
}
newData.colspan = cellInfo.colspan
newData.rowspan = cellInfo.rowspan
if (newData.rowspan > 1 || newData.colspan > 1) {
newData.activeMergedCellsItems.push({
colStart: newData.columnIndex,
colEnd: newData.columnIndex + (newData.colspan - 1),
rowStart: newData.rowIndex,
rowEnd: newData.rowIndex + (newData.rowspan - 1)
})
}
const matchedMergedCell = getMatchedMergedCell(newData.rowIndex, newData.columnIndex, newData.activeMergedCellsItems)
if (
matchedMergedCell != null &&
matchedMergedCell.rowStart !== matchedMergedCell.rowEnd &&
matchedMergedCell.rowStart !== newData.rowIndex
) {
newData.placeholderCell = true
newData.colspan = (matchedMergedCell.colEnd - matchedMergedCell.colStart) + 1
} else {
newData.placeholderCell = false
}
chunks.push(optionsToUse.fn(cellInfo.value, { data: newData }))
}
return new Handlebars.SafeString(chunks.join(''))
}
} else {
currentData = data
}
return Handlebars.helpers.each(currentData, optionsToUse)
}

@@ -22,3 +233,58 @@

const Handlebars = require('handlebars')
return new Handlebars.SafeString(`<pptxImage src="${options.hash.src}" />`)
if (!options.hash.src) {
throw new Error(
'pptxImage helper requires src parameter to be set'
)
}
if (
!options.hash.src.startsWith('data:image/png;base64,') &&
!options.hash.src.startsWith('data:image/jpeg;base64,') &&
!options.hash.src.startsWith('http://') &&
!options.hash.src.startsWith('https://')
) {
throw new Error(
'pptxImage helper requires src parameter to be valid data uri for png or jpeg image or a valid url. Got ' +
options.hash.src
)
}
const isValidDimensionUnit = value => {
const regexp = /^(\d+(.\d+)?)(cm|px)$/
return regexp.test(value)
}
if (
options.hash.width != null &&
!isValidDimensionUnit(options.hash.width)
) {
throw new Error(
'pptxImage helper requires width parameter to be valid number with unit (cm or px). got ' +
options.hash.width
)
}
if (
options.hash.height != null &&
!isValidDimensionUnit(options.hash.height)
) {
throw new Error(
'pptxImage helper requires height parameter to be valid number with unit (cm or px). got ' +
options.hash.height
)
}
const content = `$pptxImage${
Buffer.from(JSON.stringify({
src: options.hash.src,
width: options.hash.width,
height: options.hash.height,
usePlaceholderSize:
options.hash.usePlaceholderSize === true ||
options.hash.usePlaceholderSize === 'true'
})).toString('base64')
}$`
return new Handlebars.SafeString(`<pptxImage>${content}</pptxImage>`)
}
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc