849 lines
24 KiB
JavaScript
849 lines
24 KiB
JavaScript
import {
|
||
AlignmentType,
|
||
BorderStyle,
|
||
Document,
|
||
LineRuleType,
|
||
Packer,
|
||
Paragraph,
|
||
ShadingType,
|
||
Table,
|
||
TableCell,
|
||
TableLayoutType,
|
||
TableRow,
|
||
TextRun,
|
||
VerticalAlign,
|
||
WidthType
|
||
} from 'docx';
|
||
import { saveAs } from 'file-saver';
|
||
import JSZip from 'jszip';
|
||
import { TableUtils } from '@/common/js/TableUtils';
|
||
import { isMathFormulaTableRecord } from '@/utils/mathFormulaModule';
|
||
|
||
/** 斑马纹 rgb(250, 231, 232) */
|
||
const ODD_ROW_FILL = 'FAE7E8';
|
||
/** 标题 rgb(210, 90, 90) */
|
||
const TITLE_COLOR = 'D25A5A';
|
||
/** 引用 blue rgb(0, 130, 170) */
|
||
const BLUE_COLOR = '0082AA';
|
||
const FONT_NAME = 'Charis SIL';
|
||
/** 六号 = 7.5pt */
|
||
const TABLE_FONT_SIZE = 15;
|
||
const TITLE_FONT_SIZE = TABLE_FONT_SIZE;
|
||
/** Word「为字体调整字间距」:1 磅或更大(w:kern 以半磅为单位) */
|
||
const FONT_KERN_MIN_1PT = 2;
|
||
/** Word「为字体调整字间距」:小二(18pt)或更大 */
|
||
const FONT_KERN_MIN_XIAO_ER = 36;
|
||
|
||
function cmToTwips(cm) {
|
||
return Math.round((cm * 1440) / 2.54);
|
||
}
|
||
|
||
/** 页面边距:上/下 2.54cm,左/右 1.91cm */
|
||
const PAGE_MARGINS = {
|
||
top: cmToTwips(2.54),
|
||
bottom: cmToTwips(2.54),
|
||
left: cmToTwips(1.91),
|
||
right: cmToTwips(1.91)
|
||
};
|
||
|
||
/** 表格属性:宽 98.3%,整体居中,无环绕 */
|
||
const TABLE_WIDTH_PERCENT = 98.3;
|
||
/** 表格选项(图二):单元格边距 上/下 0、左/右 0.19cm,自动适应内容 */
|
||
const TABLE_CELL_MARGIN_TWIPS = cmToTwips(0.19);
|
||
const TABLE_CELL_MARGINS = {
|
||
top: 0,
|
||
bottom: 0,
|
||
left: TABLE_CELL_MARGIN_TWIPS,
|
||
right: TABLE_CELL_MARGIN_TWIPS
|
||
};
|
||
/** 行属性:允许跨页断行,不指定行高 */
|
||
const TABLE_ROW_OPTIONS = {
|
||
cantSplit: false
|
||
};
|
||
/** 全篇段落:固定行距 10 磅(1pt = 20 twips → 10pt = 200) */
|
||
const WORD_PARAGRAPH_SPACING = {
|
||
before: 0,
|
||
after: 0,
|
||
line: 200,
|
||
lineRule: LineRuleType.EXACT,
|
||
beforeAutoSpacing: false,
|
||
afterAutoSpacing: false
|
||
};
|
||
|
||
const WORD_DOCUMENT_STYLES = {
|
||
default: {
|
||
document: {
|
||
paragraph: {
|
||
spacing: WORD_PARAGRAPH_SPACING
|
||
},
|
||
run: {
|
||
font: FONT_NAME,
|
||
size: TABLE_FONT_SIZE,
|
||
kern: FONT_KERN_MIN_1PT
|
||
}
|
||
}
|
||
},
|
||
paragraphStyles: [
|
||
{
|
||
id: 'Normal',
|
||
name: 'Normal',
|
||
quickFormat: true,
|
||
spacing: WORD_PARAGRAPH_SPACING,
|
||
run: {
|
||
font: FONT_NAME,
|
||
size: TABLE_FONT_SIZE,
|
||
kern: FONT_KERN_MIN_1PT
|
||
}
|
||
},
|
||
{
|
||
id: 'Heading1',
|
||
name: 'Heading 1',
|
||
basedOn: 'Normal',
|
||
next: 'Normal',
|
||
quickFormat: true,
|
||
spacing: WORD_PARAGRAPH_SPACING,
|
||
run: {
|
||
font: FONT_NAME,
|
||
size: TITLE_FONT_SIZE,
|
||
bold: true,
|
||
color: TITLE_COLOR,
|
||
kern: FONT_KERN_MIN_1PT
|
||
}
|
||
}
|
||
]
|
||
};
|
||
|
||
/** 每个表格块末尾空行数 */
|
||
const TABLE_BLOCK_EMPTY_LINE_COUNT = 4;
|
||
|
||
function createWordParagraph(children, options) {
|
||
const opts = options || {};
|
||
let alignment = AlignmentType.JUSTIFIED;
|
||
if (opts.alignment !== undefined && opts.alignment !== null) {
|
||
alignment = opts.alignment;
|
||
}
|
||
|
||
const paragraphOptions = {
|
||
spacing: WORD_PARAGRAPH_SPACING,
|
||
indent: {
|
||
left: 0,
|
||
right: 0
|
||
},
|
||
children
|
||
};
|
||
|
||
if (opts.title) {
|
||
paragraphOptions.alignment = AlignmentType.CENTER;
|
||
paragraphOptions.style = 'Normal';
|
||
} else {
|
||
paragraphOptions.alignment = alignment;
|
||
paragraphOptions.style = 'Normal';
|
||
}
|
||
|
||
return new Paragraph(paragraphOptions);
|
||
}
|
||
|
||
function appendTableBlockEmptyLines(children) {
|
||
let i = 0;
|
||
for (i = 0; i < TABLE_BLOCK_EMPTY_LINE_COUNT; i += 1) {
|
||
children.push(createTableParagraph([new TextRun('')], AlignmentType.JUSTIFIED));
|
||
}
|
||
}
|
||
|
||
/** 表格标题段落:居中,固定行距 10pt,段前段后 0,无缩进(非大纲一级) */
|
||
function createTitleParagraph(children) {
|
||
return createWordParagraph(children, { alignment: AlignmentType.CENTER });
|
||
}
|
||
|
||
function decodeTableCaptionXmlText(text) {
|
||
return String(text || '')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"')
|
||
.replace(/'/g, "'")
|
||
.replace(/&/g, '&');
|
||
}
|
||
|
||
function extractTableCaptionParagraphPlainText(pInner) {
|
||
const parts = [];
|
||
String(pInner || '').replace(/<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>/g, function (_match, value) {
|
||
parts.push(decodeTableCaptionXmlText(value));
|
||
});
|
||
return parts.join('').replace(/[\u200B-\u200D\uFEFF]/g, '').trim();
|
||
}
|
||
|
||
function isBodyTableCaptionPlainText(text) {
|
||
return /^Table\s+\d+/i.test(String(text || '').trim());
|
||
}
|
||
|
||
function applyTableCaptionCenterParagraphPr(pInner) {
|
||
if (/<w:pPr>/.test(pInner)) {
|
||
let inner = pInner
|
||
.replace(/<w:jc\b[^>]*\/>/g, '')
|
||
.replace(/<w:jc\b[^>]*>[\s\S]*?<\/w:jc>/g, '');
|
||
return inner.replace(/<w:pPr>/, '<w:pPr><w:jc w:val="center"/>');
|
||
}
|
||
return '<w:pPr><w:jc w:val="center"/></w:pPr>' + pInner;
|
||
}
|
||
|
||
/** 正文表格标题(Table N…)紧挨 w:tbl 前的段落强制居中,不含首页元数据表格 */
|
||
export function patchBodyTableCaptionCenterToXml(xml) {
|
||
return xml.replace(/((?:<w:p\b[^>]*>[\s\S]*?<\/w:p>\s*)+)(\s*<w:tbl\b)/gi, function (match, paragraphGroup, tblOpen) {
|
||
const blocks = paragraphGroup.match(/<w:p\b[^>]*>[\s\S]*?<\/w:p>/gi) || [];
|
||
if (!blocks.length) {
|
||
return match;
|
||
}
|
||
|
||
const firstText = extractTableCaptionParagraphPlainText(
|
||
blocks[0].replace(/^<w:p\b[^>]*>/, '').replace(/<\/w:p>$/, '')
|
||
);
|
||
if (!isBodyTableCaptionPlainText(firstText)) {
|
||
return match;
|
||
}
|
||
|
||
const centered = blocks.map(function (block) {
|
||
return block.replace(/(<w:p\b[^>]*>)([\s\S]*?)(<\/w:p>)/, function (_full, open, inner, close) {
|
||
return open + applyTableCaptionCenterParagraphPr(inner) + close;
|
||
});
|
||
});
|
||
return centered.join('') + tblOpen;
|
||
});
|
||
}
|
||
|
||
/** 导出后处理:除首页表格外,正文表格标题居中 */
|
||
async function patchBodyTableCaptionCenter(blob) {
|
||
if (!blob) {
|
||
return blob;
|
||
}
|
||
|
||
const zip = await JSZip.loadAsync(await blob.arrayBuffer());
|
||
const documentFile = zip.file('word/document.xml');
|
||
if (!documentFile) {
|
||
return blob;
|
||
}
|
||
|
||
let xml = await documentFile.async('string');
|
||
if (!/\bTable\s+\d+/i.test(xml)) {
|
||
return blob;
|
||
}
|
||
|
||
xml = patchBodyTableCaptionCenterToXml(xml);
|
||
|
||
zip.file('word/document.xml', xml);
|
||
return zip.generateAsync({
|
||
type: 'blob',
|
||
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||
});
|
||
}
|
||
|
||
/** 图片标题:与表格标题相同字号/颜色/段落,但两端对齐;末尾无句点则补 . */
|
||
function ensureFigureTitleTrailingPeriod(text) {
|
||
const trimmed = String(text || '').trim();
|
||
if (!trimmed) {
|
||
return '';
|
||
}
|
||
if (trimmed.endsWith('.')) {
|
||
return trimmed;
|
||
}
|
||
return trimmed + '.';
|
||
}
|
||
|
||
function createFigureTitleParagraph(children) {
|
||
return createWordParagraph(children, { alignment: AlignmentType.JUSTIFIED });
|
||
}
|
||
|
||
function appendFigureTitleParagraphs(children, titleHtml) {
|
||
const segments = splitHtmlSegments(titleHtml)
|
||
.map(function (segment) {
|
||
return htmlToPlainText(segment).trim();
|
||
})
|
||
.filter(Boolean);
|
||
if (!segments.length) {
|
||
return;
|
||
}
|
||
|
||
segments.forEach(function (segment, index) {
|
||
const text =
|
||
index === segments.length - 1 ? ensureFigureTitleTrailingPeriod(segment) : segment;
|
||
children.push(
|
||
createFigureTitleParagraph([
|
||
new TextRun({
|
||
text: text,
|
||
bold: true,
|
||
color: TITLE_COLOR,
|
||
font: FONT_NAME,
|
||
size: TITLE_FONT_SIZE,
|
||
kern: FONT_KERN_MIN_1PT
|
||
})
|
||
])
|
||
);
|
||
});
|
||
}
|
||
|
||
function createTableParagraph(children, alignment) {
|
||
return createWordParagraph(children, { alignment });
|
||
}
|
||
|
||
function splitHtmlSegments(html) {
|
||
const raw = String(html || '');
|
||
if (!raw) {
|
||
return [''];
|
||
}
|
||
const parts = raw.split(/<br\s*\/?>/gi);
|
||
if (!parts.length) {
|
||
return [''];
|
||
}
|
||
return parts;
|
||
}
|
||
|
||
function decodeHtmlEntities(text) {
|
||
return String(text || '')
|
||
.replace(/ /g, ' ')
|
||
.replace(/&/g, '&')
|
||
.replace(/</g, '<')
|
||
.replace(/>/g, '>')
|
||
.replace(/"/g, '"');
|
||
}
|
||
|
||
function htmlToPlainText(html) {
|
||
const raw = String(html || '');
|
||
if (typeof document !== 'undefined') {
|
||
const node = document.createElement('div');
|
||
node.innerHTML = raw;
|
||
return (node.textContent || node.innerText || '').replace(/\u00a0/g, ' ').trim();
|
||
}
|
||
return decodeHtmlEntities(
|
||
raw
|
||
.replace(/<br\s*\/?>/gi, '\n')
|
||
.replace(/<[^>]*>/g, '')
|
||
).trim();
|
||
}
|
||
|
||
function isBlueElement(node) {
|
||
if (!node || node.nodeType !== Node.ELEMENT_NODE) {
|
||
return false;
|
||
}
|
||
const tag = node.tagName.toLowerCase();
|
||
if (tag === 'blue') {
|
||
return true;
|
||
}
|
||
if (tag === 'span' && node.classList && node.classList.contains('blue')) {
|
||
return true;
|
||
}
|
||
return !!(node.classList && node.classList.contains('color-highlight'));
|
||
}
|
||
|
||
/** 参考文献标号 [1] / [1, 2]:逗号后补空格(与 HTML 排版规则一致,重复 3 次) */
|
||
function normalizeCitationBracketCommaSpacing(text) {
|
||
return String(text || '').replace(/\[([^\]]*)\]/g, function (match, inner) {
|
||
let fixed = inner;
|
||
for (let i = 0; i < 3; i++) {
|
||
fixed = fixed.replace(/(\d),(\d)/g, '$1, $2');
|
||
}
|
||
return '[' + fixed + ']';
|
||
});
|
||
}
|
||
|
||
/** 匹配正文参考文献标号:\[[0-9, -\-\]{1,}\] */
|
||
const CITATION_BRACKET_PATTERN = /\[[0-9, \-]+\]/g;
|
||
|
||
function appendRunsForCitationText(text, style, appendRun) {
|
||
const normalized = normalizeCitationBracketCommaSpacing(text);
|
||
if (!normalized) {
|
||
return;
|
||
}
|
||
|
||
let lastIndex = 0;
|
||
let match;
|
||
CITATION_BRACKET_PATTERN.lastIndex = 0;
|
||
while ((match = CITATION_BRACKET_PATTERN.exec(normalized)) !== null) {
|
||
if (match.index > lastIndex) {
|
||
appendRun(normalized.slice(lastIndex, match.index), style);
|
||
}
|
||
appendRun(match[0], Object.assign({}, style, { blue: true }));
|
||
lastIndex = match.index + match[0].length;
|
||
}
|
||
|
||
if (lastIndex < normalized.length) {
|
||
appendRun(normalized.slice(lastIndex), style);
|
||
}
|
||
}
|
||
|
||
function htmlToTextRuns(html, options) {
|
||
const { bold = false } = options || {};
|
||
const runs = [];
|
||
const raw = String(html || '');
|
||
|
||
if (!raw) {
|
||
return [
|
||
new TextRun({
|
||
text: '',
|
||
font: FONT_NAME,
|
||
size: TABLE_FONT_SIZE,
|
||
bold,
|
||
kern: FONT_KERN_MIN_1PT
|
||
})
|
||
];
|
||
}
|
||
|
||
if (typeof document === 'undefined') {
|
||
const plainRuns = [];
|
||
appendRunsForCitationText(htmlToPlainText(raw), { bold, italics: false, sup: false, sub: false, blue: false }, function (
|
||
text,
|
||
style
|
||
) {
|
||
plainRuns.push(
|
||
new TextRun({
|
||
text,
|
||
font: FONT_NAME,
|
||
size: TABLE_FONT_SIZE,
|
||
bold: bold || style.bold,
|
||
italics: style.italic,
|
||
superScript: style.sup,
|
||
subScript: style.sub,
|
||
color: style.blue ? BLUE_COLOR : undefined,
|
||
kern: FONT_KERN_MIN_1PT
|
||
})
|
||
);
|
||
});
|
||
if (plainRuns.length) {
|
||
return plainRuns;
|
||
}
|
||
return [
|
||
new TextRun({
|
||
text: htmlToPlainText(raw),
|
||
font: FONT_NAME,
|
||
size: TABLE_FONT_SIZE,
|
||
bold,
|
||
kern: FONT_KERN_MIN_1PT
|
||
})
|
||
];
|
||
}
|
||
|
||
const root = document.createElement('div');
|
||
root.innerHTML = raw;
|
||
|
||
function appendRun(text, style) {
|
||
if (!text) {
|
||
return;
|
||
}
|
||
runs.push(
|
||
new TextRun({
|
||
text,
|
||
font: FONT_NAME,
|
||
size: TABLE_FONT_SIZE,
|
||
bold: bold || style.bold,
|
||
italics: style.italic,
|
||
superScript: style.sup,
|
||
subScript: style.sub,
|
||
color: style.blue ? BLUE_COLOR : undefined,
|
||
kern: FONT_KERN_MIN_1PT
|
||
})
|
||
);
|
||
}
|
||
|
||
function walk(node, style) {
|
||
if (node.nodeType === Node.TEXT_NODE) {
|
||
appendRunsForCitationText(
|
||
decodeHtmlEntities(node.textContent).replace(/\u00a0/g, ' '),
|
||
style,
|
||
appendRun
|
||
);
|
||
return;
|
||
}
|
||
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||
return;
|
||
}
|
||
|
||
const tag = node.tagName.toLowerCase();
|
||
if (tag === 'br') {
|
||
appendRun('\n', style);
|
||
return;
|
||
}
|
||
|
||
const nextStyle = {
|
||
bold: style.bold,
|
||
italic: style.italic,
|
||
sup: style.sup,
|
||
sub: style.sub,
|
||
blue: style.blue
|
||
};
|
||
if (tag === 'b' || tag === 'strong') {
|
||
nextStyle.bold = true;
|
||
}
|
||
if (tag === 'i' || tag === 'em') {
|
||
nextStyle.italic = true;
|
||
}
|
||
if (tag === 'sup') {
|
||
nextStyle.sup = true;
|
||
}
|
||
if (tag === 'sub') {
|
||
nextStyle.sub = true;
|
||
}
|
||
if (isBlueElement(node)) {
|
||
nextStyle.blue = true;
|
||
}
|
||
|
||
Array.from(node.childNodes).forEach((child) => walk(child, nextStyle));
|
||
}
|
||
|
||
walk(root, {
|
||
bold: false,
|
||
italic: false,
|
||
sup: false,
|
||
sub: false,
|
||
blue: false
|
||
});
|
||
|
||
if (!runs.length) {
|
||
return [
|
||
new TextRun({
|
||
text: htmlToPlainText(raw),
|
||
font: FONT_NAME,
|
||
size: TABLE_FONT_SIZE,
|
||
bold,
|
||
kern: FONT_KERN_MIN_1PT
|
||
})
|
||
];
|
||
}
|
||
|
||
return runs;
|
||
}
|
||
|
||
/** 表头:相邻片段之间补空格,再规范为“一词一空” */
|
||
function formatHeaderWordSpacing(html) {
|
||
const raw = String(html || '');
|
||
if (!raw) {
|
||
return '';
|
||
}
|
||
if (typeof document !== 'undefined') {
|
||
const root = document.createElement('div');
|
||
root.innerHTML = raw;
|
||
const parts = [];
|
||
|
||
function collect(node) {
|
||
if (node.nodeType === Node.TEXT_NODE) {
|
||
const text = decodeHtmlEntities(node.textContent).replace(/\u00a0/g, ' ').trim();
|
||
if (text) {
|
||
parts.push(text);
|
||
}
|
||
return;
|
||
}
|
||
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||
return;
|
||
}
|
||
if (node.tagName.toLowerCase() === 'br') {
|
||
parts.push('\n');
|
||
return;
|
||
}
|
||
Array.from(node.childNodes).forEach(collect);
|
||
}
|
||
|
||
collect(root);
|
||
return parts
|
||
.join(' ')
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
}
|
||
|
||
return htmlToPlainText(raw)
|
||
.split(/\s+/)
|
||
.filter(Boolean)
|
||
.join(' ');
|
||
}
|
||
|
||
function htmlToHeaderTextRuns(html) {
|
||
return [
|
||
new TextRun({
|
||
text: formatHeaderWordSpacing(html),
|
||
font: FONT_NAME,
|
||
size: TABLE_FONT_SIZE,
|
||
bold: true,
|
||
kern: FONT_KERN_MIN_1PT
|
||
})
|
||
];
|
||
}
|
||
|
||
/** 表格边框:黑色 0.5 磅(OOXML sz 单位为 1/8 pt → 0.5pt = 4) */
|
||
const TABLE_BORDER = {
|
||
style: BorderStyle.SINGLE,
|
||
size: 4,
|
||
color: '000000'
|
||
};
|
||
const TABLE_BORDER_NONE = {
|
||
style: BorderStyle.NONE,
|
||
size: 0,
|
||
color: 'FFFFFF'
|
||
};
|
||
|
||
function createBorder(show) {
|
||
return show ? TABLE_BORDER : TABLE_BORDER_NONE;
|
||
}
|
||
|
||
function createTableCell(cell, options) {
|
||
const { header = false, odd = false, lastRow = false } = options;
|
||
if (!cell || cell.rowspan === 0 || cell.colspan === 0) {
|
||
return null;
|
||
}
|
||
|
||
const cellOptions = {
|
||
verticalAlign: VerticalAlign.CENTER,
|
||
borders: {
|
||
top: createBorder(header),
|
||
bottom: createBorder(header || lastRow),
|
||
left: createBorder(false),
|
||
right: createBorder(false)
|
||
},
|
||
children: splitHtmlSegments(cell.text).map((segment) =>
|
||
createTableParagraph(
|
||
header ? htmlToHeaderTextRuns(segment) : htmlToTextRuns(segment, { bold: false }),
|
||
AlignmentType.LEFT
|
||
)
|
||
)
|
||
};
|
||
|
||
if (cell.colspan > 1) {
|
||
cellOptions.columnSpan = cell.colspan;
|
||
}
|
||
if (cell.rowspan > 1) {
|
||
cellOptions.rowSpan = cell.rowspan;
|
||
}
|
||
if (odd) {
|
||
cellOptions.shading = {
|
||
fill: ODD_ROW_FILL,
|
||
type: ShadingType.CLEAR,
|
||
color: 'auto'
|
||
};
|
||
}
|
||
|
||
return new TableCell(cellOptions);
|
||
}
|
||
|
||
function createTableRow(cells) {
|
||
return new TableRow({
|
||
...TABLE_ROW_OPTIONS,
|
||
children: cells
|
||
});
|
||
}
|
||
|
||
function buildTableRows(table) {
|
||
const headers = (table && table.tableHeader) || [];
|
||
const contents = (table && table.tableContent) || [];
|
||
const oddRowIds = (table && table.oddRowIds) || [];
|
||
const rows = [];
|
||
|
||
headers.forEach((row) => {
|
||
const cells = (row || []).map((cell) => createTableCell(cell, { header: true })).filter(Boolean);
|
||
if (cells.length) {
|
||
rows.push(createTableRow(cells));
|
||
}
|
||
});
|
||
|
||
contents.forEach((row, rowIndex) => {
|
||
const odd = oddRowIds.length ? oddRowIds.includes(row.rowId) : rowIndex % 2 === 0;
|
||
const cells = (row || [])
|
||
.map((cell) =>
|
||
createTableCell(cell, {
|
||
odd,
|
||
lastRow: rowIndex === contents.length - 1
|
||
})
|
||
)
|
||
.filter(Boolean);
|
||
if (cells.length) {
|
||
rows.push(createTableRow(cells));
|
||
}
|
||
});
|
||
|
||
return rows;
|
||
}
|
||
|
||
function createWordDocument(children) {
|
||
return new Document({
|
||
styles: WORD_DOCUMENT_STYLES,
|
||
sections: [
|
||
{
|
||
properties: {
|
||
page: {
|
||
margin: PAGE_MARGINS
|
||
},
|
||
grid: {
|
||
linePitch: 200
|
||
}
|
||
},
|
||
children
|
||
}
|
||
]
|
||
});
|
||
}
|
||
|
||
export function prepareTableExportItem(item) {
|
||
if (!item || isMathFormulaTableRecord(item)) {
|
||
return null;
|
||
}
|
||
|
||
if (item.table && item.table.tableHeader) {
|
||
return {
|
||
title: item.title || '',
|
||
note: item.note || '',
|
||
table: item.table
|
||
};
|
||
}
|
||
|
||
try {
|
||
const tableList = typeof item.table === 'string' ? JSON.parse(item.table) : item.table;
|
||
const splitResult = TableUtils.splitTable(tableList);
|
||
const rowResult = TableUtils.addRowIdToData(splitResult.content);
|
||
return {
|
||
title: item.title || '',
|
||
note: item.note || '',
|
||
table: {
|
||
tableHeader: splitResult.header,
|
||
tableContent: rowResult.rowData,
|
||
oddRowIds: rowResult.rowIds
|
||
}
|
||
};
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
|
||
function buildTableWordChildren(processedItem, options) {
|
||
const opts = options || {};
|
||
const appendTrailingEmptyLines = opts.appendTrailingEmptyLines !== false;
|
||
const title = (processedItem && processedItem.title) || '';
|
||
const note = (processedItem && processedItem.note) || '';
|
||
const table = (processedItem && processedItem.table) || {};
|
||
const children = [];
|
||
|
||
if (title) {
|
||
splitHtmlSegments(title).forEach((segment) => {
|
||
children.push(
|
||
createTitleParagraph([
|
||
new TextRun({
|
||
text: htmlToPlainText(segment),
|
||
bold: true,
|
||
color: TITLE_COLOR,
|
||
font: FONT_NAME,
|
||
size: TITLE_FONT_SIZE,
|
||
kern: FONT_KERN_MIN_1PT
|
||
})
|
||
])
|
||
);
|
||
});
|
||
}
|
||
|
||
const tableRows = buildTableRows(table);
|
||
if (tableRows.length) {
|
||
children.push(
|
||
new Table({
|
||
width: {
|
||
size: TABLE_WIDTH_PERCENT,
|
||
type: WidthType.PERCENTAGE
|
||
},
|
||
alignment: AlignmentType.CENTER,
|
||
layout: TableLayoutType.AUTOFIT,
|
||
margins: TABLE_CELL_MARGINS,
|
||
borders: {
|
||
top: TABLE_BORDER_NONE,
|
||
bottom: TABLE_BORDER_NONE,
|
||
left: TABLE_BORDER_NONE,
|
||
right: TABLE_BORDER_NONE,
|
||
insideHorizontal: TABLE_BORDER_NONE,
|
||
insideVertical: TABLE_BORDER_NONE
|
||
},
|
||
rows: tableRows
|
||
})
|
||
);
|
||
}
|
||
|
||
if (note) {
|
||
splitHtmlSegments(note).forEach((segment) => {
|
||
children.push(
|
||
createTableParagraph(htmlToTextRuns(segment, { bold: false }), AlignmentType.JUSTIFIED)
|
||
);
|
||
});
|
||
}
|
||
|
||
if (appendTrailingEmptyLines) {
|
||
appendTableBlockEmptyLines(children);
|
||
}
|
||
|
||
return children;
|
||
}
|
||
|
||
export function buildTableWordDocument(processedItem) {
|
||
return createWordDocument(buildTableWordChildren(processedItem));
|
||
}
|
||
|
||
export function buildAllTablesWordDocument(items) {
|
||
const children = [];
|
||
(items || []).forEach((item) => {
|
||
const prepared = prepareTableExportItem(item);
|
||
if (!prepared) {
|
||
return;
|
||
}
|
||
const blockChildren = buildTableWordChildren(prepared);
|
||
blockChildren.forEach((child) => {
|
||
children.push(child);
|
||
});
|
||
});
|
||
return createWordDocument(children);
|
||
}
|
||
|
||
function sanitizeFileName(name) {
|
||
const base = String(name || 'table')
|
||
.replace(/<[^>]+>/g, '')
|
||
.replace(/[\\/:*?"<>|]/g, '')
|
||
.trim()
|
||
.slice(0, 80);
|
||
return base || 'table';
|
||
}
|
||
|
||
export async function downloadTableWord(processedItem, fileName) {
|
||
const doc = buildTableWordDocument(processedItem);
|
||
let blob = await Packer.toBlob(doc);
|
||
blob = await patchBodyTableCaptionCenter(blob);
|
||
saveAs(blob, `${sanitizeFileName(fileName || processedItem.title)}.docx`);
|
||
}
|
||
|
||
export async function downloadAllTablesWord(items, fileName) {
|
||
const exportItems = [];
|
||
(items || []).forEach((item) => {
|
||
const prepared = prepareTableExportItem(item);
|
||
if (prepared) {
|
||
exportItems.push(prepared);
|
||
}
|
||
});
|
||
|
||
if (!exportItems.length) {
|
||
const error = new Error('NO_TABLES');
|
||
throw error;
|
||
}
|
||
|
||
const doc = buildAllTablesWordDocument(exportItems);
|
||
let blob = await Packer.toBlob(doc);
|
||
blob = await patchBodyTableCaptionCenter(blob);
|
||
saveAs(blob, `${sanitizeFileName(fileName || 'tables')}.docx`);
|
||
}
|
||
|
||
export {
|
||
appendFigureTitleParagraphs,
|
||
appendTableBlockEmptyLines,
|
||
buildTableWordChildren,
|
||
createFigureTitleParagraph,
|
||
createTableParagraph,
|
||
createWordDocument,
|
||
ensureFigureTitleTrailingPeriod,
|
||
FONT_KERN_MIN_1PT,
|
||
FONT_KERN_MIN_XIAO_ER,
|
||
FONT_NAME,
|
||
htmlToPlainText,
|
||
htmlToTextRuns,
|
||
PAGE_MARGINS,
|
||
patchBodyTableCaptionCenter,
|
||
splitHtmlSegments,
|
||
TABLE_FONT_SIZE,
|
||
TITLE_COLOR,
|
||
WORD_PARAGRAPH_SPACING
|
||
};
|