用 BASH 指令將 XML 檔排版
【摘要】一個可以將 XML 檔排版的 BASH 小程式。
【目錄】
【前言】【指令檔】
【後語】
【前言】
一、有不少現成的工具可以將 XML 檔自動排版。個人自己試著用 BASH 指令寫了一個做為練習,兩年半前曾發表於 Google 的 Blogger,近日稍加修飾,記錄於此。
二、使用方法:
將【指令檔】一節的內容存成 formatXML.sh
檔,在虛擬終端機執行:
bash formatXML.sh filename.xml
filename.xml
是要被處理的檔名;一次只處理一檔。
處理好的檔案會以相同的檔名放入 NewDir
目錄(若無此目錄,會自動新增)。
三、預設的縮排單位是兩個空格,以 Unit
變數設定,可自行更改。
四、因為無法預知 XML 的註解內容,所以一律改成跨行註解,頭尾標記從行首開始,內容則縮排一個單位。
【指令檔】
#!/bin/bash # 將 XML 檔排版 # 檢查是否有給 XML 檔,若無,即中止 if [ -z "${1}" ]; then echo echo " Usage: ./${0##*/} XML-file" echo echo " You have to specify an XML file." echo exit fi # 縮排一階的單位;需用單引號,下同 Unit=' ' # 縮排總數 Indent='' # 本行標籤種類 Case='' # 前一行標籤種類;是否要縮排,大多決定於前一行 CasePrev='' # 註解的標記;0 表本行非註解,1 表是註解 Comment=0 # 中間檔檔名;程式會先產生一個所有標籤自成一行且完全未縮排的中間檔,最後再將之刪除 TempFile='tempfile' # XML 檔的路徑 echo ${1} | grep '/' &> /dev/null if [ ${?} -eq 0 ]; then OldPath="${1%/*}" # 如果給的是全路徑,即取出其路徑 else OldPath="${PWD}" # 如果只給檔名,則用目前工作路徑 fi # 取出檔名 FileName="${1##*/}" # 輸出檔的路徑 NewPath="${OldPath}/NewDir" # 新增存放輸出檔的目錄;若已有,會有錯誤訊息,所以將之導開 mkdir "${NewPath}" &> /dev/null # 若無輸出檔,即新增之;若已有,將之清空 : > "${NewPath}/${FileName}" # 建立中間檔 cat ${1} | sed -r 's/\r//g; s/\\/\\\\/g; s/</\n</g; s/>/>\n/g; s/(<!--)/\1\n/g; s/(-->)/\n\1/g' | # 改為 Unix 換行格式;反斜線要先 escaped;所有標籤自成一行;註解之頭尾分開 sed -r 's/^[ \t]*//g; s/[ \t]*$//g' | # 去除頭尾空白;這不能與前指令合併,因為有些空白是在分行時才產生的 sed -r '/^$/d' > "${NewPath}/${TempFile}" # 去除空白行;這不能與前指令合併,因為有些空白行是在去除頭尾空白時才產生的 # 主程式段 # 先判斷標籤種類,再行縮排 while read do if [ "${REPLY}" = '<!--' -o "${REPLY}" = '-->' ]; then # 若內容是 '<!--' 或 '-->',是註解 # 註解與眾不同,需先濾除,故不能將此檢查後移 Case="comment" Comment=1 # 註解標記 on elif [ "${REPLY:0:2}" = '<?' -a "${REPLY:(-2)}" = '?>' ]; then # 若頭二字元是 '<?' 且末二字元是 '?>',是宣告序文 Case="prolog" elif [ "${REPLY:0:1}" = '<' -a "${REPLY:(-2)}" = '/>' ]; then # 單獨成行的標籤,其倒數第二字元是 '/' Case="self" elif [ "${REPLY:0:2}" = '</' -a "${REPLY:(-1)}" = '>' ]; then # 結束標籤的第二字元是 '/' Case="end" elif [ "${REPLY:0:1}" = '<' -a "${REPLY:(-1)}" = '>' -a "${REPLY:1:1}" != '/' -a "${REPLY:(-2):1}" != '/' ]; then # 若第二字元和倒數第二字元不是 '/',是開始標籤 Case="start" elif [ "${REPLY:0:1}" != '<' -a "${REPLY:(-1)}" != '>' ]; then # 若頭尾不是 '<' 和 '>',應該是文字內容 Case="text" else # 以上皆非,做備用 Case="exception" fi if [ "${Comment}" -eq 1 ]; then # 註解先另外處理 if [ "${REPLY:0:4}" = '<!--' ]; then # 註解開始標記,行首開始 echo "${REPLY}" >> "${NewPath}/${FileName}" elif [ "${REPLY:(-3)}" = '-->' ]; then # 註解結束標記,行首開始 echo "${REPLY}" >> "${NewPath}/${FileName}" Comment=0 # 註解已結束,故標為 off else # 剩下的是內容,縮排一階 echo "${Unit}${REPLY}" >> "${NewPath}/${FileName}" fi CasePrev=comment # 是否要縮排,大多決定於前一行,故需記錄下來 # 讀下一行時,此行變前一行 else # 非註解行 case "${Case}" in prolog) # 本行是宣告;一律從行首開始 echo "${REPLY}" >> "${NewPath}/${FileName}" CasePrev=prolog ;; text) # 一般文字 case "${CasePrev}" in start) # 前一行是開始標籤;縮排加一階 Indent="${Indent}${Unit}" echo "${Indent}${REPLY}" >> "${NewPath}/${FileName}" ;; text | end | self | comment) # 其餘狀況;同前一行 echo "${Indent}${REPLY}" >> "${NewPath}/${FileName}" ;; *) # 預防有意外狀況 echo "!!!EXCEPTION > ${REPLY}" >> "${NewPath}/${FileName}" ;; esac CasePrev=text ;; start) # 本行是開始標籤 case "${CasePrev}" in prolog) # 前一行是宣告;行首開始 echo "${REPLY}" >> "${NewPath}/${FileName}" ;; start) # 前一行是開始標籤;縮排加一階 Indent="${Indent}${Unit}" echo "${Indent}${REPLY}" >> "${NewPath}/${FileName}" ;; text | end | self | comment) # 其餘狀況;同前一行 echo "${Indent}${REPLY}" >> "${NewPath}/${FileName}" ;; *) echo "!!!EXCEPTION > ${REPLY}" >> "${NewPath}/${FileName}" ;; esac CasePrev=start ;; end) # 本行是結束標籤 case "${CasePrev}" in text | end | self) # 前一行是一般文字、結束標籤、單行標籤;縮排減一階 Indent="${Indent:${#Unit}}" echo "${Indent}${REPLY}" >> "${NewPath}/${FileName}" ;; start | comment) # 前一行是開始標籤、註解;同前一行 echo "${Indent}${REPLY}" >> "${NewPath}/${FileName}" ;; *) echo "!!!EXCEPTION > ${REPLY}" >> "${NewPath}/${FileName}" ;; esac CasePrev=end ;; self) # 本行是單行標籤 case "${CasePrev}" in start) # 前一行是開始標籤;縮排加一階 Indent="${Indent}${Unit}" echo "${Indent}${REPLY}" >> "${NewPath}/${FileName}" ;; prolog | text | end | self | comment) # 其餘狀況;同前一行 echo "${Indent}${REPLY}" >> "${NewPath}/${FileName}" ;; *) echo "!!!EXCEPTION > ${REPLY}" >> "${NewPath}/${FileName}" ;; esac CasePrev=self ;; *) # 預防有意外狀況 echo "!!!EXCEPTION > ${REPLY}" >> "${NewPath}/${FileName}" CasePrev=exception ;; esac fi done < "${NewPath}/${TempFile}" # 刪除中間檔 rm "${NewPath}/${TempFile}" exit
【後語】
本文只是提供參考,或許還有不少 bugs,請自行修正。