[Scala] 轉換非 Big5 至 XML 脫逸字元。

話說前一陣子 Scala 社群裡面在吵 Scala 這個程式語言到底簡單還是難,剛好這幾天在看程式時發現這個讓人想大喊 WTF,然後投稿到 The Daily WTF 上的 Java 函式,所以就拿它來開刀了……

原本的 Java Code 長的像下面這樣:

public static String isNotBig5(String ori)
{
    char[] c = ori.toCharArray();
    char[] c1 = new char[1];
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < c.length; i++) {
        c1[0] = c[i];
        //System.out.println(c1[0]+" "+(int)c1[0]); //
        String onechar = "";
        try {
            onechar = new String( (new String(c1)).getBytes() , "Big5");
        }
        catch (Exception e) {}
        if (onechar.equals("?") && (int)c[i] != 63) { // ?是正常的"?"
            sb.append("&#");
            sb.append(Integer.toString(c1[0]));
            sb.append(";");
        }
        else {
            sb.append(c1[0]);
        }
    }
    return sb.toString();
}

因為這段程式實在醜到一種極緻,完全違反了所有 Clean Code 的原則。老實說,雖然一開始我知道這個函式是幹啥用的,但我完全無法理解這個函式到底在搞什麼鬼(看那和實際用途差了十萬八千里的函式名稱),而且它完全無法在非 Windows 的作業系統上正常使用。

最後,經過一連串的實驗,我終於看出來這個函式的演算法了(雖然他的實作是有問題的),其實這個演算法很簡單:

  1. 針對字串裡的每一個字元,做下列的動作
    1. 檢查該字元是不是在 Big5 可以表示的範圍內
    2. 如果是,將該字元轉成字串,例如從 'A' 到 "A"
    3. 如果不是,將該字元轉成 XML 字元參引,例如 '喆' 轉成 "&#21894;"
  2. 將上一個步驟所產生出來的所有字串,合併(連接)成單一字串。

所以,為了讓我自己往後好過一點,我用 Scala 照著同樣的演算法幹出了另一個版本,不完美,但我想應該至少清楚很多,以下就是 Scala 版的程式碼,其中 normalizeString 的角色就是原來 Java 版中的 isNotBig5():

def normalizeString (string: String) =
    string.flatMap(escapeNonBig5Character _)

def escapeNonBig5Character (character: Char): String = {

    def isNotBig5 (character: Char) =
        character.toString.getBytes("Big5")(0) == '?' &&
        character != '?'

     // 不是 Big5 的字元就轉成字元參引
     // 不然的話維持原樣
    isNotBig5(character) match {
        case true => "&#%d;" format(character.toInt)
        case false => character.toString
    }
}

def example () {
    val content = "我是陶喆\n我是游鍚堃\n這是測試"
    println (normalizeString(content))
}

如果你是從 Java 背景來的,那麼注意下面幾點應該就可以看懂這個程式:

  • String#flatMap 是指針對字串裡的每一個字元,套用一個將字元轉成字串的函式(所以這時計算出的東西會是 Array of String),最後再將此 Array of String 合併成單一的字串。
  • escapeNonBig5Character 會將字元轉成字串,規則如下:
    • 如果該字元可以在 Big5 編碼下表示,就維持原樣,但轉成字串,也就是從 'A' 轉到 "A"
    • 如果該字元無法以 Big5 編碼表示,那就將該字元轉成 &#XXXXXX; 的格式
  • 判斷字元可否在 Big5 下表示的方式:
    • 例用 String#getBytes("BIG5") 將其轉碼到 BIG5,如果他本來不是問號,卻變成問號(解析不能),那這個字就無法用 Big5 來表示。

老實講,是很簡單的轉換法,但上面那段看了就想大喊 WTF 的程式碼,我不知道為什麼能把這麼簡單的東西給搞到這麼複雜……

至於 Scala 到底難不難?!我想這真的很難說,如果你不知道什麼是 High order function (flatMap 就是一個例子),那麼 Scala 版的可能你也覺得不好懂,但相對的,我每次在寫 Scala 時都覺得真的很像在『用嘴巴寫程式』。

仔細對照一下,你會發現 Scala 的版本非常接近於一開始我們針對 Java 版的演算法所做的『描述』,所以,快點把 Java 給丟了唄(大誤)!

BTW,到目前為止我最認同的是這句話:I don't expect a car to be built by average people, but by people with proper engineering background.

畢竟當你讓一個專業的來的時候,Scala 難不難根本不是個問題……

回響