普通视图

发现新文章,点击刷新页面。
昨天以前O3noBLOG

2024 北海道 Part 3

2025年12月6日 16:16

札幌電視塔

前篇,第九天天氣不太好,一早雨就不小,一時忘了確認本來安排好的行程,結果就漏掉本來要去的甜點店き花の杜 壺屋,不知道還有沒有機會再去旭川啊,真是扼腕。

總之接下來就往南要經由美瑛富良野去札幌了,首先第一站因為下雨就直接跑去旭川神社買えぞみくじ,然後再去美瑛神社買。

美瑛神社

美瑛有展示全部的えぞみくじ,這邊的是米袋:

美瑛神社

美瑛神社:

美瑛神社

美瑛神社的祈福烏鴉:

美瑛神社

然後就到處打發時間,去了美瑛車站、富良野チーズ工房、Furano Marche 和四季彩之丘,不過花季還沒到就是。

美瑛車站 四季彩之丘

跑來跑去的途中可以看到遠遠的十勝岳很漂亮,不過這是天氣剛好讓山形更明顯,不然後面沒被雲霧遮住的時候沒這麼樣的三角形,前幾天的十勝川溫泉的十勝川就是這邊流下去的,山上其實也有十勝岳溫泉。

十勝岳 狐狸

晚餐是臨時找的 MASAJIN 富良野店,不過因為沒有預約老闆說可以幫我們約下一批,所以就先跑去王子飯店旁邊的ニングルテラス(Ningle Terrace、精靈露台),這邊的 Ningle 雖然翻譯為精靈,不過其實不是起源於西方的故事,而是日本作家倉本聰的小說,想知道長怎樣的可以看他們以前出的 photo story

Ningle Terrace Ningle Terrace

晚餐挑 MASAJIN 就是為了成吉思漢燒肉啦,旭川沒吃到大黑屋還好這邊有吃到,很好吃。

MASAJIN 富良野店 MASAJIN 富良野店 MASAJIN 富良野店 MASAJIN 富良野店

超好吃的馬鈴薯,中間黑黑的似乎是鹽昆布:

MASAJIN 富良野店

第九天晚上住的是小木屋 Log Cotage Himawari,是這趟唯一的民宿,上面拍到狐狸的地方也在這附近(而且我還拍到狐狸怎麼叫),內部空間還蠻夠的,不過相機不太好拍就是,整體而言也還不差,而且有附早餐,又離富田農場超近。

Log Cotage Himawari Log Cotage Himawari

第十天第一個景點就是富田農場啦,主打就是薰衣草,因為住的近很早就到,明明是還沒開門時間停車場就一堆車了,而且後來人還越來越多,不過這邊還是蠻不錯的,比起四季彩之森,我更喜歡這邊,也買了不少薰衣草產品。

富田農場 富田農場 富田農場

午餐去 Furano Burger,然後飯後繞去錦山天満宮買えぞみくじ,這邊很特別是要操作吊車抓的:

錦山天満宮 錦山天満宮

然後還逛了旁邊的...俗又大碗的超市「産直生鮮市場」

産直生鮮市場 江別店 産直生鮮市場 江別店

之後就去三井 Outlet 購物,晚上則是住薄野的 VESSEL HOTEL,為的就是有名的早餐,不過會挑這間其實是因為中島公園的沒空房了,直接講結論,有點後悔,主要是因為這邊的空間太小人很多,用餐用的不太舒服,應該不強求要吃 VESSEL HOTEL 的早餐的,不過千金難買早知道。

然後接下來的第十天,因為還有車子可以用,所以就想排個近郊的景點,本來是安排北海道神宮然後去吃 Chocolatier Masále 本店,結果旅途中發現有個定山溪溫泉也很近而且可以划船,好像是當天早上才打電話預約,剛好接電話(多語服務)的還是台灣人,所以還蠻順利,總之就改成跑去那邊了,要是早這樣安排就會改成住定山溪會更好。

定山渓温泉 定山渓温泉 定山渓温泉 定山溪

午餐隨便找了間點心店吃,然後就準備回程,不過為了讓小孩睡覺我還先開去札幌湖才繞回去札幌市,札幌湖是好像是因為蓋了定山溪水壩才形成的,開車過去的路還穿過水壩正上方,蠻特別的,在水壩的正中間還有一個定山溪水壩堤頂展望台,不過這段沒有地方可以停車,觀光客要到這個地方要去水壩下游那邊有個園區有個停車場,然後爬樓梯上來,有機會的話蠻想來挑戰的,不過就算沒這樣跑,沿主要道路走札幌湖也有五個展望台,我這次照片主要其實是在第一個拍的,這邊才看得到水壩,湖景也是很美的,回來研究的時候有看到有人拍到秋天湖面很平靜都是倒影的,更加漂亮。

Sapporo Lake Sapporo Lake

之後終於再次往札幌前進,接下來幾天就都住在札幌市內,旅館是狸小路商店街中間的 Via Inn,位置很方便而且也不貴,最後一天就是直接拉行李去大通公園搭巴士去機場,走路的路程也還好,蠻推的。

接下來幾天就是札幌市區了,比較沒特別行程,跑了 Pokemon Center、狸小路商店街從頭到尾、走到札幌車站來回、島村樂器、根室花丸壽司、DONGURI 麵包店、藻岩山 山頂展望台等。

札幌茅乃舍 島村樂器 島村樂器 DONGURI DONGURI 藻岩山 山頂展望台 藻岩山 山頂展望台

其中狸小路商店街,一共有七段,這是我第一次從頭走到尾,最後的7丁目真的很特別,風格遽變,看起來窮很多,其他區的天棚甚至還是可以打開的:

狸小路商店街 狸小路商店街 狸小路商店街 7丁目 狸小路商店街

然後這趟也有特別找一家パフェ(parfait)吃,選的店家是パフェ、珈琲、酒、佐藤,不過當初在規劃的時候看同個地址的地標變成佐藤堂,去現場才搞清楚,佐藤堂是他們新開的甜點品牌,現在這個地址一樓是佐藤堂,要內用吃パフェ要到樓上。

パフェ、珈琲、酒、佐藤 パフェ、珈琲、酒、佐藤 佐藤堂

最後一天一早就去大通公園等巴士,事前就有先來勘查過,其實選機場巴士去機場就是會很怕沒位子就慘了,不過這站是蠻前面的站所以還好,結果巴士也沒坐滿,總之如果順利的話搭機場巴士去機場是會比搭火車地鐵輕鬆的,不用辛苦搬行李找地方放和換車等。

順利的到達最後一天的景點:新千歲機場,飛機是下午六七點,我們早上十點多就到機場了,結論就是時間不夠!我們去集印章、逛一個小博物館、看雪初音展覽、買一些商品(熱門商店隊伍很長都放棄)、然後去拿完美行的商品,還忙到商店區都沒拍照,結果最後還是很接近時間才到登機門,最後有一個通道上的專門店像是小叮噹、ROYCE 等幾乎都是快速走過沒辦法停下來看,還有下次的話我要前一天晚上先來機場住!

新千歲機場 新千歲機場 SNOWMIKU SKY TOWN SNOWMIKU SKY TOWN 新千歲機場

最後的最後就放上一張這次拍到的一張我很喜歡的照片吧。

小樽市

世界上最透明的故事

2024年12月23日 17:36

世界でいちばん透きとおった物語

(圖是日本的四個季節不同版本的封面)

可能會捏到的感想!上市一陣子了,本來是想要找個好日子幫書拍照後才發表,不過實在拖的有點久了,剛好又找到上面那張圖,就決定拿來用了。

總之還是多一行警告一下XD!

其實因為宣傳關鍵字的關係,加上最近有不少日本印刷品有用到這特性(像是獵人、還有報紙廣告),我一拿到書就發現其中奧妙了,不過其實這個奧妙讓我閱讀時的體驗,也確確實實的和其他紙書不一樣,簡單的形容,就是視野內的畫面特別乾淨、明亮,不過這些形容又太過強烈,思考了一陣子,確實用「透明」來形容非常適合,甚至我覺的作者杉井光在撰寫時,文句的挑選可能也有為了呼應「透明」而下了不少功夫,結果就是這個「透明」的主軸其實延伸到各個地方:故事主軸、文章風格、出版形式、閱讀體驗都有,要稱為世界上最透明確實當之無愧。

然後仔細研究一下發現日版是一年前出版的了,所以最近常見日本的印刷出版品利用這特性或許就是這本書帶起的風潮。

回到閱讀體驗這件事上,其實在我意識到這件事情之後,我聯想起作者另外一本我剛好前陣子才在看的輕小說「樂園 NOISE」,在第一集第一段就有提到女主角當時追求的是現實上不可能存在的無雜音的音樂,男主角後來用電腦生成無雜音的音源讓女主角實際有機會聽到自己演奏出的無雜音的音樂,要知道作者描寫音樂的文字是評價很高的,但是這邊卻完全沒有無雜音音樂的形容(一部分是因為用耳機主角自己沒聽到),但是其實也給讀者一個懸念,好想聽聽看比比看所謂無雜音的音樂啊。

或許只是想太多,不過或許,世界上最透明的故事 那個奧妙的緣起其實就從樂園 NOISE 提到的那個無雜音音樂,只是他用不是音樂的方式傳達給讀者了。

最後想談談這本書本身,「光是能出版,就稱得上是奇蹟了。」這件事,要我形容的話,這本書就像是我們現在看的一些文藝復興時代的藝術品,在工藝上達到一個時代登峰造極之下的產物,未來的人看到只會覺得「太精緻,做的太巧妙了吧,難以想像用現在的人力要做出一樣的東西」一般這樣,足以作為代表紙書時代的一本書。

補一些連結:

最後列上人名 作者:杉井光 譯者:簡捷 責編:菜承歡

突然想到漏了一件事,這就一定要看過才知道了,就是那個最後那個詞,我以為是謝謝你來到這個世上之類的溫馨想法,結果看到有人說是謝謝你給我這個點子,思考一番後覺得後者比較對,因為對兩個人來說都是成立的想法,不過就比較冷血一點就是。

btw 作者收到中文版,還有確定要出續集了。

世界でいちばん透きとおった物語、台湾版の見本誌をいただきました。
完璧な形で作っていただいたこと、ただただ感謝と敬服です。 pic.twitter.com/PD2GP2eq6j

-- 杉井光 (@hikarus225) October 10, 2024

『世界でいちばん透きとおった物語2』、来年の1月末発売です。続編は今年中に出す、と各所で言っておきながら原稿が遅れまくって今年中に間に合いませんでした。申し訳ない......https://t.co/Xq3E1CJsOJ

-- 杉井光 (@hikarus225) November 25, 2024

InnoDB 修復紀錄

2024年12月22日 17:24

前兩天意外發現 blog 的資料庫(MariaDB)死掉起不來了,加上我剛好沒有新的備份,所以這兩天就花了些時間慢慢修復,總之首先,我根據一開始的錯誤訊息大概搜尋了一下,發現說可以把 /var/lib/mysql 目錄下的 ibdata1ib_logfile0ib_logfile1aria_log_control 等檔案砍掉試試看(當時完全不知道是什麼),於是我簡單備份後就砍掉重啟試試看,結果 MariaDB 真的就復活了,只是我接著要跑 mysqldump 時就出現其他的錯誤:

mariadb-dump: Got error: 1932: "Table 'blog.mt_asset' doesn't exist in engine" when using LOCK TABLES

而且因為我的 shell script 太簡單,沒檢查結果,這錯誤還把我的本地備份覆蓋掉了😂,總之搜尋研究一陣子之後,發現這問題是因為 mt_asset 資料表是用 InnoDB,然後 InnoDB 有些資訊是集中在 ibdata1 的,不能只靠資料庫目錄下的 mt_asset.* 檔案來還原,結果我的作法就是把備份的 ibdata1 還原回來,試著在 my.cnf 裡加上:

[mysql]
innodb_force_recovery = 1

如果用 service 的話就要放到 [mysqld] 區段內,數值從 1 試到 6,然後 su 到 mysql 這個帳號下手動執行 mariadbd 指令來啟動資料庫,其實第一次嘗試是失敗的,到第二次試到 6 時才成功,然後趕快跑 mysqldump,結果有成功跑完,不過因為之前 mt_asset 有出過錯誤,我就認真檢查了一下 dump 出來的資料,結果果然, mt_asset 只有結構沒有資料,於是又檢查其它的 InnoDB 資料表的 dump 的輸出,結果是有的有資料有的沒有,不過由於資料本身應該還在,所以我接著嘗試用 mariadb 命令列模式進去 DB 手動 SELECT mt_asset 的資料,發現真的都還在,不知道為何 dump 會失敗,不過總之我就試試看死馬當活馬醫,用 mysqldump 匯出單一個資料表的資料:

mariadb-dump -u user_name -p db_name mt_asset > blog.mt_asset.sql

結果,竟然成功匯出了,於是就手工把每個有問題的 InnoDB 匯出,然後手工整合回去全資料庫的 dump 出來的 sql 檔案,有些資料表還是沒資料,不確定是不是本來就沒有的,不過看起來也不是重要資料,不影響 MovableType 運作,所以就不管。

然後因為我好像用不太到 InnoDB 的優點,加上發生過一次這種事情,冷備份比較簡單的 MyISAM 還是比較適合我,就順手都改成 MyISAM 資料表;同時,又想到因為我的 CHARSET 還是用 utf8,所以一直都還不能用 emoji,之前也有想過要換到 utf8mb4,只是一直沒動手,就趁機一起手工改了 mt_entry 表下的幾個相關的欄位:

`entry_title` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`entry_excerpt` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`entry_text` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`entry_text_more` text CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL,

這樣大致上就把 sql 檔案準備好了,回到這次發生問題的原因,我推測是用 pacman 更新套件時,更新到 MariaDB 後,不知道為何造成 ibdata1 檔案損毀,一開始的錯誤訊息試有看到說某個檔案是 MariaDB 10.4.8 建立的,所以我是先退回 10.4.8 然後才進行上述的修正,還好上次已經操作過一次了,之後為了避免再次有太久沒更新出事,就決定也要把 MariaDB 更新一下,所以其實是先更新最後才把 sql 檔案匯入的。

更新完把 /var/lib/mysql 目錄下的 ibdata1ib_logfile0ib_logfile1 都砍掉,重新建立資料庫後才匯入,還好一切順利,詳細的說明其實在 MariaDB 的 Knowledge Base 也都有,最後就補上兩個我覺得很有幫助的文件:

  • Cold Backup: 一樣是 MariaDB 的 Knowledge Base 的官方文件,有提到 InnoDB 要冷備份時要備份哪些檔案;
  • Tag: InnoDB: Error: tablespace id in file: 一篇說明如果這時候要直接還原 InnoDB 資料表的話要怎樣弄,非常麻煩(網站已死,所以是 Internet Archive 的)。

另外補充,如果要在 MovableType 4.x 使用 emoji,除了資料庫欄位的設定外,還需要修改一點程式碼,詳見我的修改紀錄 🥳。

onAutofill

2024年10月19日 13:17

Credit Card autofill

在現在這個網路標準橫行的時代,要遇到還沒廣泛標準化的東西其實是越來越難了,不過我最近還是遇到了一個,那就是 autofill 的偵測。

首先要說的是,autofill(自動填入)和 autocomplete(自動補完)嚴格定義下是不一樣的,雖然都可以透過 autocomplete 來控制相關的行為,但是 autocomplete 其實只能算是 autofill 的一種,而我遇到的就是非 autocomplete 的,信用卡資料的自動填入,那問題在哪呢?

問題就是這種 autofill 發生時,瀏覽器不一定會觸發 change/input 事件,如果表單設計成自動檢查表單輸入,然後輸入都正確才讓人送出的話就會有使用體驗的問題發生,因為這種設計的欄位檢查通常就是綁在 <input>change/input 事件上,結果就是如果瀏覽器自動填入,然後又沒觸發 change/input 事件,於是就不會執行到欄位檢查,表單也就會一直維持在無法送出的狀態,產生的副作用就是使用者體驗反而比按下送出按鈕才作表單檢查還要來的差。

那麼在 Web 標準中,change 事件應該何時觸發的定義是為何呢?在 HTML 4.01 是這樣寫的:

The onchange event occurs when a control loses the input focus and its value has been modified since gaining focus. This attribute applies to the following elements: INPUT, SELECT, and TEXTAREA.

按照古時候網路標準的規範,autofill 不是使用者和 DOM 之間的互動,沒有經過 focus blur,所以沒有觸發 change 事件也是合理,事實上也就是現在部分瀏覽器的行為;不過在現在的 HTML Living Standard 是這樣寫的:

The change event fires when the value is committed, if that makes sense for the control, or else when the control loses focus.

觸發的時機除了失去 focus 時之外,還多了資料 commit(提交)時,變成兩種時機,而這邊的提交主要指的是像 type=color 或是 type=date 那種,瀏覽器有支援,有提供另外頁面內的小工具讓使用者方便挑選值的時候,使用者選好,瀏覽器更新值進入 <input> 的 value 的動作,那 autofill 更新值該算是 commit 嗎?其實文件內也是有講到的,就在同個章節的後面:

When the user agent is to change an input element's value on behalf of the user (e.g. as part of a form prefilling feature), the user agent must queue an element task on the user interaction task source given the input element to first update the value accordingly, then fire an event named input at the input element, with the bubbles and composed attributes initialized to true, then fire an event named change at the input element, with the bubbles attribute initialized to true.

這段就是說當瀏覽器代表使用者改變 input 的值時,也是要發一個 input 一個 change 事件,這段文字的重點在於 "on behalf of the user",就是「代表使用者做事」,後面舉的例是 prefill 時,prefill 通常發生在 帳號/密碼 欄位,發生時間點又不太一樣,可能是在 render DOM 時就發生;不過根據文字解釋其實 autofill 應該也符合 "on behalf of the user"。

雖然 HTML 標準有規範了,但是現實世界總是不會這麼美好,不然也不會有這篇文章了,那麼現實世界是怎樣呢?我遇到的狀況就是有些瀏覽器是照著舊的規範,完全沒有事件,發現問題後我就上網搜尋一番之後發現,這問題其實已經很久了,早在 2010 年,@avernet 就寫了一篇 Autocomplete and JavaScript Change Event,紀錄了當年的這個問題,根據不同欄位、不同瀏覽器會有不同的行為,即使到了今天,也還是同樣情形,文章最後建議的解法也是很無奈的:

  1. 關掉相關功能 autocomplete=off
  2. 定時檢查

總之就是讓人不喜歡的解法,那麼時至今日,有沒有比較好的方法呢?其實還真的有,而且蠻聰明的,Klarna 的 Tommy Brunn 在 2016 年寫了 Detecting autofilled fields in Javascript 一文介紹了這種方法,透過 CSS pseudo-class :autofill 和 CSS animation 配上 animationStart 事件,首先 CSS 這樣:

input:autofill {  
  animation-name: autofill;
  animation-duration: 500ms;
  animation-fill-mode: both;
}

@keyframes autofill {
  from {
    background: var(--color1);
  }
  to {
    background: var(--color2);
  }
}

然後 JS 監聽事件並確定動畫名稱沒錯,就可以做事了:

inputNode.addEventListener('animationstart', (event) => {  
  const { currentTarget, animationName } = event;
  
  if (animationName === 'autofill') {
    // do what ever you want, or
    // trigger `change` event
    currentTarget.dispatchEvent(new Event('change'));
    // trigger custom event
    currentTarget.dispatchEvent(new Event('autofill'));
  }  
}, false);

完全成為真的 event based,不用定時檢查了,不過缺點是要 CSS 搭配,不是純 JS 的方案,維護上比較麻煩一些,另外就是 Tommy Brunn 文章內用的是 :--webkit-autofill,但是現在完全可以用沒有 prefix 的 pseudo class 了。

以上的程式碼範例就可以處理好瀏覽器內建的自動填入事件,不過現實世界除了瀏覽器內建的自動填入,還有很多的第三方工具支援,像是各種 password manager: 1Password, LastPass, Dashlane 等,這些工具自動填入的行為又不太一樣,我確實有發現有其中一兩家的行為也是 value 會改變,但是不會有 input 和 change 事件,幸好這些工具都會加上各自自訂的 attribute,所以可以另外透過 observer 監看 attribute 的變化來判斷是否有相關的事件,目前我所知道有以下的 attributeName 可以檢查:

  • data-dashlane-autofilled Dashlane 的
  • data-com-onepassword-filled 1Password 的
  • chrome-autofilled iOS Chrome,超容易漏掉

至於 LastPass 目前測試結果是不會有自訂的 attribute,但是會有 change 事件,所以也可以照常運作(不過相對的就完全沒有提供給使用者的視覺提示好像怪怪的)。

這篇內容大概就到這邊,雖然沒有提供很完整的程式碼,不過這些資訊應該很夠幫助其他人完成 autofill 事件的偵測了,其實這次弄信用卡資訊的輸入欄位真是費了不少心力,很多細節可以弄,也很多 domain knowledge(都靠 lib 搞定就是),真是想不到只是信用卡欄位也這麼多眉角。

溝通

2024年5月16日 20:19

とこなめ招き猫通り

照片是常滑招財貓大道的「除憂解難」。

沒想到這週這麼熱鬧,前後兩天分別發生兩件和溝通有關的熱烈討論(網路對罵?),第一件事情比較少人知道,是發生在 COSCUP 的 telegram 社群,雖然是公開社群但是因為沒有直接的公開網址所以我就不寫網路 id 了。有社群朋友(後面用 A 代稱)想要辦 BoF 需要一些硬體設備,但是因為今年的 BoF 公告還沒出來,所以他想知道大會方是不是有機會能協助(幫忙借用設備),可以的話就會提出申請,其實如果熟悉 BoF 的應該都知道大會方只有提供場地,不過我當時在開車也沒辦法馬上幫忙回,總之就有其他朋友幫忙回了,四貓作為工作人員也在幫忙釐清對方的需求,當時大概就是有位社群朋友回文時多了一句:

但如果認為大會方該要提供的話,你們預期大會方能從哪搞到這個資源呀

其實我覺得 A 一開始的文字並沒有這樣的意思,但總之結果這段文字就激起 A 的情緒了,然後就口氣變差,出現了一些情緒回文,接著就變成 A 和一些其他社群朋友開始互吵,其中心穎四貓和我其實都有想要幫忙緩和一下,不過雙邊都沒想要暫停一下,所以就一發不可收拾吵了整個下午,總之直到最後都還是有些情緒文字。

其實這件事有點讓我聯想到「精製動畫坊(HGA)」,非常早期就在台灣經營動畫模型領域的商品,年過四十小時候就有接觸動畫的人可能多少都會聽過 HGA 的大名,黃老闆其實人很好,然後店內也有一群熟客,其實很多人都在圈子內有點名氣,不過就是這群熟客其實也某種程度製造了一個 AT 力場,會讓不在圈子內的人想要進去有阻力,其實我在光顧 HGA 的幾次經驗也沒真的實際接觸到黃老闆以外的那些人,但是我還是要說那個氛圍確實是存在的,有朋友就說是「成也熟客、敗也熟客」。

當然我不是在說 A 的發言都沒錯,A 後來的幾則發言我都覺得這我也很難幫忙緩和了,所以要我說的話,在那個時間點這或許已經是一場無可挽回的難過事件了,不過我還是想說,非當事人的幫忙有時候真的是沒幫上忙啊。

然後隔天發生的另一件事,則是在推特上的「最困難的就是和工程師溝通了!有夠固執不懂變通!」炎上事件了:

這也是位年薪百萬的學員喔!恭喜恭喜!

她說,她現在不只要跨部門討論專案,還得跨國溝通,公司裡什麼樣的同事都有。

「最困難的就是和工程師溝通了!有夠固執不懂變通!」

這是第 N 位跟我抱怨工程師有夠難溝通的學員了。
(我也這麼覺得!)https://t.co/s7jjdgJ4I4

-- Akane Lee (@akane_lee) May 14, 2024

然後推特上一堆工程師就受不了了,紛紛分享出他們遇過的很瞎的或是他們的看法,當然也是有一些比較沒意義的酸文,不過其實有一些蠻值得一讀的,像是海總理的這串(共九則,請點過去看完整文串):

覺得對方難溝通,大多是因為

「你只想得到你想要的,不想聽你不懂的」

-- 海總理 (@tzangms) May 16, 2024

其實這個炎上事件我覺得是屬於用詞不精確所引發的,因為 Akane 一開始的文字直接使用「工程師」,沒有加上「部分」或是「一些」之類的修飾,就直接被當成是貼標籤,其實她也有提到他有合作過很好溝通的工程師,更何況她的課程也不是溝通課,文章的受眾也不是工程師,這也讓我想到我在研究所寫論文時,有一個眉角就是不能用什麼「最好」、「全部」這種太過絕對的形容,而是要用「最好的之一」、「大部分」等比較相對一點的,因為你永遠不知道你的最好,是不是真的「最好」,或是更直接的利害關係,口試前你永遠不知道口試委員有沒有知道你沒查到的資料;當然,或許就算一開始的文字就有修飾過,也無法完全避免這件事情生,Ash 是這樣說的:

看大家討論這麼熱烈,大家在職場上真的都是傷痕累累呢。我們都是帶著過去的傷痕去跟下一段新的職涯邂逅。

有時候你覺得同事很尖銳,但他針對的可能並不是你,而是整個環境或者他的過去。

-- Ash Wu (@hSATAC) May 15, 2024

然後我才發現原來這已經是工程師的集體創傷了嗎?難怪大家反應這麼大,後來還有說到「男性說教(mansplaining)」這我就覺得不太行,不過她還提到收到的回饋是女性工程師普遍比較好溝通,所以我也實在很難說到底是什麼狀況,畢竟我不是當事人,不過確實在這個圈子要說完全沒有性別差異/問題也是不可能,只是這到底算不算是「男性說教」,我就真的很存疑了,畢竟這也沒有客觀的計量方式來判斷,畢竟我才發生過跟親近的人解釋一些事情,之後發現他完全沒聽進去,然後才了解到:「啊,我剛剛被認為是在男性說教。」

最後還是來分享點比較有趣的東西吧,Huli 趁機也重貼了一個老影片「The Expert」:

這影片其實我也很久以前就看過了,只是沒想到下面 Tzeng Yuxio 貼了另一個真正的專家完美達成上面的需求的過程:

十年前的解答我今天才看到!

同場加映:剛好昨天在 FB 的「陳名珉」粉絲專頁有這麼一篇文章,是在講傳統市場的變化,其實追根究底也是溝通的需求在哪一端的問題(海總理的那串)。

QR Code 發明歷史展

2024年1月12日 23:42

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

去年去名古屋時,有去了 Toyota 產業技術紀念館,館內的展覽其實著重在技術介紹,分為紡織和汽車,其實我以前不知道 Toyota 是紡織起家的,展覽的內容非常不錯,很多機器都還可以運作,而且也可以讓訪客按按鈕自動展示,非常用心:

トヨタ産業技術記念館

トヨタ産業技術記念館

トヨタ産業技術記念館

トヨタ産業技術記念館

而除了兩個展覽館之外,其實這段時間在入口附近還有一個特別企劃的 QR Code 發明歷史展,應該是因為去年是 QR Code 發明的 30 週年吧,我自然是對這個很有興趣,雖然展區小小的但是也有些收穫,為了寫這篇文章我也又查了不少資料。

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

QR Code 的很多資訊其實在 Wikipedia 上都有,不過我是看展覽才注意到原來是因日本電装(Denso)內部的需求而要開始研發二維條碼,設計出 QR Code 的原昌宏當然也是日本電装的員工,該社的研發能力蠻強的,日本電装還有一個東西是我以前在介紹日本郵遞區號時有提過的,就是日本導航常用的 Mapcode,都是非常廣泛使用的東西,由於是 Toyota 子公司,這些發明也都是圍繞著汽車相關而生的,Mapcode 主要應用在導航,而 QR Code 當初主要是為了「かんばん」系統的汽車生產零件管理,而會能像現在這樣廣泛的被使用,我覺得有兩個主要的因素,第一個是原昌宏在設計 QR Code 時,底層的設計很通用,而沒有特化於汽車零件,另一個就是日本電装當時雖然有申請專利,但各種考量後決定公開來讓大家自由使用,接下來就來看看展覽的內容吧:

專利書:

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

原昌宏是設計 QR Code 的人,渡部元秋則是主要的軟體開發者,一開始的研發團隊就只有這兩位,另外三位當中的野尻忠雄是主管,另外兩位則是 Toyota 中央研究所的成員,應該是協助軟體開發的。

還有列出其他的關係者,以前比較沒機會看到的團隊成員、負責人還有推動公開標準化的成員等:

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

讀本裡面有介紹其他的二維條碼:

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

簡單的歷史:

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

中間有提到開放的原因之一,就是為了搶佔市占率,然後他們可以從 code reader 和印刷市場來賺取收益。

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

在各個關係者當中,負責標準化的柴田彰於 2022 年八月有在 Digital Practice 發表「QRコードの事業戦略と標準化」一文,內容很多,有各種背景介紹,競爭對手,還有像是 Toyota 內部的「かんばん」系統在導入 QR Code 前後的標籤;與其他二維條碼的比較,有一組對照不同條碼相同設定相同資料的面積差異;還有標準化的各種紀錄,像是 ISO 15394 的包裹標籤在使用 QR Code 前後的差異等,非常值得看看。

然後就是我覺得最貴重的研究手稿了,不知道有沒有數位化保存:

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

然後是各種的變形版 QR Code:

QRコード発明ヒストリー~QRコードはトヨタグループの発明だった!~

展覽中列出的有:

  • Micro QR Code: 比較小的 QR Code,只有一個定位點;
  • rMQR Code(Rectangular Micro QR Code): 矩形的 Micro QR Code,可以弄成長方形列印在比較狹窄的地方;
  • SQRC(Security QR Code): 包含隱藏資料區的 code,要特定的 code reader 才可以讀取到隱藏內容,還有配合用特殊油墨列印的防複製的方法;
  • Frame QR: 中間有留白一塊可以任意加工的版本。

除了以上四種之外,其實還有:

  • iQR: 也是可以做成長方形的 code,不過其實是過渡規格,沒有標準化,相關的設計後來就是用在 rMQR 中;
  • tQR: t 是 toughness,是東京都營地下鐵和 Denso Wave(日本電装子公司,負責 QR Code 的部門已經拆分至此)合作的,專門用來判斷列車車門位置用的,官方的介紹就有提到為什麼需要這種機制判斷車門位置,為的像是對應不同編成的列車或是不同車門配置的車廂等等,另外有一份日本自動識別系統協會的會報(4-6 頁)裡面也有介紹部分的系統運作機制,而 tQR 就是這個系統所使用的特規的 QR Code,第一眼應該就可以發現三個定位點的位置不一樣,然後編碼的容錯率則有加強到 50%。

tQR

然後除了這些,回來查資料後我還發現其實普通的 QR Code 就可以多個連接,日文就稱為「連接」,英文則是稱為 Structured Append,最多可以連接 16 個,如下圖:

Structure Append QR Code

這個機制除了可以增加資料量外,也是早期在 iQR 或是 rMQR 出來前,可以把正方形轉成長方形的一種方式,不過有支援的裝置和軟體比較少,例如我用 iPhone 相機就不支援一次全部讀取,然後如果要讀取單一個 code 就可能會跑出「找不到可用的資料」,事實上,前面介紹的 QR Code 的變形版本,iPhone 內建的相機也都不支援。

Structure Appended QR Code

所以接下來就要介紹最後一個收穫了,官方的 QR Code Scanner App:クルクル(QRQR),其實我很久以前,就一直很好奇到底有沒有所謂官方的 QR Code reader 了,古早以前都是用 ZXing 的 Barcode Scanner,不過其實這是 Open Source 專案,現在也已經沒在維護了。而 Denso Wave 的官方 app,則是在 2017 年才和 Arara Inc(Denso Wave 是股東之一)一起合作推出的,當時名稱叫「Q」,後來則更名為 QRQR,免費,有多語系也沒廣告,佛心,然後支援很多種格式,包括了 Barcode、兩種標準的 QR Code,還有 Micro QR、iQR、rMQR、Frame QR 以及剛剛最後提到的連結的 QR Code,解碼速度也很快,不過曾經有過偷收集資料的壞紀錄,所以要不要用就自行評估啦。

クルクル(QRQR)

源氏香 南知多

