Files
tougao_web/scripts/test-ref-comment-standalone.mjs
2026-06-17 09:35:15 +08:00

155 lines
5.1 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 {
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));
}