前幾天同事討論到要將地址資料中的阿拉伯數字都轉成中文大寫(一二三四...),我想起了前些時候看到的Microsoft Visual Studio International Feature Pack 2.0就內建了數字轉中文大寫的功能,試作如下。

程式主要是用Regex去比對出數字(\d+)的部分,逐一換成中文大寫。而更換時我用算位置的方法而不直接用Replace,以免把"12弄123號"搞成"十二弄十二3號";也因為要算位置,加上每次更換完字串長度可能會改變,所以也不能直接用foreach (Match m in Regex.Matches(…))把所有數字挑出來一次處理,必須用while (Match.Success)個個擊破。[2009-12-23更新: 以下寫法不夠精簡,另外有三百"一"十少一的問題,請改參考下方的改良版]

using System;
using System.Text.RegularExpressions;
using Microsoft.International.Formatters;
using System.Globalization;
 
namespace TestChtNumber
{
    class Program
    {
        static void Main(string[] args)
        {
            string addr = "台北市中正區重慶南路1段122號";
            Match m;
            while ((m=Regex.Match(addr, "\\d+")).Success)
            {
                int n = int.Parse(m.Value);
                string t =
                    EastAsiaNumericFormatter.FormatWithCulture("Ln", n,
                    null, new CultureInfo("zh-TW"));
                //"L"-大寫,壹貳參... "Ln"-一二三... "Lc"-貨幣,同L
                addr = addr.Substring(0, m.Index) + t +
                       addr.Substring(m.Index + m.Value.Length);
            }
            Console.WriteLine(addr);
            Console.Read();
        }
    }
}

得到結果: 台北市中正區重慶南路一段一百二十二號

很好用吧!! 另外,元件也支援簡繁轉換的功能,是另一項天上掉下來的禮物,不要錯過了。

【延伸閱讀】

【更新2009/12/23】接獲線報,這元件有個Bug,"310"會被轉成"三百十",而我們口語會說"三百一十",好好的美人兒偏偏要長一顆痣,實在是... (氣) 晚點再來想要如何整容。

【更新2009/12/23】感謝Dino與Phoenix的補充,讓我這條老狗又學到新把戲了(MatchEvaluator是好物呀!),以下我想到為解決三百"一"十問題的修正版,歡迎大家批評指教!

【更新2015/04/12】感謝YinChang補充,再加入「拾萬」要轉為「壹拾萬」之邏輯。


using System;
using System.Text.RegularExpressions;
using Microsoft.International.Formatters;
using System.Globalization;
 
namespace TestNumber
{
    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 2000; i++)
            {
                Console.Write(FormatChineseNumber(i, false) + " ");
            }
            Console.WriteLine(FormatChineseNumber(
                string.Format("中華民國{0}年{1}月{2}日",
                DateTime.Today.Year - 1911,
                DateTime.Today.Month,
                DateTime.Today.Day), false));
            Console.WriteLine(FormatChineseNumber(12.345M, false));
            Console.WriteLine(FormatChineseNumber(3100000M, true));
            Console.WriteLine(FormatChineseNumber(100000M, true));
            Console.Read();
        }
 
        /// <summary>
        /// 將字串的數字部分轉為中文大寫
        /// </summary>
        /// <param name="s">原始字串</param>
        /// <param name="moneyChar">
        /// 是否使用金額大寫,true時使用"壹貳參肆...", false時則為"一二三四..."
        /// </param>
        /// <returns>轉換後字串</returns>
        static string FormatChineseNumber(string s, bool moneyChar)
        {
            return Regex.Replace(s, "\\d+", m =>
            {
                int n = int.Parse(m.Value);
                return FormatChineseNumber(n, moneyChar);
            });
        }
 
        /// <summary>
        /// 修正EastAsiaNumericFormatter.FormatWithCulture出現"三百十"之問題,
        /// 本函數會將其修正為三百一十的慣用寫法
        /// 2015-04-12更新,增加拾萬改為壹拾萬邏輯
        /// </summary>
        /// <param name="n">要轉換的數字</param>
        /// <param name="moneyChar">
        /// 是否使用金額大寫,true時使用"壹貳參肆...", false時則為"一二三四..."
        /// </param>
        /// <returns>轉為中文大寫的數字</returns>
        static string FormatChineseNumber(decimal n, bool moneyChar)
        {
            //"L"-大寫,壹貳參... "Ln"-一二三... "Lc"-貨幣,同L
            string t =
                EastAsiaNumericFormatter.FormatWithCulture(
                moneyChar ? "L" : "Ln", n,
                null, new CultureInfo("zh-TW"));
            string pattern = moneyChar ?
                    "[^壹貳參肆伍陸柒捌玖]拾" :
                    "[^一二三四五六七八九]十";
            string one = moneyChar ? "壹" : "一";
            string res = Regex.Replace(t, pattern, m =>
            {
                return m.Value.Substring(0, 1) + one +
                    m.Value.Substring(1);
            });
            //拾萬需補為壹拾萬
            if (moneyChar && res.StartsWith("拾"))
            {
                res = "壹" + res;
            }
            return res;
        }
    }
}


Comments

# by chuck

原來還有這種東西喔 真好用EastAsiaNumericFormatter.FormatWithCulture

# by Dino

透過黑大理解到 Microsoft Visual Studio International Feature Pack 2.0 的功能了! 不過我覺得 Regex 的做法 這樣可能會更好: -------------- string addr = "台北市中正區重慶南路1段122號"; string tran = Regex.Replace(addr, @"\d+", delegate(Match match) { int n = int.Parse(match.Value); return EastAsiaNumericFormatter.FormatWithCulture("Ln", n, null, new CultureInfo("zh-TW")); }); --------------

# by Phoenix

原來已經有人知道.Net Regex的秘密能力了@@ 用Lambda Expression再簡化程式碼。 ------------------------------------------ string addr = "台北市中正區重慶南路1段122號"; addr = Regex.Replace(addr, "\\d+", m => EastAsiaNumericFormatter.FormatWithCulture( "Ln", int.Parse(m.Value), null, new CultureInfo("zh-TW"))); Console.WriteLine(addr); Console.Read();

# by Dino

To Phoenix, 讚!

# by Jeffrey

to Dino, Phoenix, 感謝補充,我又學到新東西囉(灑花~~)。我在三百"一"十改良版中已融入MatchEvaluator+Lambda的做法,謝謝兩位!

# by YinChang

感謝分享關於所提的"310"會被轉成"三百十"的bug, 補充說明一下轉大寫貨幣要注意一下(剛好被客戶抓到), 如:"拾萬元整"正確是"壹拾萬元整" 應該是同一bug,需多判斷開頭為"拾"需在前頭補上"壹"

# by Jeffrey

to YinChang, 已在程式再加入「拾萬」補為「壹拾萬」邏輯,感謝你的補充讓程式更完整。

# by Gemo

發現使用.NET 6實作時發現 原本指定的CultureInfo(“zh-CHT)會被assign它的Parent, zh-Hant 但後面的if條件卻沒有zh-Hant 然後就會出現"The specified format is not supported in this culture.

Post a comment