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 ''; } /** 新增参考文献 payload,p_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; } /** * 按序号批量保存:已有条目走 editRefer,Word 多出的条目走 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 }; }