Files
tougao_web/src/utils/exportManuscriptAuthor.js
2026-07-06 09:11:20 +08:00

2617 lines
83 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,
ExternalHyperlink,
ImageRun,
LineRuleType,
Paragraph,
Table,
TableCell,
TableLayoutType,
TableRow,
TextRun,
VerticalAlign,
WidthType,
HeightRule
} from 'docx';
import JSZip from 'jszip';
import { htmlToPlainText, TABLE_FONT_SIZE, FONT_KERN_MIN_1PT, FONT_KERN_MIN_XIAO_ER, FONT_NAME, PAGE_MARGINS, WORD_PARAGRAPH_SPACING } from '@/utils/exportTableWord';
import { formatAuthorFirstName, formatAuthorLastName } from '@/utils/productionSubmissionImport';
import {
PAGE_HEADER_MARGIN_TWIPS,
PAGE_FOOTER_MARGIN_TWIPS,
PAGE_HEIGHT_TWIPS,
PAGE_WIDTH_TWIPS,
getDocumentGridProperties
} from '@/utils/exportWordLayout';
import orcidIconUrl from '@/assets/img/orcid.png';
import { createTmrLogoImageRun } from '@/utils/exportManuscriptWordHeader';
/** 标题:阿里巴巴普惠体 H / Calibri四号 14pt */
const TITLE_FONT_EAST_ASIA = '阿里巴巴普惠体 H';
const TITLE_FONT_ASCII = 'Calibri';
const TITLE_FONT_SIZE = 28;
/** 作者Calibri 粗体,六号 7.5pt */
const AUTHOR_FONT_ASCII = 'Calibri';
const AUTHOR_FONT_SIZE = TABLE_FONT_SIZE;
/** 机构:宋体 / Calibri六号 */
const AFFILIATION_FONT_EAST_ASIA = '宋体';
const AFFILIATION_FONT_ASCII = 'Calibri';
const AFFILIATION_FONT_SIZE = TABLE_FONT_SIZE;
/** 空行字号:小五 9pt、六号 7.5pt */
const FONT_SIZE_XIAO_WU = 18;
/** 标题下空行:宋体小五 */
const EMPTY_LINE_AFTER_TITLE = {
fontEastAsia: AFFILIATION_FONT_EAST_ASIA,
fontAscii: AFFILIATION_FONT_ASCII,
size: FONT_SIZE_XIAO_WU
};
/** 作者下空行:楷体六号 */
const EMPTY_LINE_AFTER_AUTHOR = {
fontEastAsia: '楷体',
fontAscii: '楷体',
size: AFFILIATION_FONT_SIZE
};
/** 机构下空行:宋体六号 */
const EMPTY_LINE_AFTER_AFFILIATION = {
fontEastAsia: AFFILIATION_FONT_EAST_ASIA,
fontAscii: AFFILIATION_FONT_ASCII,
size: AFFILIATION_FONT_SIZE
};
const AUTHOR_SUPER_COLOR = '0070C0';
/** Abstract 标题Charis SIL 小四 12pt加粗斜体 */
const ABSTRACT_HEADING_FONT_SIZE = 24;
/** 首页表格右栏 AbstractCharis SIL 六号 */
const METADATA_TABLE_FONT_ASCII = FONT_NAME;
const METADATA_TABLE_FONT_EAST_ASIA = FONT_NAME;
const METADATA_TABLE_FONT_SIZE = AFFILIATION_FONT_SIZE;
/** 首页表格左栏图二红框Calibri 小六 6.5pt,粗斜体 */
const HOME_PAGE_LEFT_FONT_ASCII = AFFILIATION_FONT_ASCII;
const HOME_PAGE_LEFT_FONT_EAST_ASIA = AFFILIATION_FONT_EAST_ASIA;
const HOME_PAGE_LEFT_FONT_SIZE = 13;
/** Citation 正文蓝色 rgb(0, 112, 192) */
const CITATION_CONTENT_COLOR = '0070C0';
/** 缺少 online_date / 其它元数据时占位文案 */
const MISSING_DATA_PLACEHOLDER = 'No data';
/** 缺少数据时显示正红色 rgb(255, 0, 0) */
const MISSING_DATA_COLOR = 'FF0000';
/** @deprecated 使用 MISSING_DATA_COLOR */
const MISSING_DATE_COLOR = MISSING_DATA_COLOR;
const MANUSCRIPT_MONTH_NAMES = [
'January',
'February',
'March',
'April',
'May',
'June',
'July',
'August',
'September',
'October',
'November',
'December'
];
/** 首页表格左栏段落(图三):段前/段后 0、固定行距 10 磅、两端对齐、对齐网格 */
const HOME_PAGE_LEFT_PARAGRAPH_SPACING = {
before: 0,
after: 0,
line: 200,
lineRule: LineRuleType.EXACT,
beforeAutoSpacing: false,
afterAutoSpacing: false
};
const HOME_PAGE_LEFT_PARAGRAPH_SPACING_OOXML =
'<w:spacing w:before="0" w:after="0" w:line="200" w:lineRule="exact"' +
' w:beforeAutospacing="0" w:afterAutospacing="0"/>';
const HOME_PAGE_LEFT_PARAGRAPH_OOXML =
'<w:ind w:left="0" w:right="0"/>' +
'<w:jc w:val="both"/>' +
'<w:adjustRightInd w:val="1"/>' +
'<w:snapToGrid w:val="1"/>' +
'<w:widowControl w:val="0"/>';
/** Abstract 结构化标签:粗体蓝色 rgb(0, 112, 192) */
const ABSTRACT_LABEL_COLOR = '0070C0';
const ABSTRACT_SECTION_LABEL_PATTERN = /\b(Background:|Methods:|Results:|Conclusion:)\s*/gi;
/** Abstract 段落(图一):段前 0.1 行、段后 0、固定行距 10 磅、两端对齐、对齐网格 */
const ABSTRACT_PARAGRAPH_SPACING = {
before: 0,
after: 0,
line: 200,
lineRule: LineRuleType.EXACT,
beforeAutoSpacing: false,
afterAutoSpacing: false
};
const ABSTRACT_PARAGRAPH_SPACING_OOXML =
'<w:spacing w:beforeLines="10" w:after="0" w:line="200" w:lineRule="exact"' +
' w:beforeAutospacing="0" w:afterAutospacing="0"/>';
const ABSTRACT_PARAGRAPH_OOXML =
'<w:ind w:left="0" w:right="0"/>' +
'<w:jc w:val="both"/>' +
'<w:adjustRightInd w:val="1"/>' +
'<w:snapToGrid w:val="1"/>' +
'<w:widowControl w:val="0"/>';
/** ORCID 图标Word 中宽高均为 0.20 cm */
const ORCID_ICON_SIZE_CM = 0.2;
function cmToImagePixels(cm) {
return Math.round((cm * 96) / 2.54);
}
const ORCID_ICON_SIZE = cmToImagePixels(ORCID_ICON_SIZE_CM);
/** 页眉元数据区(双栏 Abstract 等):单倍行距 */
const HEADER_PARAGRAPH_SPACING = {
before: 0,
after: 0,
line: 240,
lineRule: LineRuleType.AUTO,
beforeAutoSpacing: false,
afterAutoSpacing: false
};
/** 标题块:作者 / 机构 / 空行 / 共同一作 / 通讯作者首行 — 固定行距 11 磅 */
const HEADER_LINE_SPACING_11PT = {
before: 0,
after: 0,
line: 220,
lineRule: LineRuleType.EXACT,
beforeAutoSpacing: false,
afterAutoSpacing: false
};
/** 通讯作者 E-mail 行 — 固定行距 10 磅 */
const HEADER_LINE_SPACING_10PT = {
before: 0,
after: 0,
line: 200,
lineRule: LineRuleType.EXACT,
beforeAutoSpacing: false,
afterAutoSpacing: false
};
const CO_FIRST_NOTE =
'#These authors contributed equally to this work and are co-first authors for this paper.';
/** 标题段落间距:段前 0.5 行、段后 0 行、单倍行距(行单位由 XML 补丁写入 beforeLines */
const TITLE_PARAGRAPH_SPACING = {
after: 0,
line: 240,
lineRule: LineRuleType.AUTO,
beforeAutoSpacing: false,
afterAutoSpacing: false
};
/** docx 9.x 未暴露的 OOXML 段落属性,导出后写入 document.xml */
/** 不允许西文在单词中间换行Word 中文版式下应移除 w:wordWrap而非写入 val=false */
const TITLE_PARAGRAPH_OOXML =
'<w:widowControl w:val="false"/>' +
'<w:overflowPunct/>' +
'<w:kinsoku/>' +
'<w:topLinePunct w:val="false"/>' +
'<w:autoSpaceDE/>' +
'<w:autoSpaceDN/>' +
'<w:snapToGrid w:val="0"/>';
/** 段前 0.5 行 = beforeLines 50以百分之一行为单位 */
const TITLE_PARAGRAPH_SPACING_OOXML =
'<w:spacing w:beforeLines="50" w:after="0" w:line="240" w:lineRule="auto"' +
' w:beforeAutospacing="0" w:afterAutospacing="0"/>';
const COMPETING_INTERESTS_TEXT = 'The authors declare no conflicts of interest.';
const PEER_REVIEW_TEXT =
'We thank the peer reviewers for their constructive comments and suggestions, which helped improve the quality of this manuscript.';
/** 元数据双栏表格:无边框 */
const METADATA_TABLE_BORDER_NONE = {
style: BorderStyle.NONE,
size: 0,
color: 'FFFFFF'
};
const METADATA_TABLE_BORDERS = {
top: METADATA_TABLE_BORDER_NONE,
bottom: METADATA_TABLE_BORDER_NONE,
left: METADATA_TABLE_BORDER_NONE,
right: METADATA_TABLE_BORDER_NONE,
insideHorizontal: METADATA_TABLE_BORDER_NONE,
insideVertical: METADATA_TABLE_BORDER_NONE
};
function cmToTwips(cm) {
return Math.round((cm * 1440) / 2.54);
}
/** 首页表格Abstract + Author contributions单元格指定宽度左 6.44cm,右 11.36cm */
const HOME_PAGE_TABLE_CELL_LEFT_WIDTH_CM = 6.44;
const HOME_PAGE_TABLE_CELL_RIGHT_WIDTH_CM = 11.36;
const HOME_PAGE_TABLE_CELL_LEFT_WIDTH_TWIPS = cmToTwips(HOME_PAGE_TABLE_CELL_LEFT_WIDTH_CM);
const HOME_PAGE_TABLE_CELL_RIGHT_WIDTH_TWIPS = cmToTwips(HOME_PAGE_TABLE_CELL_RIGHT_WIDTH_CM);
/** 元数据表格Abstract + Author contributionsWord 表格属性 图五 */
const METADATA_TABLE_WIDTH_CM = HOME_PAGE_TABLE_CELL_LEFT_WIDTH_CM + HOME_PAGE_TABLE_CELL_RIGHT_WIDTH_CM;
const METADATA_TABLE_WIDTH_TWIPS = cmToTwips(METADATA_TABLE_WIDTH_CM);
const METADATA_TABLE_ROW_MIN_HEIGHT_CM = 18.31;
const METADATA_TABLE_ROW_MIN_HEIGHT_TWIPS = cmToTwips(METADATA_TABLE_ROW_MIN_HEIGHT_CM);
const METADATA_TABLE_CELL_MARGIN_LR_TWIPS = cmToTwips(0.3);
const METADATA_TABLE_CELL_MARGINS = {
top: 0,
bottom: 0,
left: METADATA_TABLE_CELL_MARGIN_LR_TWIPS,
right: METADATA_TABLE_CELL_MARGIN_LR_TWIPS
};
let orcidImageCache = null;
function withPromiseTimeout(promise, ms) {
return Promise.race([
promise,
new Promise(function (resolve) {
setTimeout(function () {
resolve(null);
}, ms);
})
]);
}
/** 页眉是否需要加载 ORCID 图标(无作者且无 ORCID 时可跳过) */
export function headerNeedsOrcidImage(headerData) {
if (!headerData) {
return false;
}
const authors = headerData.authors || [];
for (let i = 0; i < authors.length; i++) {
const author = authors[i];
if (author && String(author.orcid || '').trim()) {
return true;
}
}
const previewHtml = headerData.previewAuthor && headerData.previewAuthor.author;
return previewHtml ? /orcid/i.test(String(previewHtml)) : false;
}
function createMissingAuthorParagraph() {
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: HEADER_LINE_SPACING_11PT,
children: [
createStyledTextRun(MISSING_DATA_PLACEHOLDER, {
fontAscii: AUTHOR_FONT_ASCII,
fontEastAsia: AUTHOR_FONT_ASCII,
bold: true,
color: MISSING_DATA_COLOR
})
]
});
}
export async function loadOrcidImageData() {
if (orcidImageCache) {
return orcidImageCache;
}
if (typeof fetch === 'undefined') {
return null;
}
try {
const controller = typeof AbortController !== 'undefined' ? new AbortController() : null;
const timer =
controller &&
setTimeout(function () {
controller.abort();
}, 8000);
const response = await fetch(
orcidIconUrl,
controller ? { signal: controller.signal } : undefined
);
if (timer) {
clearTimeout(timer);
}
if (!response.ok) {
return null;
}
const buffer = await response.arrayBuffer();
if (!buffer || !buffer.byteLength) {
return null;
}
orcidImageCache = new Uint8Array(buffer);
return orcidImageCache;
} catch (e) {
return null;
}
}
export async function fetchManuscriptHeaderData(apiClient, articleId, pArticleId, resolvePArticleId) {
if (!apiClient) {
return null;
}
const resolvedId = await resolvePArticleId(apiClient, articleId, pArticleId);
if (!resolvedId) {
return null;
}
const idPayload = { p_article_id: resolvedId };
const postWithTimeout = function (url) {
return withPromiseTimeout(
apiClient.post(url, idPayload).catch(function () {
return null;
}),
15000
);
};
const results = await Promise.all([
postWithTimeout('api/Production/getProductionDetail'),
postWithTimeout('api/Production/getAuthorlist'),
postWithTimeout('api/Production/getProductionPreview'),
postWithTimeout('api/Production/getOrganList')
]);
const detailRes = results[0];
const authorListRes = results[1];
const previewRes = results[2];
const organListRes = results[3];
const production =
detailRes && detailRes.code == 0 && detailRes.data && detailRes.data.production
? detailRes.data.production
: null;
const authors =
authorListRes && authorListRes.code == 0 && authorListRes.data && authorListRes.data.authors
? authorListRes.data.authors
: [];
const previewAuthor =
previewRes && previewRes.code == 0 && previewRes.data && previewRes.data.author ? previewRes.data.author : null;
const journal =
previewRes && previewRes.code == 0 && previewRes.data && previewRes.data.journal ? previewRes.data.journal : null;
const organList =
organListRes && organListRes.code == 0 && organListRes.data && organListRes.data.organs
? organListRes.data.organs
: [];
let finalReview = null;
if (production && production.article_id) {
const reviewRes = await withPromiseTimeout(
apiClient.post('api/Finalreview/getRecord', { article_id: production.article_id }).catch(function () {
return null;
}),
12000
);
if (reviewRes && reviewRes.status == 1 && reviewRes.data) {
finalReview = reviewRes.data;
}
}
let stageInfo = null;
if (production && production.journal_id && production.journal_stage_id) {
const stagesRes = await withPromiseTimeout(
apiClient
.post('api/Typeset/getStagesOnline', { journal_id: production.journal_id })
.catch(function () {
return null;
}),
12000
);
if (stagesRes && stagesRes.code == 0 && stagesRes.data && stagesRes.data.stages) {
const targetStageId = production.journal_stage_id;
stageInfo =
stagesRes.data.stages.find(function (item) {
return String(item.journal_stage_id) === String(targetStageId);
}) || null;
}
}
if (!production && !authors.length && !previewAuthor) {
return {
pArticleId: resolvedId,
production: normalizeManuscriptProduction(null, resolvedId),
authors: [],
previewAuthor: null,
journal: journal,
organList: organList,
finalReview: finalReview,
stageInfo: stageInfo
};
}
return {
pArticleId: resolvedId,
production: normalizeManuscriptProduction(production, resolvedId),
authors: authors,
previewAuthor: previewAuthor,
journal: journal,
organList: organList,
finalReview: finalReview,
stageInfo: stageInfo
};
}
function createFontOptions(eastAsia, ascii) {
return {
ascii: ascii || eastAsia,
eastAsia: eastAsia,
hAnsi: ascii || eastAsia
};
}
function getMetadataTableFontStyle(overrides) {
return Object.assign(
{
fontEastAsia: METADATA_TABLE_FONT_EAST_ASIA,
fontAscii: METADATA_TABLE_FONT_ASCII,
size: METADATA_TABLE_FONT_SIZE
},
overrides || {}
);
}
function getHomePageLeftFontStyle(overrides) {
return Object.assign(
{
fontEastAsia: HOME_PAGE_LEFT_FONT_EAST_ASIA,
fontAscii: HOME_PAGE_LEFT_FONT_ASCII,
size: HOME_PAGE_LEFT_FONT_SIZE,
bold: true,
italics: true
},
overrides || {}
);
}
/** 左栏正文Calibri 小六,非粗体非斜体 */
function getHomePageLeftContentFontStyle(overrides) {
return Object.assign(
{
fontEastAsia: HOME_PAGE_LEFT_FONT_EAST_ASIA,
fontAscii: HOME_PAGE_LEFT_FONT_ASCII,
size: HOME_PAGE_LEFT_FONT_SIZE,
bold: false,
italics: false
},
overrides || {}
);
}
function getHomePageLeftPlainFontStyle(overrides) {
return getHomePageLeftFontStyle(Object.assign({ italics: false, bold: true }, overrides || {}));
}
function createHomePageLeftParagraph(children) {
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: HOME_PAGE_LEFT_PARAGRAPH_SPACING,
widowControl: false,
children: children
});
}
function createHomePageLeftEmptyLineParagraph() {
return createHomePageLeftParagraph([createStyledTextRun('', getHomePageLeftContentFontStyle())]);
}
function createStyledTextRun(text, options) {
const opts = options || {};
const runOptions = {
text: text || '',
size: opts.size || AUTHOR_FONT_SIZE,
kern: opts.kern != null ? opts.kern : FONT_KERN_MIN_1PT
};
if (opts.fontEastAsia || opts.fontAscii) {
runOptions.font = createFontOptions(opts.fontEastAsia || opts.fontAscii, opts.fontAscii || opts.fontEastAsia);
} else if (opts.font) {
runOptions.font = opts.font;
}
if (opts.bold) {
runOptions.bold = true;
}
if (opts.italics) {
runOptions.italics = true;
}
if (opts.superScript) {
runOptions.superScript = true;
}
if (opts.color) {
runOptions.color = opts.color;
}
return new TextRun(runOptions);
}
function createHeaderBlockEmptyLineParagraph(fontOptions) {
const opts = fontOptions || EMPTY_LINE_AFTER_AFFILIATION;
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: HEADER_LINE_SPACING_11PT,
children: [
createStyledTextRun('', {
fontEastAsia: opts.fontEastAsia,
fontAscii: opts.fontAscii,
size: opts.size
})
]
});
}
function pushTitleBlockModule(children, paragraphOrList, emptyLineFontOptions) {
const items = Array.isArray(paragraphOrList) ? paragraphOrList : paragraphOrList ? [paragraphOrList] : [];
if (!items.length) {
return;
}
items.forEach(function (paragraph) {
children.push(paragraph);
});
children.push(createHeaderBlockEmptyLineParagraph(emptyLineFontOptions));
}
function pushTitleBlockParagraphs(children, paragraphOrList) {
const items = Array.isArray(paragraphOrList) ? paragraphOrList : paragraphOrList ? [paragraphOrList] : [];
items.forEach(function (paragraph) {
children.push(paragraph);
});
}
function normalizeOrcidUrl(orcid) {
const value = String(orcid || '').trim();
if (!value) {
return '';
}
if (/^https?:\/\//i.test(value)) {
return value.replace(/\/+$/, '');
}
const id = value.replace(/^https?:\/\/orcid\.org\//i, '').replace(/^\/+/, '');
return id ? 'https://orcid.org/' + id : '';
}
function createOrcidImageRun(orcidImageData, orcid) {
if (!orcidImageData || !orcidImageData.length) {
return createStyledTextRun('');
}
let imageRun;
try {
imageRun = new ImageRun({
type: 'png',
data: orcidImageData,
transformation: {
width: ORCID_ICON_SIZE,
height: ORCID_ICON_SIZE
}
});
} catch (e) {
return createStyledTextRun('');
}
const url = normalizeOrcidUrl(orcid);
if (!url) {
return imageRun;
}
return new ExternalHyperlink({
link: url,
children: [imageRun]
});
}
function getOrcidUrlFromHtmlNode(node) {
if (!node || node.nodeType !== Node.ELEMENT_NODE) {
return '';
}
let current = node;
while (current) {
if (current.tagName && current.tagName.toLowerCase() === 'a') {
const href = current.getAttribute('href') || '';
if (/orcid\.org/i.test(href)) {
return href;
}
}
current = current.parentElement;
}
const dataOrcid =
node.getAttribute('data-orcid') ||
node.getAttribute('data-orcid-id') ||
node.getAttribute('alt') ||
'';
return normalizeOrcidUrl(dataOrcid);
}
function preparePreviewAuthorHtml(html) {
let text = String(html || '');
text = text.replace(/<q>/gi, '<sup>');
text = text.replace(/<\/q>/gi, '</sup>');
text = text.replace(/<\/sup>,/gi, '</sup>, ');
text = text.replace(/<span[^>]*margin-left:\s*8px[^>]*>\s*<\/span>/gi, ' ');
text = text.replace(/,\s*$/, '');
return text.trim();
}
function decodeAuthorHtmlEntities(text) {
return String(text || '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"');
}
function htmlToAuthorLineRuns(html, orcidImageData, isAuthorNameLine) {
const runs = [];
const raw = String(html || '');
if (!raw) {
return runs;
}
if (typeof document === 'undefined') {
runs.push(
createStyledTextRun(htmlToPlainText(raw), {
fontEastAsia: AUTHOR_FONT_ASCII,
fontAscii: AUTHOR_FONT_ASCII,
bold: !!isAuthorNameLine,
})
);
return runs;
}
const root = document.createElement('div');
root.innerHTML = raw;
function appendRun(text, style) {
if (!text) {
return;
}
runs.push(
createStyledTextRun(text, {
fontEastAsia: AUTHOR_FONT_ASCII,
fontAscii: AUTHOR_FONT_ASCII,
bold: isAuthorNameLine ? true : style.bold,
italics: style.italic,
superScript: style.super,
color: style.color
})
);
}
function walk(node, style) {
if (node.nodeType === Node.TEXT_NODE) {
appendRun(decodeAuthorHtmlEntities(node.textContent).replace(/\u00a0/g, ' '), style);
return;
}
if (node.nodeType !== Node.ELEMENT_NODE) {
return;
}
const tag = node.tagName.toLowerCase();
if (tag === 'br') {
return;
}
if (tag === 'img') {
runs.push(createOrcidImageRun(orcidImageData, getOrcidUrlFromHtmlNode(node)));
return;
}
if (tag === 'span' && !node.textContent.trim()) {
return;
}
const nextStyle = {
bold: style.bold,
italic: style.italic,
super: style.super,
color: style.color
};
if (tag === 'b' || tag === 'strong') {
nextStyle.bold = true;
}
if (tag === 'i' || tag === 'em') {
nextStyle.italic = true;
}
if (tag === 'sup') {
nextStyle.super = true;
nextStyle.color = AUTHOR_SUPER_COLOR;
}
Array.from(node.childNodes).forEach(function (child) {
walk(child, nextStyle);
});
}
walk(root, {
bold: !!isAuthorNameLine,
italic: false,
super: false,
color: undefined
});
if (!runs.length) {
runs.push(
createStyledTextRun(htmlToPlainText(raw), {
fontEastAsia: AUTHOR_FONT_ASCII,
fontAscii: AUTHOR_FONT_ASCII,
bold: !!isAuthorNameLine,
})
);
}
return runs;
}
function buildAuthorLineParagraph(authorHtml, orcidImageData) {
const prepared = preparePreviewAuthorHtml(authorHtml);
if (!String(prepared || '').trim()) {
return null;
}
const firstLine = prepared.split(/<br\s*\/?>/i)[0] || prepared;
const runs = htmlToAuthorLineRuns(firstLine, orcidImageData, true);
if (!runs.length) {
return null;
}
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: HEADER_LINE_SPACING_11PT,
children: runs
});
}
function buildOrganIndexMap(organList) {
const map = {};
(organList || []).forEach(function (organ, index) {
if (organ && organ.p_article_organ_id != null) {
map[String(organ.p_article_organ_id)] = index + 1;
}
});
return map;
}
function getAuthorAffiliationLabel(author, organIndexMap) {
const nums = [];
(author && author.organs ? author.organs : []).forEach(function (organ) {
let organId = organ;
if (organ && typeof organ === 'object') {
organId = organ.p_article_organ_id;
}
const num = organIndexMap[String(organId)];
if (num && nums.indexOf(num) === -1) {
nums.push(num);
}
});
nums.sort(function (a, b) {
return a - b;
});
return nums.join(',');
}
function getAddressList(headerData) {
const previewAuthor = (headerData && headerData.previewAuthor) || {};
if (previewAuthor.addressList && previewAuthor.addressList.length) {
return previewAuthor.addressList.map(function (item) {
return htmlToPlainText(item);
});
}
return (headerData.organList || [])
.map(function (organ) {
return htmlToPlainText(organ.organ_name || '');
})
.filter(Boolean);
}
function previewAuthorHtmlHasMarkup(html) {
return /<(sup|q)\b/i.test(String(html || ''));
}
function buildAuthorLineFromAuthors(authors, organIndexMap, orcidImageData) {
const runs = [];
const list = (authors || []).filter(function (author) {
return author && getAuthorDisplayName(author);
});
if (!list.length) {
return null;
}
list.forEach(function (author, index) {
if (index > 0) {
runs.push(
createStyledTextRun(', ', {
fontAscii: AUTHOR_FONT_ASCII,
fontEastAsia: AUTHOR_FONT_ASCII,
bold: true
})
);
}
const name = getAuthorDisplayName(author);
runs.push(
createStyledTextRun(name, {
fontAscii: AUTHOR_FONT_ASCII,
fontEastAsia: AUTHOR_FONT_ASCII,
bold: true
})
);
const affiliationLabel = getAuthorAffiliationLabel(author, organIndexMap);
if (affiliationLabel) {
runs.push(
createStyledTextRun(affiliationLabel, {
fontAscii: AUTHOR_FONT_ASCII,
fontEastAsia: AUTHOR_FONT_ASCII,
bold: true,
superScript: true,
color: AUTHOR_SUPER_COLOR
})
);
}
if (author.is_first == 1 || author.is_first == '1') {
runs.push(
createStyledTextRun('#', {
fontAscii: AUTHOR_FONT_ASCII,
fontEastAsia: AUTHOR_FONT_ASCII,
bold: true,
superScript: true,
color: AUTHOR_SUPER_COLOR
})
);
}
if (author.is_report == 1 || author.is_report == '1') {
runs.push(
createStyledTextRun('*', {
fontAscii: AUTHOR_FONT_ASCII,
fontEastAsia: AUTHOR_FONT_ASCII,
bold: true,
superScript: true,
color: AUTHOR_SUPER_COLOR
})
);
}
if (author.orcid && String(author.orcid).trim()) {
runs.push(createOrcidImageRun(orcidImageData, author.orcid));
}
});
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: HEADER_LINE_SPACING_11PT,
children: runs
});
}
function buildAuthorListFallbackParagraph(authors, orcidImageData) {
return buildAuthorLineFromAuthors(authors, {}, orcidImageData);
}
function createAffiliationParagraph(index, addressText) {
const text = String(addressText || '').trim();
if (!text) {
return null;
}
const bodyText = text.endsWith('.') ? text : text + '.';
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: HEADER_LINE_SPACING_11PT,
children: [
createStyledTextRun(String(index + 1), {
fontEastAsia: AFFILIATION_FONT_EAST_ASIA,
fontAscii: AFFILIATION_FONT_ASCII,
size: AFFILIATION_FONT_SIZE,
superScript: true,
color: AUTHOR_SUPER_COLOR
}),
createStyledTextRun(bodyText, {
fontEastAsia: AFFILIATION_FONT_EAST_ASIA,
fontAscii: AFFILIATION_FONT_ASCII,
size: AFFILIATION_FONT_SIZE
})
]
});
}
function buildTitleParagraphChildren(contentRuns, logoImageData) {
const children = [];
const logoRun = logoImageData && createTmrLogoImageRun(logoImageData);
if (logoRun) {
children.push(logoRun);
}
if (contentRuns && contentRuns.length) {
children.push.apply(children, contentRuns);
}
return children;
}
function createAuthorTitleParagraph(title, logoImageData) {
const text = String(title || '').trim();
if (!text) {
return null;
}
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: TITLE_PARAGRAPH_SPACING,
widowControl: false,
overflowPunctuation: true,
autoSpaceEastAsianText: true,
children: buildTitleParagraphChildren(
[
createStyledTextRun(text, {
fontEastAsia: TITLE_FONT_EAST_ASIA,
fontAscii: TITLE_FONT_ASCII,
size: TITLE_FONT_SIZE,
bold: true,
kern: FONT_KERN_MIN_XIAO_ER
})
],
logoImageData
)
});
}
function createMissingTitleParagraph(logoImageData) {
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: TITLE_PARAGRAPH_SPACING,
widowControl: false,
overflowPunctuation: true,
autoSpaceEastAsianText: true,
children: buildTitleParagraphChildren(
[
createStyledTextRun(MISSING_DATA_PLACEHOLDER, {
fontEastAsia: TITLE_FONT_EAST_ASIA,
fontAscii: TITLE_FONT_ASCII,
size: TITLE_FONT_SIZE,
bold: true,
color: MISSING_DATA_COLOR,
kern: FONT_KERN_MIN_XIAO_ER
})
],
logoImageData
)
});
}
function createManuscriptTitleParagraph(headerData, logoImageData) {
const text = resolveManuscriptDisplayTitle(headerData);
return text
? createAuthorTitleParagraph(text, logoImageData)
: createMissingTitleParagraph(logoImageData);
}
function stripParagraphPropertyTag(pPrInner, tagName) {
const selfClosing = new RegExp('<w:' + tagName + '\\b[^>]*/>', 'g');
const paired = new RegExp('<w:' + tagName + '\\b[^>]*>[\\s\\S]*?</w:' + tagName + '>', 'g');
return String(pPrInner || '').replace(selfClosing, '').replace(paired, '');
}
function buildTitleParagraphPropertiesXml(pPrInner) {
let inner = String(pPrInner || '');
[
'spacing',
'widowControl',
'wordWrap',
'overflowPunct',
'kinsoku',
'topLinePunct',
'autoSpaceDE',
'autoSpaceDN',
'snapToGrid'
].forEach(function (tagName) {
inner = stripParagraphPropertyTag(inner, tagName);
});
return inner + TITLE_PARAGRAPH_SPACING_OOXML + TITLE_PARAGRAPH_OOXML;
}
function isTitleParagraphContent(pInner) {
if (pInner.includes(TITLE_FONT_EAST_ASIA) && pInner.includes('w:val="28"')) {
return true;
}
return (
/w:sz w:val="28"/.test(pInner) &&
/w:b(?:\/>|>)/.test(pInner) &&
/w:jc w:val="both"/.test(pInner)
);
}
/** 为导出 Word 的标题段落补齐间距、孤行控制与中文版式(图一/图二) */
export function patchAuthorTitleParagraphPropertiesToXml(xml) {
if (!xml || !xml.includes(TITLE_FONT_EAST_ASIA)) {
return xml;
}
return xml.replace(/<w:p\b[^>]*>([\s\S]*?)<\/w:p>/g, function (fullMatch, pInner) {
if (!isTitleParagraphContent(pInner)) {
return fullMatch;
}
if (pInner.includes('<w:pPr>')) {
return fullMatch.replace(/<w:pPr>([\s\S]*?)<\/w:pPr>/, function (pPrMatch, pPrInner) {
return '<w:pPr>' + buildTitleParagraphPropertiesXml(pPrInner) + '</w:pPr>';
});
}
return fullMatch.replace(
/<w:p\b([^>]*)>/,
'<w:p$1><w:pPr>' + buildTitleParagraphPropertiesXml('') + '</w:pPr>'
);
});
}
/** 为导出 Word 的标题段落补齐间距、孤行控制与中文版式(图一/图二) */
export async function patchAuthorTitleParagraphProperties(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');
xml = patchAuthorTitleParagraphPropertiesToXml(xml);
zip.file('word/document.xml', xml);
return zip.generateAsync({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
}
function buildHomePageLeftParagraphPropertiesXml(pPrInner) {
let inner = String(pPrInner || '');
['spacing', 'snapToGrid', 'adjustRightInd', 'ind', 'jc'].forEach(function (tagName) {
inner = stripParagraphPropertyTag(inner, tagName);
});
return inner + HOME_PAGE_LEFT_PARAGRAPH_SPACING_OOXML + HOME_PAGE_LEFT_PARAGRAPH_OOXML;
}
function patchHomePageLeftParagraphXml(fullMatch, pInner) {
const inner =
pInner != null
? pInner
: fullMatch.replace(/^<w:p(?:\s[^>]*)?>/, '').replace(/<\/w:p>\s*$/, '');
if (/<w:pPr>/.test(inner)) {
return fullMatch.replace(/<w:pPr>([\s\S]*?)<\/w:pPr>/, function (_pPrMatch, pPrInner) {
return '<w:pPr>' + buildHomePageLeftParagraphPropertiesXml(pPrInner) + '</w:pPr>';
});
}
return fullMatch.replace(
/<w:p(?:\s[^>]*)?>/,
function (openTag) {
return openTag + '<w:pPr>' + buildHomePageLeftParagraphPropertiesXml('') + '</w:pPr>';
}
);
}
function patchMetadataTableCellParagraphs(tcInner, patchParagraph) {
return String(tcInner || '').replace(/<w:p(?:\s[^>]*)?>[\s\S]*?<\/w:p>/g, function (pFull) {
const pInner = pFull.replace(/^<w:p(?:\s[^>]*)?>/, '').replace(/<\/w:p>\s*$/, '');
return patchParagraph(pFull, pInner);
});
}
function patchMetadataTableParagraphsInTableBlock(tblBlock) {
if (!isMetadataTableBlock(tblBlock)) {
return tblBlock;
}
let cellIndex = 0;
return tblBlock.replace(/<w:tc>([\s\S]*?)<\/w:tc>/g, function (match, tcInner) {
cellIndex += 1;
const patchParagraph =
cellIndex === 1 ? patchHomePageLeftParagraphXml : patchAbstractParagraphXml;
const patchedInner = patchMetadataTableCellParagraphs(tcInner, patchParagraph);
return '<w:tc>' + patchedInner + '</w:tc>';
});
}
function buildAbstractParagraphPropertiesXml(pPrInner) {
let inner = String(pPrInner || '');
['spacing', 'snapToGrid', 'adjustRightInd', 'ind', 'jc'].forEach(function (tagName) {
inner = stripParagraphPropertyTag(inner, tagName);
});
return inner + ABSTRACT_PARAGRAPH_SPACING_OOXML + ABSTRACT_PARAGRAPH_OOXML;
}
function patchAbstractParagraphXml(fullMatch, pInner) {
const inner =
pInner != null
? pInner
: fullMatch.replace(/^<w:p(?:\s[^>]*)?>/, '').replace(/<\/w:p>\s*$/, '');
if (/<w:pPr>/.test(inner)) {
return fullMatch.replace(/<w:pPr>([\s\S]*?)<\/w:pPr>/, function (_pPrMatch, pPrInner) {
return '<w:pPr>' + buildAbstractParagraphPropertiesXml(pPrInner) + '</w:pPr>';
});
}
return fullMatch.replace(/<w:p(?:\s[^>]*)?>/, function (openTag) {
return openTag + '<w:pPr>' + buildAbstractParagraphPropertiesXml('') + '</w:pPr>';
});
}
/** 为 Abstract 栏段落补齐间距与网格(图一) */
export async function patchAbstractHeadingParagraphProperties(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 (!/Author/i.test(xml) || !/contributions/i.test(xml) || !/Abstract/.test(xml)) {
return blob;
}
xml = xml.replace(/<w:tbl\b[^>]*>[\s\S]*?<\/w:tbl>/g, function (tblBlock) {
if (!isMetadataTableBlock(tblBlock)) {
return tblBlock;
}
return patchMetadataTableParagraphsInTableBlock(tblBlock);
});
zip.file('word/document.xml', xml);
return zip.generateAsync({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
}
function countCoFirstAuthors(authors, previewAuthorHtml) {
const list = authors || [];
const fromList = list.filter(function (author) {
return author && (author.is_first == 1 || author.is_first == '1');
}).length;
if (fromList > 0) {
return fromList;
}
const html = String(previewAuthorHtml || '');
const matches = html.match(/#/g);
return matches ? matches.length : 0;
}
function getAuthorDisplayName(author) {
if (!author) {
return '';
}
const canonical = getCanonicalAuthorDisplayName(author);
if (canonical) {
return canonical;
}
return String(author.author_name || '').trim();
}
/** 与首页作者行一致:名用连字符、姓首字母大写,如 Si-Jing Peng */
function getCanonicalAuthorDisplayName(author) {
if (!author) {
return '';
}
const first = formatAuthorFirstName(author.firstname || author.first_name || '');
const last = formatAuthorLastName(author.lastname || author.last_name || '');
if (first || last) {
return `${first} ${last}`.trim();
}
return '';
}
function escapeRegExp(text) {
return String(text || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function addAuthorNameVariant(variants, candidate, canonical) {
const value = String(candidate || '').trim();
if (!value || value === canonical) {
return;
}
variants.add(value);
}
/** 生成 Author contributions 中可能出现的错误作者写法 */
function buildAuthorNameVariants(author) {
const canonical = getCanonicalAuthorDisplayName(author);
if (!canonical) {
return [];
}
const first = formatAuthorFirstName(author.firstname || author.first_name || '');
const last = formatAuthorLastName(author.lastname || author.last_name || '');
const rawFirst = String(author.firstname || author.first_name || '').trim();
const rawLast = String(author.lastname || author.last_name || '').trim();
const firstFlat = first.replace(/-/g, '');
const variants = new Set();
addAuthorNameVariant(variants, `${firstFlat} ${last.toUpperCase()}`, canonical);
addAuthorNameVariant(variants, `${firstFlat} ${last}`, canonical);
addAuthorNameVariant(variants, `${first} ${last.toUpperCase()}`, canonical);
addAuthorNameVariant(variants, `${firstFlat.toUpperCase()} ${last.toUpperCase()}`, canonical);
addAuthorNameVariant(variants, `${rawFirst} ${rawLast.toUpperCase()}`, canonical);
addAuthorNameVariant(variants, `${rawFirst} ${rawLast}`, canonical);
addAuthorNameVariant(variants, `${rawFirst.replace(/-/g, '')} ${rawLast.toUpperCase()}`, canonical);
addAuthorNameVariant(variants, `${rawFirst.replace(/-/g, '')} ${rawLast}`, canonical);
if (rawFirst && rawLast) {
addAuthorNameVariant(
variants,
`${rawFirst.replace(/-/g, ' ')} ${rawLast.toUpperCase()}`.replace(/\s+/g, ' ').trim(),
canonical
);
}
return Array.from(variants).sort(function (a, b) {
return b.length - a.length;
});
}
/** 将 Author contributions 中的作者缩写修正为首页作者行的正确写法 */
function normalizeAuthorContributionNames(text, authors) {
let result = String(text || '');
const list = (authors || []).filter(Boolean);
if (!result.trim() || !list.length) {
return result;
}
const replacements = [];
list.forEach(function (author) {
const canonical = getCanonicalAuthorDisplayName(author);
if (!canonical) {
return;
}
buildAuthorNameVariants(author).forEach(function (variant) {
replacements.push({ from: variant, to: canonical });
});
});
replacements.sort(function (a, b) {
return b.from.length - a.from.length;
});
const seen = new Set();
replacements.forEach(function (item) {
if (seen.has(item.from)) {
return;
}
seen.add(item.from);
result = result.replace(new RegExp(escapeRegExp(item.from), 'g'), item.to);
});
return result;
}
function buildOrganAddressMap(organList) {
const map = {};
(organList || []).forEach(function (organ) {
if (organ && organ.p_article_organ_id != null) {
map[String(organ.p_article_organ_id)] = htmlToPlainText(organ.organ_name || '');
}
});
return map;
}
function stripEmailFromAffiliationText(text) {
return String(text || '')
.replace(/\s*[;,]\s*[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}\s*\.?\s*$/i, '')
.replace(/\s+[\w.+-]+@[\w.-]+\.[A-Za-z]{2,}\s*\.?\s*$/i, '')
.trim();
}
function isLikelyAffiliationIndex(text) {
return /^\d+([.,]\s*)?$/.test(String(text || '').trim());
}
function isUsableMailingAddress(text) {
const value = stripEmailFromAffiliationText(htmlToPlainText(text));
if (!value || isLikelyAffiliationIndex(value)) {
return false;
}
return true;
}
function getAuthorHeaderAffiliationText(author, addressList, organIndexMap) {
const label = getAuthorAffiliationLabel(author, organIndexMap);
if (!label || !addressList || !addressList.length) {
return '';
}
const addresses = [];
label.split(',').forEach(function (numStr) {
const index = Number(String(numStr || '').trim()) - 1;
if (index < 0 || index >= addressList.length || !addressList[index]) {
return;
}
const address = htmlToPlainText(addressList[index]);
if (address && addresses.indexOf(address) === -1) {
addresses.push(address);
}
});
return addresses.join(', ');
}
function extractAuthorOrganIds(author) {
const ids = [];
const organs = author && author.organs;
if (Array.isArray(organs)) {
organs.forEach(function (organ) {
if (organ && typeof organ === 'object') {
if (organ.p_article_organ_id != null) {
ids.push(String(organ.p_article_organ_id));
}
} else if (organ != null && String(organ).trim()) {
ids.push(String(organ).trim());
}
});
}
String(author && author.organ_id ? author.organ_id : '')
.split(',')
.forEach(function (item) {
const id = item.trim();
if (id && ids.indexOf(id) === -1) {
ids.push(id);
}
});
return ids;
}
function resolveOrganIdToAddress(organId, organAddressMap, addressList) {
const id = String(organId || '').trim();
if (!id) {
return '';
}
if (organAddressMap[id]) {
return organAddressMap[id];
}
if (/^\d+$/.test(id) && addressList && addressList.length) {
const index = Number(id) - 1;
if (index >= 0 && index < addressList.length && addressList[index]) {
return htmlToPlainText(addressList[index]);
}
}
if (!/^\d+$/.test(id)) {
return htmlToPlainText(id);
}
return '';
}
function resolveAuthorAffiliationText(author, addressList, organList) {
if (!author) {
return '';
}
const organAddressMap = buildOrganAddressMap(organList);
const organIndexMap = buildOrganIndexMap(organList);
if (isUsableMailingAddress(author.mailing_address)) {
return stripEmailFromAffiliationText(htmlToPlainText(author.mailing_address));
}
const headerAffiliation = getAuthorHeaderAffiliationText(author, addressList, organIndexMap);
if (headerAffiliation) {
return headerAffiliation;
}
const organIds = extractAuthorOrganIds(author);
const resolvedAddresses = [];
organIds.forEach(function (organId) {
const address = resolveOrganIdToAddress(organId, organAddressMap, addressList);
if (address && resolvedAddresses.indexOf(address) === -1) {
resolvedAddresses.push(address);
}
});
if (resolvedAddresses.length) {
return resolvedAddresses.join(', ');
}
const organNames = [];
if (Array.isArray(author.organ_name) && author.organ_name.length) {
author.organ_name.forEach(function (name) {
const text = htmlToPlainText(name);
if (text && !/^\d+$/.test(text)) {
organNames.push(text);
}
});
} else if (author.company) {
String(author.company)
.split('/')
.forEach(function (part) {
const text = part.trim();
if (!text) {
return;
}
const resolved = resolveOrganIdToAddress(text, organAddressMap, addressList);
if (resolved) {
organNames.push(resolved);
} else if (!/^\d+$/.test(text)) {
organNames.push(text);
}
});
}
if (organNames.length) {
return organNames.join(', ');
}
if (addressList && addressList.length) {
return htmlToPlainText(addressList[0]);
}
return '';
}
function findCorrespondingAuthors(authors) {
return (authors || []).filter(function (author) {
return author && (author.is_report == 1 || author.is_report == '1');
});
}
function findCorrespondingAuthor(authors) {
const list = findCorrespondingAuthors(authors);
return list.length ? list[0] : null;
}
function normalizeAffiliationKey(text) {
return String(text || '')
.replace(/\s+/g, ' ')
.trim()
.toLowerCase();
}
function ensureAffiliationEndsWithPeriod(text) {
const value = String(text || '').trim();
if (!value) {
return '';
}
return value.endsWith('.') ? value : value + '.';
}
function groupCorrespondingAuthorsByAffiliation(correspondingAuthors, addressList, organList) {
const groups = [];
const map = {};
correspondingAuthors.forEach(function (author) {
const affiliation = resolveAuthorAffiliationText(author, addressList, organList);
const key = normalizeAffiliationKey(affiliation) || '__empty__';
if (!map[key]) {
map[key] = {
affiliation: affiliation,
authors: []
};
groups.push(map[key]);
}
map[key].authors.push(author);
if (affiliation.length > String(map[key].affiliation || '').length) {
map[key].affiliation = affiliation;
}
});
return groups;
}
function buildCorrespondenceAuthorEntryText(author, addressList, organList) {
const name = getAuthorDisplayName(author);
const affiliation = ensureAffiliationEndsWithPeriod(
resolveAuthorAffiliationText(author, addressList, organList)
);
const email = String(author.email || '').trim();
if (!name && !email) {
return '';
}
let text = '';
if (name) {
text = name + (affiliation ? ', ' + affiliation : '.');
} else if (affiliation) {
text = affiliation;
}
if (email) {
text += (text ? ' ' : '') + 'E-mail: ' + email + '. ';
} else if (text) {
text += ' ';
}
return text;
}
function buildCorrespondenceSameInstitutionGroupText(group) {
const names = (group.authors || []).map(getAuthorDisplayName).filter(Boolean);
const affiliation = ensureAffiliationEndsWithPeriod(group.affiliation);
if (!names.length) {
return '';
}
return names.join(', ') + (affiliation ? '. ' + affiliation : '.');
}
function appendCorrespondenceBodyRuns(runs, groups, addressList, organList, textStyle) {
groups.forEach(function (group) {
const authors = group.authors || [];
if (authors.length > 1) {
const groupText = buildCorrespondenceSameInstitutionGroupText(group);
if (groupText) {
runs.push(createStyledTextRun(groupText, textStyle));
}
const emails = authors
.map(function (author) {
return String(author.email || '').trim();
})
.filter(Boolean);
if (emails.length) {
runs.push(createStyledTextRun(' E-mail: ' + emails.join('; ') + '.', textStyle));
}
return;
}
authors.forEach(function (author) {
const entryText = buildCorrespondenceAuthorEntryText(author, addressList, organList);
if (entryText) {
runs.push(createStyledTextRun(entryText, textStyle));
}
});
});
}
function createCorrespondenceParagraphs(authors, addressList, organList) {
const correspondingAuthors = findCorrespondingAuthors(authors);
if (!correspondingAuthors.length) {
return [];
}
const groups = groupCorrespondingAuthorsByAffiliation(correspondingAuthors, addressList, organList);
const textStyle = {
fontEastAsia: AFFILIATION_FONT_EAST_ASIA,
fontAscii: AFFILIATION_FONT_ASCII,
size: AFFILIATION_FONT_SIZE
};
const runs = [
createStyledTextRun('*', {
fontEastAsia: AFFILIATION_FONT_EAST_ASIA,
fontAscii: AFFILIATION_FONT_ASCII,
size: AFFILIATION_FONT_SIZE,
superScript: true,
color: AUTHOR_SUPER_COLOR
}),
createStyledTextRun('Correspondence to: ', {
fontEastAsia: AFFILIATION_FONT_EAST_ASIA,
fontAscii: AFFILIATION_FONT_ASCII,
size: AFFILIATION_FONT_SIZE,
bold: true
})
];
appendCorrespondenceBodyRuns(runs, groups, addressList, organList, textStyle);
if (runs.length <= 2) {
return [];
}
return [
new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: HEADER_LINE_SPACING_11PT,
children: runs
})
];
}
function createCoFirstParagraph(showNote) {
if (!showNote) {
return null;
}
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: HEADER_LINE_SPACING_11PT,
children: [
createStyledTextRun('#', {
fontEastAsia: AFFILIATION_FONT_EAST_ASIA,
fontAscii: AFFILIATION_FONT_ASCII,
size: AFFILIATION_FONT_SIZE,
superScript: true,
color: AUTHOR_SUPER_COLOR
}),
createStyledTextRun(CO_FIRST_NOTE.slice(1), {
fontEastAsia: AFFILIATION_FONT_EAST_ASIA,
fontAscii: AFFILIATION_FONT_ASCII,
size: AFFILIATION_FONT_SIZE
})
]
});
}
function normalizeMetadataFieldValue(text) {
return String(text || '').trim();
}
function getHeaderPArticleId(headerData) {
if (!headerData) {
return '';
}
const production = headerData.production || {};
return String(production.p_article_id || headerData.pArticleId || '').trim();
}
/** 标题为空时使用 p_article_id两者皆无时返回空字符串 */
export function resolveManuscriptDisplayTitle(headerData) {
const production = (headerData && headerData.production) || {};
const title = htmlToPlainText(production.title || '').trim();
if (title) {
return title;
}
return getHeaderPArticleId(headerData);
}
/** 页眉数据缺失时,用 p_article_id 构造最小可用 header */
export function buildMinimalManuscriptHeaderData(pArticleId) {
const id = String(pArticleId || '').trim();
if (!id) {
return null;
}
return {
pArticleId: id,
production: { p_article_id: id, title: '' },
authors: [],
previewAuthor: null,
journal: null,
organList: [],
finalReview: null,
stageInfo: null
};
}
function normalizeManuscriptProduction(production, resolvedId) {
const base = production && typeof production === 'object' ? Object.assign({}, production) : {};
if (resolvedId && !base.p_article_id) {
base.p_article_id = resolvedId;
}
return base;
}
function createMissingDataRun(styleFactory) {
const factory = styleFactory || getHomePageLeftContentFontStyle;
return createStyledTextRun(MISSING_DATA_PLACEHOLDER, factory({ color: MISSING_DATA_COLOR }));
}
function createMissingMetadataBodyParagraph(styleFactory) {
return createHomePageLeftParagraph([createMissingDataRun(styleFactory)]);
}
function createMissingAbstractBodyParagraph() {
return createAbstractParagraph([createMissingDataRun(getMetadataTableFontStyle)]);
}
function normalizeMonthName(monthText) {
const raw = String(monthText || '').trim().toLowerCase();
if (!raw) {
return '';
}
const found = MANUSCRIPT_MONTH_NAMES.find(function (name) {
return name.toLowerCase() === raw || name.toLowerCase().slice(0, 3) === raw.slice(0, 3);
});
return found || String(monthText || '').trim();
}
function formatManuscriptDateParts(day, month, year) {
const dayNum = parseInt(day, 10);
const monthNum = parseInt(month, 10);
const yearNum = parseInt(year, 10);
if (!dayNum || !monthNum || !yearNum || monthNum < 1 || monthNum > 12) {
return '';
}
const dayText = String(dayNum).padStart(2, '0');
return dayText + ' ' + MANUSCRIPT_MONTH_NAMES[monthNum - 1] + ' ' + yearNum;
}
/** 日期格式05 June 2026日两位 + 英文月份 + 年) */
function formatManuscriptDisplayDate(value) {
const text = String(value || '').trim();
if (!text || /no time/i.test(text)) {
return { text: MISSING_DATA_PLACEHOLDER, missing: true };
}
let match = text.match(/^(\d{4})[-/](\d{1,2})[-/](\d{1,2})(?:\s|$|T)/);
if (match) {
const formatted = formatManuscriptDateParts(match[3], match[2], match[1]);
if (formatted) {
return { text: formatted, missing: false };
}
}
match = text.match(/^(\d{1,2})\s+([A-Za-z]+)\s+(\d{4})$/);
if (match) {
const monthName = normalizeMonthName(match[2]);
const monthIndex = MANUSCRIPT_MONTH_NAMES.findIndex(function (name) {
return name === monthName;
});
if (monthIndex >= 0) {
return {
text: formatManuscriptDateParts(match[1], monthIndex + 1, match[3]),
missing: false
};
}
}
match = text.match(/(\d{4}-\d{2}-\d{2}|\d{1,2}\s+[A-Za-z]+\s+\d{4}|\d{4}\/\d{2}\/\d{2})/);
if (match) {
return formatManuscriptDisplayDate(match[1]);
}
return { text: text, missing: false };
}
function getProductionOnlineDate(production, headerData) {
const fromHeader = headerData && (headerData.online_date || headerData.onlineDate);
const fromProduction = production && (production.online_date || production.onlineDate);
const fromTypeset =
(production &&
production.u_typeset_info &&
(production.u_typeset_info.online_date || production.u_typeset_info.onlineDate)) ||
(production &&
production.typeset_info &&
(production.typeset_info.online_date || production.typeset_info.onlineDate));
return String(fromHeader || fromProduction || fromTypeset || '').trim();
}
/** Available online 专用:使用 online_date缺失时显示 No data. */
function formatOnlineDateDisplay(production, headerData) {
const raw = getProductionOnlineDate(production, headerData);
if (!raw || /^no time$/i.test(raw)) {
return { text: MISSING_DATA_PLACEHOLDER, missing: true };
}
const formatted = formatManuscriptDisplayDate(raw);
if (formatted.missing) {
return { text: MISSING_DATA_PLACEHOLDER, missing: true };
}
return formatted;
}
/** Accepted 专用:缺失时显示 No data. */
function formatAcceptedDateDisplay(review) {
const formatted = formatManuscriptDisplayDate(review && review.accepted_time);
if (formatted.missing) {
return { text: MISSING_DATA_PLACEHOLDER, missing: true };
}
return formatted;
}
function buildCitationRuns(headerData) {
const production = (headerData && headerData.production) || {};
const journal = (headerData && headerData.journal) || {};
const citationStyle = getHomePageLeftContentFontStyle({ color: CITATION_CONTENT_COLOR });
const journalStyle = getHomePageLeftContentFontStyle({
color: CITATION_CONTENT_COLOR,
italics: true
});
const abbr = String(production.abbr || '').trim();
const title = resolveManuscriptDisplayTitle(headerData);
const journalJabbr = String((journal && journal.jabbr) || '').trim();
if (!abbr && !title) {
return [];
}
const runs = [];
if (abbr) {
runs.push(createStyledTextRun(abbr + '. ', citationStyle));
}
if (title) {
runs.push(createStyledTextRun(title + '. ', citationStyle));
}
if (journalJabbr) {
runs.push(createStyledTextRun(journalJabbr + '.', journalStyle));
}
return runs;
}
function createMetadataLabelParagraph(label) {
return createHomePageLeftParagraph([createStyledTextRun(label, getHomePageLeftFontStyle())]);
}
function createMetadataBodyParagraph(text) {
const content = normalizeMetadataFieldValue(text);
if (!content) {
return null;
}
return createHomePageLeftParagraph([createStyledTextRun(content, getHomePageLeftContentFontStyle())]);
}
function appendMetadataInlinePlain(paragraphs, label, text) {
const content = normalizeMetadataFieldValue(text);
paragraphs.push(
createHomePageLeftParagraph([
createStyledTextRun(label + ' ', getHomePageLeftPlainFontStyle()),
content
? createStyledTextRun(content, getHomePageLeftContentFontStyle())
: createMissingDataRun()
])
);
}
function appendReviewDatesParagraph(paragraphs, finalReview, production, headerData) {
const review = finalReview || {};
const labelStyle = getHomePageLeftPlainFontStyle();
const contentStyle = getHomePageLeftContentFontStyle();
const onlineDate = formatOnlineDateDisplay(production, headerData);
const dateItems = [
{ label: 'Received:', value: formatManuscriptDisplayDate(review.received_time) },
{ label: 'Revised:', value: formatManuscriptDisplayDate(review.revision_time) },
{ label: 'Accepted:', value: formatAcceptedDateDisplay(review) },
{ label: 'Available online:', value: onlineDate }
];
const runs = [];
dateItems.forEach(function (item, index) {
runs.push(createStyledTextRun(item.label + ' ', labelStyle));
const valueStyle = item.value.missing
? getHomePageLeftContentFontStyle({ color: MISSING_DATA_COLOR })
: contentStyle;
runs.push(createStyledTextRun(item.value.text, valueStyle));
if (index < dateItems.length - 1) {
runs.push(createStyledTextRun('; ', contentStyle));
}
});
paragraphs.push(createHomePageLeftParagraph(runs));
}
function createAbstractParagraph(children) {
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: ABSTRACT_PARAGRAPH_SPACING,
widowControl: false,
children: children
});
}
function createAbstractHeadingParagraph() {
return createAbstractParagraph([
createStyledTextRun('Abstract', getMetadataTableFontStyle({
size: ABSTRACT_HEADING_FONT_SIZE,
bold: true,
italics: true,
kern: FONT_KERN_MIN_1PT
}))
]);
}
function buildAbstractContentRuns(text) {
const content = String(text || '').trim();
if (!content) {
return [];
}
const baseStyle = getMetadataTableFontStyle();
const runs = [];
const pattern = new RegExp(ABSTRACT_SECTION_LABEL_PATTERN.source, 'gi');
let lastIndex = 0;
let match;
while ((match = pattern.exec(content)) !== null) {
if (match.index > lastIndex) {
runs.push(createStyledTextRun(content.slice(lastIndex, match.index), baseStyle));
}
runs.push(
createStyledTextRun(match[0], {
fontEastAsia: baseStyle.fontEastAsia,
fontAscii: baseStyle.fontAscii,
size: baseStyle.size,
bold: true,
color: ABSTRACT_LABEL_COLOR
})
);
lastIndex = match.index + match[0].length;
}
if (lastIndex < content.length) {
runs.push(createStyledTextRun(content.slice(lastIndex), baseStyle));
}
return runs.length ? runs : [createStyledTextRun(content, baseStyle)];
}
function createAbstractContentParagraph(text) {
const runs = buildAbstractContentRuns(text);
if (!runs.length) {
return null;
}
return createAbstractParagraph(runs);
}
function createAbstractEmptyLineParagraph() {
return createAbstractParagraph([createStyledTextRun('', getMetadataTableFontStyle())]);
}
function appendMetadataBlock(paragraphs, label, text) {
const content = normalizeMetadataFieldValue(text);
paragraphs.push(createMetadataLabelParagraph(label));
if (content) {
paragraphs.push(createHomePageLeftParagraph([createStyledTextRun(content, getHomePageLeftContentFontStyle())]));
} else {
paragraphs.push(createMissingMetadataBodyParagraph());
}
}
function buildLeftMetadataParagraphs(headerData) {
const production = (headerData && headerData.production) || {};
const finalReview = (headerData && headerData.finalReview) || {};
const paragraphs = [];
const authors = (headerData && headerData.authors) || [];
const authorContribution = normalizeAuthorContributionNames(
htmlToPlainText(production.author_contribution || ''),
authors
);
appendMetadataBlock(paragraphs, 'Author contributions', authorContribution);
appendMetadataBlock(paragraphs, 'Competing interests', COMPETING_INTERESTS_TEXT);
appendMetadataBlock(paragraphs, 'Acknowledgments', htmlToPlainText(production.acknowledgment || ''));
appendMetadataBlock(paragraphs, 'Abbreviations', htmlToPlainText(production.abbreviation || ''));
const citationRuns = buildCitationRuns(headerData);
if (citationRuns.length) {
paragraphs.push(createHomePageLeftParagraph([createStyledTextRun('Citation', getHomePageLeftFontStyle())]));
paragraphs.push(createHomePageLeftParagraph(citationRuns));
paragraphs.push(createHomePageLeftEmptyLineParagraph());
}
appendMetadataBlock(paragraphs, 'Peer review information', PEER_REVIEW_TEXT);
const boardName =
finalReview && finalReview.reviewer_id && finalReview.realname ? finalReview.realname : '';
appendMetadataInlinePlain(paragraphs, 'Editorial Advisory Board:', boardName);
appendMetadataInlinePlain(paragraphs, 'Production editor:', htmlToPlainText(production.executive_editor || ''));
appendReviewDatesParagraph(paragraphs, finalReview, production, headerData);
return paragraphs.length ? paragraphs : [createMissingMetadataBodyParagraph()];
}
function buildRightMetadataParagraphs(headerData) {
const production = (headerData && headerData.production) || {};
const paragraphs = [];
paragraphs.push(createAbstractHeadingParagraph());
const abstractText = htmlToPlainText(production.abstract || '');
if (abstractText) {
const abstractParagraph = createAbstractContentParagraph(abstractText);
if (abstractParagraph) {
paragraphs.push(abstractParagraph);
}
} else {
paragraphs.push(createMissingAbstractBodyParagraph());
}
const keywords = String(production.keywords || '').trim();
if (keywords) {
if (abstractText) {
paragraphs.push(createAbstractEmptyLineParagraph());
}
paragraphs.push(
createAbstractParagraph([
createStyledTextRun('Keywords:', getMetadataTableFontStyle({ bold: true })),
createStyledTextRun(' ' + keywords, getMetadataTableFontStyle())
])
);
}
return paragraphs.length ? paragraphs : [createMissingAbstractBodyParagraph()];
}
function createMetadataTableCell(paragraphs, widthTwips) {
const children =
paragraphs && paragraphs.length ? paragraphs : [createMissingMetadataBodyParagraph()];
const cellOptions = {
verticalAlign: VerticalAlign.TOP,
margins: METADATA_TABLE_CELL_MARGINS,
borders: {
top: METADATA_TABLE_BORDER_NONE,
bottom: METADATA_TABLE_BORDER_NONE,
left: METADATA_TABLE_BORDER_NONE,
right: METADATA_TABLE_BORDER_NONE
},
children: children
};
if (widthTwips != null) {
cellOptions.width = {
size: widthTwips,
type: WidthType.DXA
};
}
return new TableCell(cellOptions);
}
function buildMetadataTable(headerData) {
/** 首页表格:左栏 Author contributions 6.44cm,右栏 Abstract 11.36cm */
const leftParagraphs = buildLeftMetadataParagraphs(headerData);
const rightParagraphs = buildRightMetadataParagraphs(headerData);
return new Table({
width: {
size: METADATA_TABLE_WIDTH_TWIPS,
type: WidthType.DXA
},
alignment: AlignmentType.LEFT,
indent: {
size: 0,
type: WidthType.DXA
},
layout: TableLayoutType.AUTOFIT,
borders: METADATA_TABLE_BORDERS,
rows: [
new TableRow({
cantSplit: false,
height: {
value: METADATA_TABLE_ROW_MIN_HEIGHT_TWIPS,
rule: HeightRule.ATLEAST
},
children: [
createMetadataTableCell(leftParagraphs, HOME_PAGE_TABLE_CELL_LEFT_WIDTH_TWIPS),
createMetadataTableCell(rightParagraphs, HOME_PAGE_TABLE_CELL_RIGHT_WIDTH_TWIPS)
]
})
]
});
}
function buildMetadataTblCellMarXml() {
return (
'<w:tblCellMar>' +
'<w:top w:w="0" w:type="dxa"/>' +
'<w:bottom w:w="0" w:type="dxa"/>' +
'<w:left w:w="' +
METADATA_TABLE_CELL_MARGIN_LR_TWIPS +
'" w:type="dxa"/>' +
'<w:right w:w="' +
METADATA_TABLE_CELL_MARGIN_LR_TWIPS +
'" w:type="dxa"/>' +
'</w:tblCellMar>'
);
}
function buildMetadataTcMarXml() {
return (
'<w:tcMar>' +
'<w:top w:w="0" w:type="dxa"/>' +
'<w:bottom w:w="0" w:type="dxa"/>' +
'<w:left w:w="' +
METADATA_TABLE_CELL_MARGIN_LR_TWIPS +
'" w:type="dxa"/>' +
'<w:right w:w="' +
METADATA_TABLE_CELL_MARGIN_LR_TWIPS +
'" w:type="dxa"/>' +
'</w:tcMar>'
);
}
function isMetadataTableBlock(tblBlock) {
const block = String(tblBlock || '');
return (
/Author/i.test(block) &&
/contributions/i.test(block) &&
/Abstract/.test(block) &&
(/Keywords:/i.test(block) || /Competing interests/i.test(block) || /Peer review information/i.test(block))
);
}
function patchMetadataTableBlock(tblBlock) {
if (!isMetadataTableBlock(tblBlock)) {
return tblBlock;
}
let block = tblBlock.replace(/<w:tblpPr[\s\S]*?<\/w:tblpPr>/g, '').replace(/<w:tblpPr\b[^>]*\/>/g, '');
if (/<w:tblPr>/.test(block)) {
block = block.replace(/<w:tblPr>([\s\S]*?)<\/w:tblPr>/, function (_match, inner) {
let pr = inner
.replace(/<w:tblW\b[^>]*\/>/g, '')
.replace(/<w:tblW\b[^>]*>[\s\S]*?<\/w:tblW>/g, '')
.replace(/<w:jc\b[^>]*\/>/g, '')
.replace(/<w:jc\b[^>]*>[\s\S]*?<\/w:jc>/g, '')
.replace(/<w:tblInd\b[^>]*\/>/g, '')
.replace(/<w:tblInd\b[^>]*>[\s\S]*?<\/w:tblInd>/g, '')
.replace(/<w:tblLayout\b[^>]*\/>/g, '')
.replace(/<w:tblLayout\b[^>]*>[\s\S]*?<\/w:tblLayout>/g, '')
.replace(/<w:tblCellMar>[\s\S]*?<\/w:tblCellMar>/g, '');
pr +=
'<w:tblW w:w="' +
METADATA_TABLE_WIDTH_TWIPS +
'" w:type="dxa"/>' +
'<w:jc w:val="left"/>' +
'<w:tblInd w:w="0" w:type="dxa"/>' +
'<w:tblLayout w:type="autofit"/>' +
buildMetadataTblCellMarXml();
return '<w:tblPr>' + pr + '</w:tblPr>';
});
} else {
block = block.replace(
/<w:tbl>/,
'<w:tbl><w:tblPr>' +
'<w:tblW w:w="' +
METADATA_TABLE_WIDTH_TWIPS +
'" w:type="dxa"/>' +
'<w:jc w:val="left"/>' +
'<w:tblInd w:w="0" w:type="dxa"/>' +
'<w:tblLayout w:type="autofit"/>' +
buildMetadataTblCellMarXml() +
'</w:tblPr>'
);
}
block = block.replace(/<w:tr>([\s\S]*?)<\/w:tr>/, function (_match, trInner) {
let inner = trInner.replace(/<w:trPr>[\s\S]*?<\/w:trPr>/g, '').replace(/<w:cantSplit\b[^>]*\/>/g, '');
const trPr =
'<w:trPr><w:trHeight w:val="' +
METADATA_TABLE_ROW_MIN_HEIGHT_TWIPS +
'" w:hRule="atLeast"/></w:trPr>';
return '<w:tr>' + trPr + inner + '</w:tr>';
});
let tcIndex = 0;
block = block.replace(/<w:tc>(\s*<w:tcPr>[\s\S]*?<\/w:tcPr>)?/g, function (_match, tcPrBlock) {
tcIndex += 1;
const widthTwips =
tcIndex === 1 ? HOME_PAGE_TABLE_CELL_LEFT_WIDTH_TWIPS : HOME_PAGE_TABLE_CELL_RIGHT_WIDTH_TWIPS;
let tcPrInner = '';
if (tcPrBlock) {
tcPrInner = tcPrBlock.replace(/<w:tcPr>([\s\S]*?)<\/w:tcPr>/, '$1');
tcPrInner = tcPrInner
.replace(/<w:vAlign\b[^>]*\/>/g, '')
.replace(/<w:tcW\b[^>]*\/>/g, '')
.replace(/<w:tcW\b[^>]*>[\s\S]*?<\/w:tcW>/g, '')
.replace(/<w:tcMar>[\s\S]*?<\/w:tcMar>/g, '');
}
return (
'<w:tc><w:tcPr>' +
tcPrInner +
'<w:tcW w:w="' +
widthTwips +
'" w:type="dxa"/>' +
'<w:vAlign w:val="top"/>' +
buildMetadataTcMarXml() +
'</w:tcPr>'
);
});
block = patchMetadataTableParagraphsInTableBlock(block);
return block;
}
function isHomePageSingleColumnSectPrInner(sectPrInner) {
return /<w:cols\b[^>]*\bw:num="1"/i.test(String(sectPrInner || '')) && !/<w:cols\b[^>]*\bw:num="2"/i.test(String(sectPrInner || ''));
}
function isHomePageDualColumnSectPrInner(sectPrInner) {
return /<w:cols\b[^>]*\bw:num="2"/i.test(String(sectPrInner || ''));
}
function replaceSectPrInnerColsWithSingle(sectPrInner) {
let next = String(sectPrInner || '')
.replace(/<w:cols\b[^>]*\/>/g, '')
.replace(/<w:cols\b[^>]*>[\s\S]*?<\/w:cols>/g, '');
return next + '<w:cols w:num="1"/>';
}
/** 首页元数据表格节结束位置:含表格后空行,不含正文双栏分节符 */
export function findHomePageMetadataTableSectionEndIndex(xml, bounds) {
if (!bounds) {
return -1;
}
let end = bounds.end;
let cursor = bounds.end;
const limit = Math.min(xml.length, bounds.end + 8000);
while (cursor < limit) {
const slice = xml.slice(cursor, limit);
const match = slice.match(/^\s*(<w:p\b[^>]*>[\s\S]*?<\/w:p>)/);
if (!match) {
break;
}
const block = match[1];
const blockEnd = cursor + match[0].length;
const sectMatch = block.match(/<w:sectPr>([\s\S]*?)<\/w:sectPr>/);
if (sectMatch && isHomePageDualColumnSectPrInner(sectMatch[1])) {
break;
}
end = blockEnd;
cursor = blockEnd;
if (sectMatch) {
break;
}
if (/\bIntroduction\b/i.test(block) || /<v:roundrect\b[^>]*fillcolor="#DEEBF7"/i.test(block)) {
break;
}
}
return end;
}
/** 强制首页元数据表格Abstract 等)所在节为单栏 */
export function patchHomePageMetadataTableSingleColumnToXml(xml) {
let bounds = findHomePageMetadataTableBounds(xml);
if (!bounds) {
return xml;
}
let result = xml;
const beforeTableSlice = result.slice(Math.max(0, bounds.start - 8000), bounds.start);
const hasSingleBreakBeforeTable = /<w:sectPr>[\s\S]*?<w:cols\b[^>]*\bw:num="1"/i.test(beforeTableSlice);
if (!hasSingleBreakBeforeTable) {
const breakXml = buildHomePageSectionBreakParagraphXml(buildHomePageContinuousSectPrInner());
result = result.slice(0, bounds.start) + breakXml + result.slice(bounds.start);
bounds = findHomePageMetadataTableBounds(result);
if (!bounds) {
return result;
}
}
const corrMatch = findCorrespondenceParagraphMatch(result);
const regionStart = corrMatch ? corrMatch.index + corrMatch[0].length : 0;
const tableSectionEnd = findHomePageMetadataTableSectionEndIndex(result, bounds);
return result.replace(/<w:sectPr>([\s\S]*?)<\/w:sectPr>/g, function (full, inner, offset) {
if (offset < regionStart || offset >= tableSectionEnd) {
return full;
}
if (isHomePageSingleColumnSectPrInner(inner)) {
return full;
}
return '<w:sectPr>' + replaceSectPrInnerColsWithSingle(inner) + '</w:sectPr>';
});
}
function buildHomePageContinuousSectPrInner() {
const grid = getDocumentGridProperties();
return (
'<w:type w:val="continuous"/>' +
'<w:pgSz w:w="' +
PAGE_WIDTH_TWIPS +
'" w:h="' +
PAGE_HEIGHT_TWIPS +
'" w:orient="portrait"/>' +
'<w:pgMar w:top="' +
PAGE_MARGINS.top +
'" w:right="' +
PAGE_MARGINS.right +
'" w:bottom="' +
PAGE_MARGINS.bottom +
'" w:left="' +
PAGE_MARGINS.left +
'" w:header="' +
PAGE_HEADER_MARGIN_TWIPS +
'" w:footer="' +
PAGE_FOOTER_MARGIN_TWIPS +
'" w:gutter="0"/>' +
'<w:pgNumType/>' +
'<w:cols w:num="1"/>' +
'<w:docGrid w:linePitch="' +
grid.linePitch +
'"/>'
);
}
function replaceParagraphSectPrInner(fullMatch, sectPrInner) {
if (/<w:sectPr>[\s\S]*?<\/w:sectPr>/.test(fullMatch)) {
return fullMatch.replace(/<w:sectPr>[\s\S]*?<\/w:sectPr>/, '<w:sectPr>' + sectPrInner + '</w:sectPr>');
}
if (/<w:pPr>/.test(fullMatch)) {
return fullMatch.replace(/<w:pPr>/, '<w:pPr><w:sectPr>' + sectPrInner + '</w:sectPr>');
}
return fullMatch.replace(/<w:p\b([^>]*)>/, '<w:p$1><w:pPr><w:sectPr>' + sectPrInner + '</w:sectPr></w:pPr>');
}
function stripParagraphSectPr(fullMatch) {
return String(fullMatch || '').replace(/<w:sectPr>[\s\S]*?<\/w:sectPr>/g, '');
}
function buildHomePageEmptyLineParagraphXml() {
return '<w:p><w:r><w:t xml:space="preserve"></w:t></w:r></w:p>';
}
function buildHomePageSectionBreakParagraphXml(sectPrInner) {
return (
'<w:p>' +
'<w:pPr><w:sectPr>' +
sectPrInner +
'</w:sectPr></w:pPr>' +
'<w:r><w:t xml:space="preserve"></w:t></w:r>' +
'</w:p>'
);
}
function findCorrespondenceParagraphMatch(xml) {
return xml.match(/<w:p\b[^>]*>[\s\S]*?Correspondence to:[\s\S]*?<\/w:p>/i);
}
function patchSectPrBetweenCorrespondenceAndHomePageTable(xml) {
const corrMatch = findCorrespondenceParagraphMatch(xml);
if (!corrMatch) {
return xml;
}
const start = corrMatch.index + corrMatch[0].length;
const tblIdx = xml.indexOf('<w:tbl', start);
if (tblIdx < 0) {
return xml;
}
const singleSectPrInner = buildHomePageContinuousSectPrInner();
const between = xml.slice(start, tblIdx).replace(/<w:sectPr>[\s\S]*?<\/w:sectPr>/g, function () {
return '<w:sectPr>' + singleSectPrInner + '</w:sectPr>';
});
return xml.slice(0, start) + between + xml.slice(tblIdx);
}
function patchCorrespondenceDoubleSectionBreaks(xml) {
const corrMatch = findCorrespondenceParagraphMatch(xml);
if (!corrMatch) {
return xml;
}
const start = corrMatch.index + corrMatch[0].length;
const tblIdx = xml.indexOf('<w:tbl', start);
if (tblIdx < 0) {
return xml;
}
const between = xml.slice(start, tblIdx);
const hasDoubleBreak =
/<w:p>[\s\S]*?<w:sectPr>[\s\S]*?w:val="continuous"[\s\S]*?<\/w:sectPr>[\s\S]*?<\/w:p>\s*<w:p>[\s\S]*?<\/w:p>\s*<w:p>[\s\S]*?<w:sectPr>[\s\S]*?w:val="continuous"[\s\S]*?<\/w:sectPr>[\s\S]*?<\/w:p>/i.test(
between
);
if (hasDoubleBreak) {
return patchSectPrBetweenCorrespondenceAndHomePageTable(xml);
}
const singleSectPrInner = buildHomePageContinuousSectPrInner();
const doubleBreakBlock =
buildHomePageSectionBreakParagraphXml(singleSectPrInner) +
buildHomePageEmptyLineParagraphXml() +
buildHomePageSectionBreakParagraphXml(singleSectPrInner);
return xml.slice(0, start) + doubleBreakBlock + xml.slice(tblIdx);
}
/** E-mail 段落后补齐 2 个连续分节符(空行 ¶ 位于两分节符之间) */
export function patchHomePageSectionBreaksToXml(xml) {
if (!xml || !/Correspondence to:/i.test(xml)) {
return xml;
}
let next = patchCorrespondenceDoubleSectionBreaks(xml);
next = patchSectPrBetweenCorrespondenceAndHomePageTable(next);
return next;
}
/** E-mail 段落后补齐 2 个连续分节符(空行 ¶ 位于两分节符之间) */
export async function patchHomePageDoubleSectionBreaks(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');
xml = patchHomePageSectionBreaksToXml(xml);
zip.file('word/document.xml', xml);
return zip.generateAsync({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
}
function isHomePageMetadataTableBlock(block) {
const text = String(block || '');
return /Author/i.test(text) && /contributions/i.test(text) && /Abstract/i.test(text);
}
/** 线性扫描定位首页表格Abstract + Author contributions避免全文正则灾难性回溯 */
export function findHomePageMetadataTableBounds(xml) {
if (!xml || !/Author/i.test(xml) || !/contributions/i.test(xml) || !/Abstract/.test(xml)) {
return null;
}
const tblToken = '<w:tbl';
const closeTag = '</w:tbl>';
let searchFrom = 0;
while (searchFrom < xml.length) {
const tblStart = xml.indexOf(tblToken, searchFrom);
if (tblStart < 0) {
return null;
}
const tblEnd = xml.indexOf(closeTag, tblStart);
if (tblEnd < 0) {
return null;
}
const tblEndExclusive = tblEnd + closeTag.length;
const block = xml.slice(tblStart, tblEndExclusive);
if (isHomePageMetadataTableBlock(block)) {
return {
start: tblStart,
end: tblEndExclusive,
block: block
};
}
searchFrom = tblEndExclusive;
}
return null;
}
/** 补齐元数据表格Abstract + Author contributionsWord 表格属性(图五) */
export function patchMetadataTablePropertiesToXml(xml) {
const bounds = findHomePageMetadataTableBounds(xml);
if (!bounds) {
return xml;
}
const patched = patchMetadataTableBlock(bounds.block);
return xml.slice(0, bounds.start) + patched + xml.slice(bounds.end);
}
/** 补齐元数据表格Abstract + Author contributionsWord 表格属性(图五) */
export async function patchMetadataTableProperties(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');
xml = patchMetadataTablePropertiesToXml(xml);
zip.file('word/document.xml', xml);
return zip.generateAsync({
type: 'blob',
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
});
}
function buildHomePageTableSectionChildren(headerData) {
return [buildMetadataTable(headerData)];
}
function buildMetadataSectionChildren(headerData) {
return buildHomePageTableSectionChildren(headerData);
}
function buildMetadataColumnChildren(headerData) {
return buildMetadataSectionChildren(headerData);
}
function buildTitleBlockChildren(headerData, orcidImageData, tmrLogoImageData) {
const children = [];
const previewAuthor = headerData.previewAuthor || {};
const authors = headerData.authors || [];
const organIndexMap = buildOrganIndexMap(headerData.organList || []);
const addressList = getAddressList(headerData);
pushTitleBlockModule(
children,
createManuscriptTitleParagraph(headerData, tmrLogoImageData),
EMPTY_LINE_AFTER_TITLE
);
let authorParagraph = null;
if (authors.length) {
authorParagraph = buildAuthorLineFromAuthors(authors, organIndexMap, orcidImageData);
}
if (!authorParagraph && previewAuthor && previewAuthor.author) {
authorParagraph = buildAuthorLineParagraph(previewAuthor.author, orcidImageData);
}
if (!authorParagraph) {
authorParagraph = createMissingAuthorParagraph();
}
pushTitleBlockModule(children, authorParagraph, EMPTY_LINE_AFTER_AUTHOR);
const affiliationParagraphs = [];
addressList.forEach(function (address, index) {
const paragraph = createAffiliationParagraph(index, address);
if (paragraph) {
affiliationParagraphs.push(paragraph);
}
});
if (affiliationParagraphs.length) {
pushTitleBlockParagraphs(children, affiliationParagraphs);
children.push(createHeaderBlockEmptyLineParagraph(EMPTY_LINE_AFTER_AFFILIATION));
}
const coFirstCount = countCoFirstAuthors(authors, previewAuthor && previewAuthor.author);
const coFirstParagraph = createCoFirstParagraph(coFirstCount > 1);
const correspondenceParagraphs = createCorrespondenceParagraphs(
authors,
addressList,
headerData.organList || []
);
const footerParagraphs = [];
if (coFirstParagraph) {
footerParagraphs.push(coFirstParagraph);
}
if (correspondenceParagraphs.length) {
footerParagraphs.push.apply(footerParagraphs, correspondenceParagraphs);
}
pushTitleBlockParagraphs(children, footerParagraphs);
return children;
}
export function buildAuthorHeaderSectionGroups(headerData, orcidImageData, tmrLogoImageData) {
if (!headerData) {
return [];
}
const titleBlockChildren = buildTitleBlockChildren(headerData, orcidImageData, tmrLogoImageData);
if (!titleBlockChildren.length) {
return [];
}
const homePageTableChildren = buildHomePageTableSectionChildren(headerData);
if (!homePageTableChildren.length) {
return [];
}
/** 首页:标题块(单栏)→ 连续分节符 → 首页元数据表格(单栏)→ 连续分节符 → 正文双栏 */
return [
{
columnCount: 1,
preventMergeAfter: true,
children: titleBlockChildren
},
{
columnCount: 1,
preventMergeBefore: true,
preventMergeAfter: true,
children: homePageTableChildren
}
];
}
/** @deprecated 使用 buildAuthorHeaderSectionGroups */
export function buildAuthorHeaderSectionChildren(headerData, orcidImageData) {
return buildAuthorHeaderSectionGroups(headerData, orcidImageData).reduce(function (all, group) {
return all.concat(group.children || []);
}, []);
}