This commit is contained in:
2026-07-31 15:07:12 +08:00
parent 3448ffe961
commit 9d92f86f08
49 changed files with 13833 additions and 1265 deletions

File diff suppressed because one or more lines are too long

1
scripts/_out.html Normal file

File diff suppressed because one or more lines are too long

15
scripts/alias-hooks.mjs Normal file
View File

@@ -0,0 +1,15 @@
import path from 'path';
import fs from 'fs';
import { pathToFileURL } from 'url';
export async function resolve(specifier, context, nextResolve) {
if (specifier.startsWith('@/')) {
let abs = path.join(process.cwd(), 'src', specifier.slice(2));
if (!fs.existsSync(abs) && !path.extname(abs)) {
if (fs.existsSync(abs + '.js')) abs += '.js';
else if (fs.existsSync(path.join(abs, 'index.js'))) abs = path.join(abs, 'index.js');
}
return { shortCircuit: true, url: pathToFileURL(abs).href };
}
return nextResolve(specifier, context);
}

19
scripts/alias-loader.mjs Normal file
View File

@@ -0,0 +1,19 @@
import path from 'path';
import fs from 'fs';
import { pathToFileURL } from 'url';
export async function resolve(specifier, context, nextResolve) {
if (specifier.startsWith('@/')) {
let rel = specifier.slice(2);
let abs = path.join(process.cwd(), 'src', rel);
if (!fs.existsSync(abs) && !path.extname(abs)) {
if (fs.existsSync(abs + '.js')) abs = abs + '.js';
else if (fs.existsSync(path.join(abs, 'index.js'))) abs = path.join(abs, 'index.js');
}
return {
shortCircuit: true,
url: pathToFileURL(abs).href
};
}
return nextResolve(specifier, context);
}

View File

@@ -0,0 +1,5 @@
import { register } from 'node:module';
import { pathToFileURL } from 'url';
import path from 'path';
register(pathToFileURL(path.join(process.cwd(), 'scripts/alias-hooks.mjs')).href);

View File

@@ -0,0 +1,45 @@
import FS from 'fs';
import path from 'path';
import JSZip from 'jszip';
const file = path.join(
'C:/Users/Administrator/Desktop/IN2026A0418001_final_20260713 1.docx'
);
const xml = await (await JSZip.loadAsync(FS.readFileSync(file))).file('word/document.xml').async('string');
const paras = xml.split(/<\/w:p>/);
function plainOf(p) {
return [...p.matchAll(/<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>/g)]
.map((x) => x[1])
.join('')
.replace(/&amp;/g, '&')
.replace(/\s+/g, ' ')
.trim();
}
function blipCount(p) {
return (p.match(/a:blip/g) || []).length;
}
function drawingCount(p) {
return (p.match(/<w:drawing/g) || []).length;
}
for (let i = 0; i < paras.length; i++) {
const plain = plainOf(paras[i]);
if (!/^Figure\s+\d+/i.test(plain)) continue;
// look back up to 8 paras for images / A B labels
const back = [];
for (let j = Math.max(0, i - 8); j < i; j++) {
const t = plainOf(paras[j]);
const b = blipCount(paras[j]);
const d = drawingCount(paras[j]);
if (b || d || t) {
back.push({ j, blips: b, drawings: d, text: t.slice(0, 80) });
}
}
const next = plainOf(paras[i + 1] || '').slice(0, 180);
console.log('\nCAPTION', plain.slice(0, 100));
console.log('BACK', JSON.stringify(back, null, 0));
console.log('NOTE?', next);
}

View File

