添加期刊引用分析

This commit is contained in:
2026-06-17 09:35:15 +08:00
parent ec4a59eedb
commit ea5695f913
36 changed files with 6129 additions and 103 deletions

View File

@@ -0,0 +1,154 @@
import {
Document,
Paragraph,
TextRun,
CommentRangeStart,
CommentRangeEnd,
CommentReference,
LevelFormat,
LevelSuffix,
AlignmentType,
Packer
} from 'docx';
import JSZip from 'jszip';
import fs from 'fs';
function htmlToPlainText(html) {
return String(html || '')
.replace(/ /g, ' ')
.replace(/<br\s*\/?>/gi, ' ')
.replace(/<[^>]*>/g, '')
.replace(/\s+/g, ' ')
.trim();
}
function parseSingleAuthorName(nameStr) {
const token = String(nameStr || '').trim();
if (!token || /^et al\.?$/i.test(token)) return null;
const parts = token.split(/\s+/).filter(Boolean);
if (!parts.length) return null;
if (parts.length === 1) return { first: '', last: parts[0] };
return { first: parts.slice(0, -1).join(' '), last: parts[parts.length - 1] };
}
function firstNameToAmaInitials(first) {
return String(first || '')
.split(/[\s-]+/)
.filter(Boolean)
.map((part) => part.charAt(0).toUpperCase())
.join('');
}
function formatOneAmaAuthor(nameStr) {
const parsed = parseSingleAuthorName(nameStr);
if (!parsed || !parsed.last) return '';
const initials = firstNameToAmaInitials(parsed.first);
return initials ? parsed.last + ' ' + initials : parsed.last;
}
function splitAuthorList(authorText) {
return String(authorText || '')
.split(/\s*,\s*/)
.map((part) => part.trim())
.filter(Boolean);
}
function isAmaAbbreviatedAuthorList(authorText) {
const authors = splitAuthorList(htmlToPlainText(authorText));
if (!authors.length) return true;
return authors.every((token) => {
if (/^et al\.?$/i.test(token)) return true;
return /^[\p{L}][\p{L}\-'']*\s+[A-Z](?:-?[A-Z])*\.?$/u.test(token);
});
}
function needsAmaAuthorAbbreviation(authorText) {
const plain = htmlToPlainText(authorText).trim();
if (!plain || isAmaAbbreviatedAuthorList(plain)) return false;
return splitAuthorList(plain).some((token) => {
const parts = token.split(/\s+/).filter(Boolean);
if (parts.length < 2) return false;
return parts[0].length > 2;
});
}
function buildAmaAuthorAbbreviation(authorText) {
const authors = splitAuthorList(htmlToPlainText(authorText));
return authors
.map((token) => (/^et al\.?$/i.test(token) ? 'et al' : formatOneAmaAuthor(token)))
.filter(Boolean)
.join(', ');
}
function buildBookAmaAuthorComment(ref) {
if (!ref || ref.refer_type !== 'book') return '';
const authorSource = ref.author ? htmlToPlainText(ref.author) : '';
if (!authorSource || !needsAmaAuthorAbbreviation(authorSource)) return '';
const amaAuthors = buildAmaAuthorAbbreviation(authorSource);
return amaAuthors ? 'AMA格式作者缩写' + amaAuthors : '';
}
const ref = {
refer_type: 'book',
author: 'Jacqueline Fawcett, Susan DeSanto-Madeya',
title: 'Contemporary Nursing Knowledge',
dateno: '(3rd Edition). Philadelphia, PA: F. A. Davis Company; 2013.',
isbn: '978-0-8036-2765-9'
};
const commentText = buildBookAmaAuthorComment(ref);
console.log('comment text:', commentText);
const commentId = 0;
const doc = new Document({
numbering: {
config: [{
reference: 'ref-num',
levels: [{
level: 0,
format: LevelFormat.DECIMAL,
text: '%1.',
alignment: AlignmentType.START,
suffix: LevelSuffix.TAB,
style: { paragraph: { indent: { left: 720, hanging: 360 } } }
}]
}]
},
comments: {
children: [{
id: commentId,
author: 'Editor',
initials: 'Ed',
date: new Date(),
children: [new Paragraph({ children: [new TextRun(commentText)] })]
}]
},
sections: [{
children: [new Paragraph({
numbering: { reference: 'ref-num', level: 0 },
children: [
new CommentRangeStart(commentId),
new TextRun('Jacqueline Fawcett, Susan DeSanto-Madeya. Contemporary Nursing Knowledge.'),
new CommentRangeEnd(commentId),
new TextRun({ children: [new CommentReference(commentId)] })
]
})]
}]
});
const buf = await Packer.toBuffer(doc);
fs.writeFileSync('test-ref-comment-standalone.docx', buf);
const zip = await JSZip.loadAsync(buf);
const docXml = await zip.file('word/document.xml').async('string');
const commentsXml = zip.file('word/comments.xml');
const relsXml = zip.file('word/_rels/document.xml.rels');
const contentTypes = zip.file('[Content_Types].xml');
console.log('has comments.xml:', !!commentsXml);
console.log('has commentReference in document:', docXml.includes('commentReference'));
console.log('has comments rel in document.xml.rels:', relsXml ? (await relsXml.async('string')).includes('comments') : false);
console.log('has comments in Content_Types:', contentTypes ? (await contentTypes.async('string')).includes('comments') : false);
if (commentsXml) {
console.log('comments preview:', (await commentsXml.async('string')).slice(0, 800));
}

