クリップボードにコピーしているテキストのコードポイントや Unicode/UTF-16 エスケープシーケンスを確認するスクリプト

クリップボードにコピーした以下のような文字

の構成とIDEで使いやすいエスケープシーケーンスを確認します。

#!/usr/bin/env python
import pyperclip
from regex import regex
def graphemes(text):
return regex.findall(r'\X', text)
def str_to_hex_codepoints_str(text):
cps = [r'0x{:X}'.format(ord(c)) for c in text]
return ' '.join(cps)
def str_to_escape_sequence_unicode(text):
ss = graphemes(text)
cps = []
for s in ss:
for c in s:
cp = ord(c)
cps.append(cp)
return ''.join([r'\U{:08X}'.format(cp) for cp in cps])
def str_to_escape_sequence_utf16(text):
ss = graphemes(text)
cps = []
for s in ss:
for c in s:
cp = ord(c)
if cp > 0x10000:
cp -= 0x10000
hsg = cp // 0x400 + 0xd800
lsg = cp % 0x400 + 0xdc00
cps.append(hsg)
cps.append(lsg)
else:
cps.append(cp)
return ''.join([r'\u{:04X}'.format(cp) for cp in cps])
def dump(text):
ss = graphemes(text)
print(text)
print(len(ss))
print(len(text))
print(str_to_escape_sequence_unicode(text))
print(str_to_escape_sequence_utf16(text))
print(str_to_hex_codepoints_str(text))
print()
# ss(str) -> s(str) = cs(list) -> c(str)
for s in ss:
print(s)
print(str_to_escape_sequence_unicode(s))
print(str_to_escape_sequence_utf16(s))
print(str_to_hex_codepoints_str(s))
cs = list(s)
for c in cs:
uni = str_to_escape_sequence_unicode(c)
u16 = str_to_escape_sequence_utf16(c)
hcp = str_to_hex_codepoints_str(c)
print(' ', uni, u16, hcp, f'({c})')
print()
def main():
text = pyperclip.paste().strip()
# text = '🫶💀🔥🥹🫛👨‍👩‍👧‍👦'
dump(text)
if __name__ == '__main__':
main()

Python や Android Studio エディター上で絵文字の編集が捗ると思って作ってみました。

クリップボードは生の絵文字データを保持できるので便利です。

UTF-16 サロゲートペア部分はもっと厳密に計算してもいいかもしれません。



【Python】絵文字を含む Unicode 文字列の文字数をカウントする方法と文字ごとの構成要素


print(len("👩‍👧‍👦"))
# 5

絵文字1文字の長さが「5」。

emoji 👩‍👧‍👦
:family_woman_girl_boy:
code point 0x1F469 0x200D 0x1F467 0x200D 0x1F466
description 👩
:woman:
ZWJ 👧
:girl:
ZWJ 👦
:boy:
Unicode
escape sequence
\U0001F469\U0000200D\U0001F467\U0000200D\U0001F466
UTF-16
escape sequence
\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66
UTF-16
char
0xD83D 0xDC69 0x200D 0xD83D 0xDC67 0x200D 0xD83D 0xDC66
view raw emoji.md hosted with ❤ by GitHub

コードポイントの個数ですね!

 

😀 文字を数える

データを15文字で作ります。

data = ('aAあ'
# Emoji 1.0 😀😍 - Grinning Face | Smiling Face with Heart-Eyes
# https://emojipedia.org/emoji-1.0/
'\U0001F600'
'\U0001F60D'
# 13.0 🥸🐻‍❄️ - Disguised Face | Polar Bear
# https://emojipedia.org/emoji-13.0/
'\U0001F978'
'\U0001F43B\U0000200D\U00002744\U0000FE0F'
# 13.1 😵‍💫🧑🏻‍❤️‍🧑🏼 - Face with Spiral Eyes | Kiss: Light Skin Tone
# https://emojipedia.org/emoji-13.1/
'\U0001F635\U0000200D\U0001F4AB'
'\U0001F9D1\U0001F3FB\U0000200D\U00002764\U0000FE0F\U0000200D\U0001F9D1\U0001F3FC'
# 14.0 🫡🫶 - Saluting Face | Heart Hands
# https://emojipedia.org/emoji-14.0/
'\U0001FAE1'
'\U0001FAF6'
# 15.0 🪿🫛 - Goose | Pea Pod
# https://emojipedia.org/emoji-15.0/
'\U0001FABF'
'\U0001FADB'
# 15.1 🍋🟩🙂↕️ - Lime | Head Shaking Vertically
# https://emojipedia.org/emoji-15.1/
'\U0001F34B\U0000200D\U0001F7E9'
'\U0001F642\U0000200D\U00002195\U0000FE0F')
view raw data.py hosted with ❤ by GitHub

この文字をカウントするのは、re でどう書くのか分からなかったので、ライブラリを使いました。

👉 regex 2022.10.31 on PyPI - Libraries.io hatena-bookmark