2023年12月9日 15:41

源氏香

前陣子去了一趟名古屋,一開始其實只是想說帶小孩去樂高樂園,結果行程規劃從一開始的名古屋市區和周邊為主,弄到後來變成市區五天租車五天的,然後打算最後一天住在機場旁邊,就想說那前一天就在附近玩玩好了,要去的地方就因此多了一個一開始完全沒考慮過的「知多半島」了,當然也有研究住宿,當時也有找到「源氏香」這間旅館,不過那時候已經有安排了下呂溫泉的溫泉旅館,所以就沒考慮這個價位的住宿了,就在一切都安排好後沒幾天,突然就看到 FB 上有人分享到他們有特別的,針對台灣人的免費體驗方案,就是入住後要在 SNS 上分享。

研究加考慮了幾天,決定還是給他申請下去,然後因為有帶小孩的關係,我也有先寫信聯絡確認過沒問題(我日文大概是簡單的可以讀但不太能寫、所以靠 ChatGPT 翻譯,旅館方他們的回信也都有用工具翻譯成中文,所以溝通還算不是太大的問題),當然有另外收費,本來有點期待可以在房間看夕陽的,不過當天因為從下呂出發,中間還去了犬山城,然後七早八早就天黑,所以到達旅館時,已經看不到太陽了,還好還有晚霞。

源氏香 夕陽

源氏香 夕陽

源氏香

大門邊還有兩盆火和一個鼓,後來看到其他人分享說請他們接送的,到飯店時還會有人擊鼓歡迎,不過我們到的時候停車場其實已經黑麻麻的了。進入大廳後馬上就聞到很特別的香味(飯店的特色),辦理入住後有抹茶和小點心,小點心我當時沒吃,回來才發現也是南知多的點心店的類似麻糬的小點,還蠻好吃的,意外的是還送了紙鶴的摺紙和色紙給我們的小朋友。

源氏香

源氏香

房間是他們最標準的和室,另外有獨立廁所、乾濕分離的衛浴、洗臉台還有兩個,其實空間也是很大,棉被則是自己鋪,不過有需要的也可以播電話到前台請他們幫忙,其他備品設備都很齊,可以自己燒水,還有一壺冷水(有冰那種)可以飲用,清潔度也沒大問題,我沒特別刁難的下去找哪裡沒乾淨,當然有些設備有點舊,不過都維護的還不錯。

源氏香

源氏香

源氏香

進到房間時天色已經幾乎要全黑了。

源氏香 夕陽

然後就是特色的品香體驗了,除了官網介紹的體驗的活動之外,房間都有固定的準備了五種香可以自己點起來品聞。

源氏香

源氏香

源氏香

我們試了其中三種,左邊兩個那個就比較接近台灣拜拜燒的香,不過第一款點的藍色的和第二天點的桃紅的都很特別,而且是好聞的味道,文章的前面沒詳述,其實源氏香號稱是日本第一間以薰香為主題的旅館(1999 年開業,其實不算老,有點意外這之前沒人想過經營這樣主題的旅宿),從一進到旅館就可以感受到,不同的公共空間有著不同的香味,像是大廳和電梯就明顯用了不一樣的香味,都是很好聞且會讓人舒服可以放鬆的味道,然後讓我印象深刻的是,這些味道其實我都沒有聞過(記憶中沒聞過),味道濃郁卻不也會刺鼻或讓人不舒服,而且我不會過敏,對我來說是個非常新的體驗,就是開了眼界這樣,原來有這樣的香味,然後原來可以這樣子來使用這些香味。

接著休息到晚餐時間,日式溫泉旅館的定番:懷石料理套餐,也有準備好中文版的菜單給我們看,有開胃菜、生魚片、肉類拼盤、蒸菜、天婦羅等,食物很美味,份量也夠,吃完時就很飽了,最後還給我們豆皮壽司帶回房間當宵夜,另外就是吃飯時都是單獨包廂,雖不是完全獨立房間但也有簾子可以完全檔著,隱私還是顧的不錯。

源氏香

源氏香

源氏香

源氏香

源氏香

飯店內很多地方都有展示薰香相關的東西,尤其是香爐。

源氏香

飯後休息一陣子後,我們就去洗澡泡湯了,旅館內的澡堂,在最頂樓十樓有男女分開兩間露天澡堂,然後八樓九樓各一間室內,一男一女的,我們想說晚上先泡室內的就好了,隔天早上再去泡露天的,不過這個決定有點失算,首先是兩間室內澡堂其實最早的規劃是包場的貸切風呂,現在改成公用的,所以很多基礎設備是沒有的,像是可以上鎖的櫃子,所以房間鑰匙只能和毛巾衣物一起塞到竹籃裡面,我本來覺得很奇怪,想說完全沒有也太奇怪,直到隔天去頂樓一看,才發現兩邊設備差太多了吧,頂樓不但更衣休息空間都大很多,有私人櫃子,還有飲水機和冰櫃,有免費冰棒,但是只有下午到晚上開放取用,所以我沒吃到...,要是還有其他人來造訪記得還是以露天風呂為優先,不然也是可以泡湯完去十樓的休息休息,晚上可以去拿冰棒吃,然後除了兩個澡堂的差異之外,還有一點要特別注意的是,澡堂開放時間不是非常棒,晚上只有到十一點,隔天早上則是九點(退房是十點)。

泡完澡,回到房間休息,看看外面,發現到看得到內海對岸(看地圖是伊勢神宮那方向最亮)的燈光,然後光害很少,所以星星也很多,用相機拍下去更多。

源氏香 夜景

源氏香 夜景

可以拍星空,算是很意外的收穫,這天還是有月亮跑出來的日子,不然應該可以拍的到更多星星。然後晚上真的是很冷,南知多冬天應該是淡季吧,雖然有虎河豚,但是好像沒什麼其他觀光客。

隔天早上,意外的發現,這個地理位置竟然可以拍日出,但是我起床時太陽已經跑出來了一陣子了,我真是太小看日本的緯度了,

源氏香

源氏香

源氏香

早餐一樣是在二樓餐廳,白天配上窗外景色真是漂亮很多,早餐也很豐盛,而且就有河豚可以吃了,不過是比較小隻的!

源氏香

源氏香

源氏香

因為澡堂早上九點就關的關係,我們排很早吃早餐,吃完飯後就去泡露天溫泉,外面依然很冷,躲在熱水裡都不敢出來,不過風景很好,其實在決定參加前,有看 Google Map 上的一些評論,有很多人說到露天溫泉水裡有很多羽毛之類ㄉ,這次我倒是沒遇到,不過不確定是因為冬天比較沒鳥來亂,還是有認真改善這個問題就是。

泡完湯後回房間休息,最後又點了一枝香。

源氏香

大廳早上還有一個迷你朝市,有賣些漁產,還有賣早餐的河豚(已經是處理成一夜干加上真空包裝了),逛完我們就退房去南知多的行程了,飯店的人也出來送別,還在門口等我們到準備好發車離開。

整體而言,這次的住宿是滿意的,雖然有發現一些小問題,不過重要的幾個項目都有不錯的體驗:房間舒適、薰香讓人印象深刻、餐飲也好吃、溫泉、海邊景觀很棒,雖然是特別的方案但是也沒給我們特別關照或是過度干擾,只有入住退房時有跟我們提醒到活動相關的規則。算是個花錢後不會讓人覺得不值的經驗,不過我發現南知多去的外國觀光客好像不多,甚至有很多是只能打電話預約的當地住宿,所以就來附上南知多我簡單研究一下覺得可以值得去看的景點(需自駕):

  • 蝦仙貝之里工廠:本來是想讓小孩去作仙貝,但是因為本來就打算去機場買一箱,所以沒時間就放棄了;
  • 羽豆岬:知多半島最南端,有神社、小展望台,還有個羽豆崎城跡,很小沒被列到兩百名城中;
  • 師崎港 朝市:就在羽豆岬附近,不是在主要道路旁邊所以我錯過了,扼碗,看到別人去逛覺得蠻有趣的;
  • 海鮮餐廳:附近很多海鮮餐廳,冬天有河豚,我有預約一間吃了虎河豚套餐,之後再來介紹(其實源氏香也有河豚套餐);
  • 其他還有一些甜食點心的店,像我本來有查了一兩家 parfait 的咖啡廳要去,結果吃完河豚套餐就太飽了;
  • 常滑市在機場旁邊,算是比較熱鬧的地方,如果不規劃到更南邊,或是沒有開車,光是常滑市排個半天一天也不錯,車站附近有陶瓷器散步道,氣氛很不錯,還有很多招財貓,靠近機場那邊還有超大的 AEON Mall 和 Costco(見照片),然後這次去了我才發現該市也是 想哭的我戴上了貓的面具 的取景地。

digital envelope routines::unsupported

2023年9月15日 11:41

Node.js 16 LTS 已經結束維護,所以手上的東西就開始需要升級升級,然後就必須要來正面面對這個我逃避已久的錯誤訊息:

digital envelope routines::unsupported

這錯誤基本上就是發生在幾個網站的專案,尤其是 build 專案時特別會容易看到,而且這個錯誤其實和一般看到的 JS 錯誤長得不太一樣,全貌其實是這樣:

digital envelope routines::unsupported

Error: error:0308010C:digital envelope routines::unsupported

首先是錯誤訊息,前面有一些 hex 值,不知道是什麼,然後下面 trace 的地方,可以看到幾乎都是 node_module 內的東西,不是因為我們自己的 code 造成的,所以就很讓人困惑,想說是不是什麼系統問題、還是有什麼偷用非公開 API 造成不相容的狀況。總之以前就是遇到這個問題就是又降版回來,沒有仔細深究,這次終於要來認真處理,不過搜尋結果,幾乎都是說加一個 --openssl-legacy-provider flag,都沒人說到底是什麼問題,尋找許久,終於在 StackOverflow 找到一則最正確的答案,沒想到和 OpenSSL 1.x 的生命已經到盡頭有關。

結果這個錯誤,其實是因為 Node.js 17 開始,從 OpenSSL 1.x 換到 3.x,然後 OpenSSL 3.x 不是向下相容的,所以有些東西有機會出錯,這邊爛掉的,其實是一些 legacy 的 hash method 預設是拿掉的,而 Webpack 在建立 bundle 檔案時,如果檔名有用到 hash 的話,預設的 hash method 用的就是已經被淘汰的 md4,然後 md4 是用 Node.js 的 crypto 來呼叫 OpenSSL 做事,Node.js 的文件也有提到支援的演算法是依據你的 OpenSSL 版本和系統而定,所以其實並沒有保證 md4 一定可以用,而如果使用了 OpenSSL 不支援的演算法,跑出來的錯誤訊息就是像上面截圖一樣特別了,然後我還特別去用 OpenSSL 3 cli 跑跑看,結果出來的錯誤訊息真的就是差不多:

OpenSSL 3 error

使用 flag 開啟舊演算法的支援其實我覺得還算可以接受,畢竟是 build 而已,不是拿來跑服務,不過這個 flag 似乎有點特殊,似乎不能直接放在 NODE_OPTIONS 裡面,而且同個程式庫要是拿到舊版 Node.js 環境去跑,加這個 flag 反而跑不起來,所以最理想還是把問題解決掉。

那這個問題應該怎麼處理呢?其實簡單說就是把套件升級升級就好了,因為現在的套件新版本都有處理這個問題,不過走上升級這條路之前可以先試試看 StackOverflow 上的解法(有可能讓你專案爛掉,請先備份):

npm audit fix --force 

如果你用的是 yarn,沒有 audit fix 可用,但是也有人提供用 npm 來修理的流程,不過我是沒試過這個流程,我自己有一個專案是靠 yarn upgrade 升級後解決問題的(實際上是把所有有用到的 loader-utils 都升級到 2.0.4,本來有個套件用到 2.0.0),剩下的還是無法修好的就要靠手工了,然後因為我處理的網站只有 Gatsby 和 CRE(Create React App) 兩種,所以以下就是只有說明這兩個系統的為主,兩者其實都是使用 Webpack 作打包工具的,而 Webpack 是從 v5.61.0 開始保證支援 Node.js 17 的,我稍微查了一下 Gatsby 是從 4.2.0,而 CRA 的則是要最新版 react-script 5.0.1 才保證支援,為什麼說是保證呢?因為 ^ 的 semver range 的關係,例如要是你的 react-script 是 5.0.0,那你本地可能會是裝到 Webpack v5.60.0,那就不支援 Node.js 17 了,像我就是有 Gatsby 3.x 的,升級到 4.x 就沒事了。

Gatsby 和 CRA 其實都還好,最慘的是 eject 過的 CRA 了,只能手工升級,基本上就是去 react-script 那邊,複製需要的檔案回到你的專案覆蓋過去,最主要的是 scripts/config/ 下的檔案,然後根據自己的修改紀錄把自己作過的修改改回去,接著更新 package.json 裡面的 dependencies,版本號就是參照 react-script 那邊的 package.json,最主要的就是 webpack 相關的,接著安裝套件後重新 build,要是還有一樣的錯誤,就看 trace 看看是哪個相依套件,看有沒有新版有修正就更新試試看,大概就是這樣,很容易漏東西所以會一直重複測試,蠻花時間的,不過最後 build 成功還是有成就感的。

PS. 還要小心其他升級的後遺症,如果是 app 最好要測試過各種行為,像我遇到 Webpack 5 不支援 polyfill Buffer 的問題,剛好那個錯誤又被 catch 掉,所以我 build 是沒問題的,就是測試跑不過,後來參考網路上的文章處理。

Vim License 的故事(下)

2023年9月11日 17:27

Vim License on choosealicense

接續前一篇

Mike 在 SPDX License List 這邊提出的問題則是,為什麼會有 Vim 要替換,但是 Vim Maintainer 不要替換這樣特別的情形,所以我就是認真的解釋,並且說明這是跟原作者 Bram 確認過的細節並附上討論,還有舉我前面提過那個最極端的例子,然後我猜最重要的是現實世界有沒有人這樣使用過,還好我還真的找到幾個專案有認真的把條款內的 Vim 替換掉(當然是連 Vim Maintainer 也換掉了),像是 Tagbar;我的 PR 是 2019/07/11 提的,然後一直來回到 9/25 回了最後一個回應之後就沒人回我了,之後到了 10/19 就突然被合併了(其實 SPDX 有定期的會議,應該是在其中有討論過要不要合併這個 PR 吧),接著等到 2020 一月我發了 PR 到 choosealicense 把 vim.txt 加進去,這次就蠻順利就合併了。

解決了上游問題,回到 Licensee 偵測的問題,我本來的 PR 沒被合併,而是 Mike 另外弄了一個比較通用的解法,是根據條款文本內的替換字串來動態調整差異的容許範圍,然後接著 2020/03/13,Licensee 9.13.1 作為第一版支援 Vim License 的 Licensee 發佈了,接下來,我理論上就只要等 GitHub 更新 Licensee 的版本就好了,這我倒是不太擔心,我是有種感覺 GitHub 是有在認真更新這些東西的,然後,時間快轉到三個月後,這段期間其實我都有偶爾上去 GitHub 看一下 Vim License 有沒有被偵測出來,想著要是終於完成的話就可以投個稿了,當時的時間大概是搞了三年這樣,總之因為過有點久了,我又去拉了最新版 Licensee 來測試,結果讓我嚇了一跳,竟然偵測不出來了!

Licensee 9.13.1

所以我又花時間下去查原因,結果是 2020/05/12 的 9.14 開始爛掉的,Mike 在 9.14 有修改一部分的演算法,在比對兩份文件時,因為有替換字串的關係,不可能用完全比對的方式,所以在有替換字串時,文件的長度差異會影響相似度,有個動態的 max_delta,如果替換字串後長度差異太大超過 max_delta 的話,就會被判定為不同的文件,在 9.14 則是把這個機制拿掉,直接把替換字串的影響弄進相似度之中,不過其中長度差異的懲罰太大,結果造成 Vim 專案本身的判斷失準,發現原因之後,我就發了 PR 調整懲罰的參數,讓 Vim 剛好可以過關這樣,結果我發 PR 時 Mike 早已經發現這個問題,也已經修改好開了 PR 只是還在 review,而該 PR 的方法是則是動態的根據替換字串的數量來調整文件長度差異的值,除此之外,Vim License 還被加到測試之中,所以以後應該不會再發生這種問題了。

