浏览器有个实用的性能,然而可能用的频率不高,就是书签 / 珍藏的导入和导出,因为当初个别浏览器都有云同步性能,所以这个性能存在感不强。
浏览器书签是能够跨不同的浏览器导入的,所以意味着导出的文件必定是有一个标准的,我简略搜了一下没有搜到,可能是各家约定俗成的标准,并没有一个正式的规范。
通用的数据交换格局有很多,比方 xml、json、yaml,json 应该是应用最宽泛的,因为易于解析和存储,尺寸也不大,所以很适宜浏览器书签的导出,然而,实际上古代浏览器导出的书签文件是 html 文件。起因不详,也没有搜到相干信息,我猜想起因可能是 html 文件绝对于 json 来说,普通用户更为相熟,其次,html 文件能够间接应用浏览器关上,当然,json 文件也能够应用浏览器关上,然而可能间接点击的时候默认是用文本编辑器关上的,另外它们在浏览器的出现形式也不一样,html 显示的是一个一般的带有一堆超链接的页面,就是一个有点丑的网页,而 json 关上有点相似源码,不太敌对,因为个别用户导出书签就是为了在另一个浏览器导入,所以屏蔽细节并没有什么问题。
html 和 xml 是相似的,所以解析和传输也很简略,接下来看一下实例:
根本构造如上,每个文件夹下都有个书签,导出的书签源码如下:
简略剖析一下:
1. 标签字母都是大写
2.DOCTYPE 申明和一般 HTML 页面不同
3. 应用 DL 和 DT 来组织书签,DL 代表一个文件夹的内容列表,DT 代表一个内容,可能是书签也可能是文件夹,文件夹的话会有一个 H3 标签来示意书签的名字,书签的话就是间接跟一个 A 标签,DL 标签后都跟了一个小写的 p 标签,有局部标签没有闭合
4.H1 标签之前的都和书签内容没有什么关系
5. 文件夹名称 H3 标签和超链接 A 标签都有 ADD_DATE
和LAST_MODIFIED
来保留工夫信息,该属性不存在也不影响
6. 文件夹名称 H3 标签的属性 PERSONAL_TOOLBAR_FOLDER
来示意该文件夹下的内容是否显示到浏览器的工具栏,否则会默认放到浏览器的其余文件夹里,但也不肯定,有的浏览器会有本人的行为
7. 网页的题目 icon 会转换为 base64 格局放到 ICON
属性上,这个属性不存在也不影响
html 其实就是一般字符串,所以能够手动生成,常见于一些导航网站和网址珍藏工具的导出性能,如形形色色导航(http://lxqnsys.com/d),有一个须要留神的中央,就是 html 字符串必须格式化带换行和缩进,下图这种压缩过的是不行的:
生成形式也很简略,书签是树结构,所以递归循环拼接即可。
先看一下书签数据的格局,疏忽工夫和 icon:
let bookmarks = [
{
name: '',// 文件夹或书签名字
toolbar: true,// 是否显示到工具栏
folder: true,// 是否是文件夹
children: [
{
name: '',
folder: true,
children: []},
{
name: '',// 书签名称
url: ''// 书签 url
}
]
}
]
应用 ES6 的话能够间接应用模板字符串 “ 来带换行的拼接,很不便:
function createBookmarksStr (bookmarks) {
let str = `
<!DOCTYPE NETSCAPE-Bookmark-file-1>
<!-- This is an automatically generated file.It will be read and overwritten.DO NOT EDIT! -->
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
<TITLE>Bookmarks</TITLE>
<H1>Bookmarks</H1>
<DL>
<p>
`
let loop = (root) => {
let str = ''
root.forEach((item) => {if (item.folder) {
str += `
<DT>
<H3 ${item.toolbar ? `PERSONAL_TOOLBAR_FOLDER="true"` : ''}>${item.name}</H3>
<DL>
<p>
`
str += loop(item.children)
str += `
</DL>
<p>
`
} else {
str += `
<DT><A HREF="${item.url}">${item.name}</A>
`
}
})
return str
}
str += loop(bookmarks)
str += `
</DL>
<p>
`
return str
}
ES6 之前的就须要显式的拼接上换行符:
function createBookmarksStr (bookmarks) {
var str = '<!DOCTYPE NETSCAPE-Bookmark-file-1>\n<!-- This is an automatically generated file.It will be read and overwritten.DO NOT EDIT! -->\n<META HTTP-EQUIV=\"Content-Type\"CONTENT=\"text/html; charset=UTF-8\">\n<TITLE>Bookmarks</TITLE>\n<H1>Bookmarks</H1>\n<DL>\n\t<p>\n\t\t<DT>\n\t\t\t<H3 ADD_DATE=\"1568796074\"LAST_MODIFIED=\"1601707819\"PERSONAL_TOOLBAR_FOLDER=\"true\">\u4E66\u7B7E\u680F</H3>\n\t\t\t<DL>\n\t\t\t\t<p>\n\t\t\t\t\t'
var loop = function (root) {
var str = ''
root.forEach(function (item) {if (item.folder) {str += '<DT>\n\t\t\t\t\t\t<H3'+ (item.toolbar ? 'PERSONAL_TOOLBAR_FOLDER="true"':'') +'>'+item.name+'</H3>\n\t\t\t\t\t\t<DL>\n\t\t\t\t\t\t\t<p>\n\t\t\t\t\t\t\t\t'
str += loop(item.children)
str += '</DL>\n\t\t\t\t\t\t<p>\n\t\t\t'
} else {str += '<DT><A HREF=\"'+item.url+'\">'+item.name+'</A>\n\t\t\t\t\t\t\t\t'}
})
return str
}
str += loop(bookmarks)
str += '</DL>\n\t\t\t<p>\n</DL>\n<p>'
return str
}
看完了如何生成,接下来看一下如何解析,解析和拼接相似,也是通过深度优先进行遍历,只是会有一些特色判断。字符串如何转化成一棵树,最简略的必定是先转换为 DOM 元素,而后再通过操作 DOM 的 api 来进行遍历,有一些库能够用来做这件事,不过这里间接用的是iframe
:
function getBookmarksStrRootNode (str) {
// 创立 iframe
let iframe = document.createElement('iframe')
document.body.appendChild(iframe)
iframe.style.display = 'none'
// 增加书签 dom 字符串
iframe.contentWindow.document.documentElement.innerHTML = str
// 获取书签树根节点
return iframe.contentWindow.document.querySelector('dl')
}
function analysisBookmarksStr(str) {let root = getBookmarksStrRootNode(str)
}
看一下转换的后果:
书签 DOM 字符串:
转换后的 DOM 节点:
获取到书签树的根节点,接下来递归遍历即可:
function walkBookmarksTree (root) {let result = []
// 深度优先遍历
let walk = (node, list) => {
let els = node.children
if (els && els.length > 0) {for (let i = 0; i < els.length; i++) {let item = els[i]
// p 标签或 h3 标签间接跳过
if (item.tagName === 'P' || item.tagName === 'H3') {continue}
// 文件夹不必创立元素
if (item.tagName === 'DL') {walk(els[i], list)
} else {// DT 节点
let child = null
// 判断是否是文件夹
let children = item.children
let isDir = false
for(let j = 0; j < children.length; j++) {if (children[j].tagName === 'H3' || children[j].tagName === 'DL') {isDir = true}
}
// 文件夹
if (isDir) {
child = {name: item.tagName === 'DT' ? item.querySelector('h3') ? item.querySelector('h3').innerText : '':'',
folder: true,
children: []}
walk(els[i], child.children)
} else {// 书签
let _item = item.querySelector('a')
child = {
name: _item.innerText,
url: _item.href
}
}
list.push(child)
}
}
}
}
walk(root, result)
return result
}
function analysisBookmarksStr(str) {let root = getBookmarksStrRootNode(str)
let result = walkBookmarksTree(root)
}
最初解析的后果:
搞定出工,连忙去试试吧。