Matching a single grapheme \X
The grapheme matcher is supported. It conforms to the Unicode specification at http://www.unicode.org/reports/tr29/.

👉 mrabarnett/mrab-regex hatena-bookmark


print(len(regex.findall(r'\X', data)))
# 15

正しくカウントできました!

 

😀 文字の構成(要素)を確認する

作ったデータを使って、各文字に含まれるコードポイントを確認するスクリプトを書いて実行してみます。


dump(data)

def dump(data):
print(data)
# https://libraries.io/pypi/regex
ss = regex.findall(r'\X', data)
print(ss)
print(len(ss))
print()
for s in ss:
print(s)
cs = list(s)
for c in cs:
cp = ord(c)
print(' ', r'\U{:08X}'.format(cp), r'0x{:X}'.format(cp), f'({c})')
view raw dump.py hosted with ❤ by GitHub
aAあ😀😍🥸🐻‍❄️😵‍💫🧑🏻‍❤️‍🧑🏼🫡🫶🪿🫛🍋🟩🙂↕️
['a', 'A', 'あ', '😀', '😍', '🥸', '🐻\u200d❄️', '😵\u200d💫', '🧑🏻\u200d❤️\u200d🧑🏼', '🫡', '🫶', '\U0001fabf', '\U0001fadb', '🍋\u200d🟩', '🙂\u200d↕️']
15
a
\U00000061 0x61 (a)
A
\U00000041 0x41 (A)
\U00003042 0x3042 (あ)
😀
\U0001F600 0x1F600 (😀)
😍
\U0001F60D 0x1F60D (😍)
🥸
\U0001F978 0x1F978 (🥸)
🐻‍❄️
\U0001F43B 0x1F43B (🐻)
\U0000200D 0x200D (‍)
\U00002744 0x2744 (❄)
\U0000FE0F 0xFE0F (️)
😵‍💫
\U0001F635 0x1F635 (😵)
\U0000200D 0x200D (‍)
\U0001F4AB 0x1F4AB (💫)
🧑🏻‍❤️‍🧑🏼
\U0001F9D1 0x1F9D1 (🧑)
\U0001F3FB 0x1F3FB (🏻)
\U0000200D 0x200D (‍)
\U00002764 0x2764 (❤)
\U0000FE0F 0xFE0F (️)
\U0000200D 0x200D (‍)
\U0001F9D1 0x1F9D1 (🧑)
\U0001F3FC 0x1F3FC (🏼)
🫡
\U0001FAE1 0x1FAE1 (🫡)
🫶
\U0001FAF6 0x1FAF6 (🫶)
🪿
\U0001FABF 0x1FABF (🪿)
🫛
\U0001FADB 0x1FADB (🫛)
🍋🟩
\U0001F34B 0x1F34B (🍋)
\U0000200D 0x200D (‍)
\U0001F7E9 0x1F7E9 (🟩)
🙂↕️
\U0001F642 0x1F642 (🙂)
\U0000200D 0x200D (‍)
\U00002195 0x2195 (↕)
\U0000FE0F 0xFE0F (️)

【Python】絵文字を含む Unicode 文字列の文字数をカウントする方法と文字ごとの構成要素

うまくそれぞれの絵文字の構成要素が分かるようになりました。

 

😀 まとめ

Python ではコードポイントをそのまま \UXXXXXXXX の形式で書けるので便利。

Kotlin では以下から。



【Kotlin】絵文字を含む Unicode 文字列の文字数をカウントする方法と文字ごとの構成要素

絵文字 は AndroidStudio 上で扱いづらいですよね。

例えば、


"😀😍"

という2つの絵文字は、


"\uD83E\uDD78" + "\uD83D\uDC3B\u200D\u2744\uFE0F"

と書く方が編集しやすいです。(しかし分かりづらい。)

絵文字をコピーして Android Studio 上でペーストすると、

\uXXXX\uXXXX

のような「UTF-16 エスケープシーケンス」に置き換わりますよね。

(しかし置き換わらない場合もある。)

👉 Python vs Kotlin Unicode Escape Sequence (エスケープシーケンス) の記述 hatena-bookmark

 

😀 文字を数える

検証するために文字列データを作ります。

絵文字も新旧バージョンのものを含めます。

15文字です。