然後,我就等這個 PR merge 等了三個月,不過還好 merge 後馬上就發佈 9.14.1 了,接著又回到等 GitHub 更新的時間了,我就一直等,一直等,偶爾上去 GitHub 的 Vim repo 確認一下,2021 年初我還寫信去 GitHub Support 問他們當時使用的版本是哪一版,Support 還有認真去幫我確認是沒更新,然後就這樣一直等一直等到 2022 年初,有天在用 GitHub 的搜尋功能時,突然發現到,可以用條件設定搜尋特定授權條款的專案,然後我就想說可以來試試看 Vim License:license:vim,結果竟然有符合條件的專案出現!讓我大吃一驚!原來不知何時 GitHub 已經偵測的到 Vim License 了,可是 Vim 那個 repo 結果卻還是不正確,深入了解一下,推測應該是 GitHub 有 cache 機制,找了一下文件發現到有提到說如果有發現專案的授權條款標示不正確,可以聯絡 support,於是我就發了 support request,這次很快就收到回應,然後在 2022/01/12 這天,確認了 GitHub 上的 Vim 專案有正確的顯示為 Vim License 了,歷時將近五年,那天我還發推慶祝了一下。

Search result of Vim Licensed repo

自己挖了將近五年的坑今天終於填平了,今年終於可以投個稿

-- othree (@othree) January 12, 2022

之後,終於可以準備投稿,然後順利投上 2022 年的 COSCUP,題目是「歷時五年的開源貢獻,GitHub 支援 Vim License 的故事」(影片投影片),在準備分享的過程也是遭受很多的電玩干擾阻力,分享過程還算順利,自己有點小不滿意不過還算可以接受,不滿意的地方是有些我一開始有想要分享的點忘了說,不過也是在這篇文章有補齊了。

其實在初期那段等待的時間之間,我還開了一個小專案 Vim License Gen,用來幫人產生自己的專案/軟體用的 Vim License 文件,為什麼要有這個工具是因為以前的純文字文件,每行的寬度大概都在 80 個字元以內,但是每個專案的名稱都不一樣,如果直接做替換,有可能會破版,所以這個工具一是會保持版面,二是它可以正確的只替換需要替換的部分,然後這個專案就是用 Vim License 開源,並且是用自己產生 Vim License 文件,然後我也把他列為使用 Vim License 的三個專案之一(另外兩個分別是 Vim 和 vim-pathogen),這也是個 self-hosting 的實現。

Vim License Gen

故事並沒有到這邊就結束,COSCUP 分享之後的目標就是把這篇文章寫下來了,不過一拖再拖,其中一個主因是以前我是先把文章寫好順便作整理,但是這次因為準備時間不夠,無法在分享前先把文章寫好,變成後寫,沒了趕稿壓力,加上又有像是王國之淚等讓人分心的東西,就一直無限期拖搞,直到今年 2023/08/05 傳出了 Vim 的作者 Bram Moolenaar 於八月三日因病過世的消息了。

Bram 過世之後,除了緬懷之外,大家也會擔心起 Vim 的後續誰維護的問題,還好 Vim GitHub 組織內還有另外兩位成員,其中 Christian Brabandt 算是接起主持人的角色,他同時也是我用很久的 vim-airline 的作者,然後很快的也發了 PR 做了一些相關的修改,像是在 doc 裡面緬懷 Bram、更新檔案內的維護者資訊等,只不過其中一項修改,改到了 LICENSE 檔案!改的地方當然就是那個高度耦合的 Vim Maintainer 的部分:

The current maintainer is Bram Moolenaar Bram@vim.org.

改成

The current maintainers are listed here: https://github.com/orgs/vim/people.

然後我還是被 ping 了才知道這 PR 已經合併,這下我可緊張了,其實我本來也是有在想 Vim License 的問題,只不過想說 Bram 才剛過世,可以晚點再提出,當時有想了幾個方案,理想上要能同時繼續支援現有的使用 Vim License 的專案、然後也要能讓授權條款內的文字正確的更新,不過當時不管哪個方案,都無法保證兩全齊美,當時想的方案為:

  • 方案一:更改條款內文並且發布新版,可能是叫 Vim License 1.1 之類的,缺點是可能需要重新提交提交新版的條款到 choosealicense,但是現況來看幾乎不可能符合提交的一千個專案的條件;
  • 方案二:更改條款內文,繼續用一樣的條款名,不推修改到 choosealicense,缺點是 Licensee 可能辨認不出來,結果就是 Vim 專案自己無法被辨識為使用 Vim License;
  • 方案三:同二,但是該推送修改的地方如 SPDX License List 和 choosealicense 都送,缺點是使用舊版授權條款文字的可能都再也辨認不出來,另一個問題是這樣的變化 Mike 是否會接受?

就之前的紀錄,當時版本偵測出來的相似度也只有 98.45%,這樣一改下去,會不會就爆炸又偵測不出來了呢,於是我趕快找了個時間拉最新版的 Vim 和 Licnesee 下來測試,結果,竟然還是 98.45%,這結果又讓我驚呆了,直覺是因為 Licensee 的演算法改的更好了,一查下去,原來剛好在今年初的 Licensee 9.16.0 其中的一項修改就是針對替換字串提供更高的差異容許範圍,所以舊版 Vim License 下去比對會有 99.12% 的相似度,剛好足夠新版修改後的差異,而這結果就剛好克服了方案三的缺點,所以接下來應該就是會朝這個方向進行,先提交新版的文字給 SPDX License List,然後再更新 choosealicense 內的範本吧。

Vim License detection

以上,就是到目前為主的故事了,最後想來作一個總結,也就是談談當初為什麼想在 COSCUP 分享這個呢?其實我當初假想的聽眾是開源的新手,我想分享的其中一點是,對於開源專案的貢獻,我完全是照自己的步調,也不給任何一邊壓力,所有的等待過程我都沒去催促過任何人,不過標題的「五年」或許造成反效果也說不定,投稿時 Rock 有建議過我可以調整一下標題,不過我實在是想不到什麼其他好標題,結果就都沒改,後來,當天在我的前一場是 Max 的「開源軟體與社群 - 參與國際社群經驗談」(影片),就有提到這點,可以選擇你最舒服的方式參與,不用害怕參與;第二點想分享的東西則是關於在這個過程中學到的東西,像是 Licensee 的判斷原理,SPDX 相關的東西,和各個群體溝通的過程,很多東西都是我之前並不清楚的,像是我以前知道有個 SPDX License Indentifier,但是並不清楚 SPDX 是搞什麼用的,然後它們有維護 License List,甚至授權條款文本裡面可以有替換字串等,我覺得這些都是蠻有趣的知識;最後一點則是分享這一整個過程,中間一波三折,坑坑相連到天邊的轉折,我自己都覺得很有趣。

以下列出我一路以來開過的 PR、Issue 和討論:

最後的最後就來個簡單的 timeline 吧:

- 2013/07/15 Choose a license released
- 2016/09/21 Display/detect the license
- 2017/03/26 Open issue to choosealicense
- 2018/06/15 First issue closed
- 2019/06/23 Discuss the License text with Bram
- 2019/06/27 Issue about Vim License detection
- 2019/07/11 SPDX PR
- 2019/09/25 Last comment of the SPDX PR
- 2019/10/19 SPDX PR merged
- 2020/01/08 PR add License to Vim
- 2020/01/18 PR add License to vim-pathogen
- 2020/01/21 PR add vim.txt to choosealicense
- 2020/03/13 Licensee 9.13.1 released, Vim added
- 2020/05/12 Licensee 9.14 release, Vim detection failed
- 2020/07/04 PR to fix detection issue
- 2020/10/14 Issue fix merged v9.14.1
- 2021/02/05 Contact GitHub support 1st time
- 2022/01/12 Contact GitHub support 2nd time, 確認上線
- 2022/05/22 投稿、開始寫文章
- 2022/07/29 COSCUP 2022
- 2023/08/03 Bram Passed away
- 2023/08/13 Vim License file updated
- 2023/09/04 再次開始寫文章
- 2023/09/12 發表文章

Vim License 的故事(上)

2023年9月10日 23:32

‎vim-license-slide.‎001

這篇是我去年 COSCUP 分享的文字版,拖稿許久終於寫出來了,以下正文開始。

Open Source Software 一直是 GitHub 的心頭肉(?),也因此 GitHub 一直都有在各方面協助 OSS 開發者,其中也包括了對 Open Source License(開源授權)相關的協助。在 2013 年,GitHub 發佈了一個小網站choosealicense.com,用簡單的條列介紹各開源授權條款的特色,並且藉由一些問答互動來幫助開發者挑選開源軟體授權條款。

而到了 2016 年,GitHub 更進一步提供了授權條款的偵測功能,只要你的程式庫裡面有正確的授權條款資訊(像是 LICENSE 檔案),然後使用的條款也在偵測的範圍內,那在 GitHub 上就會顯示該專案所使用的授權條款,也會同時提供該授權條款的特色給訪客參考,不過這個偵測功能,能偵測到的授權條款只有一些,更精確的說,就是只有 choosealicense 網站上的那些。

在 GitHub 推出授權條款偵測功能後沒多久,我就發現到 Vim 所使用的 Vim License 並不在偵測的範圍內。Vim License 是一個很特別的授權條款,是 Vim 的作者 Bram Moolenaar 專為 Vim 使用而寫的,雖然內文是針對 Vim 本身寫的,不過其實有很多的 Vim Script 也是標注使用 Vim License,甚至常常是寫 "Same as Vim",所以實際上使用的專案並不少,所以我就一直想著,是不是有可能讓 GitHub 可以支援偵測 Vim License 呢?

我一直把這念頭放在心裡,後來終於有一天有時間有衝動,認真去研究要怎樣新增支援的授權條款,簡單來說,GitHub 用來偵測專案所使用的授權條款的工具,是一套用 Ruby 寫的,叫做 Licensee(我都當成 license + see)的工具,而這個工具所使用來參考比對的條款文本,則是從 choosealicense 專案來的(使用 git submodule 引入),如果要加新的授權條款到 choosealicense 裡面,有些條件要先達到:

  • 要有 SPDX License Identifier
  • 要名列在幾個主要的開源授權清單中
  • 至少要有一千個專案使用它
  • 要列舉三個使用該授權條款的有名的專案(作為網站上的範例)

我研究了一會兒,大部分條件沒問題,就是那個一千個專案的需求我也不是很確定,根據搜尋結果可以保證有數百個,但是有沒有一千個實在無法保證,不過我還是去開了個 issue 建議說要加入 Vim License,其中一千個專案那個條件的部分則是提供我的所知(各種搜尋結果和 Vim 生態的狀態,推論應該會有達標),時間是 2017/03/26,當時我想的是 Vim 這麼有名,應該開個需求就會有人處理了吧,然後 choosealicense 的主要貢獻者之一 Mike Linksvayer,跟我來往幾次討論之後,我就放置在那幾乎忘了他,結果過了一年後,那個 issue 就因沒有動靜被關掉了!

我開的第一張 issue 被關掉的當下當然是有點震驚的,直白一點的說我心中的想法大概是:「原來 Vim 這麼有名也是沒有特權的啊!」總之之後我還是繼續放著,直到大概又過了一年,到了 2019 年六月,我才又開始重新投入到這件事情,既然開 issue 沒人做,那就只好改開 PR 了,於是我才開始認真的研究,目標當然就是把 Vim License 推進 choosealicense 裡面,所以就是先認真的要提供的文本檔案的格式,首先我發現的是,授權條款的文本裡面,除了那些 metadata 之外,文本本身是可以有替換字串(substitution)的,像是常見的年份、名稱等:

MIT License

Copyright (c) [year] [fullname]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

...

其中,用 [] 包起來的像是 [year][fullname] 就是要替換掉的字串,發現這點之後,我也覺得需要重新檢視 Vim License 的文字是不是也有這種地方,然而一旦認真看待這點,便發現問題不小,而這個問題就是 Vim License 不是 context free 的,而是高度和 Vim 耦合在一起的,一般而言,開源授權都是 context free 的,怎樣算是 context free 的授權條款呢?就是那個條款文字要拿去哪個專案,都可以直接用,不需要去修改文字內容,頂多是修改頭尾的名稱、年份和所有權人的資訊,但是 Vim License 不是,光是 Vim 這個單字,就在文本內出現了 29 次,而這些 Vim 單字,大部分時候代表的是使用這個授權的軟體的名稱,所以假設我今天是 vim-pathogen 要使用 Vim License,那就應該要在 LICENSE 文件內把 Vim 單字都換成 pathogen.vim 才對,不過在這堆 Vim 之中,還有一些地方是代表不一樣的意義,這些地方就是在作為 Vim Maintainer 的時候,所以到底,這麼多的 Vim 都應該要改為替換的字串嗎?為此我就去問了 Vim 的作者,當然同時也是 Vim License 的作者 Bram Moolenaar,其實我蠻快就得到答案了,結論就是,Vim Maintainer 需要保留原樣,但是其他時候在作為代表使用該授權的軟體時,Vim 應該要改為該軟體的名稱。

Discuss Vim License with Bram

那麼為什麼會出現 Vim Maintainer 呢?這邊就要來介紹一下 Vim License 的內容了,條款內容簡單翻譯(非正式翻譯,請勿用於法律用途)如下:

  1. 可以自由任意的散佈未修改過的 Vim;
  2. 如果要散佈修改過的 Vim,需要符合以下條件之一;
    1. 發佈時同時提供聯絡方式,當 Vim Maintainer 向你索取你的修改,你必須無償提供,並且 Vim Maintainer 保留把你的修改加進官方版本 Vim 的權力;
    2. 如果你取得經由前一條所述方式散佈的 Vim,你可以不受限制的散佈該版本,如果有新的修改,則需依照相同的方式散佈;
    3. 發佈時同時提供和原始版本的原始碼差異;
    4. 使用前一條的方式發佈時,符合以下所有條件時可以不用提供原始碼;
      • 你所使用的授權條款不會讓 Vim Maintainer 無法免費取得你修改的內容;
      • 你必須保留你修改的內容(原始碼的差異)至少三年,如果使用者或是 Vim Maintainer 在這段時間內跟你索取修改的內容,你必須要提供;
      • 你必須確保你所提供的聯絡方式在三年內是有效的。
    5. 如果你的修改使用 GPL,你可以使用 GPLv2 發佈你修改過的版本。
  3. 如果你發佈修改過的 Vim,強烈建議你使用 Vim License 並把修改的原始碼提供給 Vim Maintainer;
  4. 不可把授權條款移除。

仔細看下來,其實這份文件,主要目標就是在確保 Vim Maintainer 能取得其他人的修改,如果不是 Vim 使用這份授權條款的話,不管你使用這份條款的軟體是什麼,有修改再發佈的話,你都必須要讓 Vim Maintainer 可以無償取得你的軟體原始碼,並且 Vim Maintainer 可以決定是否要給原軟體使用,而這也造成了 Vim License 成為了少見的,不是 context free 的開源授權,而這個細節我相信是在我跟 Bram 的討論後才是第一次闡明,當時討論時我有舉了一個極端的例子來確認:假設我有一個軟體 X,使用 Vim License 授權,後來有人修改我的 X 後改為 Y 發佈但是沒有開源,有權力去取得 Y 的修改內容並決定要不要給 X 的,其實是 Vim Maintainer,而不是我(X 的作者)。

知道哪些地方才是可以替換掉之後就簡單了,我快速的準備好要用來發 PR 的 vim.txt 檔案,然後想說先來測試一下,結果,偵測竟然失敗了!研究再三,發現偵測出來的相似度只有 97.x%,而 Licensee 設定的閾值是 98%,至於會造成相似度這麼低的原因,其實是因為文本內有太多的替換字串了,當時的 Licensee 對於替換字串的比對處理的不太好,替換前後文字的長短差異也會有影響,知道原因後我就在想要來怎麼處理,當然最簡單的方法就是用特例處理,我當時的作法就是如果是在偵測 Vim License 的話,就會降低判斷用的閾值,本地測試沒問題後,就準備提交回去給 Licensee 了,不過因為我還不確定這樣的作法好不好,所以我先開了個 issue,解釋了前因後果然後附上我目前的修改,想說問問看負責的人的想法,如果他們覺得可以接受我再發 PR,結果,Mike Linksvayer 又出現了!我才發現 choosealicense 和 Licensee 他都是主要貢獻者!至於知道他的經歷則是在 COSCUP 分享後才聽 Bobchao 說的。

