由於我在 pixnet 部落格裡撰寫程式及網頁技術的文章, 給程式上色這個需求一直都是使用套件 highlight.js. 不過有幾個地方總是不那麼滿意: 像是用於 linux 指令或批次檔的 <code class="language-bash"> 或者用於 Windows 命令提示字元 (CMD 指令或批次檔) 的 <code class="language-dos">, 它們總是會少幾個指令沒有上到色. 下面幾個方法是我這幾年來自己琢磨出來的修正 bash 和 dos 這二類程式區塊上色問題的辦法, 在此分享給有需要的朋友.

方法一: 直接修改上色的部份


這個方法適合用於補足少量 highlight.js 沒有上色的部份. 這是最直觀的方法, 而且不限制被上色的語言, 我們只要將想要上色的部份用 <span> tag 括住, 並加上需要的 style 即可. 或者也可以用預設 displayinline 的元素 (<b>, <i>...等) 修改字型變化, 請看下例的 HTML 上色片斷:

一般文字 <i style="color:red">text_to_use_red_color</i> 一般文字
一般文字 <span class="hljs-built_in">text_to_use_as_built_in</span> 一般文字

上色後的效果如下:

一般文字 text_to_use_red_color 一般文字
一般文字 text_to_use_as_built_in 一般文字

除了用 style 更動 color (顏色), font-weight (字重, 粗體), font-style (斜體). 我們也可以像第二行那樣套用 highlight.js 的標準化 CSS class: class="hljs-xxxx". 其中的 xxxx 是 highlight.js 解析出來的範圍 scope, 如下表:

