2016年3月14日 星期一

批次編輯 html/svg/gpx/xml檔: xmlstarlet 與 xpath 初探

有時候會想要對網頁檔或 xml 檔 (例如 svg 或 gpx) 做批次編輯, 比方說想把一堆 <li> 元素變成表格的 <tr> 元素 (當然也要同時把每一條的內文拆好幾個 <td> 欄位等等)。 regexp 處理文字檔很方便; 但是遇到 xml/html 就弱掉了。 正確的做法是採用 xpath 跟 xslt。 如果想做的是比較簡單的處理, 那麼可以用 xmlstarlet 這個命令列工具。

首先請安裝 xmlstarlet: apt-get install xmlstarlet 並且下載 extract.php

範例題目是這樣的: 我想找 「西班牙文-英文」 字典, 而且只要很簡單的文字對照表就好, 不要 app。 找到很讚的 First 5000 words of Spanish。 目標是要用命令列處理每一頁, 然後把它全部串成一個簡單的 table。

  1. 抓下一個頁面: wget -O 02.html http://www.memrise.com/course/737/first-5000-words-of-spanish-2/2/
  2. 頁面標籤太亂太複雜了, 等一下如果直接用 xmlstarlet 處理, 可能會卡紙。 先用 tidy 或 extract.php 整理簡化: extract.php -s '.central-column' < 02.html > a.htmltidy 02.html < a.html
  3. 擷取出西班牙文欄位 (class="col_a ...") 跟英文欄位 (class="col_b ..."): xmlstarlet sel -B -t -c '//div[contains(@class,"col_a") or contains(@class,"col_b")]' a.html > b.html
  4. 假設先前是採用 extract.php 而不是 tidy, 那麼會看到所有 <div> 黏在一起, 當中沒有換列。 幫它插入一些換列: perl -pe 's#</div><div#</div>\n<div#g' b.html > c.html
  5. 把 <div> 轉成 <tr> 跟 <td> 並且把多餘的標籤都刪掉: perl -pe 's#<div class="col_a.*?>#<tr>#; s#<div class="col_b.*?">##; s#<div class="text">#<td>#; s#</div>##g' c.html > d.html

xmlstarlet 的教學文很難找: 使用者手冊 勉強算是教學文; 其他還有 1 2 XML 轉 csv 範例

遇到問題, 如果用 xmlstarlet 當關鍵詞下去搜尋, 只有比較簡單的問題找得到答案 -- 例如篩選出某些元素, 個別逐一 改名、 刪元素、 改屬性、 ...。 以上類型的問題, 學習 基本的 xpath 語法 應該會很有幫助。 若是複雜的問題, 改用 xpath 加上問題關鍵字下去搜尋比較容易找得到答案 -- 例如搬動或複製元素, 需要第二個參數來指定目的地的運算。 但找到的答案通常叫你直接寫 xslt。 例如最後這一步本來希望也用 xmlstarlet 做; 但浪費了好幾小時還是沒研究出來該如何把一個 node 跟它的兄弟包在一起。 (上面每一對 「西文-英文」 組。) 最後還是用 regexp 比較簡單。

另一個類似的工具是 xml-coreutils 作者詳細解釋設計理念

其實我試著要學 xmlstarlet 已經兩三次了, 一直想把它拿來當成 「專門對付 xml 檔的強化版 regexp」 來用; 但每次都卡關, 所以一直沒寫心得。 看起來不是我太弱就是它真的不太好用。 我應該別再撐了, 直接學 xslt 算了嗎? 那不如用 querypath 寫程式還更自由更簡單呢!

1 則留言: