初歩のシェルスクリプトで遊ぶ[ぬかみそフォントの制作サポート(11)]

HTMLの数値文字参照を文字に変えたり整えたり

cat character.html | nkf --numchar-input --no-best-fit-chars -x

数値参照を文字に変えるなら、nkfがいいのは分かるのだけれど。
確かnkfUbuntuのインストールディスクに入ってなかったような、入ってたような。まぁ理由付けは何でもいいんですが、文字に変えたいっていうか、10進数で書いていた数値参照を16進数に変えたいな、と。

珍しく、dashよりbashが圧倒的に速い。大抵は、なんだかんだでdashのほうが速いんだけど、今回は難しいかなぁ。
dashのスクリプト、この方法だと、コードを追加して複雑にして、高速化しても、5倍くらいにしかできませんでした。今回は、遅いのを受け入れたほうが良い気がする。

#!/bin/sh
# ==================================================================================
# ■ HTML数値参照を文字に変換_dash
# 
# ・ファイル末端の改行が無いファイルにも改行が付加される
# 
# ▼ ニセの数値参照に注意
# 
# 「数値参照」「ニセ数値参照」「数値参照」
# 「行頭」「ニセ数値参照」「行末」
# 
# &#304f;  16進数指定の「x」が抜けて、10進数指定で16進数になっている例
# &#x304g; 「f」のつもりが「g」を叩いて、16進数の範囲を超える例
# 
# sedの正規表現はニセ数値参照を排除できるが、
# caseでは数値参照に挟まれたタイプミス数値参照や、行に一つだけの偽物は排除できない。
# 1行に数値参照ひとつだけ、に整形するのが理想だが、それは困難。
# printfで16進数に変換できるか試して、printfがエラーなら無変換で出力する。
# 
# 
# 
# 
# ==================================================================================



# 16進数8桁を文字に変換する
hexToCharSh(){ (

B3="${1%??????}"
B2="${1#??}"
B2="${B2%????}"
B1="${1#????}"
B1="${B1%??}"
B0="${1#??????}"
B3=`printf '%03o' 0x${B3}`
B2=`printf '%03o' 0x${B2}`
B1=`printf '%03o' 0x${B1}`
B0=`printf '%03o' 0x${B0}`

# utf8文字で出力する。改行は付加されない。
printf '\'${B3}'\'${B2}'\'${B1}'\'${B0}  | iconv -f utf32be -t utf8

# サブシェルの括弧は外すことができる。多少は高速化する。
# 括弧を外すときはexitを削除する。
exit 0
) }


while IFS='' read -r Line || test -n "${Line}" ; do

# 数値参照の前後に改行を挿入する
printf '%s\n' "${Line}" |
sed -n -e '#n

# 数値参照が存在するであろう行を通す(省略可能)
/&#.[0123456789abcdefABCDEF]*;/{

# 10進数は、8進数扱いされないように先頭ゼロを削除する
s/\(&#\)0*\([0123456789]*;\)/\1\2/g

# 改行付加
s/&#[0123456789]*;/\n&\n/g
s/&#[xX][0123456789abcdefABCDEF]*;/\n&\n/g
}
p
' |
while IFS='' read -r linePart ; do

# ----------------------------------------------------------------
# 行内分割処理
# ----------------------------------------------------------------


case ${linePart} in
# -------------------------------
( '&#'[xX]*';' ) # 16進数数値参照
# -------------------------------
linePartNum="${linePart#???}"
linePartNum="${linePartNum%?}"

# ニセ数値参照対策
# もともと「(行頭)&#??????;(行末)」だった文字列
if linePartNum=`printf '%08x' "0x${linePartNum}" 2>/dev/null` ; then
hexToCharSh ${linePartNum}

else
printf '%s' "${linePart}"

fi

;;
# -------------------------------
( '&#'*';' ) # 10進数数値参照
# -------------------------------
linePartNum="${linePart#??}"
linePartNum="${linePartNum%?}"

if linePartNum=`printf '%08x' "${linePartNum}" 2>/dev/null` ; then
hexToCharSh ${linePartNum}

else
printf '%s' "${linePart}"

fi


;;
# -------------------------------
( * ) # 数値参照以外
# -------------------------------
# 改行せずに文字だけを出力する
printf '%s' "${linePart}"

;;
esac
done
# -------------------------------
# 行末改行
# -------------------------------
echo

done


exit 0
#!/bin/bash
# ==================================================================================
# ■ HTML数値参照を文字に変換_bash専用
# 
# 
# 
# 
# 
# ==================================================================================



# 数値参照の前後に改行ではなくnull(\o000)を挿入する
sed -n -e '#n

