使用 C# 移除字串中的重音/變音符

更新於 發佈於 閱讀時間約 6 分鐘

0x00 前情提要

最近正在嘗試將 WebApi 改用 GraphQL
並使用了 HotChocolate 套件來自動生成整個 GraphQL 文件
But!
原以為一切應該要很順利的
卻遇到了一個令人困惑的錯誤訊息

HotChocolate.SchemaException: For more details look at the `Errors` property.
1. The specified name is not a valid GraphQL name. (Parameter 'value') (HotChocolate.Types.EnumType<MyEnum>)

起初我以為將 Enum 值轉換成字串只不過是 ToString() 而已
實在是不解為什麼會發生錯誤?


0x01 開始抓蟲

由於原本的 Enum 很大一包
所以我快速地掃了一遍並沒有發現任何明顯的問題
尤其整包 Enum 都是品牌名稱
直覺上不會有問題才對

後來使用了中斷點的方式抓出造成錯誤的 Enum
原來有一個 Enum 值的名稱包含了變音符號

來個例子🌰:

enum Brand 
{
// …
Hermès,
// …
}

當 Hermès ToString() 轉換為 GraphQL 的 Enum Type 時
就違反了命名規範因此噴錯


0x02 殺蟲時間

其實最簡單暴力的解決方法是建立一個對照表
將所有帶有變音符號的字母直接替換成相應的字母
但這方案不是很優雅

因此持續尋找答案的過程中
發現 Unicode 有四種不同的正規化形式
(Normalization Form,簡稱 NFC、NFD、NFKC、NFKD)

Ref:
1. .NET NormalizationForm Enum
2. UNICODE NORMALIZATION FORMS

對於處理變音符號,我們需要使用 NFC 和 NFD 這兩種形式。

static void PrintAsBytes(string s, NormalizationForm form)
{
Console.WriteLine($"{form}: {string.Join(" ", s.Normalize(form).Select(s => $"{(short)s:X4}"))}");
}
PrintAsBytes("è", NormalizationForm.FormC);
PrintAsBytes("è", NormalizationForm.FormD);

建立一個方法將不同 NormalizationForm 的字串用 bytes 的方式印出來
可以得到結果為

FormC: 00E8
FormD: 0065 0300

而 ASCII 中的 e 就是 0x65
由此可見我們可以利用 FormD 的格式找到無變音符號的字母
將變音符號去除之後就可以得到符合 ASCII 範圍內的字母了

根據上面的想法可以寫一段這樣的方法來實作