Scope name
範圍名稱
Description 說明
General purpose 一般用途類
keyword keyword in a regular Algol-style language 一般 "類 algol" 語言的關鍵字 (或稱保留字)
built_in built-in or library object (constant, class, function) 語言的內建 (功能), 或者 (標準) 函式庫裡的物件 (如: const, class, function)
type data type (in a language with syntactically significant types) (string, int, array, etc.) 資料型態 (語言中具語法意義的資料型態), 如: string (字串), int (整數), array (陣列).
literal special identifier for a built-in value (true, false, null, etc.) 字面值 (定數). 用來表示內建值的特殊識別字. 如: true (真), false (偽), null (空值)
number number, including units and modifiers, if any. 數值, 包含其單位及修飾詞, 如果存在的話.
operator operators: +, -, >>, |, == 運算子. 例如: +, -, >>, |, ==...等等
punctuation aux. punctuation that should be subtly highlighted (parentheses, brackets, etc.) 標點符號. 應該被精巧上色的輔助標點. 如: 圓括號 (parentheses), 方括號 (brackets).
property object property obj.prop1.prop2.value 物件的 (固有) 特性.
regexp literal regular expression 字面正規表示式 (不是 "字串" 轉換的那種)
string literal string, character 字面字串, 字元.
char.escape an escape character such as \n 脫逸字元. 例如: \n
subst parsed section inside a literal string 字面字串內的解析區段
symbol symbolic constant, interned string, goto label 符號: 符號定數 (或稱定義常數), 留存字串, goto 標籤.
class deprecated You probably want title.class 類別. 已棄用. 可能你要的是 title.class.
function deprecated You probably want title.function 函式. 已棄用. 可能你要的是 title.function.
variable variables 變數
variable.language variable with special meaning in a language, e.g.: this, window, super, self, etc. 在特定語言中有特別意義的變數. 例如: this, windows, super, self.
variable.constant variable that is a constant value, i.e. MAX_FILES 其值為定值的變數. 也就是 MAX_FILES.
title name of a class or a function 類別或函數的名稱
title.class name of a class (interface, trait, module, etc) 類別, 介面, 特徵, 模組... 的名稱
title.class.inherited name of class being inherited from, extended, etc. 繼承或擴充來源的類別名稱
title.function name of a function 函式名
title.function.invoke name of a function (when being invoked) 函式名 (被引用時)
params block of function arguments (parameters) at the place of declaration 函數的參數區塊 (函數宣告)
comment comments 註解
doctag documentation markup within comments, e.g. @params 位於註解裡的文件標記, 例如: @params
Meta 自我描述類
meta flags, modifiers, annotations, processing instructions, preprocessor directives, etc 旗標, 修飾詞, 註解, 操作指引, 預處理指令... 等
meta.prompt REPL or shell prompts or similar REPL(讀取-求值-輸出) 環境或 unix/linux shell 環境或類似環境的提示字串
meta keyword a keyword inside a meta block (note this is nested, not subscoped) meta 區塊內的關鍵字. 此 class 為巢狀嵌套, 而非子範圍.
meta string a string inside a meta block (note this is nested, not subscoped) meta 區塊內的字串. 此 class 為巢狀嵌套, 而非子範圍.
Tags, attributes, configs 標籤, 屬性, 設定類
section heading of a section in a config file, heading in text markup 設定檔裡的區段標題, 文字標記的標題
tag XML/HTML tag XML/HTML 標籤
name name of an XML tag, the first word in an s-expression XML 標籤的名稱, "S-表示式" 裡的第一個單字.
attr name of an attribute with no language defined semantics (keys in JSON, setting names in .ini), also sub-attribute within another highlighted object, like XML tag 屬性名稱, 不具特定語義的 (即: 非語言預先定義). (譬如: JSON 的鍵值, 或 .ini 檔裡的設定名稱);
或者某高亮物件的子屬性, 如: XML 標籤的屬性名稱.
attribute name of an attribute followed by a structured value part, like CSS properties 屬性名稱, 後面接著結構化的 (屬性值), 譬如: CSS 設定項.
Text Markup 文字標記類
bullet list item bullet 列表項目的符號
code code block 程式區塊
emphasis emphasis 強調
strong strong emphasis 加重強調
formula mathematical formula 數學公式
link hyperlink 連結
quote quotation or blockquote 引述/引用
CSS 樣式類
selector-tag tag selector 標籤類
selector-id #id selector 識別 (身份) 類
selector-class .class selector 類別類
selector-attr [attr] selector 標籤屬性類
selector-pseudo :pseudo selector 偽類 (偽類別/偽元素)
Templates 模板類
template-tag tag of a template language 樣板語言裡的標籤
template-variable variable in a template language 樣板語言裡的變數
diff 差異類
addition added or changed line 新增或有更動的行
deletion deleted line 刪除的行

注意事項:

  • 上表中有含有新式的 scope 名稱 (如: 一般用途類的 title.function.invoke), 這種 scope 名稱轉換為 CSS class 時會是:
    • hljs-title
    • function_
    • invoke__
    轉換後的 HTML 片斷會長這樣:
    <span class="hljs-title class_ other__">Render</span>
    
  • 使用這個方法, highlight.js 的 regexp 設定有時會將你括住的字串拆成數段. 此時你的 CSS 設定可能需要增加一些 Selector 和 CSS 設定又或者 CSS 設定項需要加上 !important 才能達成你想要的上色效果.
  • 這個方法在 v9 及 v10 這二個舊版本上都可以直接使用. 但是在最新的 v11 版卻是需要一個外掛 mergeHTMLPlugin: 部份原因是資安因素(註一), 部份原因則是因為 issue #2889.

註一: 參看 highlight.js 在 github 上的 "Security Policy" 頁面.

如何使用 mergeHTMLPlugin


