docxml
Version:
TypeScript (component) library for building and parsing a DOCX file
168 lines (162 loc) • 6.69 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Cell = void 0;
const Component_js_1 = require("../classes/Component.js");
const table_cell_properties_js_1 = require("../properties/table-cell-properties.js");
const parameter_checking_js_1 = require("../utilities/parameter-checking.js");
const components_js_1 = require("../utilities/components.js");
const dom_js_1 = require("../utilities/dom.js");
const namespaces_js_1 = require("../utilities/namespaces.js");
const xquery_js_1 = require("../utilities/xquery.js");
const Paragraph_js_1 = require("./Paragraph.js");
const Row_js_1 = require("./Row.js");
const Table_js_1 = require("./Table.js");
/**
* A component that represents a table cell.
*
* For MS Word to be happy any cell needs to have a paragraph as the last child. This component will
* quietly fix that for you if you don't have a paragraph there already.
*/
class Cell extends Component_js_1.Component {
constructor(cellProps, ...cellChild) {
// Ensure that properties of type `number` are not `NaN`.
(0, parameter_checking_js_1.checkForForbiddenParameters)(cellProps, parameter_checking_js_1.isValidNumber, true);
super(cellProps, ...cellChild);
}
/**
* Creates an XML DOM node for this component instance.
*/
async toNode(ancestry) {
const table = ancestry.find((ancestor) => ancestor instanceof Table_js_1.Table);
if (!table) {
throw new Error('A cell cannot be rendered outside the context of a table');
}
const children = (await this.childrenToNode(ancestry));
if (!(this.children[this.children.length - 1] instanceof Paragraph_js_1.Paragraph)) {
// Cells must always end with a paragraph, or MS Word will complain about
// file corruption.
children.push(await new Paragraph_js_1.Paragraph({}).toNode([this, ...ancestry]));
}
return (0, dom_js_1.create)(`element ${namespaces_js_1.QNS.w}tc {
$tcPr,
$children
}`, {
tcPr: (0, table_cell_properties_js_1.tableCellPropertiesToNode)({
colSpan: this.getColSpan(),
rowSpan: this.getRowSpan(),
width: table.props.columnWidths?.[table.model.getCellInfo(this).column] || null,
...this.props,
}, false),
children,
});
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
toRepeatingNode(ancestry, column, _row) {
const table = ancestry.find((ancestor) => ancestor instanceof Table_js_1.Table);
if (!table) {
throw new Error('A cell cannot be rendered outside the context of a table');
}
const info = table.model.getCellInfo(this);
if (column > info.column) {
// Colspans are only recorded on the left-most cell coordinate. No extra node needed;
return null;
}
return (0, dom_js_1.create)(`element ${namespaces_js_1.QNS.w}tc {
$tcPr,
element ${namespaces_js_1.QNS.w}p {}
}`, {
tcPr: (0, table_cell_properties_js_1.tableCellPropertiesToNode)({
width: table.props.columnWidths?.[info.column] || null,
colSpan: this.getColSpan(),
rowSpan: this.getRowSpan(),
...this.props,
}, true),
});
}
/**
* Returns `true` when this cell has no visual representation because a column-spanning or row-
* spanning neighbour overlaps it.
*/
isMergedAway(ancestry) {
const row = ancestry.find((ancestor) => ancestor instanceof Row_js_1.Row);
if (!row) {
throw new Error('A cell cannot be rendered outside the context of a row');
}
const table = ancestry.find((ancestor) => ancestor instanceof Table_js_1.Table);
if (!table) {
throw new Error('A cell cannot be rendered outside the context of a table');
}
const x = row.children.indexOf(this);
const y = table.children.indexOf(row);
if (y === -1 || x === -1) {
throw new Error('The cell is not part of this table');
}
const info = table.model.getCellInfo(this);
return info.column !== x || info.row !== y;
}
getColSpan() {
return this.props.colSpan || 1;
}
getRowSpan() {
return this.props.rowSpan || 1;
}
/**
* Asserts whether or not a given XML node correlates with this component.
*/
static matchesNode(node) {
return node.nodeName === 'w:tc';
}
/**
* Instantiate this component from the XML in an existing DOCX file.
*/
static fromNode(node, context) {
const { mergedAway, children, ...props } = (0, xquery_js_1.evaluateXPathToMap)(`
let $colStart := docxml:cell-column(.)
let $rowStart := count(../preceding-sibling::${namespaces_js_1.QNS.w}tr)
let $firstNextRow := ../following-sibling::${namespaces_js_1.QNS.w}tr[
child::${namespaces_js_1.QNS.w}tc[docxml:spans-cell-column(., $colStart) and not(
./${namespaces_js_1.QNS.w}tcPr/${namespaces_js_1.QNS.w}vMerge[
@${namespaces_js_1.QNS.w}val = "continue" or
not(./@${namespaces_js_1.QNS.w}val)
]
)]
][1]
let $rowEnd := if ($firstNextRow)
then count($firstNextRow/preceding-sibling::${namespaces_js_1.QNS.w}tr)
else count(../../${namespaces_js_1.QNS.w}tr)
let $mergeCell := boolean(./${namespaces_js_1.QNS.w}tcPr/${namespaces_js_1.QNS.w}vMerge[not(./@${namespaces_js_1.QNS.w}val)])
return map {
"mergedAway": $mergeCell,
"colSpan": if (./${namespaces_js_1.QNS.w}tcPr/${namespaces_js_1.QNS.w}gridSpan)
then ./${namespaces_js_1.QNS.w}tcPr/${namespaces_js_1.QNS.w}gridSpan/@${namespaces_js_1.QNS.w}val/number()
else 1,
"rowSpan": $rowEnd - $rowStart,
"children": array{ ./(${namespaces_js_1.QNS.w}p) },
"verticalAlignment": ./${namespaces_js_1.QNS.w}tcPr/${namespaces_js_1.QNS.w}vAlign/@${namespaces_js_1.QNS.w}val/string()
}
`, node);
if (mergedAway) {
return null;
}
return new Cell(props, ...(0, components_js_1.createChildComponentsFromNodes)(this.children, children, context));
}
}
exports.Cell = Cell;
Object.defineProperty(Cell, "children", {
enumerable: true,
configurable: true,
writable: true,
value: [
'Paragraph',
'Table',
'BookmarkRangeStart',
'BookmarkRangeEnd',
]
});
Object.defineProperty(Cell, "mixed", {
enumerable: true,
configurable: true,
writable: true,
value: false
});
(0, components_js_1.registerComponent)(Cell);