Dash所需的文档都是.docSet
后缀的文件,其实docSet文件就是一个文件夹而已,里头包含最终的html文档,以及根据html创建的索引(索引放在sqlite数据库中)。php
生成文档的方法有不少种,如 Python、Ruby、Objective-C、Node.js、PHPcss
能够选择 镜像时处理,也能够镜像后处理。只须要结果中包含html,以及sqlite 就OJBK。html
这里我选择 镜像后用NodeJS处理vue
须要用到的库为:java
fs
作一些文件的读写操做path
路径处理sync-exec
执行一些cmd命令sqlite-sync
作一些 sqlite 操做cheerio
服务器版的jQuery根据官网提供的官方文档,整个转换主要有如下5个步骤:node
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);
复制代码
镜像工具备不少,这里只推荐我尝试过的几款,而且用着还不错:git
名称 | 平台 | 地址 |
---|---|---|
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… |
实际上这几种都不是很完美,或多或少会漏一些外部站点的资源文件,目前还没找到解决办法,github
若是大家有解决办法,麻烦**@**我一下。web
我这里以SiteSucker 为例,镜像 https://cn.vuejs.org
,一级目录结构以下:sql
// 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官方文档中的资源 、中间为合并后的资源(后面要用到)、 右侧为本身镜像的资源
复制代码
本例中咱们建立的文档叫 VueJS,因此按下列结构建立目录:
mkdir -p VueJS.docset/Contents/Resources/Documents/
复制代码
把全部的html文档拷贝到Documents文件夹中,dash默认 Documents 为文档根目录
为了省事,我把须要的资源文件放在了当前项目下。
cp -r ./Documents VueJS.docset/Contents/Resources/
复制代码
在 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
左下角显示索引列表 (这里我有配置,可是没生效,后续再研究)建立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)
复制代码
再日后就是,从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();
}
}
复制代码
把全部的索引数据都插入到searchIndex之后,docSet文档就制做好了,直接双击 VueJS.docSet就能够导入Dash了。
以上示例代码都可在 Gayhub 查看