如果你看過上述 issue 就會知道: 這個 plugin 本身就是由 highlight.js 分離出來的. 但是, 由於目前並沒有人主持這個 plugin 的維護工作, 所以想要在 pixnet 痞客邦的部落格內使用這個 plugin 就只有:

  • 直接由某 CDN 業者處下載.無人維護, 無法使用此方法.
  • 直接將它貼在貼文中.
  • 尋找一處可以提供 JS 檔案內嵌功能的網路磁碟空間來放置此檔案.
    • 由於 XSS 問題, 目前我所知的網碟只剩 MS OneDrive 可用.
    • DropBox, Google Drive 都已經因改用短時效金鑰而無法用於提供 JS 檔案.
    • 不知哪天 MS OneDrive 也會失效.

好了! 廢話不多說, 來看如使用 mergeHTMLPlugin. 原本我在 pixnet 使用 highlight.js 是在文末貼上這一段:

<link href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/styles/night-owl.min.css" rel="stylesheet" />
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/11.10.0/highlight.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/highlightjs-line-numbers.js/2.8.0/highlightjs-line-numbers.min.js"></script>
<p id='myCSS'></p>
<script>
document.getElementById('myCSS').outerHTML=[
 '<style>'
,'.hljs-ln tr { background-color:inherit }'
,'.hljs-ln td { border:inherit }'
,'.hljs-ln td.hljs-ln-numbers { text-align:right; padding-right:4px}'
,'.hljs-ln td.hljs-ln-code { border-left:2px solid #999; padding-left:5px }'
,'</style>'].join('');
if (typeof(hljs) !== 'undefined') {
	hljs.highlightAll()
	hljs.initLineNumbersOnLoad({singleLine: true})
}
</script>

現在, 必需在前面多貼以下這段, 並將原本的第 13~16 行刪掉, 將它搬到下段的 11, 14~16 行. 而下段第 12 行, 為啟動 mergeHTMLPlugin 所必需的. 下段第 13 行為選項, 此設定讓 hljs 在 borwser 的 console 裡抑制警告訊息: 關於 <code> tag 內含有未轉換為 escape 字元的 HTML tag 的部份 (即: 我們想要自己上色的那一部份).

<script>
function myScript(o, url) {
	let s = document.createElement(o)
	s.src = url
	s.async = true
	document.head.appendChild(s);
	return new Promise((fa, fj) => { s.onload = ()=>fa(); s.onerror = ()=>fj(); });
}
myScript('script', "由 MS OneDrive 下載 mergeHTMLPlugin 的 URL")
.then( data => {
	if (typeof(hljs) !== 'undefined') {
		hljs.addPlugin(mergeHTMLPlugin)
		hljs.configure({ignoreUnescapedHTML: true})
		hljs.highlightAll()
		hljs.initLineNumbersOnLoad({singleLine: true})
	}
})
</script>

方法二: 暫時修正 hightlight.js


提醒

本段所敍述的方法二雖然有以下二個優點:

  • 免用 mergeHTMLPlugin.
  • 可以自動完成海量的上色修補工作.

但是, 目前個人只在 bash, dos, python 等三種上色語言試驗過. 非上述三種可能要請你進入 Browser 的除錯器, 到 console 裡自行檢驗物件 hljs.getLanguage('xxxx').keywords 是否包含了你想要的內容. 或者你也可以直接到 hljs 在 github 上的版本倉查看一下你想修正的語言的原始 JS 程式.

例如: js, html, css 等三個 highlight.js 核心語言的結構明顯的和上述三個不同.

如果需要修補的上色部份多樣性高, 數量也很多, 則可以利用 JS 暫時增加缺少的 keyword 或 built_in 等等的字串, 或者刪除不必要的 (會引發上錯色的) 字串. 例如在 bash Script 裡: suso, python, pip 指令沒有上到色; 而參數 --export 裡的 export 被上色了, 那麼我們就可以把 scope built_in 裡的 export 刪掉, 再補上 python, pip. 然後我們還想讓 sudo 指令也使用 keyword 的顏色上色.

不過這個部份, highlight.js v9, v10, v11 三個版本的作法都不同.

v9 修正方法


