使用NodeJS 生成Vue中文版 docSet 離線文件
- 取自官方 cn.vuejs.org (Version:2.5)
- 持續更新
用法
- Mac下使用者下載 Dash 使用文件 (下載 .docset 字尾檔案,雙擊匯入即可)
- Windows 和 Linux 使用者可下載 Zeal 使用本文件 (應該類似吧,我沒用過,自行搜一下吧,溜了溜了)
製作 docSet 文件
Dash所需的文件都是.docSet
字尾的檔案,其實docSet檔案就是一個資料夾而已,裡頭包含最終的html文件,以及根據html建立的索引(索引放在sqlite資料庫中)。
生成文件的方法有很多種,如 Python、Ruby、Objective-C
可以選擇 映象時處理,也可以映象後處理。只需要結果中包含html,以及sqlite 就OJBK。
這裡我選擇 映象後用NodeJS處理
需要用到的庫為:
fs
做一些檔案的讀寫操作path
路徑處理sync-exec
執行一些cmd命令sqlite-sync
做一些 sqlite 操作cheerio
伺服器版的jQuery
主要步驟:
根據官網提供的官方文件,整個轉換主要有以下5個步驟:
1. 建立Docset目錄結構(Create the Docset Folder);
2. 複製HTML檔案(Copy the HTML Documentation);
3. 建立Info.plist檔案(Create the Info.plist File);
4. 建立SQLite資料庫檔案(Create the SQLite Index);
5. 寫入SQLite資料索引(Populate the SQLite Index);
複製程式碼
1. 映象站點
映象工具有很多,這裡只推薦我嘗試過的幾款,並且用著還不錯:
名稱 | 平臺 | 地址 |
---|---|---|
HTTrack | OS X / Windows/Linux/Unix/BSD | www.httrack.com |
SiteSucker | OS X | sitesucker.us/home.html |
Cyotek WebCopy | Windows | www.cyotek.com/cyotek-webc… |
實際上這幾種都不是很完美,或多或少會漏一些外部站點的資原始檔,目前還沒找到解決辦法,
如果你們有解決辦法,麻煩**@**我一下。
我這裡以SiteSucker 為例,映象 https://cn.vuejs.org
,一級目錄結構如下:
// cn.vuejs.org
├── _00.txt
├── _downloads.html
├── coc
├── css
├── fonts
├── guide
├── images
├── index.html
├── js
├── manifest.json
├── support-vuejs
└── v2
複製程式碼
重點關注物件為以下,提取其中的內容,生成索引
cn.vuejs.org/v2/api/index.html
(API 列表)cn.vuejs.org/v2/guide/*
(官網教程列表)cn.vuejs.org/v2/style-guide/index.html
(風格指南)
參考Dash的Vue文件,一層層撥開後,發現官方用的HTTrack做的映象,並且資原始檔比我自己用HTTrack映象下來的資源齊全,一番折騰下來,也沒成功。
對比後選擇用官方的外部資源,文件內容則用自己映象的
左側為dash官方文件中的資源 、中間為合併後的資源(後面要用到)、 右側為自己映象的資源
複製程式碼
2. 建立Docset目錄結構(Create the Docset Folder)
本例中我們建立的文件叫 VueJS,所以按下列結構建立目錄:
mkdir -p VueJS.docset/Contents/Resources/Documents/
複製程式碼
3.複製HTML檔案(Copy the HTML Documentation)
把所有的html文件拷貝到Documents資料夾中,dash預設 Documents 為文件根目錄
為了省事,我把需要的資原始檔放在了當前專案下。
cp -r ./Documents VueJS.docset/Contents/Resources/
複製程式碼
4.建立Info.plist檔案(Create the Info.plist File)
在 VueJS.docset/Contents/ 中建立Info.plist檔案,注意檔名的大小寫,檔案內容如下:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleIdentifier</key>
<string>VueJS</string>
<key>CFBundleName</key>
<string>VueJS-CN</string>
<key>DocSetPlatformFamily</key>
<string>VueJS</string>
<key>isDashDocset</key>
<true/>
<key>DashDocSetFamily</key>
<string>dashtoc3</string>
<key>dashIndexFilePath</key>
<string>cn.vuejs.org/index.html</string>
</dict>
</plist>
複製程式碼
一個xml檔案,裡面都是成對的key-string配置項
dashIndexFilePath
表示在Dash中點選你的文件後,預設的主頁是什麼CFBundleName
為在dash 中的文件名稱DashDocSetFamily
左下角顯示索引列表 (這裡我有配置,但是沒生效,後續再研究)- 其他部分為一些關鍵字
5.建立SQLite資料庫檔案(Create the SQLite Index)
-
建立sqlite索引。
索引檔案的位置是:VueJS.docset/Contents/Resources/docSet.dsidx ,(Mac電腦已經預裝了sqlite)
所以直接從命令列進入Resources資料夾,在命令列中敲:
sqlite3 docSet.dsidx 複製程式碼
-
這樣就進入了sqlite資料庫,接下來,建立資料表
CREATE TABLE searchIndex(id INTEGER PRIMARY KEY, name TEXT, type TEXT, path TEXT) 複製程式碼
6.寫入SQLite資料索引(Populate the SQLite Index);
再往後就是,從html檔案中提取內容,插入索引。這是最重要的一點,這裡沒弄好,整個都沒啥用。
INSERT OR IGNORE INTO searchIndex(name, type, path) VALUES ('name', 'type', 'path');
複製程式碼
其中
name
為關鍵字,比如你想在dash中輸入一個select就可以查詢,那這個select就是關鍵字;type
為關鍵字的型別,官方支援的有很多,如Class、Function、Guide、Method等;path
為文件的錨點地址,點選目錄跳轉
官方原文為:
name
is the name of the entry. For example, if you are adding a class, it would be the name of the class. This is the column that Dash searches.type
is the type of the entry. For example, if you are adding a class, it would be "Class". For a list of types that Dash recognises, see below.path
is the relative path towards the documentation file you want Dash to display for this entry. It can contain an anchor (#). Alternatively, Dash also supportshttp://
URL entries.
以下為我的部分程式碼,完整的程式碼在 build-vue.js
// build-vue.js
/**
* 根據各個標題處理相應的錨點 新增索引
*
* @param $ dom 物件
* @param relativePath 相對路徑
* @param dir 資料夾名稱
*/
function handleTitles($,relativePath,dir) {
// 教程模組 以h2 為索引,需要新增一個h1 的索引
let h1Title = '';
if(dir === 'guide'){
$('h1').each(function (i,h) {
h1Title = Array.from(h.childNodes).map((node) => node.data ).join('');
db.run(`INSERT INTO searchIndex (name, type, path) VALUES ('${h1Title}', '${type['guide']}', '${relativePath}')`,function(res){
if(res.error) throw res.error;
console.log(res);
});
});
}
$('h2').each(function (i,h) {
if(!h.attribs.id) return;
let h2s = extractText(h); // 提取標題中的ID、文字內容
let h3s = [];
if(dir === 'api'){
h3s = collectH3s(h);
if(h3s.length<1) return
}
let entryType = type[h2s.id] || type['h2']; // 預設 Section
console.log(h2s);
let h2Num = dir === 'api' ? 1 : 0;
let h2Type = type['h2']; // h2 歸類為 Section
addDashAnchor(h,h2s.id,h2Type,h2Num);
let inTitle = `${h2s.text} ${dir === 'guide' ? ' - '+ h1Title : ''}`;
let iniType = dir ==='api' ? type['guide'] : h2Type;
db.run(`INSERT INTO searchIndex (name, type, path) VALUES ('${inTitle}', '${iniType}', '${relativePath}#${encodeURIComponent(h2s.id)}')`,function(res){
if(res.error) throw res.error;
console.log(res);
});
// api下 需要處理 h3 標題,生成相應的索引
if(dir === 'api'){
h3s.forEach(function (titleNode,index) {
let id = titleNode.attribs.id;
let text = [].slice.call(titleNode.childNodes).map( (node) => node.data).join('');
// 需要處理括號
if(text.match(/^([^(]+)\(/)) text= text.match(/^([^(]+)\(/)[1];
console.log(id,text,entryType);
addDashAnchor(titleNode,id,entryType,0);
db.run(`INSERT INTO searchIndex (name, type, path) VALUES ('${text}', '${entryType}', '${relativePath}#${encodeURIComponent(id)}')`,function(res){
if(res.error) throw res.error;
console.log(res);
});
});
}
});
/**
* 提取標題中的ID、文字內容
* @param h node
* @returns {{id: *, text: *}} id 用來生成錨點、text當做標題
*/
function extractText (h) {
let title = [].slice.call(h.childNodes).map( (node) => node.tagName === 'a' ? node.attribs.title : '').join('');
let id = h.attribs.id;
return {
id: id,
text: title ? htmlEscape(title) : id // 如果沒有就用ID 代替
}
}
// 字元轉義
function htmlEscape (text) {
return text
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/'/g, `'`)
.replace(/</g, '<')
.replace(/>/g, '>')
}
// 提取h2 附近的h3 標題列表
function collectH3s (h) {
let h3s = [];
let next = h.nextSibling;
while (next && next.tagName !== 'h2') {
if (next.tagName === 'h3') {
next.childNodes = removeTagA(next);
h3s.push(next)
}
next = next.nextSibling
}
return h3s
}
// 移除A標籤
function removeTagA(h) {
return [].slice.call(h.childNodes).filter(function (node) {
return node.tagName !== 'a'
})
}
// 新增dash規定格式的 錨點
function addDashAnchor(h,name,types,num) {
let nameStr = (`//dash_ref_${name}/${types}/${encodeURIComponent(name)}/${num}`); // 需要對URL 進行URL編碼(百分比轉義)
let dashAnchor = `<a class="dashAnchor" name="${nameStr}"/>`;
h.childNodes = removeTagA(h); // 取完title之後移除原有的錨點,新增 dash規定格式的錨點
$(h).before(dashAnchor).html();
}
}
複製程式碼
7. 匯入文件
把所有的索引資料都插入到searchIndex以後,docSet文件就製作好了,直接雙擊 VueJS.docSet就可以匯入Dash了。
截圖
以上示例程式碼均可在 Gayhub 檢視