<%* // --------------------------------------------------------------- // 1. 初始化與設定 // --------------------------------------------------------------- const HKBU_SEARCH_URL = “https://sys01.lib.hkbu.edu.hk/cmed/cmfid/index.php”; const HKBU_DETAIL_BASE = “https://sys01.lib.hkbu.edu.hk/cmed/cmfid/detail.php”; const CMU_DB_PATH = “System/CMU_Offline_Data_Map.json”;
let cmuDB = {};
try {
const dbFile = tp.file.find_tfile(CMU_DB_PATH);
if (dbFile) {
const content = await app.vault.read(dbFile);
cmuDB = JSON.parse(content);
} else {
new Notice(⚠️ 找不到 ${CMU_DB_PATH});
}
} catch (e) {
new Notice(⚠️ 讀取 CMU 資料庫失敗: ${e.message});
}
function cleanText(text) {
if (!text) return "";
return text.trim()
.replace(/\s+/g, "")
.replace(/(.)/g, "")
.replace(/【.?】/g, "")
.replace(/各等分/g, "")
.replace(/[\d一二三四五六七八九十半]+[兩錢分斤g克]/g, "");
}
let noteTitle = tp.file.title; let finalOutput = ""; let useFetchedData = false;
let data = { source: "", yamlIngredients: "", bodyIngredients: "", rolesStr: "", function: "", indication: "", category: "" };
// --------------------------------------------------------------- // 2. 核心邏輯 // --------------------------------------------------------------- try { if (noteTitle.startsWith(“未命名”) || noteTitle.startsWith(“Untitled”) || !noteTitle) { noteTitle = await tp.system.prompt(“請輸入要查詢的方劑名稱:”); if (noteTitle) await tp.file.rename(noteTitle); }
if (noteTitle) {
// =========================================================
// 階段一:搜尋 HKBU
// =========================================================
new Notice(`[1/2] 搜尋 HKBU 線上資料庫:${noteTitle}...`);
let skipToCMU = false;
try {
const hkbuRes = await requestUrl({ url: `${HKBU_SEARCH_URL}?qry=${encodeURIComponent(noteTitle)}&lang=cht` });
const parser = new DOMParser();
const hkbuDoc = parser.parseFromString(hkbuRes.text, "text/html");
const listTable = hkbuDoc.getElementById("list");
let candidates = [];
if (listTable) {
const rows = listTable.querySelectorAll("tbody tr");
for (let row of rows) {
const cols = row.querySelectorAll("td");
if (cols.length >= 3) {
const link = cols[2].querySelector("a");
if (link) {
const name = link.textContent.trim();
const href = link.getAttribute("href");
const match = href.match(/id=([A-Z0-9]+)/);
if (match) {
candidates.push({
name: name,
id: match[1],
func: cols.length >= 9 ? cols[6].textContent.trim() : "",
indi: cols.length >= 9 ? cols[8].textContent.trim() : ""
});
}
}
}
}
}
let selectedHKBU = null;
const exact = candidates.find(c => c.name === noteTitle);
if (exact) {
selectedHKBU = exact;
new Notice(`HKBU 找到完全匹配:${selectedHKBU.name}`);
} else if (candidates.length > 0) {
const options = candidates.map(c => c.name);
options.push("❌ 以上皆非,嘗試搜尋 CMU 本地資料庫");
const choice = await tp.system.suggester(options, [...candidates, "SKIP"], false, `HKBU 找到近似結果,請選擇:`);
if (choice === "SKIP") skipToCMU = true;
else if (choice) selectedHKBU = choice;
else skipToCMU = true;
} else {
skipToCMU = true;
}
if (selectedHKBU) {
useFetchedData = true;
data.function = selectedHKBU.func;
data.indication = selectedHKBU.indi;
new Notice(`解析 HKBU 詳情...`);
const detailRes = await requestUrl({ url: `${HKBU_DETAIL_BASE}?id=${selectedHKBU.id}&lang=cht` });
const detailDoc = parser.parseFromString(detailRes.text, "text/html");
const allTds = Array.from(detailDoc.querySelectorAll("td"));
// --- 1. 抓取出處 ---
for (let td of allTds) {
if (td.textContent.includes("【出處】") || td.textContent.includes("出處")) {
let raw = td.textContent.replace(/【.*?】/g, "").replace("出處", "").replace(/[::]/, "").trim();
if (raw.length > 0 && raw.length < 50) {
data.source = raw.replace(/[《》]/g, "");
break;
}
}
}
// --- 2. 抓取分類 ---
for (let td of allTds) {
if (td.textContent.includes("分類")) {
let raw = td.textContent.replace(/【.*?】/g, "").replace("分類", "").replace(/[::]/, "").trim();
if (raw.length < 20) data.category = raw;
}
}
// --- 3. 抓取成分 (新邏輯:優先找 #formula_table) ---
let rolesMap = { "君": [], "臣": [], "佐": [], "使": [], "其他": [] };
let allHerbs = [];
let hasRoles = false; // 標記是否真的抓到了君臣佐使
// 嘗試直接用 ID 抓取表格 (這是 HKBU 最準確的組成區塊)
let formulaTable = detailDoc.getElementById("formula_table");
// 如果沒有 ID,嘗試用舊方法找包含「君臣」的表格
if (!formulaTable) {
for (let tbl of detailDoc.querySelectorAll("table")) {
if (tbl.textContent.includes("君") && tbl.textContent.includes("臣") && tbl.textContent.length < 2000) {
formulaTable = tbl;
hasRoles = true; // 這種表格通常有寫君臣佐使
break;
}
}
}
if (formulaTable) {
let currentRole = "其他";
for (let row of formulaTable.querySelectorAll("tr")) {
const cells = Array.from(row.querySelectorAll("td"));
if (cells.length === 0) continue;
let rawTexts = cells.map(c => c.textContent.trim());
if (rawTexts[0].includes("【") || rawTexts[0].includes("功效")) continue;
let herbName = "";
if (hasRoles) {
// 標準君臣佐使表格邏輯
const roleKeywords = ["君", "臣", "佐", "使"];
if (roleKeywords.includes(rawTexts[0])) {
currentRole = rawTexts[0];
if (rawTexts.length > 1) herbName = rawTexts[1];
} else {
if (rawTexts[0].length > 0 && rawTexts[0].length < 8) herbName = rawTexts[0];
}
} else {
// 特殊表格 (回陽救急湯類型):沒有君臣欄位,藥名通常在第2欄 (index 1)
// 但要注意 rowspan 導致的欄位位移
// 簡單暴力法:遍歷所有 cell,只要像藥名就抓
for (let cellText of rawTexts) {
if (cellText.length > 0 && cellText.length <= 4) {
// 排除非藥名的干擾詞
const ignore = ["溫裏", "回陽", "祛寒", "通脈", "補益", "脾胃", "固守", "中州", "辛香", "走竄"];
if (!ignore.some(k => cellText.includes(k))) {
herbName = cellText;
break; // 這一行抓到一個藥名就夠了
}
}
}
}
if (herbName) {
herbName = cleanText(herbName);
const blackList = ["君", "臣", "佐", "使", "功效", "主治", "用法", "【", "】"];
let isClean = true;
for (let bad of blackList) if (herbName.includes(bad)) isClean = false;
if (isClean && herbName.length > 0 && !allHerbs.includes(herbName)) {
allHerbs.push(herbName);
rolesMap[currentRole].push(herbName);
}
}
}
}
// 如果連表格都沒抓到,才去試純文字 fallback (略)
// ...
// 構建輸出
if (allHerbs.length > 0) {
data.yamlIngredients = "\n" + allHerbs.map(h => ` - "[[${h}]]"`).join("\n");
data.bodyIngredients = allHerbs.map(h => `[[${h}]]`).join("、");
if (hasRoles) {
let str = "";
["君", "臣", "佐", "使"].forEach(r => {
str += rolesMap[r].length > 0 ? `\t- ${r}:${rolesMap[r].join("、")}\n` : `\t- ${r}:\n`;
});
data.rolesStr = str;
} else {
// 雖然抓到了成分,但沒分君臣,給出骨架
data.rolesStr = "\t- 君:\n\t- 臣:\n\t- 佐:\n\t- 使:\n";
}
}
}
} catch (e) {
console.error("HKBU Search Error:", e);
skipToCMU = true;
}
// =========================================================
// 階段二:查詢 CMU (本地離線資料庫)
// =========================================================
if (!useFetchedData && skipToCMU) {
// (CMU 邏輯保持不變)
new Notice(`[2/2] 搜尋 CMU 本地資料庫...`);
let record = cmuDB[noteTitle];
if (!record) {
const allKeys = Object.keys(cmuDB);
const matches = allKeys.filter(k => k.includes(noteTitle));
if (matches.length > 0) {
const choice = await tp.system.suggester(matches, matches, false, `CMU 找到 ${matches.length} 個近似結果:`);
if (choice) record = cmuDB[choice];
}
}
if (record) {
useFetchedData = true;
data.source = record.source;
data.function = record.function;
data.indication = record.indication;
if (record.ingredients && record.ingredients.length > 0) {
data.bodyIngredients = record.ingredients.map(h => `[[${h}]]`).join("、");
data.yamlIngredients = "\n" + record.ingredients.map(h => ` - "[[${h}]]"`).join("\n");
}
data.rolesStr = "\t- 君:\n\t- 臣:\n\t- 佐:\n\t- 使:\n";
}
}
}
} catch (e) {
new Notice(錯誤:${e.message});
useFetchedData = false;
}
// --------------------------------------------------------------- // 3. 輸出內容 // --------------------------------------------------------------- if (useFetchedData) { finalOutput = `--- tags:
- 方劑 up:
- “溫裡劑” 成分:{data.source} 功效: ${data.function}
摘要
簡述重點
組成
- 成分:{data.rolesStr}
功效與病機
- 功用:${data.function}
- 主治:${data.indication}
- 病機、辨證:
其他
- 口訣:
- 備註:
病案
問題
{noteTitle}的組成及君臣佐使為? #flashcard ? {data.bodyIngredients} ${data.rolesStr}
{noteTitle}的主治為? #flashcard ? {data.indication} `;
} else { finalOutput = `--- tags:
- 方劑 up:
- “溫裡劑” 成分: 出處: 功效:
摘要
簡述重點
組成
- 成分:
- 君:
- 臣:
- 佐:
- 使:
功效與病機
- 功用:
- 主治:
- 病機、辨證:
其他
- 口訣:
- 備註:
病案
問題
${noteTitle}的組成及君臣佐使為? flashcard
${noteTitle}的主治為? flashcard `; }
tR += finalOutput; %> <% await tp.file.move(“中藥/中醫方劑學/內/溫裡/” + tp.file.title) %>