v9 的上色字串本身就是字串. 所以直接將新增的字串附在後面, 要刪除的字串直接置換成空字串即可 (注意字串值的異動不會直接換掉原本的變數內容, 必需用 =+=). 例如:

if (typeof(hljs) !== 'undefined') {
	hljs.getLanguage('bash').k.keyword+=' sudo';
	hljs.getLanguage('bash').k.built_in=hljs.getLanguage('bash').k.built_in.replace(' export');
	hljs.getLanguage('bash').k.built_in+=' pip python';
	...
}

v10 修正方法


v10 的上色字串本身也是字串. 不過, 上述指令中的 k, 改版時換成了 keywords. 所以上例換成 v10 程式就成了:

if (typeof(hljs) !== 'undefined') {
	hljs.getLanguage('bash').keywords.keyword+=' sudo';
	hljs.getLanguage('bash').keywords.built_in=hljs.getLanguage('bash').keywords.built_in.replace(' export');
	hljs.getLanguage('bash').keywords.built_in+=' pip python';
	...
}

v11 修正方法


v11 保留了 keywords, 但是上色的字串被分解成字串陣列. 所以上例換成 v11 程式就成了:

if (typeof(hljs) !== 'undefined') {
	hljs.getLanguage('bash').keywords.keyword.push('sudo');
	hljs.getLanguage('bash').keywords.built_in.push(...['pip', 'python']);

	// remove built_in: sudo, export
	let arr = hljs.getLanguage('bash').keywords.built_in;
	let del_bulit_in = ['sudo', 'export'];
	for (let kw of del_bulit_in) {
		let idx = arr.indexOf(kw)
			if (idx != -1)
				arr.splice(idx, 1);
	}
	...
}

注意: 部份 scope 可能不存在 (例如: bash Script 的 keywords.title, keywords.type), 所以要 push 之前可能需要檢查一下該 scope 是否存在且為陣列.

幾個注意事項


舊版本的 bash script 解析 regrex 有些限制, 即使設定裡有也解析不出來:

  • v9, v10 都不支援後面有數字的指令. 例如: python3.
  • v9, v10 都不支援中間有 - 的指令. 例如: apt-get, apt-key.
  • v9 會不洽當的斷開中間有 - 的指令為其中一部份上色.
  • v9 不支援 .addPlugin().

由於上述原因 (還有資安因素) 建議大家盡量改用 v11.

還有一點: 方法一及方法二是可以混用 (例如: 需要克服上述的 -數字問題). 如果真的因為某些特定原因不能使用 v11, 例如: 要混用方法一及方法二, 但是沒有可用的網碟. 則建議使用 v10 (最後的版本是 v10.7.3).

常用的語言上色 class


  • Python 程式:
    • <code class="language-python">
    • <code class="python">
    • <code class="py">
  • Python 互動指令:
    • <code class="language-python-repl">
    • <code class="python-repl">
  • DOS 批次檔:
    • <code class="language-dos">
    • <code class="dos">
    • <code class="cmd">
    • 這個需另外載入語言 JS, 但是它不支援和有 bug 的部份還滿多的.
  • bash 指令檔:
    • <code class="language-bash">
    • <code class="bash">
  • bash 互動指令:
    • <code class="language-shell">
    • <code class="shell">
    • <code class="console">
  • 一般文字:
    • <code class="language-plaintext">
    • <code class="plaintext">
    • <code class="text">
  • C/CPP 程式:
    • <code class="language-cpp">
    • <code class="cpp">
  • JS 程式:
    • <code class="language-javascript">
    • <code class="javascript">
    • <code class="js">
  • CSS 設定:
    • <code class="language-css">
    • <code class="css">
  • HTML 網頁 (含 javascript 及 CSS):
    • <code class="language-xml">
    • <code class="html">

arrow
arrow
    創作者介紹
    創作者 MagicJackTing 的頭像
    MagicJackTing

    傑克! 真是太神奇了!

    MagicJackTing 發表在 痞客邦 留言(0) 人氣()