遊戯王カードのテキストをフォーマットするPopupMenu
OCRで読むと丸囲みが保持されないことが多い
①②の効果は
①の方法による
code:script.js
import { ygoFormatText } from "/api/code/10cho/遊戯王カードのテキストをフォーマットするPopupMenu/ygoFormatText.js";
scrapbox.PopupMenu.addButton({
title: 'ygoFormat',
onClick: text => {
const result = ygoFormatText(text);
if (result === text) return;
return result;
}
});
code:ygoFormatText.js
const replacementRules = [
// 優先度の高い順に記述(priority 大 → 先適用)
{ priority: 100, regex: /自分フィールド/g, replacement: '自場' },
{ priority: 100, regex: /相手フィールド/g, replacement: '相手の場' },
{ priority: 90, regex: /フィールド(?!魔法|ゾーン)/g, replacement: '場' },
{ priority: 90, regex: /エクストラデッキ/g, replacement: 'EXデッキ' },
{ priority: 80, regex: /シンクロ素材/g, replacement: 'S素材' },
{ priority: 80, regex: /エクシーズ素材/g, replacement: 'X素材' },
{ priority: 80, regex: /リンク素材/g, replacement: 'L素材' },
{ priority: 80, regex: /ペンデュラム効果/g, replacement: 'P効果' },
{ priority: 80, regex: /ペンデュラムスケール/g, replacement: 'Pスケール' },
// 召喚 / モンスター種別
{ priority: 70, regex: /特殊召喚/g, replacement: 'SS' },
{ priority: 70, regex: /アドバンス召喚/g, replacement: 'A召喚' },
{ priority: 70, regex: /F召喚/g, replacement: '融合召喚' },
{ priority: 70, regex: /Fモンスター/g, replacement: '融合モンスター' },
{ priority: 70, regex: /シンクロモンスター/g, replacement: 'Sモンスター' },
{ priority: 70, regex: /エクシーズモンスター/g, replacement: 'Xモンスター' },
{ priority: 70, regex: /リンクモンスター/g, replacement: 'Lモンスター' },
{ priority: 60, regex: /対象として/g, replacement: '対象にとって' },
// 記号・繋ぎ等
{ priority: 50, regex: /(・|·)/g, replacement: '-' },
{ priority: 40, regex: /(●|カードテキスト|。$)/g, replacement: '' },
// 効果番号
{ priority: 30, regex: /①1::/g, replacement: '1. ' }, { priority: 30, regex: /②2::/g, replacement: '2. ' }, { priority: 30, regex: /③3::/g, replacement: '3. ' }, { priority: 30, regex: /④4::/g, replacement: '4. ' }, { priority: 30, regex: /⑤5::/g, replacement: '5. ' }, { priority: 30, regex: /⑥6::/g, replacement: '6. ' }, // 全角記号→半角
{ priority: 20, regex: /+/g, replacement: '+' },
{ priority: 20, regex: ///g, replacement: '/' },
// 全角数字
{ priority: 10, regex: /0/g, replacement: '0' },
{ priority: 10, regex: /1/g, replacement: '1' },
{ priority: 10, regex: /2/g, replacement: '2' },
{ priority: 10, regex: /3/g, replacement: '3' },
{ priority: 10, regex: /4/g, replacement: '4' },
{ priority: 10, regex: /5/g, replacement: '5' },
{ priority: 10, regex: /6/g, replacement: '6' },
{ priority: 10, regex: /7/g, replacement: '7' },
{ priority: 10, regex: /8/g, replacement: '8' },
{ priority: 10, regex: /9/g, replacement: '9' }
];
// 「」で囲まれた箇所をまとめて ... にする
function convertQuotedNames(line) {
return line.replace(/「(^」\n+)」/g, (m, name) => { return '' + name + '';
});
}
// 保護対象([] または ...)をトークン化して store に保存
function tokenizeProtectedSegments(line, store) {
return line.replace(/(\.*?\)|(.*?)/g, (match) => { const token = __PROT_${store.length}__;
store.push(match);
return token;
});
}
// トークンを元に戻す
function restoreProtectedSegments(line, store) {
return line.replace(/__PROT_(\d+)__/g, (m, idx) => {
const i = Number(idx);
return storei !== undefined ? storei : m; });
}
// 1 行に対して置換ルールを適用する
function applyReplacementsToLine(line, rules) {
const sorted = rules.slice().sort((a, b) => b.priority - a.priority);
let out = line;
for (const r of sorted) {
out = out.replace(r.regex, r.replacement);
}
return out;
}
// 囲み文字連続箇所に区切りを入れる
function insertSeparators(line) {
return line.replace(/(\]|+)(?=(\[|+))/g, '$1-');
}
// 全文をフォーマットする関数
export function ygoFormatText(text) {
const lines = text.split(/\n/);
const out = lines.map(line => {
// 先に「」→... に変換してからトークン化する
line = convertQuotedNames(line);
// 保護対象をトークン化
const store = [];
const tokenized = tokenizeProtectedSegments(line, store);
// 置換適用
const replaced = applyReplacementsToLine(tokenized, replacementRules);
// トークン復元
const restored = restoreProtectedSegments(replaced, store);
// 囲み文字連続箇所に区切りを入れる
return insertSeparators(restored);
});
return out.join('\n');
}