macOS の標準アプリで日本語テキストを弄っていると何故か合成用の濁点(U+3099)や半濁点(U+309A)が入り込んできます。合成用の濁点や半濁点で文字化けする相手向けに変換が必要になったので作ってみました。
HTML+JavaScript版
濁点(U+3099)と半濁点(U+309A)は解析部分(出力と同じ文字列)で色を付けています。
See the Pen 合成用(半)濁点文字[U+3099,U+309A]の変換 by Ikiuo (@ikiuo) on CodePen.
動作確認は Google Chrome バージョン: 108.0.5359.98 (macOS版)で行っています。
dakuten.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>合成用の濁点と半濁点を排除する</title>
</head>
<body>
<h1>合成用の濁点と半濁点を排除する</h1>
<table border="1">
<tr>
<th>入力</th>
<td>
<textarea id="inp" rows="12" cols="80" oninput="onInput()"></textarea>
</td>
</tr>
<tr><th>出力</th><td id="out"></td></tr>
<tr><th>解析</th><td id="chk"></td></tr>
</table>
<script>
const dakuten = {
'ヽ':'ヾ', 'ゝ':'ゞ',
'か':'が', 'き':'ぎ', 'く':'ぐ', 'け':'げ', 'こ':'ご',
'さ':'ざ', 'し':'じ', 'す':'ず', 'せ':'ぜ', 'そ':'ぞ',
'た':'だ', 'ち':'ぢ', 'つ':'づ', 'て':'で', 'と':'ど',
'は':'ば', 'ひ':'び', 'ふ':'ぶ', 'へ':'べ', 'ほ':'ぼ',
'ウ':'ヴ',
'カ':'ガ', 'キ':'ギ', 'ク':'グ', 'ケ':'ゲ', 'コ':'ゴ',
'サ':'ザ', 'シ':'ジ', 'ス':'ズ', 'セ':'ゼ', 'ソ':'ゾ',
'タ':'ダ', 'チ':'ヂ', 'ツ':'ヅ', 'テ':'デ', 'ト':'ド',
'ハ':'バ', 'ヒ':'ビ', 'フ':'ブ', 'ヘ':'ベ', 'ホ':'ボ',
'〱':'〲', '〳':'〴',
'う':'ゔ',
'ワ':'ヷ', 'ヰ':'ヸ', 'ヱ':'ヹ', 'ヲ':'ヺ',
}
const handakuten = {
'は':'ぱ', 'ひ':'ぴ', 'ふ':'ぷ', 'へ':'ぺ', 'ほ':'ぽ',
'ハ':'パ', 'ヒ':'ピ', 'フ':'プ', 'ヘ':'ペ', 'ホ':'ポ',
}
function onInput()
{
const tagInp = document.getElementById('inp');
const tagOut = document.getElementById('out');
let inp = tagInp.value;
let out = '';
let chk = [];
for (let i = 0; i < inp.length; i++) {
const code = inp.charCodeAt(i);
if (code == 0x3099) {
let addchr = '゛'
if (i) {
const pre = dakuten[inp[i - 1]];
if (pre) {
out = out.slice(0, out.length - 1);
chk.pop();
addchr = pre;
}
}
chk.push(1);
out += addchr
continue;
}
if (code == 0x309a) {
let addchr = '゜';
if (i) {
const pre = handakuten[inp[i - 1]];
if (pre) {
out = out.slice(0, out.length - 1);
chk.pop();
addchr = pre;
}
}
chk.push(2);
out += addchr;
continue;
}
out += inp[i];
chk.push(0);
}
tagOut.innerText = out;
const tagChk = document.getElementById('chk');
while (tagChk.firstChild)
tagChk.removeChild(tagChk.firstChild);
const appendSpan = function(msg, flg) {
const span = document.createElement('span');
if (flg) {
span.style.setProperty('color', '#000');
span.style.setProperty('background-color', '#f88');
}
span.innerText = msg;
tagChk.append(span);
}
let last = 0;
for (let i = 1; i < out.length; i++) {
if (chk[i - 1] == chk[i])
continue;
appendSpan(out.slice(last, i), chk[i - 1]);
last = i;
}
if (last < out.length)
appendSpan(out.slice(last, out.length), chk[out.length - 1]);
}
</script>
</body>
</html>
Python 版
dakuten.py
#!/usr/bin/env python3
import argparse
import sys
parser = argparse.ArgumentParser()
parser.add_argument('-N', '--CHR', action='store_true', default=False, help='濁点と半濁点の合成用文字を通常文字にする')
parser.add_argument('-J', '--JIS', action='store_true', default=False, help='JIS X 0208 を濁点・半濁点変換の対象にする')
parser.add_argument('INPUT', help='入力ファイル (\'-\' で標準入力)')
parser.add_argument('OUTPUT', help='出力ファイル (\'-\' で標準出力)')
args = parser.parse_args()
onlychar = args.CHR
inpfile = args.INPUT
outfile = args.OUTPUT
ifp = sys.stdin if inpfile == '-' else open(inpfile, 'r')
ofp = sys.stdout if outfile == '-' else open(outfile, 'w')
# JIS X 0208
dakuten_jisx0208 = {
'ヽ': 'ヾ', 'ゝ': 'ゞ',
'か': 'が', 'き': 'ぎ', 'く': 'ぐ', 'け': 'げ', 'こ': 'ご',
'さ': 'ざ', 'し': 'じ', 'す': 'ず', 'せ': 'ぜ', 'そ': 'ぞ',
'た': 'だ', 'ち': 'ぢ', 'つ': 'づ', 'て': 'で', 'と': 'ど',
'は': 'ば', 'ひ': 'び', 'ふ': 'ぶ', 'へ': 'べ', 'ほ': 'ぼ',
'ウ': 'ヴ',
'カ': 'ガ', 'キ': 'ギ', 'ク': 'グ', 'ケ': 'ゲ', 'ケ': 'ゲ',
'サ': 'ザ', 'シ': 'ジ', 'ス': 'ズ', 'セ': 'ゼ', 'ソ': 'ゾ',
'タ': 'ダ', 'チ': 'ヂ', 'ツ': 'ヅ', 'テ': 'デ', 'ト': 'ド',
'ハ': 'バ', 'ヒ': 'ビ', 'フ': 'ブ', 'ヘ': 'ベ', 'ホ': 'ボ',
}
# CJK Miscellaneous
dakuten_unicode = {
'〱': '〲', '〳': '〴',
'う': 'ゔ',
'ワ': 'ヷ', 'ヰ': 'ヸ', 'ヱ': 'ヹ', 'ヲ': 'ヺ',
}
dakuten = dakuten_jisx0208
if not args.JIS:
for k in dakuten_unicode:
dakuten[k] = dakuten_unicode[k]
handakuten = {
'は': 'ぱ', 'ひ': 'ぴ', 'ふ': 'ぷ', 'へ': 'ぺ', 'ほ': 'ぽ',
'ハ': 'パ', 'ヒ': 'ピ', 'フ': 'プ', 'ヘ': 'ペ', 'ホ': 'ポ',
}
for line in ifp.readlines():
new_line = ''
for ch in line:
code = ord(ch)
if code == 0x3099:
if not onlychar and new_line and new_line[-1] in dakuten:
new_line = new_line[:-1] + dakuten[new_line[-1]]
continue
new_line += '゛'
continue
if code == 0x309a:
if not onlychar and new_line and new_line[-1] in handakuten:
new_line = new_line[:-1] + handakuten[new_line[-1]]
continue
new_line += '゜'
continue
new_line += ch
ofp.write(new_line)
合成用濁点と半濁点を使ったサンプル
sample.txt
ヾ ゞ
が ぎ ぐ げ ご
ざ じ ず ぜ ぞ
だ ぢ づ で ど
ば び ぶ べ ぼ
ヴ
ガ ギ グ ゲ ゲ
ザ ジ ズ ゼ ゾ
ダ ヂ ヅ デ ド
バ ビ ブ ベ ボ
〱゙ 〳゙
ゔ
ヷ ヸ ヹ ヺ
ぱ ぴ ぷ ぺ ぽ
パ ピ プ ペ ポ
テスト
実行結果
$ python3 dakuten.py sample.txt -
ヾ ゞ
が ぎ ぐ げ ご
ざ じ ず ぜ ぞ
だ ぢ づ で ど
ば び ぶ べ ぼ
ヴ
ガ ギ グ ゲ ゲ
ザ ジ ズ ゼ ゾ
ダ ヂ ヅ デ ド
バ ビ ブ ベ ボ
〲 〴
ゔ
ヷ ヸ ヹ ヺ
ぱ ぴ ぷ ぺ ぽ
パ ピ プ ペ ポ
合成文字の通常文字化
$ python3 dakuten.py sample.txt - -N
ヽ゛ ゝ゛
か゛ き゛ く゛ け゛ こ゛
さ゛ し゛ す゛ せ゛ そ゛
た゛ ち゛ つ゛ て゛ と゛
は゛ ひ゛ ふ゛ へ゛ ほ゛
ウ゛
カ゛ キ゛ ク゛ ケ゛ ケ゛
サ゛ シ゛ ス゛ セ゛ ソ゛
タ゛ チ゛ ツ゛ テ゛ ト゛
ハ゛ ヒ゛ フ゛ ヘ゛ ホ゛
〱゛ 〳゛
う゛
ワ゛ ヰ゛ ヱ゛ ヲ゛
は゜ ひ゜ ふ゜ へ゜ ほ゜
ハ゜ ヒ゜ フ゜ ヘ゜ ホ゜
JIS X 0208 のみ
$ python3 dakuten.py sample.txt - -J
ヾ ゞ
が ぎ ぐ げ ご
ざ じ ず ぜ ぞ
だ ぢ づ で ど
ば び ぶ べ ぼ
ヴ
ガ ギ グ ゲ ゲ
ザ ジ ズ ゼ ゾ
ダ ヂ ヅ デ ド
バ ビ ブ ベ ボ
〱゛ 〳゛
う゛
ワ゛ ヰ゛ ヱ゛ ヲ゛
ぱ ぴ ぷ ぺ ぽ
パ ピ プ ペ ポ
変換する文字が足りていないかもしれません…