New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@jsreport/jsreport-docx

Package Overview
Dependencies
Maintainers
2
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@jsreport/jsreport-docx - npm Package Compare versions

Comparing version 3.4.0 to 3.5.0

lib/postprocess/html/convertDocxMetaToNodes.js

4

lib/postprocess/postprocess.js

@@ -10,5 +10,9 @@ const bookmark = require('./bookmark')

const removeBlockHelper = require('./removeBlockHelper')
const html = require('./html')
module.exports = async (files, options) => {
const newBookmarksMap = new Map()
// we handle the html step as the first to ensure no other step
// work with the attribute and comment we put for the <w:p> elements for the html handling
await html(files)
await bookmark(files, newBookmarksMap)

@@ -15,0 +19,0 @@ await watermark(files)

56

lib/postprocess/processChart.js
const { DOMParser, XMLSerializer } = require('@xmldom/xmldom')
const moment = require('moment')
const toExcelDate = require('js-excel-date-convert').toExcelDate
const { serializeXml, nodeListToArray, getChartEl, getNewRelIdFromBaseId } = require('../utils')
const { serializeXml, nodeListToArray, getChartEl, getNewRelIdFromBaseId, clearEl, findChildNode, findOrCreateChildNode } = require('../utils')

@@ -29,3 +29,3 @@ module.exports = async function processChart (files, drawingEl, originalChartsXMLMap, newRelIdCounterMap) {

let chartRelsFilename = `word/charts/_rels/${chartFilename.split('/').slice(-1)[0]}.rels`
// take the original (not modifed) document
// take the original (not modified) document
let chartRelsDoc = originalChartsXMLMap.has(chartRelsFilename) ? new DOMParser().parseFromString(originalChartsXMLMap.get(chartRelsFilename)) : files.find(f => f.path === chartRelsFilename).doc

@@ -531,3 +531,3 @@

removeChildNodes('c:extLst', baseChartSerieEl)
clearEl(baseChartSerieEl, (c) => c.nodeName !== 'c:extLst')

@@ -705,3 +705,3 @@ addChartSerieItem(chartDoc, { name: 'c:idx', data: serieIdx }, baseChartSerieEl)

removeChildNodes('cx:f', newNode)
clearEl(newNode, (c) => c.nodeName !== 'cx:f')

@@ -774,3 +774,3 @@ const existingLvlNodes = findChildNode('cx:lvl', newNode, true)

removeChildNodes('cx:f', txDataNode)
clearEl(txDataNode, (c) => c.nodeName !== 'cx:f')

@@ -935,3 +935,3 @@ const txValueNode = findOrCreateChildNode(docNode, 'cx:v', txDataNode)

const refNode = findOrCreateChildNode(docNode, numType ? 'c:numRef' : 'c:strRef', parentNode)
removeChildNodes('c:f', refNode)
clearEl(refNode, (c) => c.nodeName !== 'c:f')
const cacheNode = findOrCreateChildNode(docNode, numType ? 'c:numCache' : 'c:strCache', refNode)

@@ -991,45 +991,1 @@ const existingFormatNode = findChildNode('c:formatCode', cacheNode)

}
function findOrCreateChildNode (docNode, nodeName, targetNode) {
let result
const existingNode = findChildNode(nodeName, targetNode)
if (!existingNode) {
result = docNode.createElement(nodeName)
targetNode.appendChild(result)
} else {
result = existingNode
}
return result
}
function findChildNode (nodeName, targetNode, allNodes = false) {
const result = []
for (let i = 0; i < targetNode.childNodes.length; i++) {
let found = false
const childNode = targetNode.childNodes[i]
if (childNode.nodeName === nodeName) {
found = true
result.push(childNode)
}
if (found && !allNodes) {
break
}
}
return allNodes ? result : result[0]
}
function removeChildNodes (nodeName, targetNode) {
for (let i = 0; i < targetNode.childNodes.length; i++) {
const childNode = targetNode.childNodes[i]
if (childNode.nodeName === nodeName) {
targetNode.removeChild(childNode)
}
}
}

@@ -90,3 +90,3 @@ const sizeOf = require('image-size')

// somehow there are duplicated hlinkclick els produced by word, we need to clean them up
// somehow there are duplicated linkclick els produced by word, we need to clean them up
for (let i = 1; i < linkClickEls.length; i++) {

@@ -129,3 +129,3 @@ const elLinkClick = linkClickEls[i]

// some servers returns the image content type withoyt the "image/" prefix
// some servers returns the image content type without the "image/" prefix
imageExtension = extensionsParts.length === 1 ? extensionsParts[0] : extensionsParts[1]

@@ -132,0 +132,0 @@ imageBuffer = Buffer.from(response.data)

@@ -6,3 +6,5 @@ const { DOMParser } = require('@xmldom/xmldom')

module.exports = async (files) => {
const stylesFile = files.find(f => f.path === 'word/styles.xml').doc
const stylesDoc = files.find(f => f.path === 'word/styles.xml').doc
const settingsDoc = files.find(f => f.path === 'word/settings.xml').doc
const contentTypesDoc = files.find(f => f.path === '[Content_Types].xml').doc
const documentFile = files.find(f => f.path === 'word/document.xml')

@@ -15,4 +17,63 @@ const titles = []

const TOCExists = contentTypesDoc.documentElement.getAttribute('TOCExists') === '1'
let updateFields = true
if (TOCExists) {
contentTypesDoc.documentElement.removeAttribute('TOCExists')
}
documentFile.data = await recursiveStringReplaceAsync(
documentFile.data.toString(),
'<TOCHeading>',
'</TOCHeading>',
'g',
async (val, content, hasNestedMatch) => {
if (hasNestedMatch) {
return val
}
const docEl = new DOMParser().parseFromString(content).documentElement
const tEls = nodeListToArray(docEl.getElementsByTagName('w:t'))
for (const tEl of tEls) {
const match = tEl.textContent.match(/\$docxTOCOptions([^$]*)\$/)
if (match == null || match[1] == null || match[1] === '') {
continue
}
// remove chart helper text
tEl.textContent = tEl.textContent.replace(match[0], '')
const tocOptions = JSON.parse(Buffer.from(match[1], 'base64').toString())
if (tocOptions.updateFields == null || typeof tocOptions.updateFields !== 'boolean') {
continue
}
updateFields = tocOptions.updateFields
break
}
return serializeXml(docEl)
}
)
if (TOCExists && updateFields) {
// add here the setting of the document to automatically recalculate fields on open,
// this allows the MS Word to prompt the user to update the page numbers or toc table
// when opening the generated file
const existingUpdateFieldsEl = settingsDoc.documentElement.getElementsByTagName('w:updateFields')[0]
// if the setting is already on the document we don't generate it
if (existingUpdateFieldsEl == null) {
const doc = new DOMParser().parseFromString('<w:p></w:p>')
const newUpdateFieldsEl = doc.createElement('w:updateFields')
newUpdateFieldsEl.setAttribute('w:val', 'true')
settingsDoc.documentElement.insertBefore(newUpdateFieldsEl, settingsDoc.documentElement.firstChild)
}
}
documentFile.data = await recursiveStringReplaceAsync(
documentFile.data.toString(),
'<TOCTitle>',

@@ -26,3 +87,3 @@ '</TOCTitle>',

let tocTitlePrefix = findDefaultStyleIdForName(stylesFile, 'heading 1')
let tocTitlePrefix = findDefaultStyleIdForName(stylesDoc, 'heading 1')
const tocTitleMatch = tocStyleIdRegExp.exec(tocTitlePrefix)

@@ -113,3 +174,3 @@

let tocAlternativeTitlePrefix = findDefaultStyleIdForName(stylesFile, 'toc 1')
let tocAlternativeTitlePrefix = findDefaultStyleIdForName(stylesDoc, 'toc 1')
const tocAlternativeTitleMatch = tocStyleIdRegExp.exec(tocAlternativeTitlePrefix)

@@ -116,0 +177,0 @@

@@ -6,3 +6,3 @@

const recursiveStringReplaceAsync = require('../recursiveStringReplaceAsync')
const { nodeListToArray } = require('../utils')
const { nodeListToArray, findOrCreateChildNode } = require('../utils')

@@ -164,35 +164,1 @@ module.exports = async (files) => {

}
function findOrCreateChildNode (docNode, nodeName, targetNode) {
let result
const existingNode = findChildNode(nodeName, targetNode)
if (!existingNode) {
result = docNode.createElement(nodeName)
targetNode.appendChild(result)
} else {
result = existingNode
}
return result
}
function findChildNode (nodeName, targetNode, allNodes = false) {
const result = []
for (let i = 0; i < targetNode.childNodes.length; i++) {
let found = false
const childNode = targetNode.childNodes[i]
if (childNode.nodeName === nodeName) {
found = true
result.push(childNode)
}
if (found && !allNodes) {
break
}
}
return allNodes ? result : result[0]
}

@@ -12,2 +12,3 @@ const concatTags = require('./concatTags')

const watermark = require('./watermark')
const html = require('./html')

@@ -26,2 +27,5 @@ module.exports = (files) => {

pageBreak(files)
// we handle the html step as the last to ensure no other step
// work with the attribute and comment we put in the <w:p> elements for the html handling
html(files)
}
const { nodeListToArray, findDefaultStyleIdForName } = require('../utils')
module.exports = (files) => {
const stylesFile = files.find(f => f.path === 'word/styles.xml').doc
const settingsFile = files.find(f => f.path === 'word/settings.xml').doc
const documentFile = files.find(f => f.path === 'word/document.xml').doc
const contentTypesFile = files.find(f => f.path === '[Content_Types].xml').doc
const paragraphEls = nodeListToArray(documentFile.getElementsByTagName('w:p'))
const stylesDoc = files.find(f => f.path === 'word/styles.xml').doc
const documentDoc = files.find(f => f.path === 'word/document.xml').doc
const contentTypesDoc = files.find(f => f.path === '[Content_Types].xml').doc
// we depend on the preprocess - bookmark to execute first, to get the max bookmark currently
let maxBookmarkId = contentTypesFile.documentElement.getAttribute('bookmarkMaxId')
let maxBookmarkId = contentTypesDoc.documentElement.getAttribute('bookmarkMaxId')

@@ -19,6 +17,56 @@ if (maxBookmarkId != null && maxBookmarkId !== '') {

const tocStyleIdRegExp = /^([^\d]+)(\d+)/
const tocStyleIdRegExp = /^([^\d]+)(\d+)$/
const tocHeadingStyleId = findDefaultStyleIdForName(stylesDoc, 'TOC Heading')
let tocAlternativeTitlePrefix = findDefaultStyleIdForName(stylesDoc, 'toc 1')
let tocTitlePrefix = findDefaultStyleIdForName(stylesFile, 'heading 1')
const tocAlternativeTitleMatch = tocStyleIdRegExp.exec(tocAlternativeTitlePrefix)
if (tocAlternativeTitleMatch != null && tocAlternativeTitleMatch[1] != null) {
tocAlternativeTitlePrefix = tocAlternativeTitleMatch[1]
} else {
tocAlternativeTitlePrefix = ''
}
let hasTOC = false
let paragraphTOCHeadingEl
const sdtContentEls = nodeListToArray(documentDoc.getElementsByTagName('w:sdtContent'))
for (const sdtContentEl of sdtContentEls) {
const paragraphEls = nodeListToArray(sdtContentEl.getElementsByTagName('w:p'))
const paragraphAlternativeTOCTitleEl = paragraphEls.find((pEl) => {
const styleId = getParagraphStyleId(pEl)
if (styleId == null || tocAlternativeTitlePrefix === '') {
return false
}
return styleId.startsWith(tocAlternativeTitlePrefix) && tocStyleIdRegExp.test(styleId)
})
paragraphTOCHeadingEl = paragraphEls.find((pEl) => getParagraphStyleId(pEl) === tocHeadingStyleId)
hasTOC = paragraphAlternativeTOCTitleEl != null || paragraphTOCHeadingEl != null
if (hasTOC) {
break
}
}
if (!hasTOC) {
return
}
contentTypesDoc.documentElement.setAttribute('TOCExists', '1')
if (paragraphTOCHeadingEl != null) {
const wrapperEl = documentDoc.createElement('TOCHeading')
const clonedParagraphEl = paragraphTOCHeadingEl.cloneNode(true)
wrapperEl.appendChild(clonedParagraphEl)
paragraphTOCHeadingEl.parentNode.replaceChild(wrapperEl, paragraphTOCHeadingEl)
}
let tocTitlePrefix = findDefaultStyleIdForName(stylesDoc, 'heading 1')
if (tocTitlePrefix == null) {

@@ -36,18 +84,13 @@ return

let hasTOC = false
const paragraphEls = nodeListToArray(documentDoc.getElementsByTagName('w:p'))
paragraphEls.forEach((paragraphEl, paragraphIdx) => {
const pPrEl = nodeListToArray(paragraphEl.childNodes).find((el) => el.nodeName === 'w:pPr')
let hasTOCTitle = false
if (pPrEl == null) {
return
}
const paragraphStyleId = getParagraphStyleId(paragraphEl)
const pStyleEl = nodeListToArray(pPrEl.childNodes).find((el) => el.nodeName === 'w:pStyle')
const titleRegexp = new RegExp(`^${tocTitlePrefix}(\\d+)$`)
if (paragraphStyleId != null) {
const titleRegexp = new RegExp(`^${tocTitlePrefix}(\\d+)$`)
const result = titleRegexp.exec(paragraphStyleId)
if (pStyleEl != null) {
const result = titleRegexp.exec(pStyleEl.getAttribute('w:val'))
if (result != null) {

@@ -59,77 +102,73 @@ const titleType = parseInt(result[1], 10)

if (hasTOCTitle) {
hasTOC = true
if (!hasTOCTitle) {
return
}
let evaluated = false
let startIfNode
let evaluated = false
let startIfNode
const getIfOpeningBlockRegExp = () => /{{#if\s[^}]+}}/g
const getIfClosingBlockRegExp = () => /{{\/if}}/g
const getIfOpeningBlockRegExp = () => /{{#if\s[^}]+}}/g
const getIfClosingBlockRegExp = () => /{{\/if}}/g
do {
evaluated = true
do {
evaluated = true
const originalMeaningfulTextNodes = nodeListToArray(paragraphEl.getElementsByTagName('w:t')).filter((t) => {
return t.textContent != null && t.textContent.trim() !== ''
})
const originalMeaningfulTextNodes = nodeListToArray(paragraphEl.getElementsByTagName('w:t')).filter((t) => {
return t.textContent != null && t.textContent.trim() !== ''
})
if (originalMeaningfulTextNodes.length === 0) {
break
}
if (originalMeaningfulTextNodes.length === 0) {
break
}
startIfNode = originalMeaningfulTextNodes[0].textContent.startsWith('{{#if ') ? originalMeaningfulTextNodes[0] : undefined
startIfNode = originalMeaningfulTextNodes[0].textContent.startsWith('{{#if ') ? originalMeaningfulTextNodes[0] : undefined
if (startIfNode == null) {
break
}
if (startIfNode == null) {
break
}
// pre-process headings to move `{{#if cond}}` opening block helpers to new level right before
// the current paragraph if they are at the very beginning of the heading and
// matching `{{/if}}` closing block helpers it is on the next paragraph
const ifOpeningBlockHelperMatches = paragraphEl.textContent.match(getIfOpeningBlockRegExp()) || []
const ifClosingBlockHelperMatches = paragraphEl.textContent.match(getIfClosingBlockRegExp()) || []
// pre-process headings to move `{{#if cond}}` opening block helpers to new level right before
// the current paragraph if they are at the very beginning of the heading and
// matching `{{/if}}` closing block helpers it is on the next paragraph
const ifOpeningBlockHelperMatches = paragraphEl.textContent.match(getIfOpeningBlockRegExp()) || []
const ifClosingBlockHelperMatches = paragraphEl.textContent.match(getIfClosingBlockRegExp()) || []
// leave heading untouched as the number of opening and closing block helpers are matching
if (ifOpeningBlockHelperMatches.length === ifClosingBlockHelperMatches.length) {
break
}
// leave heading untouched as the number of opening and closing block helpers are matching
if (ifOpeningBlockHelperMatches.length === ifClosingBlockHelperMatches.length) {
break
}
// inspect next paragraphs and search for the exact close if for this node
const nextParagraphEls = paragraphEls.slice(paragraphIdx + 1)
// inspect next paragraphs and search for the exact close if for this node
const nextParagraphEls = paragraphEls.slice(paragraphIdx + 1)
if (nextParagraphEls.length === 0) {
break
}
if (nextParagraphEls.length === 0) {
break
}
let closeIfTextMatchInfo
let openedIfTags = ifOpeningBlockHelperMatches.length - ifClosingBlockHelperMatches.length
let closeIfTextMatchInfo
let openedIfTags = ifOpeningBlockHelperMatches.length - ifClosingBlockHelperMatches.length
for (const nextParagraphEl of nextParagraphEls) {
const nextParagraphTextNodes = nodeListToArray(nextParagraphEl.getElementsByTagName('w:t'))
for (const nextParagraphEl of nextParagraphEls) {
const nextParagraphTextNodes = nodeListToArray(nextParagraphEl.getElementsByTagName('w:t'))
for (const [nptIndex, nptNode] of nextParagraphTextNodes.entries()) {
const childIfOpeningBlockHelperMatches = [...nptNode.textContent.matchAll(getIfOpeningBlockRegExp())]
const childIfClosingBlockHelperMatches = [...nptNode.textContent.matchAll(getIfClosingBlockRegExp())]
for (const [nptIndex, nptNode] of nextParagraphTextNodes.entries()) {
const childIfOpeningBlockHelperMatches = [...nptNode.textContent.matchAll(getIfOpeningBlockRegExp())]
const childIfClosingBlockHelperMatches = [...nptNode.textContent.matchAll(getIfClosingBlockRegExp())]
openedIfTags += childIfOpeningBlockHelperMatches.length
openedIfTags -= childIfClosingBlockHelperMatches.length
openedIfTags += childIfOpeningBlockHelperMatches.length
openedIfTags -= childIfClosingBlockHelperMatches.length
const remainingTextNodesInParagraph = nextParagraphTextNodes.slice(nptIndex + 1)
const remainingTextNodesInParagraph = nextParagraphTextNodes.slice(nptIndex + 1)
// we match only when found the close if and also there is no more text nodes in the paragraph
// that contain the close if
if (openedIfTags === 0 && remainingTextNodesInParagraph.length === 0) {
closeIfTextMatchInfo = {
paragraphNode: nextParagraphEl,
node: nptNode,
// this only works fine when the close if node does not contain another close if (like "{{/if}}{{/if}}") node in there,
// and also there is no more text on the same text node after the close if (like "{{/if}}something"), that won't work
match: childIfClosingBlockHelperMatches[0]
}
break
// we match only when found the close if and also there is no more text nodes in the paragraph
// that contain the close if
if (openedIfTags === 0 && remainingTextNodesInParagraph.length === 0) {
closeIfTextMatchInfo = {
paragraphNode: nextParagraphEl,
node: nptNode,
// this only works fine when the close if node does not contain another close if (like "{{/if}}{{/if}}") node in there,
// and also there is no more text on the same text node after the close if (like "{{/if}}something"), that won't work
match: childIfClosingBlockHelperMatches[0]
}
}
if (closeIfTextMatchInfo != null) {
break

@@ -139,82 +178,93 @@ }

if (closeIfTextMatchInfo == null) {
if (closeIfTextMatchInfo != null) {
break
}
}
// start the normalization
if (closeIfTextMatchInfo == null) {
break
}
// remove `{{#if cond}}` from heading and insert it as tmp block before the current paragraph
startIfNode.textContent = startIfNode.textContent.slice(ifOpeningBlockHelperMatches[0].length)
// start the normalization
const fakeOpenIfElement = documentFile.createElement('docxRemove')
fakeOpenIfElement.textContent = ifOpeningBlockHelperMatches[0]
// remove `{{#if cond}}` from heading and insert it as tmp block before the current paragraph
startIfNode.textContent = startIfNode.textContent.slice(ifOpeningBlockHelperMatches[0].length)
paragraphEl.parentNode.insertBefore(fakeOpenIfElement, paragraphEl)
const fakeOpenIfElement = documentDoc.createElement('docxRemove')
fakeOpenIfElement.textContent = ifOpeningBlockHelperMatches[0]
// remove `{{/if}}` from next paragraph and insert it as tmp block before
// the paragraph that contains the close if
const fakeCloseIfElement = documentFile.createElement('docxRemove')
paragraphEl.parentNode.insertBefore(fakeOpenIfElement, paragraphEl)
closeIfTextMatchInfo.node.textContent = `${
closeIfTextMatchInfo.node.textContent.slice(0, closeIfTextMatchInfo.match.index)
}${
closeIfTextMatchInfo.node.textContent.slice(closeIfTextMatchInfo.match.index + closeIfTextMatchInfo.match[0].length)
}`
// remove `{{/if}}` from next paragraph and insert it as tmp block before
// the paragraph that contains the close if
const fakeCloseIfElement = documentDoc.createElement('docxRemove')
fakeCloseIfElement.textContent = '{{/if}}'
closeIfTextMatchInfo.node.textContent = `${
closeIfTextMatchInfo.node.textContent.slice(0, closeIfTextMatchInfo.match.index)
}${
closeIfTextMatchInfo.node.textContent.slice(closeIfTextMatchInfo.match.index + closeIfTextMatchInfo.match[0].length)
}`
closeIfTextMatchInfo.paragraphNode.parentNode.insertBefore(fakeCloseIfElement, closeIfTextMatchInfo.paragraphNode.nextSibling)
fakeCloseIfElement.textContent = '{{/if}}'
const newMeaningfulTextNodes = nodeListToArray(paragraphEl.getElementsByTagName('w:t')).filter((t) => {
return t.textContent != null && t.textContent.trim() !== ''
})
closeIfTextMatchInfo.paragraphNode.parentNode.insertBefore(fakeCloseIfElement, closeIfTextMatchInfo.paragraphNode.nextSibling)
// if the new text content in paragraph start with if the do again the same normalization
if (newMeaningfulTextNodes.length > 0 && newMeaningfulTextNodes[0].textContent.startsWith('{{#if ')) {
evaluated = false
}
} while (!evaluated)
const newMeaningfulTextNodes = nodeListToArray(paragraphEl.getElementsByTagName('w:t')).filter((t) => {
return t.textContent != null && t.textContent.trim() !== ''
})
const clonedParagraphEl = paragraphEl.cloneNode(true)
const textNode = clonedParagraphEl.getElementsByTagName('w:t')[0]
// if the new text content in paragraph start with if the do again the same normalization
if (newMeaningfulTextNodes.length > 0 && newMeaningfulTextNodes[0].textContent.startsWith('{{#if ')) {
evaluated = false
}
} while (!evaluated)
// we verify that bookmark exists on title elements, if not there it means that we have to create it
if (textNode != null && textNode.parentNode.previousSibling?.nodeName !== 'w:bookmarkStart') {
const rNode = textNode.parentNode
const bookmarkStartEl = documentFile.createElement('w:bookmarkStart')
const bookmarkEndEl = documentFile.createElement('w:bookmarkEnd')
const clonedParagraphEl = paragraphEl.cloneNode(true)
const textNode = clonedParagraphEl.getElementsByTagName('w:t')[0]
maxBookmarkId++
bookmarkStartEl.setAttribute('w:id', maxBookmarkId)
bookmarkStartEl.setAttribute('w:name', `_Toc${randomInteger(30000000, 90000000)}_r'`)
bookmarkEndEl.setAttribute('w:id', maxBookmarkId)
// we verify that bookmark exists on title elements, if not there it means that we have to create it
if (textNode != null && textNode.parentNode.previousSibling?.nodeName !== 'w:bookmarkStart') {
const rNode = textNode.parentNode
const bookmarkStartEl = documentDoc.createElement('w:bookmarkStart')
const bookmarkEndEl = documentDoc.createElement('w:bookmarkEnd')
rNode.parentNode.insertBefore(bookmarkStartEl, rNode)
rNode.parentNode.insertBefore(bookmarkEndEl, rNode.nextSibling)
}
maxBookmarkId++
bookmarkStartEl.setAttribute('w:id', maxBookmarkId)
bookmarkStartEl.setAttribute('w:name', `_Toc${randomInteger(30000000, 90000000)}_r'`)
bookmarkEndEl.setAttribute('w:id', maxBookmarkId)
const wrapperEl = documentFile.createElement('TOCTitle')
wrapperEl.appendChild(clonedParagraphEl)
paragraphEl.parentNode.replaceChild(wrapperEl, paragraphEl)
rNode.parentNode.insertBefore(bookmarkStartEl, rNode)
rNode.parentNode.insertBefore(bookmarkEndEl, rNode.nextSibling)
}
const wrapperEl = documentDoc.createElement('TOCTitle')
wrapperEl.appendChild(clonedParagraphEl)
paragraphEl.parentNode.replaceChild(wrapperEl, paragraphEl)
})
// add here the setting of the document to automatically recalculate fields on open,
// this allows the MS Word to prompt the user to update the page numbers or toc table
// when opening the generated file
if (hasTOC) {
const existingUpdateFieldsEl = settingsFile.documentElement.getElementsByTagName('w:updateFields')[0]
if (maxBookmarkId != null) {
contentTypesDoc.documentElement.setAttribute('bookmarkMaxId', maxBookmarkId)
}
}
// if the setting is already on the document we don't generate it
if (existingUpdateFieldsEl == null) {
const newUpdateFieldsEl = documentFile.createElement('w:updateFields')
newUpdateFieldsEl.setAttribute('w:val', 'true')
function getParagraphStyleId (pEl) {
const pPrEl = nodeListToArray(pEl.childNodes).find((el) => el.nodeName === 'w:pPr')
settingsFile.documentElement.insertBefore(newUpdateFieldsEl, settingsFile.documentElement.firstChild)
}
if (pPrEl == null) {
return
}
if (maxBookmarkId != null) {
contentTypesFile.documentElement.setAttribute('bookmarkMaxId', maxBookmarkId)
const pStyleEl = nodeListToArray(pPrEl.childNodes).find((el) => el.nodeName === 'w:pStyle')
if (pStyleEl == null) {
return
}
const styleId = pStyleEl.getAttribute('w:val')
if (styleId == null || styleId === '') {
return
}
return styleId
}

@@ -221,0 +271,0 @@

@@ -117,2 +117,188 @@ const { XMLSerializer } = require('@xmldom/xmldom')

function getClosestEl (el, targetNodeNameOrFn, targetType = 'parent') {
let currentEl = el
let parentEl
const nodeTest = (n) => {
if (typeof targetNodeNameOrFn === 'string') {
return n.nodeName === targetNodeNameOrFn
} else {
return targetNodeNameOrFn(n)
}
}
if (targetType !== 'parent' && targetType !== 'previous') {
throw new Error(`Invalid target type "${targetType}"`)
}
do {
if (targetType === 'parent') {
currentEl = currentEl.parentNode
} else if (targetType === 'previous') {
currentEl = currentEl.previousSibling
}
if (currentEl != null && nodeTest(currentEl)) {
parentEl = currentEl
}
} while (currentEl != null && !nodeTest(currentEl))
return parentEl
}
function clearEl (el, filterFn) {
// by default we clear all children
const testFn = filterFn || (() => false)
const childEls = nodeListToArray(el.childNodes)
for (const childEl of childEls) {
const result = testFn(childEl)
if (result === true) {
continue
}
childEl.parentNode.removeChild(childEl)
}
}
function findOrCreateChildNode (docNode, nodeName, targetNode) {
let result
const existingNode = findChildNode(nodeName, targetNode)
if (!existingNode) {
result = docNode.createElement(nodeName)
targetNode.appendChild(result)
} else {
result = existingNode
}
return result
}
function findChildNode (nodeNameOrFn, targetNode, allNodes = false) {
const result = []
let testFn
if (typeof nodeNameOrFn === 'string') {
testFn = (n) => n.nodeName === nodeNameOrFn
} else {
testFn = nodeNameOrFn
}
for (let i = 0; i < targetNode.childNodes.length; i++) {
let found = false
const childNode = targetNode.childNodes[i]
const testResult = testFn(childNode)
if (testResult) {
found = true
result.push(childNode)
}
if (found && !allNodes) {
break
}
}
return allNodes ? result : result[0]
}
function createNode (doc, name, opts = {}) {
const attributes = opts.attributes || {}
const children = opts.children || []
const newEl = doc.createElement(name)
for (const [attrName, attrValue] of Object.entries(attributes)) {
newEl.setAttribute(attrName, attrValue)
}
for (const child of children) {
newEl.appendChild(child)
}
return newEl
}
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)
}
function pxToPt (val) {
if (typeof val !== 'number') {
return null
}
return val * 72 / 96
}
function ptToHalfPoint (val) {
if (typeof val !== 'number') {
return null
}
return val * 2
}
// pt to twentieths of a point (dxa)
function ptToTOAP (val) {
if (typeof val !== 'number') {
return null
}
return val * 20
}
function lengthToPx (value) {
if (!value) {
return null
}
if (typeof value === 'number') {
return value
}
const pt = value.match(/([.\d]+)pt/i)
if (pt && pt.length === 2) {
return parseFloat(pt[1], 10) * 96 / 72
}
const em = value.match(/([.\d]+)r?em/i)
if (em && em.length === 2) {
return parseFloat(em[1], 10) * 16
}
let px = value.match(/([.\d]+)px/i)
if (px && px.length === 2) {
return parseFloat(px[1], 10)
}
const pe = value.match(/([.\d]+)%/i)
if (pe && pe.length === 2) {
return (parseFloat(pe[1], 10) / 100) * 16
}
// if no unit is specified and number, assume px
px = value.match(/([.\d]+)/i)
if (px && px.length === 2) {
return parseFloat(px[1], 10)
}
return null
}
module.exports.findDefaultStyleIdForName = (stylesDoc, name, type = 'paragraph') => {

@@ -148,14 +334,16 @@ const styleEls = nodeListToArray(stylesDoc.getElementsByTagName('w:style'))

module.exports.pxToEMU = (val) => {
return Math.round(val * 914400 / 96)
}
module.exports.lengthToPt = (value) => {
const sizeInPx = lengthToPx(value)
module.exports.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)
if (sizeInPx == null) {
return sizeInPx
}
return pxToPt(sizeInPx)
}
module.exports.pxToEMU = pxToEMU
module.exports.cmToEMU = cmToEMU
module.exports.ptToHalfPoint = ptToHalfPoint
module.exports.ptToTOAP = ptToTOAP
module.exports.serializeXml = (doc) => new XMLSerializer().serializeToString(doc).replace(/ xmlns(:[a-z0-9]+)?=""/g, '')

@@ -166,2 +354,7 @@ module.exports.getNewRelId = getNewRelId

module.exports.getChartEl = getChartEl
module.exports.getClosestEl = getClosestEl
module.exports.clearEl = clearEl
module.exports.findOrCreateChildNode = findOrCreateChildNode
module.exports.findChildNode = findChildNode
module.exports.createNode = createNode
module.exports.nodeListToArray = nodeListToArray
{
"name": "@jsreport/jsreport-docx",
"version": "3.4.0",
"version": "3.5.0",
"description": "jsreport recipe rendering docx files",

@@ -39,14 +39,18 @@ "keywords": [

"axios": "0.24.0",
"cheerio": "1.0.0-rc.12",
"image-size": "0.7.4",
"js-excel-date-convert": "1.0.2",
"moment": "2.29.4",
"nanoid": "3.2.0",
"parse-css-sides": "3.0.1",
"semaphore-async-await": "1.5.1",
"string-replace-async": "2.0.0",
"style-attr": "1.3.0",
"tinycolor2": "1.4.2",
"unescape": "1.0.1"
},
"devDependencies": {
"@jsreport/jsreport-assets": "3.4.3",
"@jsreport/jsreport-core": "3.7.0",
"@jsreport/jsreport-handlebars": "3.2.0",
"@jsreport/jsreport-assets": "3.4.4",
"@jsreport/jsreport-core": "3.8.1",
"@jsreport/jsreport-handlebars": "3.2.1",
"@jsreport/studio-dev": "3.2.0",

@@ -53,0 +57,0 @@ "handlebars": "4.7.7",

@@ -10,2 +10,7 @@ # @jsreport/jsreport-docx

### 3.5.0
- add initial support for embedding html in docx (docxHtml helper)
- add helper `docxTOCOptions` to support configuring TOC behavior (only option available there right now is `updateFields` which controls if the generated docx file should show a prompt when it is being open in Word to decide if the TOC should be updated)
### 3.4.0

@@ -12,0 +17,0 @@

@@ -378,1 +378,16 @@ /* eslint no-unused-vars: 0 */

}
function docxHtml (options) {
const Handlebars = require('handlebars')
if (options.hash.content == null) {
throw new Error('docxHtml helper requires content parameter to be set')
}
return new Handlebars.SafeString('$docxHtml' + Buffer.from(JSON.stringify(options.hash)).toString('base64') + '$')
}
function docxTOCOptions (options) {
const Handlebars = require('handlebars')
return new Handlebars.SafeString('$docxTOCOptions' + Buffer.from(JSON.stringify(options.hash)).toString('base64') + '$')
}
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