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

486 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { htmlToPlainText } from '@/utils/manuscriptCitationRelevance';
import { parseReferencesBulkContent } from '@/utils/manuscriptReferenceHtml';
import { extractReferencesSectionFromWordHtml, fetchWordReferencesSection } from '@/utils/manuscriptWordReferences';
import { importWordDocumentWithMath } from '@/utils/wordMathImport';
import { applyDealContentToFields, parseReferenceFieldsFromHtml, normalizeImportedReferenceFields, normalizeImportedReferenceText, finalizeReferenceFields } from '@/utils/parseReferenceFields';
function mapUploadedItemsToRows(items) {
return (items || []).map(function (item, index) {
const referType = String(item.refer_type || 'journal').toLowerCase();
const fields = parseReferenceFieldsFromHtml(item.html, referType);
return {
index: index + 1,
html: item.html || '',
refer_type: fields ? fields.refer_type : referType,
fields: fields
};
});
}
/** 解析本地 Word 文件中的 References 条目 */
export async function parseLocalWordReferencesFile(file) {
if (!file) {
return { found: false, rows: [], count: 0 };
}
const html = await importWordDocumentWithMath(file, { keepReferences: true });
const extracted = extractReferencesSectionFromWordHtml(html);
if (!extracted.found) {
return { found: false, rows: [], count: 0 };
}
const items = parseReferencesBulkContent(extracted.html);
const rows = mapUploadedItemsToRows(items);
return {
found: rows.length > 0,
rows: rows,
count: rows.length
};
}
/** 从 gridData 稿件路径下载并解析 References */
export async function parseManuscriptWordReferences(apiClient, options) {
const extracted = await fetchWordReferencesSection(apiClient, options || {});
if (!extracted.found) {
return { found: false, rows: [], count: 0 };
}
const items = parseReferencesBulkContent(extracted.html);
const rows = mapUploadedItemsToRows(items);
return {
found: rows.length > 0,
rows: rows,
count: rows.length
};
}
function previewExistingReference(row) {
if (!row) return '';
if (row.refer_frag) {
return htmlToPlainText(row.refer_frag).replace(/\s+/g, ' ').trim();
}
const parts = [];
if (row.author) parts.push(row.author);
if (row.title) parts.push(row.title);
if (row.joura) parts.push(row.joura);
if (row.dateno) parts.push(row.dateno);
if (row.doilink) parts.push('Available at: ' + row.doilink);
return parts.join(' ').replace(/\s+/g, ' ').trim();
}
function normalizeWordRefBatchRow(row) {
if (!row) return row;
let normalized = normalizeImportedReferenceFields(row);
if (normalized.doilink) {
normalized.doilink = normalizeDoilinkFull(normalized.doilink);
normalized.doi = normalizeDoiValue(normalized.doilink);
}
if (normalized.dateno) {
normalized.dateno = normalized.dateno.replace(/^(\d{4}):/, '$1;');
}
normalized = finalizeReferenceFields(normalized, row.wordHtml || normalized.content || '');
return Object.assign({}, row, normalized);
}
/** 按序号将 Word 条目与现有参考文献一一对应 */
export function buildWordRefBatchRows(existingRows, uploadedRows) {
const existing = existingRows || [];
const uploaded = uploadedRows || [];
const maxLen = Math.max(existing.length, uploaded.length);
const rows = [];
for (let i = 0; i < maxLen; i++) {
const existingRow = existing[i] || null;
const uploadedRow = uploaded[i] || null;
const fields = (uploadedRow && uploadedRow.fields) || null;
const prevExistingRow = i > 0 ? existing[i - 1] : null;
rows.push(
normalizeWordRefBatchRow({
index: i + 1,
apply: !!(uploadedRow && fields),
p_refer_id: existingRow ? existingRow.p_refer_id : null,
pre_p_refer_id: existingRow
? existingRow.pre_p_refer_id
: prevExistingRow
? prevExistingRow.p_refer_id
: null,
existingType: existingRow ? existingRow.refer_type : '',
existingPreview: previewExistingReference(existingRow),
refer_type: fields ? fields.refer_type : uploadedRow ? uploadedRow.refer_type : 'journal',
author: fields ? fields.author : '',
title: fields ? fields.title : '',
joura: fields ? fields.joura : '',
dateno: fields ? fields.dateno : '',
doilink: fields ? fields.doilink : '',
doi: fields ? fields.doi || fields.doilink : '',
isbn: fields ? fields.isbn : '',
content: fields ? fields.content : '',
wordHtml: uploadedRow ? uploadedRow.html : '',
unmatched: !existingRow || !uploadedRow,
missingExisting: !existingRow,
missingUploaded: !uploadedRow
})
);
}
return rows;
}
/** 调用 dealContent 精修字段拆解 */
export async function enrichWordRefBatchRowsWithDealContent(apiClient, rows) {
const list = rows || [];
const updated = [];
for (let i = 0; i < list.length; i++) {
const row = list[i];
if (!row || !row.wordHtml) {
updated.push(normalizeWordRefBatchRow(row));
continue;
}
try {
const content = normalizeImportedReferenceText(htmlToPlainText(row.wordHtml));
const res = await apiClient.post('api/References/dealContent', { content: content });
const data = res && res.data ? res.data : null;
if (data) {
const merged = applyDealContentToFields(
{
refer_type: row.refer_type,
author: row.author,
title: row.title,
joura: row.joura,
dateno: row.dateno,
doilink: row.doilink,
doi: row.doi || row.doilink,
isbn: row.isbn,
content: row.content
},
data
);
const nextRow = normalizeWordRefBatchRow(
Object.assign({}, row, merged, {
refer_type: merged.refer_type || row.refer_type,
doi: merged.doi || merged.doilink || row.doi || row.doilink
})
);
updated.push(nextRow);
continue;
}
} catch (err) {
// 保留本地拆解结果
}
updated.push(normalizeWordRefBatchRow(row));
}
return updated;
}
function normalizeDoiValue(doilink) {
let raw = String(doilink || '').trim();
if (!raw) return '';
raw = raw.replace(/^https?:\/\/doi\.org\//i, '');
raw = raw.replace(/^doi:/i, '');
return raw.toLowerCase();
}
function normalizeDoilinkFull(doilink) {
const raw = String(doilink || '').trim();
if (!raw) return '';
if (/^https?:\/\//i.test(raw)) return raw;
const doi = normalizeDoiValue(raw);
return doi ? 'https://doi.org/' + doi : raw;
}
function appendReferPart(content, part) {
const piece = String(part || '').trim();
if (!piece) return content;
let out = String(content || '').trim();
if (!out) return piece;
if (out.endsWith('.')) {
return out + piece;
}
return out + '.' + piece;
}
/** journal 的 content 字段,与 editRefer 接口一致 */
export function buildJournalReferContent(fields) {
const author = String((fields && fields.author) || '').trim();
const title = String((fields && fields.title) || '').trim();
const joura = String((fields && fields.joura) || '').trim();
const dateno = String((fields && fields.dateno) || '').trim();
const doilink = normalizeDoilinkFull(fields && fields.doilink);
let content = author;
if (title) {
if (!content) {
content = title;
} else if (content.endsWith('.')) {
content = content + ' ' + title;
} else {
content = content + '. ' + title;
}
}
if (joura) {
content = content + ' ' + joura;
if (!/\.\s*$/.test(joura)) {
content = content + '.';
}
}
if (dateno) {
const datenoForContent = dateno.replace(/^(\d{4});/, '$1:');
content = appendReferPart(content, datenoForContent);
}
if (doilink) {
content = content + '.Available at: ' + doilink;
}
return content;
}
/** book 的 content 字段 */
export function buildBookReferContent(fields) {
const author = String((fields && fields.author) || '').trim();
const title = String((fields && fields.title) || '').trim();
const dateno = String((fields && fields.dateno) || '').trim();
const isbn = String((fields && fields.isbn) || '').trim();
let content = author;
if (title) {
content = appendReferPart(content, title);
}
if (dateno) {
content = appendReferPart(content, dateno);
}
if (isbn) {
content = content + '. Available at: ISBN: ' + isbn;
}
return content;
}
export function buildEditReferPayload(row, pArticleId) {
const normalized = normalizeWordRefBatchRow(row || {});
const referType = String(normalized.refer_type || 'journal').toLowerCase();
const base = {
p_article_id: pArticleId != null && pArticleId !== '' ? pArticleId : '',
p_refer_id: normalized.p_refer_id,
pre_p_refer_id: normalized.pre_p_refer_id != null && normalized.pre_p_refer_id !== '' ? normalized.pre_p_refer_id : ''
};
if (referType === 'book') {
return Object.assign(base, {
doi: '',
refer_type: 'book',
author: normalized.author || '',
title: normalized.title || '',
dateno: normalized.dateno || '',
isbn: normalized.isbn || '',
joura: '',
doilink: '',
content: buildBookReferContent(normalized)
});
}
if (referType === 'other') {
return Object.assign(base, {
doi: normalizeDoiValue(normalized.doilink || normalized.doi),
refer_type: 'other',
author: '',
title: '',
dateno: '',
joura: '',
doilink: normalizeDoilinkFull(normalized.doilink) || '',
isbn: '',
content: normalized.content || ''
});
}
const doilink = normalizeDoilinkFull(normalized.doilink);
const doi = normalizeDoiValue(normalized.doilink || normalized.doi);
return Object.assign(base, {
doi: doi,
refer_type: 'journal',
author: normalized.author || '',
title: normalized.title || '',
joura: normalized.joura || '',
dateno: normalized.dateno || '',
doilink: doilink,
isbn: '',
content: buildJournalReferContent(normalized)
});
}
export function extractAddedReferId(data) {
if (!data || typeof data !== 'object') {
return '';
}
if (data.p_refer_id != null && data.p_refer_id !== '') {
return String(data.p_refer_id);
}
return '';
}
/** 新增参考文献 payloadp_refer_id 留空pre_p_refer_id 指向前一条 */
export function buildAddReferPayload(row, pArticleId, preReferId) {
const payload = buildEditReferPayload(row, pArticleId);
delete payload.p_refer_id;
payload.pre_p_refer_id = preReferId != null && preReferId !== '' ? String(preReferId) : '';
return payload;
}
export function canWordRefBatchRowSave(row) {
if (!row || row.missingUploaded) {
return false;
}
if (row.p_refer_id) {
return true;
}
return !!row.missingExisting;
}
const WORD_REF_BATCH_FIELD_DEFS = {
journal: ['author', 'title', 'joura', 'dateno', 'doilink'],
book: ['author', 'title', 'dateno', 'isbn'],
other: ['content']
};
const WORD_REF_BATCH_REQUIRED_FIELDS = {
journal: ['title', 'joura', 'dateno'],
book: ['author', 'title', 'dateno'],
other: ['content']
};
export function getWordRefBatchFieldKeys(referType) {
const type = String(referType || 'journal').toLowerCase();
return (WORD_REF_BATCH_FIELD_DEFS[type] || WORD_REF_BATCH_FIELD_DEFS.journal).slice();
}
export function isWordRefBatchRowParseIncomplete(row) {
if (!row || row.missingUploaded) {
return false;
}
const type = String(row.refer_type || 'journal').toLowerCase();
const required = WORD_REF_BATCH_REQUIRED_FIELDS[type] || WORD_REF_BATCH_REQUIRED_FIELDS.journal;
return required.some(function (key) {
return !String(row[key] || '').trim();
});
}
export function stripWordRefBatchHtmlPreview(html) {
return htmlToPlainText(String(html || ''))
.replace(/\s+/g, ' ')
.trim();
}
/** 切换为 other 时:用 Word 原文(或已拆字段拼接)填入 content */
export function buildWordRefBatchContentFromRow(row) {
if (!row) {
return '';
}
const fromWord = stripWordRefBatchHtmlPreview(row.wordHtml);
if (fromWord) {
return normalizeImportedReferenceText(fromWord);
}
const parts = [];
if (row.author) {
parts.push(String(row.author).trim());
}
if (row.title) {
parts.push(String(row.title).trim());
}
if (row.joura) {
parts.push(String(row.joura).trim());
}
if (row.dateno) {
parts.push(String(row.dateno).trim());
}
let content = parts
.filter(Boolean)
.join(' ')
.replace(/\s+\./g, '.')
.replace(/\s+/g, ' ')
.trim();
const doilink = String(row.doilink || '').trim();
if (doilink && !/Available at:/i.test(content)) {
content = content + '. Available at: ' + doilink;
}
return normalizeImportedReferenceText(content || row.content || '');
}
export function applyWordRefBatchOtherType(row) {
if (!row) {
return row;
}
row.refer_type = 'other';
row.content = buildWordRefBatchContentFromRow(row);
const plain = String(row.content || '');
const urlMatch = plain.match(/Available at:\s*(https?:\/\/\S+)/i);
if (urlMatch) {
row.doilink = urlMatch[1].replace(/[.,;)\]]+$/, '');
}
return row;
}
/**
* 按序号批量保存:已有条目走 editReferWord 多出的条目走 addReferByParticleid
* 每条成功后用返回/已有的 p_refer_id 作为下一条的 pre_p_refer_id。
*/
export async function saveWordRefBatchRows(apiClient, options) {
const opts = options || {};
const rows = (opts.rows || [])
.filter(function (row) {
return row && row.apply && canWordRefBatchRowSave(row);
})
.sort(function (a, b) {
return (a.index || 0) - (b.index || 0);
});
const pArticleId = opts.pArticleId;
const useUserAdd = opts.role === 'user';
const addEndpoint = useUserAdd ? 'api/Preaccept/addRefer' : 'api/Preaccept/addReferByParticleid';
let lastReferId = null;
let ok = 0;
let fail = 0;
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
try {
if (row.p_refer_id) {
const payload = buildEditReferPayload(row, pArticleId);
if (lastReferId != null && lastReferId !== '') {
payload.pre_p_refer_id = lastReferId;
}
const res = await apiClient.post('api/Preaccept/editRefer', payload);
if (res && res.code == 0) {
ok += 1;
lastReferId = String(row.p_refer_id);
} else {
fail += 1;
}
continue;
}
if (row.missingExisting) {
const payload = buildAddReferPayload(row, pArticleId, lastReferId);
if (useUserAdd && opts.articleId != null && opts.articleId !== '') {
payload.article_id = opts.articleId;
}
const res = await apiClient.post(addEndpoint, payload);
if (res && res.code == 0) {
ok += 1;
const newId = extractAddedReferId(res.data);
if (newId) {
lastReferId = newId;
}
} else {
fail += 1;
}
continue;
}
fail += 1;
} catch (err) {
fail += 1;
}
}
return { ok: ok, fail: fail };
}