vim.txt in Licensee

Mike Linksvayer 當時是 GitHub 的 Policy Director 專門負責公共相關的事務,而在之前則是當過 CC 的 CTO 和 VP,也曾擔任過數個不同組織的董事會成員,像是 OpenHatch、Software Freedom Conservancy 和 Open Definition Advisory Council,所以可以說是已經在自由開源領域活躍已久,甚至是特長於授權條款相關的領域。不過其實當時我不知道,也沒想說要去了解,主要對他的印象是頭像看起來有點嚴肅,一直給我 T1000 的感覺,加上我後來就是幾乎都是跟他往來為主(應該說我提交的貢獻就是他在把關的),所以某些層面上來看我其實是對他有點心生畏懼的。

Mike 在 Licensee 這邊提出的問題是,我在 Vim License 裡面定的那些替換字串,並沒有出現在 SPDX License List 那邊,SPDX 全名是 Software Package Data Exchange,其實這是屬於一般開發者比較少接觸的到的東西,目前最常看到的應用扣除 License List 之外,應該是軟體供應鍊相關的東西,不過其實他們所維護的 SPDX License List 幾乎已經成為業界標準,尤其是那個 license 的 identifier 更是到處都看得到,像是 npm 的 package.json 裡面就是使用它。然後其實 SPDX License List 這邊的檔案格式(自訂的 XML)和 Licensee 用的檔案格式(自訂的純文字)不一樣,不過由於 SPDX License List 已經是一個大家都會當作參考索引的資料源頭,所以也自然的成為 choosealicense 的上游(upstream),這次經驗我也讓我了解到開源的圈子真的是蠻重視 upstream 的,之前我也在 neovim 遇過類似的情形,總之也因此我應該要先去提交 PR 修改 SPDX License List,然後才發 PR 給 choosealicense,所以我就整理整理後去那邊發了 PR 加上這些替換字串,然後,Mike Linksvayer 又出現了!他也是 SPDX License List 的貢獻者之一!

下一篇

Common Log Format

2023年8月14日 21:29

Common Log File Format

這篇算是一篇軟體的考古文吧,最近對部落格做了些調整,其中一個改變就是把 Google Analytic 拿掉了,一部分是因為現在已經不能用 UA 而要改用 GA4 了,然後其實我也想拿掉很久了,這次就順便把它移除,不過我還是有興趣想知道不同文章大家感興趣的程度差異,所以就又研究起以前那種根據 HTTP server log 來整理網站統計資訊的工具,其實以前一直沒成功拿掉 GA 的原因之一就是找不到好的替代工具,一直以來我比較有印象的就是 AWStats,只是那個介面我實在是受不了,然後搜尋其他替代工具的過程也不太順利,直到這次重新研究之後,發現到一個關鍵字 Common Log Format,這聽起來很一般的詞,在軟體工程界其實已經變成是一個專有名詞了。

Common Log Format 的 Wikipedia 條目 寫著這是一種 HTTP server log 的標準格式,不過我覺得應該只能算是 de facto standard(業界標準),因為沒有任何機構真的定義並作為標準發布過,現在網路上可以找到 W3C 的一份格式說明,但是那其實是 CERN 時代的 httpd 這個軟體的說明文件,趁這個機會,我就想著要來搞清楚幾個我一直很疑惑的幾個和 log 相關的單字,分別就是一開始說到的 common,然後就是 combinedextended,這幾個關鍵字都是我以前在設定 Apache HTTPD 的時後常常會看到的,甚至其它的 web server 也有用到,但是一直沒有搞的很清楚,而且使用的字我也覺得很奇怪,像是 combined 是在 combine 什麼。

結果就是,這些問題的答案,幾乎都在 NCSA(National Center for Supercomputing Applications、美國國家超級電腦應用中心) 開發的 HTTPd 軟體文件中,NCSA HTTPd 也就是最早提出這種 log format 的 HTTP server,而 NCSA HTTPd 的 log,其實預設下是有三個的,分別是:

TransferLog 其實就是現在大家說的 access log,格式就是所謂的 CLF,不過其實當時是寫作 Common Log File(CLF) Format,紀錄的資料格式就是:

host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb

- host: Either the DNS name or the IP number of the remote client
- rfc931: Any information returned by identd for this person, - otherwise.
- authuser: If user sent a userid for authentication, the user name, - otherwise.
- DD: Day
- Mon: Month (calendar name)
- YYYY: Year
- hh: hour (24-hour format, the machine's timezone)
- mm: minutes
- ss: seconds
- request: The first line of the HTTP request as sent by the client.
- ddd: the status code returned by the server, - if not available.
- bbbb: the total number of bytes sent, *not including the HTTP/1.0 header*, - if not available

然後文件還有定義了一個可擴充版的 Extended CLF Format,允許在這些 log 的末端加上其他的資料,如果 LogOptions 設定為 Combined 的話,三種 log 會 combine 在一起,使用 Extended CLF Format,多加上了 referrer 和 user-agent 資訊,這也就是 Combined 這個格式名稱的由來,而這邊有另外一個容易混淆的東西,就是 W3C 有一份很古老的 Extended Log File Format 的 Working Draft,這份文件定義的格式和 CLF 其實是沒關係的,所以看文件時,有比較仔細的文件就會寫到是 W3C 的 extended 還是 NCSA 的。

雖然我沒仔細查先後關係,不過 CERN 版的 HTTPd 應該是後來才實作了 NCSA 版的 log format,文件內則是稱為 Common Logfile Format,簡稱也是 CLF,不過單字有一點點不一樣就是,當然格式是一樣的,然後其實它也保留了 CERN HTTPd 的舊版 log,格式是:

time remotehost request 

實作是:

fprintf(log, "%24.24s %s %s\n",
		asctime(gorl), HTClientHost, n_noth(HTReqLine));

其中的 %24.24s 我還是研究了好一陣子才看懂第一個 24 是最短長度,資料不夠長時會加上空白,然後 . 後面的是精確度,遇到字串時則會變成最長長度,超過的就會不輸出,asctime 則是一個內建函數可以把時間轉成字串,格式是:

Www Mmm dd hh:mm:ss yyyy

長度剛好是 24 個字元,至於那個變數名稱 gorl 則是我花最多時間才參透的,它的意思是:「GMT time or Local time」,但是它不是 indicator 那種二元值,而是變數本身就是那個時間,而那個時間可能是 GMT 時區時間也可能是本地時間。

這樣,其實很多細節的小疑問都有了解答,像是以前看 log 常常看到兩個 - 接連出現,其實代表的是連續兩個沒有值的欄位,其中一個是現在已經幾乎沒用到的 Identification Protocol(RFC1413),也是個古早的東西,我稍微看了一下好像 IRC 有用到;然後因為其實沒有標準,所以以前和現在的日期格式用的已經不一樣了,現在是普遍有加上時區,當時 NCSA 的和後來 CERN 實做的都沒有時區資訊;另外就是 Apache HTTPD 文件 的範例也有提到 RefererLog 和 AgentLog,我當時看到時就想說怎麼會有人只要這兩種資訊的 log;發現 CLF 這個專有名詞後,我也循線找到更多的 web log 分析工具了,目前我是先挑了 goaccess 來用。

最後整理一下,這三個關鍵字在 web log 的情境下時:

  • Common 格式,通常指的是 Common Log File(CLF) Format;
  • Extended,不考慮 W3C 的版本的話,這邊指的是 NCSA Extended CLF Foramt,如果 CLF Format 定義的欄位不夠用,需要更多資訊時,就可以使用這種格式,多的資訊是加在 log 尾端;
  • Combined 格式,多加了 referrer 和 user-agent 的 web log,使用 NCSA 版 Extended CLF Foramt 的格式,combine 指的是合併 TransferLog、RefererLog 和 AgentLog 三種 log。

實際上 NCSA HTTPd 就不只 Common 和 Combined 兩種,還有 ServerName 可以加上,當然也是使用 Extended 格式,不過目前流傳下來,最常見的就是這兩種了。

Common Log Format

2023年8月14日 21:29

Common Log File Format

這篇算是一篇軟體的考古文吧,最近對部落格做了些調整,其中一個改變就是把 Google Analytic 拿掉了,一部分是因為現在已經不能用 UA 而要改用 GA4 了,然後其實我也想拿掉很久了,這次就順便把它移除,不過我還是有興趣想知道不同文章大家感興趣的程度差異,所以就又研究起以前那種根據 HTTP server log 來整理網站統計資訊的工具,其實以前一直沒成功拿掉 GA 的原因之一就是找不到好的替代工具,一直以來我比較有印象的就是 AWStats,只是那個介面我實在是受不了,然後搜尋其他替代工具的過程也不太順利,直到這次重新研究之後,發現到一個關鍵字 Common Log Format,這聽起來很一般的詞,在軟體工程界其實已經變成是一個專有名詞了。

Common Log Format 的 Wikipedia 條目 寫著這是一種 HTTP server log 的標準格式,不過我覺得應該只能算是 de facto standard(業界標準),因為沒有任何機構真的定義並作為標準發布過,現在網路上可以找到 W3C 的一份格式說明,但是那其實是 CERN 時代的 httpd 這個軟體的說明文件,趁這個機會,我就想著要來搞清楚幾個我一直很疑惑的幾個和 log 相關的單字,分別就是一開始說到的 common,然後就是 combinedextended,這幾個關鍵字都是我以前在設定 Apache HTTPD 的時後常常會看到的,甚至其它的 web server 也有用到,但是一直沒有搞的很清楚,而且使用的字我也覺得很奇怪,像是 combined 是在 combine 什麼。

結果就是,這些問題的答案,幾乎都在 NCSA(National Center for Supercomputing Applications、美國國家超級電腦應用中心) 開發的 HTTPd 軟體文件中,NCSA HTTPd 也就是最早提出這種 log format 的 HTTP server,而 NCSA HTTPd 的 log,其實預設下是有三個的,分別是:

TransferLog 其實就是現在大家說的 access log,格式就是所謂的 CLF,不過其實當時是寫作 Common Log File(CLF) Format,紀錄的資料格式就是:

host rfc931 authuser [DD/Mon/YYYY:hh:mm:ss] "request" ddd bbbb

- host: Either the DNS name or the IP number of the remote client
- rfc931: Any information returned by identd for this person, - otherwise.
- authuser: If user sent a userid for authentication, the user name, - otherwise.
- DD: Day
- Mon: Month (calendar name)
- YYYY: Year
- hh: hour (24-hour format, the machine's timezone)
- mm: minutes
- ss: seconds
- request: The first line of the HTTP request as sent by the client.
- ddd: the status code returned by the server, - if not available.
- bbbb: the total number of bytes sent, *not including the HTTP/1.0 header*, - if not available

然後文件還有定義了一個可擴充版的 Extended CLF Format,允許在這些 log 的末端加上其他的資料,如果 LogOptions 設定為 Combined 的話,三種 log 會 combine 在一起,使用 Extended CLF Format,多加上了 referrer 和 user-agent 資訊,這也就是 Combined 這個格式名稱的由來,而這邊有另外一個容易混淆的東西,就是 W3C 有一份很古老的 Extended Log File Format 的 Working Draft,這份文件定義的格式和 CLF 其實是沒關係的,所以看文件時,有比較仔細的文件就會寫到是 W3C 的 extended 還是 NCSA 的。

雖然我沒仔細查先後關係,不過 CERN 版的 HTTPd 應該是後來才實作了 NCSA 版的 log format,文件內則是稱為 Common Logfile Format,簡稱也是 CLF,不過單字有一點點不一樣就是,當然格式是一樣的,然後其實它也保留了 CERN HTTPd 的舊版 log,格式是:

time remotehost request 

實作是:

fprintf(log, "%24.24s %s %s\n",
		asctime(gorl), HTClientHost, n_noth(HTReqLine));

其中的 %24.24s 我還是研究了好一陣子才看懂第一個 24 是最短長度,資料不夠長時會加上空白,然後 . 後面的是精確度,遇到字串時則會變成最長長度,超過的就會不輸出,asctime 則是一個內建函數可以把時間轉成字串,格式是:

Www Mmm dd hh:mm:ss yyyy

長度剛好是 24 個字元,至於那個變數名稱 gorl 則是我花最多時間才參透的,它的意思是:「GMT time or Local time」,但是它不是 indicator 那種二元值,而是變數本身就是那個時間,而那個時間可能是 GMT 時區時間也可能是本地時間。

這樣,其實很多細節的小疑問都有了解答,像是以前看 log 常常看到兩個 - 接連出現,其實代表的是連續兩個沒有值的欄位,其中一個是現在已經幾乎沒用到的 Identification Protocol(RFC1413),也是個古早的東西,我稍微看了一下好像 IRC 有用到;然後因為其實沒有標準,所以以前和現在的日期格式用的已經不一樣了,現在是普遍有加上時區,當時 NCSA 的和後來 CERN 實做的都沒有時區資訊;另外就是 Apache HTTPD 文件 的範例也有提到 RefererLog 和 AgentLog,我當時看到時就想說怎麼會有人只要這兩種資訊的 log;發現 CLF 這個專有名詞後,我也循線找到更多的 web log 分析工具了,目前我是先挑了 goaccess 來用。

最後整理一下,這三個關鍵字在 web log 的情境下時:

  • Common 格式,通常指的是 Common Log File(CLF) Format;
  • Extended,不考慮 W3C 的版本的話,這邊指的是 NCSA Extended CLF Foramt,如果 CLF Format 定義的欄位不夠用,需要更多資訊時,就可以使用這種格式,多的資訊是加在 log 尾端;
  • Combined 格式,多加了 referrer 和 user-agent 的 web log,使用 NCSA 版 Extended CLF Foramt 的格式。

實際上 NCSA HTTPd 就不只 Common 和 Combined 兩種,還有 ServerName 可以加上,當然也是使用 Extended 格式,不過目前流傳下來,最常見的就是這兩種了。

Vim Boss Passed Away

2023年8月11日 20:16

Vim

Vim 的作者 Bram Moolenaar 在月初過世,消息出來至今大約已經過了一週,Vim 官網也在兩天前有了正式公告,現在除了各方的緬懷之外,Vim 的未來也是令人非常在意,這陣子也都大概有些方向了,目前狀況是 Vim 的另外一位維護者 Christian Brabandt 在負責,包括了維護 Vim 本身、網站主機和網站修改、各種使用到的服務的統整(像是 binary 放哪)、未來捐款的處理方式等等,其實事情很多,而我這篇文章則是要紀錄一些這幾天看到的東西。

Bram Moolenaar 之前其實在 Google 工作了很久,到 2021 年十月才退休,在 The Register 的報導中,有當時的訪談的部分內容,只不過當時因為種種原因沒有成為一篇報導刊出,其實看內容也感覺的出來他當時還有些退休計畫,然後接著一年後,2022 年十月,Bram 有在 mailing-group 裡面提到自己有健康問題,當時就已經有中斷 Vim 的維護工作了,然後就是今年過世的消息了,講真的,措手不及,而且 Bram 也才 62 歲,歐洲國家的預期壽命其實都有七八十的。

回到我與 Vim 之間的關係,除了我早期花很多心力在 html5.vim 之外,其實我目前還是 runtime 裡面 csscomplete.vim 的維護者,剛剛查一下才發現我也好久沒更新了,然後就是去年 COSCUP 我分享的 Vim License 的事情了,在 Vim License 的文本裡面的那一個特別的條文,就是開源與否的爭議是交給 Vim 的維護者決定,文本內還直接寫了現在的維護者是 Bram,然後現在就有個問題是這個條文需不需要修改,除此之外還有一個就是 vim.org 的 mail server 不知道有沒有辦法轉移,不然 maintainer@vim.org 也會無法繼續使用;其實就我所知,那個條文好像沒有真的發揮效用過,我覺得未來會用上它的機會應該也是很小,而且未來也不知道會不會有單一的 maintainer,我自己判斷社群應該會傾向維持條文不動吧。

最後就是,其實 Git 的每個 commit 的作者(author)和提交者(committer)可以是不同人的,而 Vim 早期,一直走的是老派的發 email 提交 Patch 給 Bram,然後由 Bram commit 進去 repository 裡面的流程,我當年想要用 Gmail 提交還發生過檔案內容太長,Gmail 無法發送這種信件的問題,後來是用 cli 的工具來發信,後來才開始有收 GitHub PR,但是 Bram 並不是直接用上面的功能來 merge,而是一樣拉 patch 下來,保留 author 資訊後 commit 進 repository,所以以前在 GitHub 上就很容易看到文章一開始那張圖那樣,全部都是 Bram 頭像的樣子,現在新的 commit 也開始進來了,所以最新的地方已經看不到這個樣子了。

比宇宙更遠的地方

2023年8月2日 19:46

宇宙よりも遠い場所

在剛進入串流時代之時,我想著以後不太需要買什麼 DVD/BD 之類的了,因為想看的東西應該以後網路上都可以看的到,不過過了幾年下來,我的想法又慢慢的改變了,因為這些作品的授權都有個期限,過期之後不一定會重新上架,甚至連自家的原創作品都可能會下架,所以我慢慢的心中開始有了幾部作品,是覺得應該收藏一份實體版下來,其中的評斷標準算是我個人主觀,不過有一個條件倒是比較明確,就是希望讓我兒子長大後能看的,其實在這清單之中也沒幾個作品,而且很少有新增,不過就在最近新增了一部:「比宇宙更遠的地方」,簡稱為よりもい(yorimoi)。

這部我一直都有聽到不錯的評價,第一印象是屬於那種「一群女孩去完成一件事」的日常系作品,其實我在看之前也沒有深入了解,本來以為就是一群女高中生因為對南極的憧憬,想方設法去南極旅遊的過程吧,畢竟這種類型作品很多是流水帳般將主角們有趣的生活演出來,這樣的日常系作品雖然也是會很有趣,但是就比較會變成打發時間用的,可能看過一陣子就會慢慢淡忘。

結果,看完よりもい之後我第一個感想就是:「真是太好我有看到這部作品了。」我也實在是非常幸運能在作品出來將近五年後還能在沒被捏他到的情況下看完這部作品。其實這陣子因為小孩去過暑假,所以我多了很多時間可以補看之前沒空看的東西,看了很多不錯的作品,也在推特上紀錄了一些心得,不過よりもい是第一部我特別想寫一篇文章紀錄的,因為他帶給我的不是風景多漂亮、劇情多有趣,而是更深刻的,故事中幾位角色的成長與自我突破。

以下有劇情,沒看過的話建議迴避,或是可以轉去看看 YouTube,匯雨有一個比較沒劇透的推坑影片

防雷保護
防雷保護
防雷保護

雖然劇情主軸是前往南極,不過故事的核心其實是成長,而且每個角色都有一個明確的成長目標,不是什麼不著邊際的東西,所以每當有人有了自我突破,觀眾都能確實的感受到:小決(キマリ、kimari)想要能夠更獨立,不再依賴親友惠;報瀬(しらせ、shirase)則是需要走出母親過世的心理陰影(?);日向的部分我就先跳過,她的部分來的又急又快,雖然前面早有鋪陳,但是我一直沒放在心上,後來覺得根本是編劇給觀眾的一場襲擊,但是還好有這段,讓這個角色的一切行為變得合理許多;結月則是想要交朋友,但是她其實連怎樣算是朋友也不知道,而在這邊,小決藉由 Line 來形容她心中的朋友這段我也是很喜歡。

故事的最高潮當然就是從一開始就一心只想去南極的報瀬的部分了,她的母親三年前在南極失蹤(沒找到屍體、不過可以說等於死亡了),因為南極任務一去就是幾個月,所以她本來就已經是和外祖母一起生活邊等媽媽回來,母親失蹤,她的生活也沒什麼變化,結果讓她沒有母親已經過世的實感,所以她一直想去南極確認母親是不是真的不在了,在劇中她也還時不時的寄信給媽媽,最後,他們終於到了內陸的基地,小決他們在基地中找到報瀬媽媽的筆電,報瀬打開電腦,收信軟體跟著啟動和收信,包括了她寄給母親的信,隨著未讀信件的增加,報瀬終於體認到母親過世的事實,大哭了一場。

其實小時候,我都會覺得什麼婚禮喪禮這些儀式很麻煩,不過長大後,我的想法跟著我的經歷有了變化,我發現儀式這些東西其實是為了人心而存在的,因為人心有慣性,生命中的重要的東西的喪失,人是可以一直逃避假裝沒有發生一樣,所以才會有這些儀式存在的必要,藉由這些儀式的突兀感,讓人真真正正的體認到接下來不一樣了,不論是生命中少了些存在,或是多了重要的人都是,而對於報瀬來說,在那個時候,才真的是她跟媽媽道別的儀式吧。

故事中還有很多我很喜歡的小細節,像是キマリ這個暱稱的由來,然後這個暱稱卻又和她的個性不合;報瀬(しらせ)的發音其實和破冰船的船名白瀨(劇中退役前和現實世界)一樣,而那個昭和基地和白瀨號破冰船其實是日本現在都還在用的,而且兩個都長得和真的一樣,去到日本極地研究所的網站一開始就可以看到基地的空拍;還有像是去到內陸為什麼比較冷雖然沒明說,不過想在那邊建立天文台,應該是海拔高很多位置,日本其實有在富士冰穹建立過一個基地作科學研究,那邊海拔 3810 公尺,距離昭和基地一千公里,氣溫也低上非常多;白瀨號和昭和基地內的郵筒,現在都還有在運作,特別的郵戳也一直都有,還有相關的報導,好像有一直更新,不確定是不是每年換新圖案,我還有找到去年日本郵局的特別郵戳公告;最後我還查了那幾年的觀測隊員同行者,沒有發現看起來和動畫製作有關係的人,極地研究所應該是提供照片給動畫公司吧。

昭和基地

其他我還有查到一些有趣的資料,日本的南極觀測船總共有四艘:

其實我一開始覺得有點奇怪,為什麼四代目還是叫しらせ而不換名字呢?後來才發現,日本首位的南極探險家就叫做白瀬矗(Shirase Nobu),所以しらせ這個名字的傳承其實比我一開始想的還要久遠。

另外一個是日本南極觀測隊確實有人罹難過,而且遺體是八年後才發現。可以想見那邊的地域是多麼艱險,然後我才想起幾年前去過紐西蘭的南極中心,裡面就有一個設施是可以讓人體驗暴風雪,雖然有提供超厚的防寒服,不過氣溫極低,風一直在吹的情況下,體感溫度還是降的很快的。

International Antarctic Centre

最後附上一些其他人的文章:

新加坡 2022

2023年5月12日 21:03

新加坡

去年因緣際會去了一趟新加坡,是我第一次去新加坡,看到不少建築覺得實在很厲害,想說紀錄一下。

這趟行程中,我看到覺得很厲害特別想介紹的建築有三個,首先第一個是聖陶沙上的 Resort World,規模超大,有飯店商場賭場等等,環球影城也是其中的一部分,不過只是這樣還不會到讓我覺得超厲害,最讓我驚艷的其實是它的地下車道,我不確定他是往下挖還是往上蓋(應該是往上蓋比較簡單),不過整個 Resort World 人行的地方,那些商店廣場的下面其實都是空的:

新加坡

我只有拍這張照片,不過就是你仔細看你會發現對面深不見底,是不可思議的大的地下空間,裡面有錯綜複雜的車道、接送區、巴士站、停車場、甚至還有卡丁車賽車場,這個是屬於理論上做的出這種東西,技術上應該也沒太大困難,只是我沒想到會有人做出這麼大規模的地下車道系統。

第二個是彰宜機場的 JEWEL 的中間的那一區,就是有很多植物和中間一個瀑布(不知道該不該稱為瀑布,其實它叫 Rain Vortex)的那一區,那一區其實叫 Forest Valley。

新加坡

新加坡

這區我覺得讓我感到覺得驚豔的地方有兩個,第一個是那個瀑布,要怎樣能讓他不漏水。

新加坡

這個瀑布水流到 B2 那層時,是一整圈透明的牆(我不確定材質)圍起來,不知道要怎樣能確保水不會滲漏,我有直接摸摸看,也找不到接縫,該不會是整塊玻璃一體成形吧。

PS. 我後來找到 News Asia 的專題,那塊是壓克力的,不過我還是不知道是怎樣可以弄成無縫的這麼大一塊,然後這個專題對於那個瀑布的介紹是聚焦在水流怎樣下來又上去的。

Changi Jewel B2

當時很趕時間沒拍照,還好 Google 街景有。

除了瀑布的漏水問題外,這區還有非常多的植物,而且不是小顆的花花草草,也有比較高大的樹木,置身在其中會覺得非常難以想像有人會放這麼多的植物在室內空間(請見影片),然後我就又想到他們的樹根會一直成長變長,不知道要如何確保數十年後,這些樹木的下面,通常是商店的地方,不會有漏水的問題出現...

最後一個要介紹的,是金沙酒店(Marina Bay Sands)的 Apple Store:

新加坡

其實本來也沒特別有安排要去金沙酒店,後來也是最後一晚了不知道要幹嘛就跑去看水舞光雕表演,然後才發現這個 Apple Store,遠遠看到的時候就覺得:「不可能吧!」真的走到旁邊看的時候就覺得非常誇張,怎麼這麼有錢有閒弄一個浮在水中間的球。

新加坡

裡面往上看圓頂。

新加坡

圓弧形自動門,等很久都等不到全關,一直有人進出。

新加坡

新加坡

不意外的對得很齊,甚至對齊到地板的金屬飾板(請見影片)。

逛一逛意外的發現有電扶梯通往樓下,覺得不是獨立在水上的一顆球嗎,怎麼還有地下室,然後又想到,那行動不便的怎麼辦,蘋果不可能沒考慮到吧,尋找一陣子之後,發現了一個鏡面的圓柱體(請見影片),裡面就藏著電梯,然後下到樓下走一走,才發現是有通道金沙酒店商場內的。

新加坡

我只能說這個建築設計真的是讓我當場覺得驚呆了,當時我以為這個 Apple Store 是一開始在蓋金沙酒店時就一起設計進去的,結果回來研究一下資料發現不是,這個 Apple Store 是後蓋的,努力搜尋一番有找到篇文章有些施工當時的照片。

基本上就是圍了一塊範圍把水排掉後把它蓋起來,是可以想像得到的工法,只是想不到會有人這樣設計(Foster + Partners 設計的),而且真的願意這樣施工,然後不免俗的,我又擔心起會不會漏水的問題了,不過後來想一想如果認真要處理防水應該不是問題吧,實在是被台灣的房子會漏水的觀念荼毒太久了。

新加坡整體而言,城市的魅力還是蠻高的,尤其是這些建築的設計更是讓我驚艷,光是來看看這些建築我就覺得蠻不錯的,不過物價實在是很高,然後天氣也是蠻熱的,有機會的話我是會願意再來一次去看看這次沒見到的地方,像是植物園那邊,金沙酒店這次也沒仔細逛(金沙酒店本身也是很厲害,商場裡面還有人工河和瀑布)。

大宮鐵道博物館

2023年5月4日 17:03

The Railroad Museum

大宮鐵道博物館(簡稱鐵博)也是知名景點了,網路上一堆介紹,所以這篇除了我們的遊記之外,還會有些博物館本身以外的內容。

大宮鐵道博物館基本上就是 JR 東日本的博物館,其實東京市區還有一些其他公司的博物館,像是 Tokyo Metro 有一間地下鉄博物館、東武也有自己的東武博物館,不過 JR 還是乘載比較多數人的回憶,加上還有新幹線的加持,大宮鐵道博物館對我們來說還是參觀的首選,不過既然是 JR 東日本的博物館,我在查詢交通時,就出現一個問號,為什麼要到大宮站後,要搭乘另外一個非 JR 的 New Shuttle(伊奈線) 才能到鐵道博物館呢?

The Railroad Museum

New Shuttle 列車駕駛室,車到站時還有特別的進站音樂,好像是銀河鐵道 999 的主題曲。

我當初還研究地圖許久,覺得其實好像是走的到的距離,不過還好我當天沒真的去用走的,實際上距離超遠,千萬不要用走的!不過如果你真的想用走的,其實路邊有放一台蒸気機関車D51 187号機可以看,似乎是大宮工廠生產的第一號火車頭。

New Shuttle 的軌道很特別,似乎是加掛在 JR 的高架鐵道兩邊的,回來台灣我才開始研究,發現其實 New Shuttle 是埼玉和 JR 東日本為主合資的地方線路,因為是大股東了,所以名正言順的拿鐵道博物館來作宣傳也完全不是問題,官網首頁圖片就是新幹線、New Shuttle 和鐵道博物館一起入框。

(根據朋友的補充,應該是因為新幹線開通後佔掉了軌道空間,所以才由 JR 和地方出資建了這種地區線路)

New Shuttle

從 Google 街景上可以看到,New Shuttle 軌道看起來是另外加掛在 JR 軌道主結構旁邊。

從大宮搭一站就可以到達鉄道博物館站,這一站裡面有很多很多的轉蛋機,都是以鐵道為主的,有興趣的可以在這邊好好的轉一轉,出站後直走就是鐵道博物館方向,應該沒人會走錯。

The Railroad Museum

The Railroad Museum

現在門票都要先買好(便利商店機台買),現場不賣,日本越來越多地方是用這種售票機制,可以順便控制人流,另外當日票是會比較貴的。進到鐵道博物館後,第一個映入眼簾的就是天皇專用列車的機關車了。

The Railroad Museum

然後這邊開始分成左右兩邊,簡單說明一下,進館後左邊是往北館方向,基本上就是去小孩區,往右邊是往南就是網路上很多照片的車輛展示區。

The Railroad Museum

展示區展示很多車輛,我就不一一介紹,不過特別想說的是,這區走到底可以走出去,外面有一個火車便當攤,那邊還有一台在戶外的已經退役的 Max 號新幹線,家裡有小朋友有買大科學的話應該很熟悉。

The Railroad Museum

然後其實這個戶外區繼續走下走,可以到南館,有一些體驗活動是在這邊之外,這裡還有 400 系和 E5 系的新幹線列車,這些都是現役的車型,我們沒注意到錯過了這區有點可惜,除了這幾台新幹線,其實初代那個圓頭藍白配色的新幹線也是有獨立一個區域給它(台灣也有一台,但是保存和展示就...)。

The Railroad Museum

然後說到便當攤,其實博物館內有兩個,分別在往南館和往北館方向的戶外,我們那天先經過南館方向的沒開,以為現在沒有賣火車便當就跑去二樓的餐廳吃,結果其實當天北館方向的有開。

我們因為小孩還小,所以這天不是很深入的行程,屬於走馬看花的方式,主要是因為很多要抽選體驗的設施,都是要小學以上,所以我們逛的區域就是車輛展示區、二樓的日本食堂餐廳午餐、火車模型區、科學區和迷你運轉列車(可以大人開小孩搭),這樣隨意逛一逛也是到下午兩點多了,回程不免俗的要逛一下紀念品商店,當時還有日本全國 IC 連通十週年紀念特區。

The Railroad Museum

The Railroad Museum

The Railroad Museum

科學區很像是科教館那樣,有很多和火車有關係的物理現象的介紹。

The Railroad Museum

特別可以買到各家吉祥物的機會。

我是沒在這邊買紀念品,反而在回到 JR 大宮站時,在站內發現另外一間小小的鐵道商品店,結果反而在這邊買了一些磁鐵,然後這邊還有特別展示了一款結合日本傳統技藝製作的 Suica 企鵝商品,看起來很吸引人,不過上面貼著完售,所以我只有大概記一下,想說之後再查查看。

JR 磁鐵

回來之後我就開始想要找到當時看到的那樣商品,一開始是了解到,博物館裡面的紀念品商店是 TRAINIART,這間商店的商品是比較貼近生活一點,和我在大宮站看到那間店不太一樣,於是又搜尋好一會兒,才找到大宮站裡面的紀念品商店是 Railyard,這間的商品就比較沒有設計和加工過,像我買的磁鐵就是那些牌子原原本本的樣子拿來用,不過找到這兩間商店,上他們在 JRE Mall 的網路商店找,也找不到我想要找的那樣商品,倒是意外看到一個很好笑的紀念品:

こんな商品あったんだ#TRAINIART では、どこかでみたことある!?面白い商品を販売中!

その名も・・・
【駅構内にあるゴミ箱マルチポーチ(JR東日本編)】です

ぜひホンモノのゴミ箱と見比べてみてください pic.twitter.com/lhHoi300no

-- TRAINIART(トレニアート)【公式】 (@trainiart) April 9, 2023

沒錯,就是大家曾經都非常熟悉的那個 JR 月台的超大垃圾桶,這次去搭 JR 幾趟其實我都沒看到了,事實上這兩年這些超大垃圾桶一直在減少,連鐵道博物館內都有展示出來了,不知道是本來就有還是是因為快要變成歷史痕跡之一了呢。

PS. 日本這幾年比較有為了環保改變一些東西,像是有很多購物提袋已經變要付錢了

The Railroad Museum

回到鐵道商品,這兩間商店都和 JR 關係不小,除了大宮之外,TRAINIART 在東京車站還有分店,而 Railyard 則是在秋葉原站,如果想要逛逛但是沒要到鐵博也是可以在都內解決。至於我一直很想找到的那樣商品到底如何了呢?後來我終於用圖片搜尋找到了,是印傳屋的合作商品:

Suica 印傳屋

http://www.jizaingxinden.com/product/suica.html

在東京車站那邊有專賣的店,線上也有一些商品,不過也已經沒黑皮黑漆的款式了(我在大宮站看到說完售的款式)。

而除了鐵道商品專門店外,東京蠻多分店的百元商店 Seria,也有出 JR 貨櫃款的小鐵盒,有兩種尺寸各三種顏色,我這次只有看到比較小的款式:

Seria JR Container

這其實是山田化學的產品,山田化學有不少塑膠生活製品,品質也不差,像我以前就買了好幾個 SIKIRI 10 號收納盒裝摩比人,這個貨櫃小鐵盒也是,只不過 JR 款是和 Seria 合作的商品,只有 Seria 賣。

文章最後,想說一下我出發前心中關於鐵博的兩個疑問,其中第一個是 New Shuttle 是哪來的,是不是一定要搭它,在前面已經提過了,第二個問題則是,為什麼在大宮?這個問題的答案也是在到現場後有了一些概念,在搭乘 New Shuttle 從大宮到鐵博時,經過的那段路就可以看到整片都是 JR 的廠房,回來仔細研究一下,才了解大宮那邊以前是大宮工廠,是生產車輛的地方,現在則是車輛整備中心,鐵博那塊地以前則是車輛解體工廠,而大宮市還是曾經官方認定的12個鐵道都市之一(因鐵道而發展起來的)。

荒川線(Sakura Tram)一日遊

2023年5月3日 10:56

都電荒川線

續前一篇,這次東京自由行最後一個整天我們因為所以然後選擇了荒川線一日遊,結果而言蠻不錯的,網路上介紹過荒川線一日遊的文章其實也不少,所以這篇就來紀錄我們去的地方就好。

都電荒川線

會選擇這條路線除了因為鬧區人太多很可怕之外,還有一個原因是我們剛好住在東池袋,然後荒川線有一站就是東池袋四丁目,走路就可以到,我們的行程就是先搭到最後一站三ノ輪橋,然後慢慢往回走,回到池袋。

都電荒川線

三ノ輪橋那邊有一個三ノ輪橋おもいで館(回憶館)是都營交通的設施,裡面有相關的紀念品在販售,還有放一點古老的紀念物,還有大張的紙本路線圖可以拿,不過我更想要以前那種 Tokyo Metro 的路線圖小卡啊。

都電荒川線

三ノ輪橋的商店街是比較不熱鬧,我們在這邊就是隨意逛逛而已,有很多比較老式的商店,像是日式肉店、味噌店、蔬菜店等。

都電荒川線

都電荒川線

都電荒川線

我們在其中一間とりふじ(鳥富士)賣熟食的買了一些串燒填肚子。

都電荒川線

接著就往回搭車去到荒川二丁目,據說這邊可以拍到荒川線電車和天空樹的合影,找了幾個路口才找到比較好的位置,結果其實比較靠近荒川区役所前了。

都電荒川線

拍完天空樹後就繼續搭車到荒川車庫前,這一站其實就是荒川線的基地,除了車庫之外,還有荒川電車営業所也在這,司機也是在這邊交班,來這站的主要目的是來旁邊的都電おもいで広場(Toden Memorial Square)玩,這邊有放兩台很老的都電列車,還有展示一些歷史資料和物件。

Toden Memorial Square 都電おもいで広場

Toden Memorial Square 都電おもいで広場

Toden Memorial Square 都電おもいで広場

操縱席可以隨便小朋友玩。

都電荒川線

一個設計得很巧妙的模擬裝置,拿來讓小朋友玩倒是很不錯,我就不說穿其中秘密了。

Toden Memorial Square 都電おもいで広場

古早的都電路線圖,在轉變成地鐵為主之前,路上電車的線路其實是很多的。

Toden Memorial Square 都電おもいで広場

除了車庫之外,供電的主線路也在這邊。

因為小朋友玩的很高興,在這邊停留許久後,才繼續前往下一站庚申塚,這一站可以通到巢鴨商店街,也算是有點名氣的地方,其實這條商店街的全名是:巣鴨地蔵通り商店街

巢鴨商店街真的很好逛,我們一路上買了飛安商店的鯛魚燒(有上過電視,Tabelog 上好像也是很高分,真的蠻好吃,回來才發現完全沒拍照就吃光光了,而且我們回程還買了第二次也沒拍到照片)、まる天花枝串、鹽大福、杉養蜂園的霜淇淋等一堆東西吃,那邊有名的雜貨店マルジ也是超好逛,有超多婆婆媽媽會喜歡的商品,然後還有超便宜的 OS Drug,我一路上看到最便宜的合力他命就是在這邊看到的,含稅比別人未稅還要便宜,店內也一直是滿滿的結帳人潮,如果時間夠多我們大概還會在這邊找間咖啡廳吃個點心休息一下吧,這邊也有幾間咖啡廳,還有不少的餐廳,三大便利商店和 Aeon 小超市,生活機能也很強。

巢鴨地藏通商店街

巢鴨地藏通商店街

巢鴨地藏通商店街

郵局的郵筒上還有他們的吉祥物。

巢鴨地藏通商店街

巢鴨地藏通商店街

マルジ還有一間專賣紅內褲的分店也很有名。

巢鴨地藏通商店街

隨處可見他們的吉祥物,商店街的向心力感覺很高,這理也是這整趟行程中我最喜歡的商店街了。

走到另外一頭,靠近 JR 巢鴨站那邊才發現那一側其實也很熱鬧,查了一下還有不少的住宿可以選擇,像是位置最顯眼的 APA Hotel 看起來就有很多房間,或許以後也可以考慮住在這塊,交通上有山手線可以搭,而且也有一條地鐵,雖然那條好像對於觀光客不是那麼有機會用到的路線。

巢鴨地藏通商店街

逛完巢鴨商店街後,我們就搭車回東池袋四丁目了,因為已經快要晚餐時間,而且有預約的餐廳了,我們就沒有往另外一個方向去逛了,其實另外一個方向也還有想去的站,其中最想去的就是鬼子母神前了吧。

文章最後,其實有一個站我特別想介紹,就是大塚駅前了,要是沒有帶小孩出們我這站一定會特別下車拍照,這個站特別的地方是在路上電車的軌道有開過 JR 大塚站的正前方,結果呈現一個很特別的,路上電車跑在車站的正前方的畫面,可惜我只能找 Google 地圖街景來給大家看看想像一下而已,這邊是行人要過軌道要等電車過去,看起來就像是普通行人紅綠燈而已,可惜沒有平交道的竿子,要是整個長得更像鐵路平交道,那這邊的街貌就會更特別了。

大塚駅前

2023 Tokyo

2023年5月1日 23:18

東京

睽違三年,終於又去了日本了,這次因為太久沒去所以排比較多天,來回共十一天,從週五跨一個週間到週一,這篇想紀錄一下這次的行程規劃、後來實際進行的狀況以及覺得有點缺憾錯過的東西。

這趟的主行程其實是三年前本來要去的,結果遇到疫情爆發,當時想的就是帶小朋友去迪士尼玩和順便去看富士山泡溫泉,所以這次行程規劃也就把那次的缺憾放進來,不過我們決定的有點晚,加上日本最近一直有國內旅遊補助,所以安排住宿的時候一直蠻困擾的,最後基本上規劃成:

4/14 入境、上野採買
4/15 銀座採買
4/16 淺草、台場
4/17 迪士尼
4/18 迪士尼
4/19 富士山
4/20 富士山
4/21 大宮鐵道博物館
4/22 隨意
4/23 隨意
4/24 出境

在帶著一個拖油瓶的情況下,入住的飯店一共換了四間其實有點多,前三天住上野御徒町,然後迪士尼樂園大飯店,大倉東京灣酒店,然後河口湖,最後在池袋王子住四天,這樣安排主要是迪士尼本家飯店一來比較貴,二來也很難同一間連住兩天,本來是想說第二天玩完回去市區住,方便隔天搭巴士去河口湖,本來預期的兩大挑戰是:

  1. 從上野到迪士尼,需要轉車加上搬運大量行李
  2. 從迪士尼往富士山,需要去東京車站轉巴士,還要寄送行李到最後的住宿地:池袋王子大飯店

這兩個路程我都有仔細研究過,轉車要去哪一站,搭車要搭哪個位置上車比較好,還有怎樣的路線才有電梯等等,不過最後都沒派上用場,首先是從迪士尼離開去河口湖這段,我在出發前就決定改成租車去,這樣雖然有比較貴,但是好處多很多,最棒的就是我時間變多所以還多去了木更津的三井 outlet,以及穿越東京灣的 Aqualine 和海中間的水上螢火蟲休息站(本來是想繞東京灣一圈的,結果發現要兩個小時),尤其是後者我實在蠻有興趣,但是作為觀光客實在很難有機會,所以我的規劃就是一早搭 Disney Resort Line 單軌去舞濱站轉公車,不用跑去其他地方,直接在車站前面的公車站等公車,搭到猫実站(念作 Nekozane 四個音,我一時以為是三個音,還好司機聽得出來)然後走到 Toyota 租車浦安やなぎ通り店,接著開車回去大倉裝行李。

PS. 我蠻喜歡這間 Toyota 租車的,還有飲料可以給你外帶上車,包括咖啡、果汁、紅茶花傳無糖紅茶,相較於後來還車的東池袋店真是差很多。

至於第一個挑戰,其實本來還沒打算花錢解決,後來因為第一天光是從機場到飯店帶著拖油瓶就累死了,我們帶了兩個大行李箱、小孩有給他推車坐,雖然基本上是都找的到斜坡和無障礙的路線,但是常常需要一直繞路,並且有些路段也不是很好推,體力消耗不小,加上第三天招待我們的朋友也覺得花錢解決比較好,而且前兩天採買過後行李已經變多,所以那天變成直接用 uber 叫大車子來去迪士尼樂園大飯店,意外的很好叫。

回來介紹一下實際的行程吧:

第一天到羽田、搭單軌去轉 Tokyo Metro 到上野御徒町,住在 hotel MONday Ueno Okachimachi,這間我蠻推薦的,我有在 FB 上寫下推薦的原因,有興趣的可以去參考看看,辦理入住後然後直接在上野商圈買藥粧和電器,電器這次目標其實只有兩台吹風機,其中一台是 Panasonic 的最新款吹風機 EH-NA0J,我現在正在用的是 NA96,用快八年了,其實一直有想升級,但是一直換不下手,直到今年機器造型改很大,我才決定要買下去,而且那時候在日本便宜蠻多的(台灣有代理這台),我是在多慶屋買,有 10% 退稅 + 2% off 然後再加上 JCB 的回饋 10% off,然後信用卡自己的回饋又有 3-5% off,加總起來至少是 25% off 的折扣。這天的晚餐是上野的山家豬排,其實本來沒特別指定要吃這家的,只是走到一半小孩開始耍賴,然後剛好就在這家附近,也快開門了,我也有印象是也算有名的店所以就決定吃他,店面非常傳統,只收現金。

上野

多慶屋

山家

第二天早上去 PARCO_ya 和松坂屋百貨亂逛,中午吃 HARBS,下午就去銀座逛街,晚上則在東京車站,本來要去一番街找吃的結果跑到丸之內,亂走跑到 KITTE,想說去看東京車站,可惜這天雨下的很大,小孩也不好顧,還是放棄拍完全完工後的東京車站正面照了,只能期待下次來東京了。其實理想上我們有換飯店,購物應該放到後面,不過因為 JCB 回饋 10% 活動實在太香了,然後又剛好只到這天結束,所以只好改成先購物的行程,光這兩天買的東西就多一個登機箱了。

東京車站

東京

第三天天氣很好,一早就從上野去淺草,吃了鯛魚燒,逛了西松屋買小孩衣服(觀光客應該不會跑來這間),午餐買豬排三明治,本來要搭水上巴士的 EMERALDAS 到台場的,結果竟然整天的票都賣光了,真的是完全想不到這麼熱門,這時候我犯了一個錯誤,其實這邊還有一家東京水邊線可以搭(其實也有一點距離),我忘了去那邊看看還有沒有位子,後來改成搭地鐵轉百合海鷗號去台場,到台場看獨角獸鋼彈,然後和朋友會合晚餐,還有搭車走夜間的首都高和去了一下東京鐵塔旁邊。

天空樹

淺草寺

東京

台場 UC 鋼彈

第四天早上搭 Uber 大車直接去迪士尼樂園大飯店,寄放行李後搭 Disney Resort Line 到 Disney Sea,晚上就住迪士尼樂園大飯店。

Tokyo Disney Land Hotel

Tokyo Disney Sea

Tokyo Disney Sea

Tokyo Disney Land Hotel

第五天早上先衝去 Disney Land 玩了美女與野獸,後來大概十點我回到飯店處退房,順便把行李轉給門房請他們送去大倉東京灣酒店,這個免費服務其實我查了很久,迪士尼網站每個語言資訊不太一樣,只有繁體中文版網頁有明確寫是免費服務,總之在海邊那幾間公認飯店都可以送行李過去,他會跟你確認你有在那邊訂房就是,然後從飯店內小超商買了一些食物當午餐回去園區,晚上搭單軌去搭轉接巴士到飯店。

Enchanted Tale of Beauty and the Beast

Tokyo Disney Land

Tokyo Disney Land

Tokyo Disney Resort

第六天早上太晚出門,總之就是搭單軌、轉公車然後去浦安的 Toyota 租車把車開回酒店載行李,之後穿過東京灣去木更津 outlet 順便午餐,之後逛了兩個多小時(太晚出門只有兩個多小時逛),然後回程去 Aqualine 中間那個海上螢火蟲休息站晃一下,之後就一路往河口湖,結果在市區塞車花不少時間,到飯店時已經快要過晚餐時間了,有點驚險。這天住的是秀峰閣湖月,是我每天刷網頁刷了很久才訂到的,晚餐是在房間用餐,每個房間都有很棒的景可以看,運氣好的話在房間就可以看到逆富士。

PS. 秀峰閣湖月的小賣店的商品都還蠻不錯的,可以逛逛。

東京行

租的車其實有點小,行李差點塞不下,不過車子本身蠻新的。

木更津三井 Outlet

樂高竟然比較比台灣便宜一點,匯率真是太低了。

海螢火蟲

海螢火蟲

很特別的海上休息站,海螢火蟲,天氣好時可以看到富士山,不過那天只有一片白。

海螢火蟲

這種休息站在日本屬於 PA(Parking Area),還有一種更多服務的的則是 SA(Service Area),如果有要在日本開車上高速,還有兩個一定要知道的就是 IC (Inter Change) 指的是交流道,JCT(junction)則是複數高速公路交會的地方,可以換線但是不能上下高速公路。

東京

河口湖

晚上用相機長曝才拍的出來的逆富士,肉眼其實只看到一片黑,仔細看才會發現非常淡的輪廓,所以我才判斷可以用相機拍出來,不過這次沒帶腳架,不然應該可以拍更漂亮。

第七天我一早就起床看有沒有機會看到逆富士,不過很可惜,一大早富士山都被雲遮住,直到太陽出來才開始散開,這時候湖面也已經不平靜了,是還好我前一天晚上有用相機長鋪拍到逆富士,飯店也有個池子讓人可以拍到逆富士,也不是毫無收穫,其實這兩天在富士見預報都只有 1 分,所以能看到富士山其實我已經蠻滿意了,之後跑了兩三個景點,午餐吃了FUJI de 223 いなりと発酵カフェ,然後就直奔回東京市區了,這天晚上是要住池袋王子,照著導航經過九彎十八拐的首都高後,沒想到東池袋出口下來,馬上左轉就是池袋王子的大門,然後還車的東池袋 Toyota 租車,就繼續往前,右轉之後馬上就到了,超級方便(不過我為了加油又去繞了一圈),晚餐就在 Sunshine City 解決,這天應該多逛一下 Sunshine City 裡面的店的,因為後來真的要逛時反而沒時間了,商店又很早關門。

秀峰閣湖月

秀峰閣湖月

河口浅間神社 遥拝所 【天空の鳥居】

上去遙拜所時太陽高度太高了,光線不太好,這塊其實還算是開發中的地方,旁邊的植物都還沒長出來。

第八天照預定是去大宮鐵道博物館,因為想說是熱門景點,應該平日去比較好,結果真的人不多,逛起來蠻舒適的,拍照拍的很快樂,不過少逛到一間新幹線的展示區小可惜,然後體驗區我們幾乎都沒去,因為小孩還太小,大部分都要是小學生才可以操作,也因此時間控制的還不錯,大概半天多一點就結束這段行程,還可以回去池袋逛百貨;大宮鐵道博物館這個景點真的蠻不錯的,有興趣的人光是展示區就可以逛很久,還有很多可以體驗的裝置,一些鐵路相關的物理原理介紹等等,然後紀念品店也很大,這次去還剛好有全國 IC 聯通的十週年特區,下午則是回池袋隨便逛逛。

The Railroad Museum

The Railroad Museum

The Railroad Museum

大宮站有一個鐵道商店:Railyard 我還蠻喜歡的,還看到 Suica 的印傳錢包,回來研究了好一會兒才找到,詳細紀錄在 Twitter 那邊。

第九天早上決定去新宿看巨大貓咪,不過早上先去池袋西武百貨找高湯包,意外在那邊超市買到養樂多 Y1000,到新宿後想說先跑去西口看看藥粧要補買,結果沒買藥粧,反而在 Yodobashi 西口買了一堆遊戲和布娃娃(我的瑪利歐和路易),後來午餐去吃 Shake Shack,之後才去到東口看貓咪,然後人實在太多了,看完貓咪就逃回池袋去逛水族館。

養樂多 1000

新宿東口巨大貓咪

Sunshine Aquarium

第十天是最後一個整天,有鑑於昨天新宿人實在太多,所以我就放棄鬧區行程(本來有考慮去渋谷,有 Sibuya Sky 和任天堂商店),決定去荒川線(Sakura Tram)一日遊,直接走去池袋四丁目站搭去終點然後往回跑,去拍了天空樹加荒川線電車,然後荒川車庫前面的 memoral hall 看老車和一些都營電車的歷史紀錄,然後去巢鴨商店街,我個人很喜歡巢鴨商店街這種有一點年代感但是又夠熱鬧的商店街,還偷偷買了他們的吉祥物鴨鴨,之後再單獨一篇介紹這段行程;之後晚餐回到池袋的敘敘院,是前兩天訂到的,之後去 Uniqlo 補充一點衣物,我大概一年會買一件 DRY-EX 不同花色,日本的選擇也多一點,然後買新的 AIRism 淘汰現在穿的,這間也是可以退稅的 Uniqlo 超大型店,然後回到 Sunshine City,本來想逛 one piece 商店或是 pokemon store 或是 BANDAI NAMCO cross store,後來只有逛了最後一間,走馬看花一下,裡面還有 SJHA 特展,但是特展比商店還早關門...

都電荒川線

都電荒川線

都電荒川線

巢鴨地藏通商店街

BANDAI NAMCO cross store

最後一天就是有約了機場接送,十點就搭車去羽田空港,然後領了一堆完美行買的土產,去報到和托運行李,然後去 HANEDA Airport Garden 逛逛,這一整棟是新的,和羽田第三航廈在二樓有連通,二樓都是商店、也有連通到旅館和溫泉,本來有想說要不要在這邊吃飯,不過那時候完全不餓,而且飛機上也有供餐,所以就沒去餐廳,然後就發現他規劃的蠻好,商店全部集中在二樓,而且在空橋就有商店了,餐廳則是在一樓,所以如果只是要買買紀念品,從二樓連通道過去不用上下樓,對於還要推車和行李的旅客來說很方便,而且商店也還蠻好逛的,KOKUYO 也有一間專店,還擺出我本來想要買的剪刀 HASA 系列,不過當時行李已經托運了。

Haneda Airport Garden

HANEDA Airport Garden 還有旅館和溫泉,可以參考 Henry 的文章

有旅程就會有收穫和缺憾,這次有幾個讓人印象深刻的經驗,第一個是迪士尼的美女與野獸,現在很熱門,我們因為提早入園所以沒排太久,一般推薦是可以買 DPA 入場,不過事實上排隊進城堡後,還有很多場景蠻棒的,走 DPA 通道好像就會跳過這些,回到設施本身,我只能說很棒,值得體驗。

Enchanted Tale of Beauty and the Beast

Enchanted Tale of Beauty and the Beast

Enchanted Tale of Beauty and the Beast

第二個則是夜晚的首都高,這次旅程我第一次上首都高,結果這次一共上去了五趟,其中只有第一趟是晚上上去的,是我朋友帶我們去吃飯前後經過首都高,必須說夜晚的首都高配上東京鐵塔的景致真的很特別,除去首都高夜景之外,我後來自己開上去經過 Aqualine 還有回程繞東京一大圈然後到東池袋出口,也真是讓我體會到首都高的錯綜複雜,甚至在皇居附近其實也有路線,但是是完全地下化了,日本人真的是對天皇很敬重啊,期待日本橋那邊也有機會重見天日。

實際上走首都高也讓我對於東京有多大有更深刻的體會,必須說他們的電車地鐵速度真的是很快,開車走高速公路很多地方都會覺得怎麼會有這麼遠。

至於缺憾的部分,其實很多,不過大部分都算是小事就是:

  • Sunshine City 動漫商店沒什麼逛到,那個 SJHA 特展也幾乎沒看到;
  • 銀座那邊有些展覽和也沒時間看;
  • 銀座伊東屋讓人有些失望,商品品項蠻少的,然後人很多幾乎無法逛;
  • 錯過新宿歌舞伎町的藍色招牌和 Suica 廣場的企鵝銅像;
  • 沒拍到東京車站正面;
  • 沒搭到松本零士設計的東京遊船,這已經是我第二次錯過了,第一次只有一台時我剛好遇到維修日;
  • 沒有上高樓展望台,Shibuya Sky 沒去,也沒有去重新開張的 Sunshine City 60;
  • 沒去任天堂商店;
  • 回來才發現水戸岡鋭治剛出,沒順便買;
  • 沒和小孩搭到小小世界,剛好我跑去處理退房和行李;
  • 沒喝到無酒精檸檬堂和 Jack Daniel 的威士忌可樂調酒。

東京

最後,想提一下午後的紅茶無糖版,我必須說多年前台灣有上市前是真的蠻好喝的,台灣剛上市時我也覺得很好喝,可是後來我喝就開始覺得味道不對,我一直在想會不會是台灣版本的味道不對,結果這次去日本特地買了一瓶驗證,還真的味道就是變差了,真是讓人傷心,還好後來我買到其他款不錯的,Craft Boss 的無糖紅茶,不過這款比較難買。

東京

延伸的遊記:

wbr 的這些那些

2023年1月13日 22:52

在 responsive design 成為主流之後,有個問題也隨之被突顯出來,就是文字的換行,尤其是標題文字的換行位置,現在的瀏覽器的換行方式簡單來說就是超出區塊範圍的東西都往下放到下一行,所以在某些情況下,就會有第一行很長第二行很短的狀況,視覺上非常不平衡,以下圖為例,網頁的副標題沒有作特殊處理,所以會有可能會變成兩行長度差距很大的樣子:

line breaking

這個問題有蠻多解決方法的,目前我知道的就有:

  1. 微調 responsvie 的樣式來避免出現不平衡的狀態
  2. 在特定地方插入 &nbsp; 避免換行
  3. 用 flex layout 來控制換行位置

基本上就是只要你不擇手段,問題還是可以解決的,不過我一直以來都是會偏好用標準的方法來解決問題,所以整理了一下我所知道可以拿來用的東西:

  1. <wbr> HTML element
  2. white-space CSS property
  3. <nobr> HTML element
  4. text-wrap CSS property

首先的想法是 <wbr> 配上 white-space: nowrap; 或是 <nobr>,不過意外的是大部分瀏覽器都不支援這個組合,也就是說,包在 <nobr> 內的 <wbr> 的地方在現在大部分主流瀏覽器內是不會換行的:

<nobr>ChatGPT: Optimizing<wbr>Language Models<wbr>for Dialogue</nobr>

這就激起了我的好奇心了,於是我開始仔細的找資料,看看 <nobr><wbr> 到底是怎樣運作的。首先,就來看看 <wbr> 吧,雖然他第一次出現在 W3C 的文件內就是在 HTML5,但是其實它已經出現了 20 年以上,最早是作為 Netscape 的 HTML 2 extension 的一員:

The WBR element stands for Word BReak. This is for the very rare case when you have a NOBR section and you know exactly where you want it to break. Also, any time you want to give the Netscape Navigator help by telling it where a word is allowed to be broken. The WBR element does not force a line break (BR does that) it simply lets the Netscape Navigator know where a line break is allowed to be inserted if needed.

在 Internet Archive 上找到的備份,最早的定義其實明確的說著 <wbr> 處應該是要可以優先於 <nobr> 的,我甚至還在 bugzilla 上找到一個 24 年前的 bug report 在講這件事,根據這張票最後的關掉前的討論,其實可以用 </nobr><nobr> 來達成一樣的效果,然後 Firefox 不打算支援 <wbr>,看到這邊,我只能說這解法怎麼這麼天才(稱讚的意味)。

查到這邊,我還是很好奇為什麼現在的主流瀏覽器依然 <wbr> 優先度比 <nobr> 還低,所以繼續找資料,這次看的就是最新的文件了,首先是 <wbr>定義,在 HTML5 中變成:

The wbr element represents a line break opportunity.

從 "word break" 變成 "line break opportunity" 了,<nobr> 則和 <wbr> 一樣一開始是 Netscape extension,不過他倒是沒有進到 HTML5,事實上,雖然 <nobr> 不是 HTML5 的一員,但是文件中定義的 default style 還是有它

br { display-outside: newline; } /* this also has bidi implications */
nobr { white-space: nowrap; }
wbr { display-outside: break-opportunity; } /* this also has bidi implications */
nobr wbr { white-space: normal; }

其實就是等價於 white-space: nowrap,而 white-space 屬性在 CSS2 時是用來定義空白的處理方式:

This property declares how white space inside the element is handled.

到 CSS Text Level 3 時定義說的更明確,是用來決定遇到 "line break opportunity" 時的處理方式和是不是要合併 space 字元的屬性:

其實在 Level 4 更是可以分開設定兩種屬性,white-space 變成一個 shorthand,而看到這些定義的演進,其實也讓人發現最早命名時其實只有考慮到西方語言的特色。

看到這邊,會發現有一個新的名詞:"line break opportunity",有些地方是稱為 "wrap opportunity",這個名詞其實是出現在 CSS Module Text 文件中的,顧名思義,就是可以換行的位置,而這份文件也是定義換行邏輯的文件,不過這邊其實沒把換行演算法(line breaking algorithm)明確定義下來,而是闡明各種相關的 CSS 屬性和它們會怎樣影響換行的結果,例如換行有分強制(<br>)和非強制(<wbr>),然後不同 CSS 屬性會影響這些換行點的出現與否,至於文本之中,哪些地方可以換行,就是換行演算法的部分了,這部份在 W3C 文件沒有定義死,所以是允許瀏覽器自己決定的,不過有提供一些參考文件,像是 Unicode 的附件 14:"Unicode Line Breaking Algorithm",或是稱為 UAX14,這份文件要搭配 Unicode Database(UCD) 的 Line_Break Property 資料,文件中的第五章有仔細的說明各種不同的 line breaking class,然後資料庫則是定義了所有 Unicode 字元的 Line_Break property,接著的第六章就是最重要的換行演算法了,這邊列出了 31 條規則,基本上是反向列舉,說哪些地方不能換行,例如 WJ(word joiner)前後都不能換行,數字中間的符號前後也不能換行之類的,不過不確定哪些瀏覽器是實作 UAX14 的,Chrome 似乎有用到 UCD,Firefox 則是以 JIS X 4051 為基礎做的換行演算法,其實 JIS X 4051 是我所知道,二戰後世界,最早的正式的文字編排的標準,查到的紀錄是 1989 有一版,而 UAX14 第一個非草稿的版本則是要到 1999 年才出來。

回到現在主流瀏覽器不支援 <wbr> 放在 white-space: nowrap 裡面無法換行的問題,其實要回到這個 HTML 標籤在繪製時,是不是有 magic 的,什麼是 magic 呢?簡單說就是,你能不能用 CSS 來定義該標籤的樣子,以及你能不能用 CSS 來改掉這些預設的樣式,而 <br><wbr> 正好是有 magic 的標籤,可以想想看,要怎樣用 CSS 讓文字內產生一個換行點,可能有人會說剛剛上面才看到的,不過,其實那幾個 display-outside 的值只存在於以前的草稿中,CSS-WG 決議不為了這個問題新增多的 display 狀態,所以問題就回到 HTML-WG 這邊了,CSS-WG 的 fantasai 其實有給了一組 default style:

br { all: unset !important; display: contents !important; content: "\a" !important; white-space: pre !important; }
wbr { all: unset !important; display: contents !important; content: "\200B" !important; }

我把 !important 拿掉整理一下:

br {
  all: unset;
  display: contents;
  content: "\a";
  white-space: pre;
}
wbr {
  all: unset;
  display: contents;
  content: "\200B";
}

其中,\a 就是換行字元\200B 則是 ZWSP,zero width space,因為是 zero width,所以看不到,然後又因為是 space,所以可以用來把字切開,也就表示可以在該處斷行。不過這個版本有些問題,因為有很多瀏覽器還不支援在 ::before, ::after 以外的物件上套用 content 屬性,所以 fantasai 又提供一版用 ::before 的版本:

br, wbr { all: unset !important; display: contents !important; white-space: pre !important; }
br::before { all: unset !important; content: "\a" !important; }
wbr::before { all: unset !important; content: "\200B" !important; }

但是,實際上直接拿這組定義來用,還是一樣有問題,就是有些瀏覽器已經讓 <wbr> 有 magic 了,結果 wbr::before 是沒有用的,目前 HTML 標準的修改也就還卡在這邊(issue 則是另外一個),HTML-WG 的 Domenic 開了這個 PR 要讓 <br><wbr> 就用 magic 來實現效果,不過這討論已經停很久了,所以最後會是怎樣的方案還不知道。

總之目前的結論就是,現在如果想要讓 <wbr> 照其定義的一樣,可以在 <nobr> 或是 white-space: nowrap 內產生換行,是辦不到的,不過可以用其他的標籤來辦到,像是:

.wbr::before {
  display: inline;
  content: "\00200B";
  white-space: normal;
}

配上

<nobr>ChatGPT: Optimizing<span class="wbr">Language Models<span class="wbr">for Dialogue</nobr>

這個寫法也有出現在一份由 Leif Halvard Silli 在提交 bug 給 WebKit 所做的 test 內

到這邊,大概已經把自己控制換行位置的部分講的差不多了,不過其實,還有一個方法可以處理一開始所提到的換行結果不理想的問題,就是在 CSS Text Module Level 4 中有一個新的屬性叫做 text-wrap,其中有一個屬性值是 balance,合起來就是

text-wrap: balance;

這樣設定,預期的結果就是會換行換在每一行的寬度最接近的位置,不過當然還沒有瀏覽器支援,連 caniuse 都還查不到 text-wrap 屬性,只是還是有 JS 的解決方案:

最後的最後補上一些參考資料,一篇是古老的 IE 時代的東西,一篇則是現在的相關 CSS 屬性,一篇則是 balance wrap 的介紹:

❌
❌