雖然無名的 blog 服務,一直都沒有做得很好,不過寶貝還是不願意撤離無名,直到這次參加 OSDC.tw寶貝很興奮地寫了一大篇文章,結果發表時,無名卻把文章藏了一個晚上才出現,這才讓寶貝同意放棄無名。所以就用超快速度,申請了 domain name,架了個 wordpress 給寶貝用。Wordpress 可以在後台 import RSS,所以接下來的問題,就是如何把無名 blog 裡的資料,以 RSS 格式弄下來了。

之前 gslin 有弄一個無名備份的服務,不過可能也是因為鳥蛋 MySQL 的 utf-8 問題的緣故,提供服務的那個網站已經連不到了。還好,隱約中有印象,該程式已經進 OpenSVN 了,所以就去那邊找。不過卻被 OpenSVN 的首頁搞迷糊了,只有讓人申請的頁面,完全找不到 hosted projects 的列表,這個站到底可以幹嘛,實在讓我搞不清楚。

所以只好再請出 Google 大神,試了很多組關鍵字,總算以「opensvn backup trac wretch blog」這五個關鍵字,撈出了在 blogspot 的《無名小站相簿備份服務》這篇文章,這才找到 OpenSVN 上的 Backup project 的鍊結。這實在是太曲折了啊,OpenSVN 有必要這麼講不清楚,說不明白嗎?實在是令我無法理解。

總之,找到主網頁後就好辦了,但又發現個問題,從 Backup project 的站上,完全找不到 repository 的 URL 要怎麼寫。只好亂猜一通,還好最後還是又被我猜到了,用以下的指令,即可下載最新版的 Backup project 的程式碼。

SHELL> svn checkout https://opensvn.csie.org/backup gslin-backup

裡面的 blog/wretch 目錄下,就有可以用來備份無名的程式。除了介紹用的 index.html 外,另外有兩組程式,皆是由一個負責前端的 php 檔和一個負責後端實際工作的 perl 檔組成。主檔名為 rss20-wretchblog 的這組,是用來將 RSS 檔裡的文章,「發表」到無名 blog 上的,因此使用時,需要用到無名的帳號與密碼。主檔名為 wretchblog-rss20 的這組,則剛好相反,是用來把無名 blog 上的文章,「備份」下來成為一個 RSS 2.0 的檔案,由於只需要抓資料,所以這一組只需要無名的帳號即可。由於懶得弄前端,所以研究了一下,確認只要使用以下的指令,就可以將寶貝在無名的 blog,完整備份下來:

SHELL> wretchblog-rss20.pl afoofa > afoofa-wretch.xml

資料抓下來後,先檢視一下,結果發現 <pubdate> 裡都是空的。追了一下,原來是抓文章時間的 regex,可能是因為模版不同的關係,寫得並不對。按照以下修改,即可修正:

Index: wretchblog-rss20.pl
===================================================================
--- wretchblog-rss20.pl (revision 101)
+++ wretchblog-rss20.pl (working copy)
@@ -119,7 +119,7 @@
        my $month = $1;
        my $year = $3 % 100;

-       if ($body =~ /<div\s+class="posted">.*(\d+):(\d+)\s*(\w+)\s*發表/is) {
+       if ($body =~ /<div\s+class="posted">.* at (\d+):(\d+)\s*(\w+)\s*/is) {
            my $tmp = sprintf("%02d %.3s %02d %02d:%02d:00 CST", $day, $month, $year, $1 + ($3 eq 'PM' ? 12 : 0), $2);
            my $unixtime = str2time($tmp);

修改之後,<pubdate> 就可以正確抓出來了。至此,我很高興地就把備份下來的資料,import 進寶貝的 wordpress 了。可惜,最後還是棋差一著,寶貝跟我說,有些表情符號爛掉了。>.<< 不見了。檢查了一下程式碼,發現是由最後面的 xmlencode() 這個 subroutine 負責將欄位裡的 HTML entities 編碼,這個 subroutine 是這樣子寫得:

sub xmlencode($)
{
    my $str = shift();
    $str =~ s/</&lt;/g;
    $str =~ s/&/&amp;/g;
    return $encoded;
}

唉呀呀,這真是偷懶的寫法啊。先別說整套 HTML entities 只處理了兩個,程式的邏輯也不對。碰到 < 會先被轉成 &lt;,然後又接著會被轉成 &amp;lt;,這根本就不對了,第二次轉換不應該發生。但因為很晚了,所以就懶得弄了,寶貝的新 blog 直接開張。

Index: wretchblog-rss20.pl
===================================================================
--- nbsp;wretchblog-rss20.pl (revision 101)
+++ wretchblog-rss20.pl (working copy)
@@ -136,9 +136,24 @@
 sub xmlencode($)
 {
     my $str = shift();
+    my $encoded = '';
+    while ($str =~ m/[<>&]/o) {
+        $encoded .= $`;
+        if ($& eq '<') {
+            $encoded .= '&lt;';
+        }
+        elsif ($& eq '>') {
+            $encoded .= '&gt;';
+        }
+        elsif ($& eq '&') {
+            $encoded .= '&amp;';
+        }
+        else {
+            $encoded .= $&;
+        }
+        $str = $';
+    }

-    $str =~ s/</&lt;/g;
-    $str =~ s/&/&amp;/g;
+    return $encoded;
+}

-    return $str;
-}

剛剛想,總該還是要處理一下才對,所以又做了一下上面列出的 patch,只掃過輸入值一次就好,並補上另一個較常見的 &gt;,以避免 import 到 wordpress時,又被 wordpress 弄爛。在 wordpress 裡,是使用 wp-admin/import/rss.php 這個檔案,來處理 RSS 的 import,裡面的 class RSS_Import 的 unhtmlentities(),就是用來逆轉 HTML entities 以轉換回原來的字元用的。這個函式用的是 PHP 提供的 HTML_ENTITIES 表格,應該所有 HTML entities 都齊備了。理論上,修改 xmlencode() 時,我也應該要把整套 HTML entities 都補上,以對應 wordpress 的 import,但因為其實大部分都不常用到,所以最後我還是只轉了 <>& 這三個字元而已。

測試了一下,結果正如所願,因此回報 Backup project 寫了這個 ticket:Wrong regex order in xmlencode()[1]


  1. 竟然是第一個 ticket。