@@ -0,0 +1,82 @@
import FS from 'fs';
import path from 'path';
import JSZip from 'jszip';
const file = path.join(
'C:/Users/Administrator/Desktop/IN2026A0418001_final_20260713 1.docx'
);
const buf = FS.readFileSync(file);
const zip = await JSZip.loadAsync(buf);
const xml = await zip.file('word/document.xml').async('string');
const drawings = (xml.match(/<w:drawing/g) || []).length;
const blips = (xml.match(/a:blip/g) || []).length;
console.log('drawings', drawings, 'blips', blips);
const colors = {};
for (const m of xml.matchAll(/w:color\s+w:val="([^"]+)"/g)) {
colors[m[1]] = (colors[m[1]] || 0) + 1;
}
console.log(
'colors top',
Object.entries(colors)
.sort((a, b) => b[1] - a[1])
.slice(0, 20)
);
// Extract paragraphs containing "Figure" with nearby text
const paras = xml.split(/<\/w:p>/);
let figParaCount = 0;
for (let i = 0; i < paras.length; i++) {
const p = paras[i];
const texts = [...p.matchAll(/<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>/g)].map((x) => x[1]);
const plain = texts.join('').replace(/&amp;/g, '&').trim();
if (!/^Figure\s+\d+/i.test(plain) && !/^Fig\.?\s*\d+/i.test(plain)) continue;
figParaCount += 1;
const hasDrawing = /<w:drawing/.test(p);
const colorVals = [...p.matchAll(/w:color\s+w:val="([^"]+)"/g)].map((x) => x[1]);
// next non-empty para
let nextPlain = '';
for (let j = i + 1; j < Math.min(i + 5, paras.length); j++) {
const nt = [...paras[j].matchAll(/<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>/g)]
.map((x) => x[1])
.join('')
.replace(/&amp;/g, '&')
.trim();
if (nt) {
nextPlain = nt.slice(0, 220);
break;
}
}
// prev: look back for drawing-only paragraphs
let prevHasDrawing = false;
for (let j = i - 1; j >= Math.max(0, i - 6); j--) {
if (/<w:drawing/.test(paras[j])) {
prevHasDrawing = true;
break;
}
const pt = [...paras[j].matchAll(/<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>/g)]
.map((x) => x[1])
.join('')
.trim();
if (pt && !/^[AB]$/.test(pt)) break;
}
console.log('---');
console.log('caption:', plain.slice(0, 160));
console.log('colors:', [...new Set(colorVals)]);
console.log('sameParaDrawing:', hasDrawing, 'prevHasDrawing:', prevHasDrawing);
console.log('next:', nextPlain);
}
console.log('figParaCount', figParaCount);
// Check standalone A/B label paragraphs near figures
let abCount = 0;
for (const p of paras) {
const plain = [...p.matchAll(/<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>/g)]
.map((x) => x[1])
.join('')
.trim();
if (/^[AB]$/.test(plain)) abCount += 1;
}
console.log('standalone A/B paras', abCount);

View File

@@ -0,0 +1,29 @@
import FS from 'fs';
import path from 'path';
import { JSDOM } from 'jsdom';
const { window } = new JSDOM('<!DOCTYPE html><html><body></body></html>');
global.window = window;
global.document = window.document;
global.DOMParser = window.DOMParser;
global.Node = window.Node;
global.HTMLElement = window.HTMLElement;
global.FileReader = window.FileReader;
global.Blob = window.Blob;
const { parseWordFigures } = await import('../src/utils/wordFigureImport.js');
const filePath = path.join(
'C:/Users/Administrator/Desktop/IN2026A0418001_final_20260713 1.docx'
);
const buf = FS.readFileSync(filePath);
const result = await parseWordFigures(buf);
console.log('parsedCount', result.parsedCount);
result.figures.forEach((f, idx) => {
console.log('----', idx + 1);
console.log('num', f.figureNumber);
console.log('title', f.title.slice(0, 130));
console.log('note', (f.note || '').slice(0, 150));
console.log('imgs', f.imageCount);
console.log('red', /data-word-caption-color="red"|#d25a5a/i.test(f.titleHtml || ''));
});

View File

@@ -0,0 +1,35 @@
import {
extractFigureNumbersFromText,
paragraphMentionsFigureNumber,
parseFigureNumberFromTitle,
findFirstAmIdForFigureNumber
} from '../src/utils/manuscriptMediaReferences.js';
const samples = [
'Figure 3A and Figure 3B.',
'as shown in Figure 3A and Figure 3B.',
'See Fig. 3 and Fig. 3A',
'as shown in <blue>Figure 3A</blue> and <blue>Figure 3B</blue>.'
];
for (const s of samples) {
console.log(JSON.stringify(s), '=>', extractFigureNumbersFromText(s), 'mentions3', paragraphMentionsFigureNumber(s, 3));
}
console.log(
'title',
parseFigureNumberFromTitle('<span style="color:#d25a5a">Figure 3 Co-occurrence map</span>')
);
console.log(
'find',
findFirstAmIdForFigureNumber(
[
{ am_id: 1, type: 0, content: 'hello' },
{
am_id: 146120,
type: 0,
content: 'as shown in <blue>Figure 3A</blue> and <blue>Figure 3B</blue>.'
},
{ am_id: 3, type: 1, content: '', ami_id: 99 }
],
3
)
);