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; /** 首页表格右栏 Abstract:Charis 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 = ''; const HOME_PAGE_LEFT_PARAGRAPH_OOXML = '' + '' + '' + '' + ''; /** 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 = ''; const ABSTRACT_PARAGRAPH_OOXML = '' + '' + '' + '' + ''; /** 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 = '' + '' + '' + '' + '' + '' + ''; /** 段前 0.5 行 = beforeLines 50(以百分之一行为单位) */ const TITLE_PARAGRAPH_SPACING_OOXML = ''; 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 contributions):Word 表格属性 图五 */ 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(//gi, ''); text = text.replace(/<\/q>/gi, ''); text = text.replace(/<\/sup>,/gi, ', '); text = text.replace(/]*margin-left:\s*8px[^>]*>\s*<\/span>/gi, ' '); text = text.replace(/,\s*$/, ''); return text.trim(); } function decodeAuthorHtmlEntities(text) { return String(text || '') .replace(/ /g, ' ') .replace(/&/g, '&') .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/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(//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(']*/>', 'g'); const paired = new RegExp(']*>[\\s\\S]*?', '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(/]*>([\s\S]*?)<\/w:p>/g, function (fullMatch, pInner) { if (!isTitleParagraphContent(pInner)) { return fullMatch; } if (pInner.includes('')) { return fullMatch.replace(/([\s\S]*?)<\/w:pPr>/, function (pPrMatch, pPrInner) { return '' + buildTitleParagraphPropertiesXml(pPrInner) + ''; }); } return fullMatch.replace( /]*)>/, '' + buildTitleParagraphPropertiesXml('') + '' ); }); } /** 为导出 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(/^]*)?>/, '').replace(/<\/w:p>\s*$/, ''); if (//.test(inner)) { return fullMatch.replace(/([\s\S]*?)<\/w:pPr>/, function (_pPrMatch, pPrInner) { return '' + buildHomePageLeftParagraphPropertiesXml(pPrInner) + ''; }); } return fullMatch.replace( /]*)?>/, function (openTag) { return openTag + '' + buildHomePageLeftParagraphPropertiesXml('') + ''; } ); } function patchMetadataTableCellParagraphs(tcInner, patchParagraph) { return String(tcInner || '').replace(/]*)?>[\s\S]*?<\/w:p>/g, function (pFull) { const pInner = pFull.replace(/^]*)?>/, '').replace(/<\/w:p>\s*$/, ''); return patchParagraph(pFull, pInner); }); } function patchMetadataTableParagraphsInTableBlock(tblBlock) { if (!isMetadataTableBlock(tblBlock)) { return tblBlock; } let cellIndex = 0; return tblBlock.replace(/([\s\S]*?)<\/w:tc>/g, function (match, tcInner) { cellIndex += 1; const patchParagraph = cellIndex === 1 ? patchHomePageLeftParagraphXml : patchAbstractParagraphXml; const patchedInner = patchMetadataTableCellParagraphs(tcInner, patchParagraph); return '' + patchedInner + ''; }); } 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(/^]*)?>/, '').replace(/<\/w:p>\s*$/, ''); if (//.test(inner)) { return fullMatch.replace(/([\s\S]*?)<\/w:pPr>/, function (_pPrMatch, pPrInner) { return '' + buildAbstractParagraphPropertiesXml(pPrInner) + ''; }); } return fullMatch.replace(/]*)?>/, function (openTag) { return openTag + '' + buildAbstractParagraphPropertiesXml('') + ''; }); } /** 为 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(/]*>[\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 ( '' + '' + '' + '' + '' + '' ); } function buildMetadataTcMarXml() { return ( '' + '' + '' + '' + '' + '' ); } 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(//g, '').replace(/]*\/>/g, ''); if (//.test(block)) { block = block.replace(/([\s\S]*?)<\/w:tblPr>/, function (_match, inner) { let pr = inner .replace(/]*\/>/g, '') .replace(/]*>[\s\S]*?<\/w:tblW>/g, '') .replace(/]*\/>/g, '') .replace(/]*>[\s\S]*?<\/w:jc>/g, '') .replace(/]*\/>/g, '') .replace(/]*>[\s\S]*?<\/w:tblInd>/g, '') .replace(/]*\/>/g, '') .replace(/]*>[\s\S]*?<\/w:tblLayout>/g, '') .replace(/[\s\S]*?<\/w:tblCellMar>/g, ''); pr += '' + '' + '' + '' + buildMetadataTblCellMarXml(); return '' + pr + ''; }); } else { block = block.replace( //, '' + '' + '' + '' + '' + buildMetadataTblCellMarXml() + '' ); } block = block.replace(/([\s\S]*?)<\/w:tr>/, function (_match, trInner) { let inner = trInner.replace(/[\s\S]*?<\/w:trPr>/g, '').replace(/]*\/>/g, ''); const trPr = ''; return '' + trPr + inner + ''; }); let tcIndex = 0; block = block.replace(/(\s*[\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(/([\s\S]*?)<\/w:tcPr>/, '$1'); tcPrInner = tcPrInner .replace(/]*\/>/g, '') .replace(/]*\/>/g, '') .replace(/]*>[\s\S]*?<\/w:tcW>/g, '') .replace(/[\s\S]*?<\/w:tcMar>/g, ''); } return ( '' + tcPrInner + '' + '' + buildMetadataTcMarXml() + '' ); }); block = patchMetadataTableParagraphsInTableBlock(block); return block; } function isHomePageSingleColumnSectPrInner(sectPrInner) { return /]*\bw:num="1"/i.test(String(sectPrInner || '')) && !/]*\bw:num="2"/i.test(String(sectPrInner || '')); } function isHomePageDualColumnSectPrInner(sectPrInner) { return /]*\bw:num="2"/i.test(String(sectPrInner || '')); } function replaceSectPrInnerColsWithSingle(sectPrInner) { let next = String(sectPrInner || '') .replace(/]*\/>/g, '') .replace(/]*>[\s\S]*?<\/w:cols>/g, ''); return next + ''; } /** 首页元数据表格节结束位置:含表格后空行,不含正文双栏分节符 */ 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*(]*>[\s\S]*?<\/w:p>)/); if (!match) { break; } const block = match[1]; const blockEnd = cursor + match[0].length; const sectMatch = block.match(/([\s\S]*?)<\/w:sectPr>/); if (sectMatch && isHomePageDualColumnSectPrInner(sectMatch[1])) { break; } end = blockEnd; cursor = blockEnd; if (sectMatch) { break; } if (/\bIntroduction\b/i.test(block) || /]*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 = /[\s\S]*?]*\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(/([\s\S]*?)<\/w:sectPr>/g, function (full, inner, offset) { if (offset < regionStart || offset >= tableSectionEnd) { return full; } if (isHomePageSingleColumnSectPrInner(inner)) { return full; } return '' + replaceSectPrInnerColsWithSingle(inner) + ''; }); } function buildHomePageContinuousSectPrInner() { const grid = getDocumentGridProperties(); return ( '' + '' + '' + '' + '' + '' ); } function replaceParagraphSectPrInner(fullMatch, sectPrInner) { if (/[\s\S]*?<\/w:sectPr>/.test(fullMatch)) { return fullMatch.replace(/[\s\S]*?<\/w:sectPr>/, '' + sectPrInner + ''); } if (//.test(fullMatch)) { return fullMatch.replace(//, '' + sectPrInner + ''); } return fullMatch.replace(/]*)>/, '' + sectPrInner + ''); } function stripParagraphSectPr(fullMatch) { return String(fullMatch || '').replace(/[\s\S]*?<\/w:sectPr>/g, ''); } function buildHomePageEmptyLineParagraphXml() { return ''; } function buildHomePageSectionBreakParagraphXml(sectPrInner) { return ( '' + '' + sectPrInner + '' + '' + '' ); } function findCorrespondenceParagraphMatch(xml) { return xml.match(/]*>[\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('[\s\S]*?<\/w:sectPr>/g, function () { return '' + singleSectPrInner + ''; }); 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('[\s\S]*?[\s\S]*?w:val="continuous"[\s\S]*?<\/w:sectPr>[\s\S]*?<\/w:p>\s*[\s\S]*?<\/w:p>\s*[\s\S]*?[\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 = ' 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 || []); }, []); }