-
Notifications
You must be signed in to change notification settings - Fork 220
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix support for pptxTable and also support for vertical tables
fix #1003 technically i replicated the full docxTable implementation however it seems there are some differences with the xml representation used in the pptx, so i skipped the dynamic rows/columns support and tests for now. it requires a bit more investigation and code to support dynamic rows/columns in pptx
- Loading branch information
Showing
4 changed files
with
580 additions
and
28 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,46 +1,340 @@ | ||
|
||
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) | ||
} | ||
const { nodeListToArray } = require('../utils') | ||
const regexp = /{{#?pptxTable [^{}]{0,500}}}/ | ||
|
||
// the same idea as list, check the docs there | ||
module.exports = (files) => { | ||
for (const f of files.filter(f => f.path.endsWith('.xml'))) { | ||
const doc = f.doc | ||
const elements = doc.getElementsByTagName('w:t') | ||
|
||
let openedDocx = false | ||
const elements = doc.getElementsByTagName('a:t') | ||
const openTags = [] | ||
|
||
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) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
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] | ||
} |
Oops, something went wrong.