smartypants.js

大阪新年

SmartyPants 這個東西也是很久了,和 Markdown 差不多時期,都是 John Gruber 幫當時的網路文字出版軟體(ex: MovableType)所做的,而 SmartyPants 是用來處理一些標點符號的,其中,最容易被人注意到的就是引號「"」的轉換了。

雖然鍵盤上的引號按鍵只有一個,但是傳統的文書寫作上,引號是有分左邊(開始)的「“」和右邊(結束)的「”」,只不過早期為了減少鍵盤按鍵數,還有字元編碼上的限制,所以合併成為只有一個,不過隨著計算機的發展,可以使用的字元編碼資料量增加後,就還是有定義了開始和結束的兩種引號,並且有單引號和雙引號兩種:

  • 左單引號 ‘
  • 右單引號 ’
  • 左雙引號 “
  • 右雙引號 ”

理想上,寫作文章時也應該正確的使用這些引號,不過其實因為輸入上比較麻煩,一般人打字也不會特別注意,所以常常被忽略,比較常見的是軟體本身在使用者輸入文字時自動做轉換,例如 Apple 的 Pages、Keynote、微軟的 Skype 等等,講到 Keynote 自動轉換引號這點就要另外岔題一下,就是偶爾都會看到頭影片裡面的程式碼,其中的引號也被轉換過,其實就是因為貼上 code 到 Keynote 的時候被轉換了,如果作者沒有注意到的話就直接釋出,然後讀者 copy 程式碼出來試試看時,就會編譯失敗而無法執行。回到標點符號上,這類標點符號其實不止有引號,SmartyPants 可以處理的還包括:

  • --轉成 en-dash –
  • ---轉成 em-dash —
  • ...轉成 ellipsis …

而且它對於引號的轉換還算是聰明,會判斷是不是真的用來包起文字的,還支援用 backtick 來模擬的雙引號,看起來像:

``quoted string''

另為也會針對年代的特殊寫法做處理,例如'80s會轉成 ’80s,並且會避開 HTML 標籤的部分,不會把 HTML 標籤裡面的屬性值的引號也做轉換,像是<html lang="en">這種。前陣子因為工作上要處理翻譯字串,想要順便好好的處理這些符號,所以就想到了這個工具,因為我一般寫小 script 做事情是用 JavaScript 然後用 node 來執行,所以要用 SmartyPants 就沒辦法那麼直接了,一開始先找 JavaScript 的 solution,找到一個簡單的,只用幾條 regular epxression 的版本,其實運作的也還不錯,但是無法避開不處理 HTML 標籤,所以產生出來的結果也不能用,接著改成用 STDIO 丟給 John Gruber 的 Perl 版,結果這效率實在太差,所以又繼續研究一番,發現還有一套叫 typogr 的文字處理工具有實做,不過不想要太多功能,所以最後我決定自己把 Perl 版 SmartyPants 移植成 NPM module,也因此有了 smartypants.js

這次開發我選擇的語言是 TypeScript,原因可以參考我上一篇文章 20k-for-of,然後用 Makefile 加上一些指令轉成類似 jQuery UMD 形式的 JavaScript 檔案發佈到 NPM 上,目前的 smartypants.js 是完完全全把 Perl 版的邏輯翻過來,不過只有實作轉換編碼的部分加上 CLI 的介面,另外 Perl 版的只能轉換成 HTML entity 的格式,不過現在 UTF-8 已經算是很廣泛應用的文字編碼了,所以我還加上了一個轉成 UTF-8 字元的版本,用 UTF-8 編碼的雙引號其實很不錯,在 JSON 或是 csv 裡面也不用 escape,肉眼看起來也美觀許多。

實做過程比較緊張的地方大概就是要把 Perl 的 regular expression 轉成 JavaScript 的版本了,一度看到沒看過的用法都覺得會不會沒辦法用 JavaScript 做起來,還好沒用到很神奇的語法。另外它處理 HTML 語法的部分,其實是先過一個簡單的 lexer 轉成 token list,裡面兩種 token 分別就是一般文字和 HTML 標籤兩種,接著只針對一般文字 token 來做標點符號轉換,不過其實這個 tokenize 的部分有 bug,例如:

<span title=">">HAHA</span>

就會被分解成如下的 token:

  • <span title=">
  • ">HAHA
  • </span>

而且這種方法就也無法處理到一些文字內容的 HTML 屬性,例如 title 和 alt 等,所以接下來還可以做的改進,一個就是改進這部分的 lexer,然後也要對這些屬性值轉換標點符號,大概需要建立一個白名單,另外還有一個想加強的地方就是補上 test case 了,目前完全沒有相關的 test case 可以用,所以其實也沒很正式的驗證過,不過總之目前是都還運作正常,如果有相似需求的人可以參考一下。