使用 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

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


    2會員
    7內容數
    留言0
    查看全部
    發表第一個留言支持創作者!
    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
    接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
    Thumbnail
    🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
    Thumbnail
    寶寶隨著年齡日漸成長,剛出生的嬰兒只有黑白世界, 3-6 個月慢慢可以看到紅、黃、藍、綠等顏色、1 歲左右才能看到清楚且充滿色彩的世界!近幾年宅在家,再加上遠距教學,使用平板、電腦、手機的時間大幅增加
    Thumbnail
    長時間接觸3C產品有逐漸年輕化及普遍化的趨勢,3C產品所帶來的健康危害,是不容忽視的課題。過度近距離長時間使用3C產品,眼睛不自覺用力,易導致眼軸拉長及近視度數快速加深,嚴重時會導致黃斑部傷害,增加視網膜剝離及失明的風險!
    Thumbnail
    你是否擔心孩子有網路沉迷的狀況?要如何減低家中兒童3C網路沉迷的風險呢? 對此,兒童發展專家王宏哲便分享了自身的觀察及好用的3C親子溝通法供各位家長參考。其認為家長可從「依年齡層調整使用3C時間」、「和孩子的約定」兩個面向著手!
    Thumbnail
    父母如何制定3C使用規則,需瞭解自己給予孩子使用3C的目的與時機,並制定好頻率與時間,確實執行並監督管理。
    Thumbnail
      雖然Dart 語言本身支援跨平台的編譯方式,但在實務開發時還是不免需要使用外部非Dart語言所提供的函式庫進行功能開發且由於C 語言是最為廣泛且通用的程式語言,因此Dart語言也有提供支援與C語言函式庫互通性的方式;本篇主要是以MSVC作為C的編譯器來實作說明如何引用C語言會遇到的作法。
    Thumbnail
    3C除了會侵襲孩子的視力,連情緒、專注力、人際互動都會受影響, 但要因此禁止小孩接觸3C產品嗎? 其實3C並不可怕,而是錯誤的使用情境~ 🙋‍♀️本篇作伴要來給 爸比媽咪們建立孩子使用3C的小原則, 一起來為孩子的健康把關吧💓
    Thumbnail
    使用3C,對我來說是一種矛盾。 一方面,我不覺得用禁止的方式就會有好結果,畢竟總有一天孩子會接觸3C,雖然有人說過等孩子大一點再接觸也不遲,但我可以讓孩子二歲左右就使用剪刀,五歲時由他在後陽台用鐵槌和釘子釘木板⋯⋯,但為何面對3C產品就開始猶豫了?
    Thumbnail
    接下來第二部分我們持續討論美國總統大選如何佈局, 以及選前一週到年底的操作策略建議 分析兩位候選人政策利多/ 利空的板塊和股票
    Thumbnail
    🤔為什麼團長的能力是死亡筆記本? 🤔為什麼像是死亡筆記本呢? 🤨作者巧思-讓妮翁死亡合理的幾個伏筆
    Thumbnail
    寶寶隨著年齡日漸成長,剛出生的嬰兒只有黑白世界, 3-6 個月慢慢可以看到紅、黃、藍、綠等顏色、1 歲左右才能看到清楚且充滿色彩的世界!近幾年宅在家,再加上遠距教學,使用平板、電腦、手機的時間大幅增加
    Thumbnail
    長時間接觸3C產品有逐漸年輕化及普遍化的趨勢,3C產品所帶來的健康危害,是不容忽視的課題。過度近距離長時間使用3C產品,眼睛不自覺用力,易導致眼軸拉長及近視度數快速加深,嚴重時會導致黃斑部傷害,增加視網膜剝離及失明的風險!
    Thumbnail
    你是否擔心孩子有網路沉迷的狀況?要如何減低家中兒童3C網路沉迷的風險呢? 對此,兒童發展專家王宏哲便分享了自身的觀察及好用的3C親子溝通法供各位家長參考。其認為家長可從「依年齡層調整使用3C時間」、「和孩子的約定」兩個面向著手!
    Thumbnail
    父母如何制定3C使用規則,需瞭解自己給予孩子使用3C的目的與時機,並制定好頻率與時間,確實執行並監督管理。
    Thumbnail
      雖然Dart 語言本身支援跨平台的編譯方式,但在實務開發時還是不免需要使用外部非Dart語言所提供的函式庫進行功能開發且由於C 語言是最為廣泛且通用的程式語言,因此Dart語言也有提供支援與C語言函式庫互通性的方式;本篇主要是以MSVC作為C的編譯器來實作說明如何引用C語言會遇到的作法。
    Thumbnail
    3C除了會侵襲孩子的視力,連情緒、專注力、人際互動都會受影響, 但要因此禁止小孩接觸3C產品嗎? 其實3C並不可怕,而是錯誤的使用情境~ 🙋‍♀️本篇作伴要來給 爸比媽咪們建立孩子使用3C的小原則, 一起來為孩子的健康把關吧💓
    Thumbnail
    使用3C,對我來說是一種矛盾。 一方面,我不覺得用禁止的方式就會有好結果,畢竟總有一天孩子會接觸3C,雖然有人說過等孩子大一點再接觸也不遲,但我可以讓孩子二歲左右就使用剪刀,五歲時由他在後陽台用鐵槌和釘子釘木板⋯⋯,但為何面對3C產品就開始猶豫了?