淺析瀏覽器書簽的導(dǎo)入和導(dǎo)出

瀏覽器有個實(shí)用的功能,但是可能用的頻率不高,就是書簽/收藏的導(dǎo)入和導(dǎo)出,因?yàn)楝F(xiàn)在一般瀏覽器都有云同步功能,所以這個功能存在感不強(qiáng)。

瀏覽器書簽是可以跨不同的瀏覽器導(dǎo)入的,所以意味著導(dǎo)出的文件肯定是有一個規(guī)范的,我簡單搜了一下沒有搜到,可能是各家約定俗成的規(guī)范,并沒有一個正式的標(biāo)準(zhǔn)。

通用的數(shù)據(jù)交換格式有很多,比如xml、json、yaml,json應(yīng)該是使用最廣泛的,因?yàn)橐子诮馕龊痛鎯?,尺寸也不大,所以很適合瀏覽器書簽的導(dǎo)出,但是,實(shí)際上現(xiàn)代瀏覽器導(dǎo)出的書簽文件是html文件。原因不詳,也沒有搜到相關(guān)信息,我猜測原因可能是html文件相對于json來說,普通用戶更為熟悉,其次,html文件可以直接使用瀏覽器打開,當(dāng)然,json文件也可以使用瀏覽器打開,但是可能直接點(diǎn)擊的時候默認(rèn)是用文本編輯器打開的,另外它們在瀏覽器的呈現(xiàn)方式也不一樣,html顯示的是一個普通的帶有一堆超鏈接的頁面,就是一個有點(diǎn)丑的網(wǎng)頁,而json打開有點(diǎn)類似源碼,不太友好,因?yàn)橐话阌脩魧?dǎo)出書簽就是為了在另一個瀏覽器導(dǎo)入,所以屏蔽細(xì)節(jié)并沒有什么問題。

html和xml是類似的,所以解析和傳輸也很簡單,接下來看一下實(shí)例:

基本結(jié)構(gòu)如上,每個文件夾下都有個書簽,導(dǎo)出的書簽源碼如下:



簡單分析一下:

1.標(biāo)簽字母都是大寫

2.DOCTYPE聲明和普通HTML頁面不同

3.使用DL和DT來組織書簽,DL代表一個文件夾的內(nèi)容列表,DT代表一個內(nèi)容,可能是書簽也可能是文件夾,文件夾的話會有一個H3標(biāo)簽來表示書簽的名字,書簽的話就是直接跟一個A標(biāo)簽,DL標(biāo)簽后都跟了一個小寫的p標(biāo)簽,有部分標(biāo)簽沒有閉合

4.H1標(biāo)簽之前的都和書簽內(nèi)容沒有什么關(guān)系

5.文件夾名稱H3標(biāo)簽和超鏈接A標(biāo)簽都有ADD_DATELAST_MODIFIED來保存時間信息,該屬性不存在也不影響

6.文件夾名稱H3標(biāo)簽的屬性PERSONAL_TOOLBAR_FOLDER來表示該文件夾下的內(nèi)容是否顯示到瀏覽器的工具欄,否則會默認(rèn)放到瀏覽器的其他文件夾里,但也不一定,有的瀏覽器會有自己的行為

7.網(wǎng)頁的標(biāo)題icon會轉(zhuǎn)換為base64格式放到ICON屬性上,這個屬性不存在也不影響

html其實(shí)就是普通字符串,所以可以手動生成,常見于一些導(dǎo)航網(wǎng)站和網(wǎng)址收藏工具的導(dǎo)出功能,如五花八門導(dǎo)航(http://lxqnsys.com/d),有一個需要注意的地方,就是html字符串必須格式化帶換行和縮進(jìn),下圖這種壓縮過的是不行的:

生成方式也很簡單,書簽是樹結(jié)構(gòu),所以遞歸循環(huán)拼接即可。

先看一下書簽數(shù)據(jù)的格式,忽略時間和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
}

看完了如何生成,接下來看一下如何解析,解析和拼接類似,也是通過深度優(yōu)先進(jìn)行遍歷,只是會有一些特征判斷。字符串如何轉(zhuǎn)化成一棵樹,最簡單的肯定是先轉(zhuǎn)換為DOM元素,然后再通過操作DOM的api來進(jìn)行遍歷,有一些庫可以用來做這件事,不過這里直接用的是iframe

function getBookmarksStrRootNode (str) {
    // 創(chuàng)建iframe
    let iframe = document.createElement('iframe')
    document.body.appendChild(iframe)
    iframe.style.display = 'none'
    // 添加書簽dom字符串
    iframe.contentWindow.document.documentElement.innerHTML = str
    // 獲取書簽樹根節(jié)點(diǎn)
    return iframe.contentWindow.document.querySelector('dl')
}
function analysisBookmarksStr(str) {
    let root = getBookmarksStrRootNode(str)
}

看一下轉(zhuǎn)換的結(jié)果:

書簽DOM字符串:

轉(zhuǎn)換后的DOM節(jié)點(diǎn):

獲取到書簽樹的根節(jié)點(diǎn),接下來遞歸遍歷即可:

function walkBookmarksTree (root) {
    let result = []
    // 深度優(yōu)先遍歷
    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標(biāo)簽或h3標(biāo)簽直接跳過
                if (item.tagName === 'P' || item.tagName === 'H3') {
                    continue
                }
                // 文件夾不用創(chuàng)建元素
                if (item.tagName === 'DL') {
                    walk(els[i], list)
                } else {// DT節(jié)點(diǎn)
                    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)
}

最后解析的結(jié)果:

搞定收工,趕緊去試試吧。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容