2617 lines
83 KiB
JavaScript
2617 lines
83 KiB
JavaScript
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 =
|
||
'<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 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(/<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(/ /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(/<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 contributions)Word 表格属性(图五) */
|
||
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 contributions)Word 表格属性(图五) */
|
||
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 || []);
|
||
}, []);
|
||
}
|