val data = "aAあ" +
// Emoji 1.0 😀😍 - Grinning Face | Smiling Face with Heart-Eyes
// https://emojipedia.org/emoji-1.0/
"\uD83D\uDE00" + "\uD83D\uDE0D" +
// 13.0 🥸🐻‍❄️ - Disguised Face | Polar Bear
// https://emojipedia.org/emoji-13.0/
"\uD83E\uDD78" + "\uD83D\uDC3B\u200D\u2744\uFE0F" +
// 13.1 😵‍💫🧑🏻‍❤️‍🧑🏼 - Face with Spiral Eyes | Kiss: Light Skin Tone
// https://emojipedia.org/emoji-13.1/
"\uD83D\uDE35\u200D\uD83D\uDCAB" + "\uD83E\uDDD1\uD83C\uDFFB\u200D\u2764\uFE0F\u200D\uD83E\uDDD1\uD83C\uDFFC" +
// 14.0 🫡🫶 - Saluting Face | Heart Hands
// https://emojipedia.org/emoji-14.0/
"\uD83E\uDEE1" + "\uD83E\uDEF6" +
// 15.0 🪿🫛 - Goose | Pea Pod
// https://emojipedia.org/emoji-15.0/
"\uD83E\uDEBF" + "\uD83E\uDEDB" +
// 15.1 🍋🟩🙂↕️ - Lime | Head Shaking Vertically
// https://emojipedia.org/emoji-15.1/
"\uD83C\uDF4B\u200D\uD83D\uDFE9" + "\uD83D\uDE42\u200D\u2195\uFE0F"
view raw data.kt hosted with ❤ by GitHub

比較的に新しい Unicode Emoji 15.1 の2つの絵文字は、今現在、まともに見たことがありません。

このデータの文字数をカウントするのは、


println(Regex("\\X").findAll(data).count())
// 15

というかんじでしょうか。

Unicode extended grapheme clusters are supported by the grapheme cluster matcher \X.

👉 2.2 Extended Grapheme Clusters and Character Classes with Strings hatena-bookmark
👉 Unicord support - Pattern  |  Android Developers hatena-bookmark

強く開発環境に影響されると思いますので、各バージョン安定版最新に更新して揃えておくべきでしょう。


android {
  // ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_17
    targetCompatibility JavaVersion.VERSION_17
  }

  kotlinOptions {
    jvmTarget = JavaVersion.VERSION_17.toString()
  }
}

 

😀 文字の構成(要素)を確認する

人間が見ている文字は内部的には複数の要素から構成されています。

emoji 👩‍👧‍👦
:family_woman_girl_boy:
code point 0x1F469 0x200D 0x1F467 0x200D 0x1F466
description 👩
:woman:
ZWJ 👧
:girl:
ZWJ 👦
:boy:
Unicode
escape sequence
\U0001F469\U0000200D\U0001F467\U0000200D\U0001F466
UTF-16
escape sequence
\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66
UTF-16
char
0xD83D 0xDC69 0x200D 0xD83D 0xDC67 0x200D 0xD83D 0xDC66
view raw emoji.md hosted with ❤ by GitHub

絵文字周りで開発している方にはどこかで見たようなすごく便利な表です。

文字列を渡せば簡単に確認できるようにメソッドにしておきます。

先ほどの1文字ごとに分割したあとにその内部を要素に分割して表示します。

fun dump(data: String) {
println(data)
val ss = Regex("\\X").findAll(data)
.map { match -> match.value }
.toList()
println(ss)
println(ss.size)
println()
ss.forEach { s ->
println("$s ${s.toUtf16EscapeSequence()}")
s.codePoints().forEach { cp ->
val hcp = "0x%X".format(cp)
val c = Character.toChars(cp).joinToString("")
println(" ${cp.toUtf16EscapeSequence()} $hcp ($c)")
}
}
}
view raw dump.kt hosted with ❤ by GitHub
fun String.toUtf16EscapeSequence(): String {
// String.chars() returns IntStream under 0x10000 Int only,
// no need to consider utf-16 surrogate pair
return this.chars()
.asSequence()
.joinToString("") { i -> "\\u%04X".format(i) }
}
fun Int.toUtf16EscapeSequence(): String {
val cp = this // code point
return Character.toChars(cp).joinToString("")
.toUtf16EscapeSequence()
}

data は、先述の15文字を使ってみました。


dump(data)

AndroidStudio のデバッグウィンドウやターミナルでも表示できない絵文字はありますが、内部的には問題なく処理できてるように見えます。

 

😀 まとめ

今回、新しくはっきり認識できたことは、

kotlin の String.length は、


その文字(列)の Char (/uXXXX) の数を表している。

ということ。コードポイントの数ではない。

Char は、コードポイントを 内部的な UTF-16サロゲートペア 分割されたあとの要素。

emoji 👩‍👧‍👦
:family_woman_girl_boy:
code point 0x1F469 0x200D 0x1F467 0x200D 0x1F466
description 👩
:woman:
ZWJ 👧
:girl:
ZWJ 👦
:boy:
Unicode
escape sequence
\U0001F469\U0000200D\U0001F467\U0000200D\U0001F466
UTF-16
escape sequence
\uD83D\uDC69\u200D\uD83D\uDC67\u200D\uD83D\uDC66
UTF-16
char
0xD83D 0xDC69 0x200D 0xD83D 0xDC67 0x200D 0xD83D 0xDC66
view raw emoji.md hosted with ❤ by GitHub


println("👩‍👧‍👦".length)
// 8

👉 kotlin/Char.kt at 924c28507067cbfbf78a6509ea89eabe496e34ca · JetBrains/kotlin · GitHub hatena-bookmark



👉 絵文字が意図しない白黒で表示される ➡️ - Unicode Variation Selector hatena-bookmark