static string RemoveDiacritics(string s) {
return string.Concat(Regex.Matches(s.Normalize(NormalizationForm.FormD), @"[A-Za-z0-9_]").Normalize(NormalizationForm.FormC);
}

但這樣的做法閱讀起還有那麼一點彆扭
後來發現微軟有提供 CharUnicodeInfo.GetUnicodeCategory 的方法
利用 UnicodeCategory 來識別是否應該去除

static string RemoveDiacritics(string s) {
return string.Concat(s.Normalize(NormalizationForm.FormD).Where(c => CharUnicodeInfo.GetUnicodeCategory(c) != UnicodeCategory.NonSpacingMark)).Normalize(NormalizationForm.FormC);
}

這樣的程式碼更容易閱讀
讓意圖更加明確


0x03 Unicode Ascii Folding Filter

上面有提到的其中一種做法是用對照表的方式做替換
而其實在查找的過程中
發現 Apache 專案中有個 ASCII Folding Filter 的方法
就是用超大的 switch case 製作的
網路上也有人把這個邏輯改寫成 C# 版本
但後來沒有使用這個版本
因為 ASCII Folding Filter 做的事情不只移除變音符號
還處理了所有的 Unicode 的類型
這是擷取 source code 中的註解內容

This class converts alphabetic, numeric, and symbolic Unicode characters which are not in the first 127 ASCII characters (the “Basic Latin” Unicode block) into their ASCII equivalents, if one exists.

如果是文章或是使用者輸入的文字
要轉進只允許 ASCII 的系統會比較合適


0xFF 後記

這次的問題出自於在建立這個 Enum 時
大家都是習慣性的複製貼上
由於這次提供原始資料的來源是歐洲公司
因此名稱上就容易出現變音符
而現在多數的程式語言也都支援 Unicode
所以貼上時也不會出問題

這時突然想到一件有趣的事情
例如 Swift 甚至可以用 emoji 來寫 code

raw-image

ref: https://docs.swift.org/swift-book/documentation/the-swift-programming-language/thebasics/#Naming-Constants-and-Variables

就嘗試了一下看看 C# 的 enum 是不是也可以用 emoji
經過嘗試之後發現
中文是可以的
但 emoji 不行
或 unicode 的符號 (e.g. ①) 也不行
查了一下原因找到了一個 GitHub 的討論串
滿有趣的

raw-image

希望這篇文章的小發現你也會覺得有趣


avatar-img
2會員
7內容數
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
Justin Shaw's Salon 的其他內容
Hosts File 是一種可以取代 DNS 查詢的步驟 直接指定 domain 所指向的 IP 位址 甚至是不存在的 domain 也可以使用 hosts file 來給定 IP 位址
講完了 Story 的拆解 其中提到了 Scope 那麼 Scope 是什麼呢? 以及伴隨著 Scope  很常聽到的 Acceptance Criteria (AC) 又扮演了什麼樣的角色? 0x00 回顧 在系列文章中的第一篇 From Scrum to LeSS — Roles
當 Story 被確定下來之後 要如何切割 Story  讓他們可以在 Sprint 期間能 Done 過去經驗我們都知道 當 Story 太大的時候要拆小 但問題就來了 小要小到多小 有可能小到 Task 嗎?
從一開始 Story 的出生 就會被放進 Product Backlog 經過漫長的等待 終於在某次的 Sprint 中被提到 Sprint Backlog 接著透過獅子🦁及猿猴🦍們的努力 將 Coffin 轉換成 Code Story 終於蛻變成了 PSPI
最近上完了一門 LeSS in Action 的課程 雖然在 Scrum Team 少說也四五年了 這期間也考過了 CSM, PMP 認證 但這次的課程後對 Scrum 又有了新的認識 同時也回顧一下自己在執行了將近五年的 Scrum 開發經驗
Hosts File 是一種可以取代 DNS 查詢的步驟 直接指定 domain 所指向的 IP 位址 甚至是不存在的 domain 也可以使用 hosts file 來給定 IP 位址
講完了 Story 的拆解 其中提到了 Scope 那麼 Scope 是什麼呢? 以及伴隨著 Scope  很常聽到的 Acceptance Criteria (AC) 又扮演了什麼樣的角色? 0x00 回顧 在系列文章中的第一篇 From Scrum to LeSS — Roles
當 Story 被確定下來之後 要如何切割 Story  讓他們可以在 Sprint 期間能 Done 過去經驗我們都知道 當 Story 太大的時候要拆小 但問題就來了 小要小到多小 有可能小到 Task 嗎?
從一開始 Story 的出生 就會被放進 Product Backlog 經過漫長的等待 終於在某次的 Sprint 中被提到 Sprint Backlog 接著透過獅子🦁及猿猴🦍們的努力 將 Coffin 轉換成 Code Story 終於蛻變成了 PSPI
最近上完了一門 LeSS in Action 的課程 雖然在 Scrum Team 少說也四五年了 這期間也考過了 CSM, PMP 認證 但這次的課程後對 Scrum 又有了新的認識 同時也回顧一下自己在執行了將近五年的 Scrum 開發經驗
你可能也想看
Google News 追蹤
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本文介紹了在進行資料分析時,將類別欄位轉換為數值欄位的方法,包括Label Encoding、One-Hot Encoding、Binary Encoding、Target Encoding和Frequency Encoding。每種方法的應用範例、優缺點和適用場景都有詳細說明。
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
本篇文章講解了字符編碼的基礎知識,包括ASCII, Unicode 和 UTF-8的誕生背景、解決的問題以及轉換方式。瞭解這些知識有助於解決在讀檔案時用錯誤的編碼方式轉換就會出現亂碼等問題。文章內容涉及電腦技術中的字符編碼相關歷史緣由,可幫助讀者解決相關疑問。
Thumbnail
內容涵蓋資料型別、型別轉換、自訂型別、元組型別、集合型別和字典型別等主題。文章首先詳述內建型別如bool、byte、char等的定義和使用,接著討論型別轉換,包括隱含轉換和明確轉換。之後文章介紹自訂型別的建立,以及元組、集合、陣列和字典型別的操作與例子。
Thumbnail
在API介接中使用x-www-form-urlencoded格式時,可能會遇到一些踩坑的情況,本文分享了作者在這方面遇到的問題和解決方法。
Thumbnail
軟體開發時應該要有固定的命名規則,以提高程式的可讀性,本篇文章帶你認識常見的幾個命名方法。
Thumbnail
題目敘述 題目會給我們一個字串s。 要求我們移除字串中的星號,還有刪除星號左手邊最靠近的第一個字元。 以字串的形式返回輸出答案。 題目的原文敘述 測試範例 Example 1: Input: s = "leet**cod*e" Output: "lecoe" Explanation:
Thumbnail
題目敘述 題目會給定我們一個字串s,要求我們反轉字串s中所有母音字元的順序,並且以字串的形式輸出。 註: 母音字元為a, e, i, o, u 或者 A, E, I, O, U 題目的原文敘述 測試範例 Example 1: Input: s = "hello" Output: "ho
Thumbnail
題目敘述 題目會給定我們兩個字串word1 和 word2。 允許我們不限制次數進行下列兩種操作: 任意調換其中兩個字元的位置。 把字串中的某個字元全部置換成另一個字元,同時把另一個字元同時置換成某個字元。(例如把字串中原本的a都換成b,把原本的b都換成a) 問我們能不能通過上述兩項操作,
Thumbnail
題目敘述 題目會給我們兩個字串作為輸入,分別是字串s和字串t,問我最少要做幾次字元轉換,讓字串t和字串s成為Anagram"同字母異序詞"? 註: 例如 god 和 dog 就是 Anagram 同字母異序詞,也是就說,組成字母相同,但是順序可以重新排列。 題目的原文敘述 測試範例 Ex
Thumbnail
隨著理財資訊的普及,越來越多台灣人不再將資產侷限於台股,而是將視野拓展到國際市場。特別是美國市場,其豐富的理財選擇,讓不少人開始思考將資金配置於海外市場的可能性。 然而,要參與美國市場並不只是盲目跟隨標的這麼簡單,而是需要策略和方式,尤其對新手而言,除了選股以外還會遇到語言、開戶流程、Ap
Thumbnail
嘿,大家新年快樂~ 新年大家都在做什麼呢? 跨年夜的我趕工製作某個外包設計案,在工作告一段落時趕上倒數。 然後和兩個小孩過了一個忙亂的元旦。在深夜時刻,看到朋友傳來的解籤網站,興致勃勃熬夜體驗了一下,覺得非常好玩,或許有人玩過了,但還是想寫上來分享紀錄一下~
Thumbnail
本文介紹了在進行資料分析時,將類別欄位轉換為數值欄位的方法,包括Label Encoding、One-Hot Encoding、Binary Encoding、Target Encoding和Frequency Encoding。每種方法的應用範例、優缺點和適用場景都有詳細說明。
Thumbnail
一般在使用 TypeScript 的時候,大家都有遇過定義列舉資料的情境吧。 不過不管是 enum 和 literal 的方式其實都有些小缺點,以下推薦一個個人認為體驗更好的方式。
Thumbnail
本篇文章講解了字符編碼的基礎知識,包括ASCII, Unicode 和 UTF-8的誕生背景、解決的問題以及轉換方式。瞭解這些知識有助於解決在讀檔案時用錯誤的編碼方式轉換就會出現亂碼等問題。文章內容涉及電腦技術中的字符編碼相關歷史緣由,可幫助讀者解決相關疑問。
Thumbnail
內容涵蓋資料型別、型別轉換、自訂型別、元組型別、集合型別和字典型別等主題。文章首先詳述內建型別如bool、byte、char等的定義和使用,接著討論型別轉換,包括隱含轉換和明確轉換。之後文章介紹自訂型別的建立,以及元組、集合、陣列和字典型別的操作與例子。
Thumbnail
在API介接中使用x-www-form-urlencoded格式時,可能會遇到一些踩坑的情況,本文分享了作者在這方面遇到的問題和解決方法。
Thumbnail
軟體開發時應該要有固定的命名規則,以提高程式的可讀性,本篇文章帶你認識常見的幾個命名方法。
Thumbnail
題目敘述 題目會給我們一個字串s。 要求我們移除字串中的星號,還有刪除星號左手邊最靠近的第一個字元。 以字串的形式返回輸出答案。 題目的原文敘述 測試範例 Example 1: Input: s = "leet**cod*e" Output: "lecoe" Explanation:
Thumbnail
題目敘述 題目會給定我們一個字串s,要求我們反轉字串s中所有母音字元的順序,並且以字串的形式輸出。 註: 母音字元為a, e, i, o, u 或者 A, E, I, O, U 題目的原文敘述 測試範例 Example 1: Input: s = "hello" Output: "ho
Thumbnail
題目敘述 題目會給定我們兩個字串word1 和 word2。 允許我們不限制次數進行下列兩種操作: 任意調換其中兩個字元的位置。 把字串中的某個字元全部置換成另一個字元,同時把另一個字元同時置換成某個字元。(例如把字串中原本的a都換成b,把原本的b都換成a) 問我們能不能通過上述兩項操作,
Thumbnail
題目敘述 題目會給我們兩個字串作為輸入,分別是字串s和字串t,問我最少要做幾次字元轉換,讓字串t和字串s成為Anagram"同字母異序詞"? 註: 例如 god 和 dog 就是 Anagram 同字母異序詞,也是就說,組成字母相同,但是順序可以重新排列。 題目的原文敘述 測試範例 Ex