76 lines
2.1 KiB
TypeScript
76 lines
2.1 KiB
TypeScript
/**
|
||
* 文本提取 - 标题、价格、参数表、描述
|
||
* 从 docs/extension/plan.md §6.6 移植(简化版)
|
||
*/
|
||
import type { SiteProfile, TextRule } from '../profiles/types';
|
||
|
||
export interface TextMaterial {
|
||
kind: TextRule['kind'];
|
||
content: string;
|
||
pairs?: Array<{ key: string; value: string }>; // table 模式的结构化结果
|
||
}
|
||
|
||
function clean(s: string): string {
|
||
return s.replace(/\s+/g, ' ').trim();
|
||
}
|
||
|
||
function extractOne(rule: TextRule): TextMaterial | null {
|
||
for (const sel of rule.selectors) {
|
||
let nodes: NodeListOf<Element>;
|
||
try {
|
||
nodes = document.querySelectorAll(sel);
|
||
} catch {
|
||
continue;
|
||
}
|
||
if (!nodes.length) continue;
|
||
|
||
// table 模式:参数表
|
||
if (rule.extract === 'table') {
|
||
const pairs: Array<{ key: string; value: string }> = [];
|
||
nodes.forEach(row => {
|
||
const k = clean(row.querySelector(rule.tableKeySelector ?? '')?.textContent ?? '');
|
||
const v = clean(row.querySelector(rule.tableValueSelector ?? '')?.textContent ?? '');
|
||
if (k && v) pairs.push({ key: k.replace(/[::]$/, ''), value: v });
|
||
});
|
||
if (pairs.length) {
|
||
return {
|
||
kind: rule.kind,
|
||
content: pairs.map(p => `${p.key}: ${p.value}`).join('\n'),
|
||
pairs
|
||
};
|
||
}
|
||
continue;
|
||
}
|
||
|
||
// join 模式:标题被拆成多个 span
|
||
if (rule.extract === 'join') {
|
||
let text = '';
|
||
nodes.forEach(n => { text += n.textContent ?? ''; });
|
||
text = clean(text);
|
||
if (text) return { kind: rule.kind, content: text };
|
||
continue;
|
||
}
|
||
|
||
// first 模式:只取第一个
|
||
const first = clean(nodes[0].textContent ?? '');
|
||
if (first) return { kind: rule.kind, content: first };
|
||
}
|
||
return null;
|
||
}
|
||
|
||
export function collectTexts(profile: SiteProfile): {
|
||
materials: TextMaterial[];
|
||
missingRequired: string[];
|
||
} {
|
||
const materials: TextMaterial[] = [];
|
||
const missingRequired: string[] = [];
|
||
|
||
for (const rule of profile.textRules) {
|
||
const m = extractOne(rule);
|
||
if (m) materials.push(m);
|
||
else if (rule.required) missingRequired.push(rule.kind);
|
||
}
|
||
|
||
return { materials, missingRequired };
|
||
}
|