/&#.[0123456789abcdefABCDEF]*;/{
s/\(&#\)0*\([0123456789]*;\)/\1\2/g
s/&#[0123456789]*;/\o000&\o000/g
s/&#[xX][0123456789abcdefABCDEF]*;/\o000&\o000/g
}
p
' | {
# ----------------------------------------------------------------
# 行内分割処理
# ----------------------------------------------------------------
# nullでreadに読み込む文字列を分割。
# シェル変数 nullSepPart に、元テキストの改行を含めて読み込む。
# 数値参照のみ独立行に分離する。
# readで最終行を読み込むための「test -n」を付加
while IFS='' read -r -d $'\000' nullSepPart  || test -n "${nullSepPart}" ; do

case ${nullSepPart} in
# -------------------------------
( '&#'[xX]*';' ) # 16進数数値参照
# -------------------------------
nullSepPartNum="${nullSepPart#???}"
nullSepPartNum="${nullSepPartNum%?}"
if printf -v nullSepPartNum '%x' "0x${nullSepPartNum}" 2>/dev/null ; then
printf '\U'"${nullSepPartNum}"
else
printf '%s' "${nullSepPart}"
fi

;;
# -------------------------------
( '&#'*';' ) # 10進数数値参照
# -------------------------------
nullSepPartNum="${nullSepPart#??}"
nullSepPartNum="${nullSepPartNum%?}"
if printf -v nullSepPartNum '%x' "${nullSepPartNum}" 2>/dev/null ; then
printf '\U'"${nullSepPartNum}"
else
printf '%s' "${nullSepPart}"
fi


;;
# -------------------------------
( * ) # 数値参照以外
# -------------------------------
# 改行せずに文字だけを出力する
printf '%s' "${nullSepPart}"

;;
esac


done
}
#!/bin/sh
# ==================================================================================
# ■ HTML数値参照を16進数小文字に統一_dash
# 
# ・ファイル末端の改行が無いファイルにも改行が付加される
# 
# 
# 
# ==================================================================================


normarizeDecSh(){ (
# ${1} 入力10進数

  if test "${1}" -ge        0 && test "${1}" -le     255 ; then #     ff
printf '&#x%02x;' "${1}"

elif test "${1}" -ge      256 && test "${1}" -le   65535 ; then #   ffff
printf '&#x%04x;' "${1}"

elif test "${1}" -ge    65535 && test "${1}" -le 1114111 ; then # 10ffff
printf '&#x%06x;' "${1}"

else
exit 1

fi

) }



# ----------------------------------------------------------------------
# 行単位処理
# ----------------------------------------------------------------------
while IFS='' read -r Line || test -n "${Line}" ; do

# 数値参照の前後に改行を挿入する
printf '%s\n' "${Line}" |
sed -n -e '#n

# 数値参照が存在するであろう行を通す(省略可能)
/&#.[0123456789abcdefABCDEF]*;/{

# 10進数は、8進数扱いされないように先頭ゼロを削除する
s/\(&#\)0*\([0123456789]*;\)/\1\2/g

# 改行付加
s/&#[0123456789]*;/\n&\n/g
s/&#[xX][0123456789abcdefABCDEF]*;/\n&\n/g
}
p
' |
while IFS='' read -r linePart ; do

# ----------------------------------------------------------------
# 行内分割処理
# ----------------------------------------------------------------


case ${linePart} in
# -------------------------------
( '&#'[xX]*';' ) # 16進数数値参照
# -------------------------------
linePartNum="${linePart#???}"
linePartNum="${linePartNum%?}"

# ニセ数値参照対策
# もともと「(行頭)&#??????;(行末)」だった文字列
if linePartNum=`printf '%d' "0x${linePartNum}" 2>/dev/null` ; then
#printf '&#x%s;' "${linePartNum}"
normarizeDecSh "${linePartNum}"

else
printf '%s' "${linePart}"

fi

;;
# -------------------------------
( '&#'*';' ) # 10進数数値参照
# -------------------------------
linePartNum="${linePart#??}"
linePartNum="${linePartNum%?}"

if linePartNum=`printf '%d' "${linePartNum}" 2>/dev/null` ; then
#printf '&#x%s;' "${linePartNum}"
normarizeDecSh "${linePartNum}"

else
printf '%s' "${linePart}"

fi


;;
# -------------------------------
( * ) # 数値参照以外
# -------------------------------
# 改行せずに文字だけを出力する
printf '%s' "${linePart}"

;;
esac
done
# -------------------------------
# 行末改行
# -------------------------------
echo

done


exit 0