View File

@@ -0,0 +1,40 @@
import {
Document,
Paragraph,
TextRun,
CommentRangeStart,
CommentRangeEnd,
CommentReference,
LevelFormat,
LevelSuffix,
AlignmentType,
Packer
} from 'docx';
import JSZip from 'jszip';
import fs from 'fs';
import { buildBookAmaAuthorComment } from '../src/utils/referenceAuthorAma.js';
import { buildManuscriptWordDocument } from '../src/utils/exportManuscriptWord.js';
const ref = {
refer_type: 'book',
author: 'Jacqueline Fawcett, Susan DeSanto-Madeya',
title: 'Contemporary Nursing Knowledge',
dateno: '(3rd Edition). Philadelphia, PA: F. A. Davis Company; 2013.',
isbn: '978-0-8036-2765-9'
};
console.log('comment text:', buildBookAmaAuthorComment(ref));
const doc = buildManuscriptWordDocument([], '', [ref], null, null);
const buf = await Packer.toBuffer(doc);
fs.writeFileSync('test-ref-comment.docx', buf);
const zip = await JSZip.loadAsync(buf);
const docXml = await zip.file('word/document.xml').async('string');
const commentsXml = zip.file('word/comments.xml');
console.log('has comments.xml:', !!commentsXml);
console.log('has commentReference:', docXml.includes('commentReference'));
console.log('has commentRangeStart:', docXml.includes('commentRangeStart'));
if (commentsXml) {
console.log('comments preview:', (await commentsXml.async('string')).slice(0, 500));
}

View File

@@ -0,0 +1,49 @@
import {
Document,
Paragraph,
TextRun,
DeletedTextRun,
InsertedTextRun,
LevelFormat,
LevelSuffix,
AlignmentType,
Packer
} from 'docx';
import JSZip from 'jszip';
import fs from 'fs';
const doc = new Document({
features: { trackRevisions: true },
sections: [{
children: [new Paragraph({
children: [
new DeletedTextRun({
id: 1,
author: 'Editor',
date: new Date().toISOString(),
text: 'David Harvey ',
font: 'Charis SIL',
size: 15
}),
new InsertedTextRun({
id: 2,
author: 'Editor',
date: new Date().toISOString(),
text: 'Harvey D ',
font: 'Charis SIL',
size: 15
}),
new TextRun('Contemporary Nursing Knowledge.')
]
})]
}]
});
const buf = await Packer.toBuffer(doc);
fs.writeFileSync('test-ref-revision.docx', buf);
const zip = await JSZip.loadAsync(buf);
const docXml = await zip.file('word/document.xml').async('string');
const settingsXml = await zip.file('word/settings.xml').async('string');
console.log('has w:ins:', docXml.includes('<w:ins'));
console.log('has w:del:', docXml.includes('<w:del'));
console.log('trackRevisions:', settingsXml.includes('trackRevisions'));