Files
tougao_web/src/utils/exportTableWord.js
2026-07-10 09:12:09 +08:00

1023 lines
29 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
AlignmentType,
BorderStyle,
CommentRangeEnd,
CommentRangeStart,
CommentReference,
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';
import { expandCitationBracket } from '@/utils/manuscriptCitationRelevance';
import { tokenizeReferenceHighlightSegments } from '@/utils/manuscriptMediaReferences';
/** 斑马纹 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(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&apos;/g, "'")
.replace(/&amp;/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;
}
/** 从 tbl 前向前收集紧邻的 w:p避免全文正则灾难性回溯 */
function collectParagraphBlocksImmediatelyBefore(xml, tblIdx, minIndex) {
const blocks = [];
let end = tblIdx;
while (end > minIndex && blocks.length < 8) {
let ws = end;
while (ws > minIndex && /\s/.test(xml.charAt(ws - 1))) {
ws -= 1;
}
if (ws <= minIndex) {
break;
}
const closeIdx = xml.lastIndexOf('</w:p>', ws - 1);
if (closeIdx < minIndex) {
break;
}
const openIdx = xml.lastIndexOf('<w:p', closeIdx);
if (openIdx < minIndex) {
break;
}
const raw = xml.slice(openIdx, ws);
if (!/<\/w:p>\s*$/i.test(raw)) {
break;
}
blocks.unshift({
start: openIdx,
raw: raw,
inner: raw.replace(/^<w:p\b[^>]*>/i, '').replace(/<\/w:p>\s*$/i, '')
});
end = openIdx;
}
return blocks;
}
function isHomePageMetadataTableBlock(block) {
const text = String(block || '');
return /Author/i.test(text) && /contributions/i.test(text) && /Abstract/i.test(text);
}
/** 正文表格标题Table N…紧挨 w:tbl 前的段落强制居中,不含首页元数据表格 */
export function patchBodyTableCaptionCenterToXml(xml) {
if (!xml || xml.indexOf('<w:tbl') < 0) {
return xml;
}
const parts = [];
let cursor = 0;
const tblToken = '<w:tbl';
const closeTag = '</w:tbl>';
while (cursor < xml.length) {
const tblIdx = xml.indexOf(tblToken, cursor);
if (tblIdx < 0) {
parts.push(xml.slice(cursor));
break;
}
const tblEnd = xml.indexOf(closeTag, tblIdx);
if (tblEnd < 0) {
parts.push(xml.slice(cursor));
break;
}
const tblEndExclusive = tblEnd + closeTag.length;
const tblBlock = xml.slice(tblIdx, tblEndExclusive);
if (isHomePageMetadataTableBlock(tblBlock)) {
parts.push(xml.slice(cursor, tblEndExclusive));
cursor = tblEndExclusive;
continue;
}
const blocks = collectParagraphBlocksImmediatelyBefore(xml, tblIdx, cursor);
const isCaption =
blocks.length &&
isBodyTableCaptionPlainText(extractTableCaptionParagraphPlainText(blocks[0].inner));
if (isCaption) {
parts.push(xml.slice(cursor, blocks[0].start));
blocks.forEach(function (entry) {
parts.push(
entry.raw.replace(/(<w:p\b[^>]*>)([\s\S]*?)(<\/w:p>)/i, function (_full, open, inner, close) {
return open + applyTableCaptionCenterParagraphPr(inner) + close;
})
);
});
const afterBlocks = blocks[blocks.length - 1].start + blocks[blocks.length - 1].raw.length;
parts.push(xml.slice(afterBlocks, tblIdx));
} else {
parts.push(xml.slice(cursor, tblIdx));
}
parts.push(tblBlock);
cursor = tblEndExclusive;
}
return parts.join('');
}
/** 导出后处理:除首页表格外,正文表格标题居中 */
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, alignment) {
return createWordParagraph(children, { alignment: alignment || AlignmentType.CENTER });
}
function hasFigureAnnotation(noteHtml) {
return !!htmlToPlainText(String(noteHtml || '')).trim();
}
function appendFigureTitleParagraphs(children, titleHtml, options) {
const opts = options || {};
const alignment = hasFigureAnnotation(opts.note) ? AlignmentType.JUSTIFIED : AlignmentType.CENTER;
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
})
],
alignment
)
);
});
}
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(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/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 + ']';
});
}
function createCitationCommentMatcher(citationComments) {
const byRef = {};
(citationComments || []).forEach(function (comment) {
if (!comment || comment.refNo == null) {
return;
}
const key = String(comment.refNo);
if (!byRef[key]) {
byRef[key] = [];
}
byRef[key].push(comment);
});
Object.keys(byRef).forEach(function (key) {
byRef[key].sort(function (a, b) {
return Number(a.occurrence || 0) - Number(b.occurrence || 0);
});
});
const nextIndex = {};
return {
matchBracket: function (inner) {
const nums = expandCitationBracket(inner);
for (let i = 0; i < nums.length; i += 1) {
const refNo = nums[i];
const key = String(refNo);
const idx = nextIndex[key] || 0;
nextIndex[key] = idx + 1;
const list = byRef[key];
if (list && list[idx]) {
return list[idx].commentId;
}
}
return null;
}
};
}
function appendRunsForReferenceHighlightText(text, style, appendRun, commentMatcher) {
const normalized = normalizeCitationBracketCommaSpacing(text);
if (!normalized) {
return;
}
const segments = tokenizeReferenceHighlightSegments(normalized);
segments.forEach(function (segment) {
if (!segment || !segment.text) {
return;
}
if (segment.blue && /^\[/.test(segment.text)) {
const bracketText = segment.text;
const inner = bracketText.slice(1, -1);
const commentId = commentMatcher ? commentMatcher.matchBracket(inner) : null;
if (commentId != null && typeof appendRun.onCommentBracket === 'function') {
appendRun.onCommentBracket(bracketText, Object.assign({}, style, { blue: true }), commentId);
} else {
appendRun(bracketText, Object.assign({}, style, { blue: true }));
}
return;
}
if (segment.blue) {
appendRun(segment.text, Object.assign({}, style, { blue: true }));
return;
}
appendRun(segment.text, style);
});
}
function createStyledExportTextRun(text, style, boldDefault) {
return new TextRun({
text,
font: FONT_NAME,
size: TABLE_FONT_SIZE,
bold: boldDefault || (style && style.bold),
italics: style && style.italic,
superScript: style && style.sup,
subScript: style && style.sub,
color: style && style.blue ? BLUE_COLOR : undefined,
kern: FONT_KERN_MIN_1PT
});
}
function createCitationAppendRun(runs, boldDefault, commentMatcher) {
const appendRun = function (text, style) {
if (!text) {
return;
}
runs.push(createStyledExportTextRun(text, style, boldDefault));
};
if (commentMatcher) {
appendRun.onCommentBracket = function (bracketText, style, commentId) {
runs.push(new CommentRangeStart(commentId));
runs.push(createStyledExportTextRun(bracketText, style, boldDefault));
runs.push(new CommentRangeEnd(commentId));
runs.push(
new TextRun({
children: [new CommentReference(commentId)]
})
);
};
}
return appendRun;
}
function htmlToTextRuns(html, options) {
const opts = options || {};
const bold = opts.bold === true;
const citationComments = opts.citationComments || null;
const commentMatcher =
citationComments && citationComments.length ? createCitationCommentMatcher(citationComments) : null;
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 = [];
const appendRun = createCitationAppendRun(plainRuns, bold, commentMatcher);
appendRunsForReferenceHighlightText(
htmlToPlainText(raw),
{ bold, italics: false, sup: false, sub: false, blue: false },
appendRun,
commentMatcher
);
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;
const appendRun = createCitationAppendRun(runs, bold, commentMatcher);
function walk(node, style) {
if (node.nodeType === Node.TEXT_NODE) {
appendRunsForReferenceHighlightText(
decodeHtmlEntities(node.textContent).replace(/\u00a0/g, ' '),
style,
appendRun,
commentMatcher
);
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 htmlToTextRunsWithCitationComments(html, options, citationComments) {
return htmlToTextRuns(html, Object.assign({}, options || {}, { citationComments: citationComments || [] }));
}
/** 表头:相邻片段之间补空格,再规范为“一词一空” */
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;
}
/** 稿件导出:标题 + 表格 + 表注均为单栏(与图片块相同,由 exportManuscriptWord 分节) */
export function buildTableWordManuscriptParts(processedItem, options) {
const blockChildren = buildTableWordChildren(processedItem, options);
const tableIndex = blockChildren.findIndex(function (child) {
return child instanceof Table;
});
if (tableIndex < 0) {
return {
titleChildren: blockChildren,
tableChildren: [],
noteChildren: []
};
}
return {
titleChildren: blockChildren.slice(0, tableIndex),
tableChildren: blockChildren.slice(tableIndex, tableIndex + 1),
noteChildren: blockChildren.slice(tableIndex + 1)
};
}
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,
htmlToTextRunsWithCitationComments,
PAGE_MARGINS,
patchBodyTableCaptionCenter,
splitHtmlSegments,
TABLE_FONT_SIZE,
TITLE_COLOR,
WORD_PARAGRAPH_SPACING
};