fix(HtmlSerializer)
Signed-off-by: Marcel Klehr <mklehr@gmx.net>
This commit is contained in:
Родитель
8fc6ab688f
Коммит
08b4466271
|
@ -2,7 +2,7 @@
|
|||
"manifest_version": 3,
|
||||
"name": "floccus bookmarks sync",
|
||||
"short_name": "floccus",
|
||||
"version": "4.19.1",
|
||||
"version": "5.0.0",
|
||||
"description": "__MSG_DescriptionExtension__",
|
||||
"icons": {
|
||||
"48": "icons/logo.png",
|
||||
|
@ -10,13 +10,6 @@
|
|||
"128": "icons/logo_128.png"
|
||||
},
|
||||
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "floccus@handmadeideas.org",
|
||||
"strict_min_version": "57.0"
|
||||
}
|
||||
},
|
||||
|
||||
"default_locale": "en",
|
||||
|
||||
"permissions": ["alarms", "bookmarks", "storage", "unlimitedStorage", "tabs", "identity"],
|
||||
|
@ -42,6 +35,6 @@
|
|||
},
|
||||
|
||||
"background": {
|
||||
"scripts": ["dist/js/background-script.js"]
|
||||
"service_worker": "dist/js/background-script.js"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
"async-lock": "^1.2.8",
|
||||
"async-parallel": "^1.2.3",
|
||||
"batching-toposort": "^1.2.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"cordova-plugin-device": "^2.1.0",
|
||||
"cordova-plugin-inappbrowser": "^5.0.0",
|
||||
"core-js": "3.x",
|
||||
|
@ -4188,8 +4189,7 @@
|
|||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
|
||||
"dev": true
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||
},
|
||||
"node_modules/boolean": {
|
||||
"version": "3.2.0",
|
||||
|
@ -4626,6 +4626,42 @@
|
|||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio": {
|
||||
"version": "1.0.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
|
||||
"integrity": "sha512-VqR8m68vM46BNnuZ5NtnGBKIE/DfN0cRIzg9n40EIq9NOv90ayxLBXA8fXC5gquFRGJSTRqBq25Jt2ECLR431Q==",
|
||||
"dependencies": {
|
||||
"cheerio-select": "^2.1.0",
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"htmlparser2": "^8.0.1",
|
||||
"parse5": "^7.0.0",
|
||||
"parse5-htmlparser2-tree-adapter": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cheerio-select": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
|
||||
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-select": "^5.1.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "2.1.8",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz",
|
||||
|
@ -5288,6 +5324,32 @@
|
|||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/cssesc": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||
|
@ -5595,6 +5657,19 @@
|
|||
"node": ">=6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domain-browser": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz",
|
||||
|
@ -5605,6 +5680,44 @@
|
|||
"npm": ">=1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
"integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/duplexify": {
|
||||
"version": "3.7.1",
|
||||
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz",
|
||||
|
@ -5737,6 +5850,17 @@
|
|||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
|
||||
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/env-paths": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
|
||||
|
@ -8391,6 +8515,24 @@
|
|||
"integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/htmlparser2": {
|
||||
"version": "8.0.2",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz",
|
||||
"integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==",
|
||||
"funding": [
|
||||
"https://github.com/fb55/htmlparser2?sponsor=1",
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"domutils": "^3.0.1",
|
||||
"entities": "^4.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-cache-semantics": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
|
||||
|
@ -10982,7 +11124,6 @@
|
|||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
|
@ -11433,6 +11574,29 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz",
|
||||
"integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==",
|
||||
"dependencies": {
|
||||
"entities": "^4.4.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5-htmlparser2-tree-adapter": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz",
|
||||
"integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==",
|
||||
"dependencies": {
|
||||
"domhandler": "^5.0.2",
|
||||
"parse5": "^7.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/pascalcase": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz",
|
||||
|
|
|
@ -85,6 +85,7 @@
|
|||
"async-lock": "^1.2.8",
|
||||
"async-parallel": "^1.2.3",
|
||||
"batching-toposort": "^1.2.0",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"cordova-plugin-device": "^2.1.0",
|
||||
"cordova-plugin-inappbrowser": "^5.0.0",
|
||||
"core-js": "3.x",
|
||||
|
|
|
@ -465,7 +465,8 @@ function createXBEL(rootFolder, highestId) {
|
|||
|
||||
function createHTML(rootFolder, highestId) {
|
||||
let output = `<!DOCTYPE NETSCAPE-Bookmark-file-1>
|
||||
<html>`
|
||||
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">
|
||||
<TITLE>Bookmarks</TITLE>`
|
||||
|
||||
output +=
|
||||
'<!--- highestId :' +
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import Serializer from '../interfaces/Serializer'
|
||||
import { Bookmark, Folder, ItemLocation } from '../Tree'
|
||||
import { Bookmark, Folder, ItemLocation, ItemType, TItem, TItemType } from '../Tree'
|
||||
import { load } from 'cheerio'
|
||||
|
||||
class HtmlSerializer implements Serializer {
|
||||
serialize(folder) {
|
||||
return `<DL><p>${this._serializeFolder(folder, '')}</p></DL>`
|
||||
return `<DL><p>\n${this._serializeFolder(folder, '')}</DL><p>\n`
|
||||
}
|
||||
|
||||
_serializeFolder(folder, indent) {
|
||||
|
@ -12,58 +13,112 @@ class HtmlSerializer implements Serializer {
|
|||
if (child instanceof Bookmark) {
|
||||
return (
|
||||
`${indent}<DT>` +
|
||||
`<A HREF="${child.url}" TAGS="${''}" id="${child.id}">${child.title}</A>`
|
||||
`<A HREF="${child.url}" TAGS="${''}" ID="${child.id}">${child.title}</A>\n`
|
||||
)
|
||||
} else if (child instanceof Folder) {
|
||||
const nextIndent = indent + ' '
|
||||
return (
|
||||
`${indent}<DT><h3 id="${child.id}">${child.title}</h3>\n` +
|
||||
`${indent}<DL><p>${this._serializeFolder(
|
||||
`${indent}<DT><H3 ID="${child.id}">${child.title}</H3>\n` +
|
||||
`${indent}<DL><p>\n${this._serializeFolder(
|
||||
child,
|
||||
nextIndent
|
||||
)}</p></DL>`
|
||||
)}${indent}</DL><p>\n`
|
||||
)
|
||||
}
|
||||
})
|
||||
.join('\n')
|
||||
.join('')
|
||||
}
|
||||
|
||||
deserialize(html): Folder {
|
||||
const parser = new DOMParser()
|
||||
const document = parser.parseFromString(html, 'text/html')
|
||||
const rootFolder = new Folder({id: '', title: '', location: ItemLocation.SERVER})
|
||||
const dl = document.querySelector('dl')
|
||||
const counter = {highestId: 1}
|
||||
deserializeDL(dl, rootFolder, counter)
|
||||
return rootFolder
|
||||
}
|
||||
}
|
||||
|
||||
function deserializeDL(dl:Element, parentFolder:Folder, counter:{highestId: number}) {
|
||||
for (let element:Element = dl.querySelector('dt'); element; element = element.nextElementSibling) {
|
||||
const child = element.firstElementChild
|
||||
if (child instanceof HTMLHeadingElement) {
|
||||
const folder = new Folder({
|
||||
parentId: parentFolder.id,
|
||||
title: child.textContent,
|
||||
id: child.id ? parseInt(child.id) : counter.highestId++,
|
||||
location: ItemLocation.SERVER
|
||||
})
|
||||
parentFolder.children.push(folder)
|
||||
if (child.nextElementSibling instanceof HTMLDListElement) {
|
||||
const dl = child.nextElementSibling
|
||||
deserializeDL(dl, folder, counter)
|
||||
}
|
||||
} else if (child instanceof HTMLAnchorElement) {
|
||||
parentFolder.children.push(new Bookmark({
|
||||
parentId: parentFolder.id,
|
||||
url: child.href,
|
||||
title: child.textContent,
|
||||
id: child.id ? parseInt(child.id) : counter.highestId++,
|
||||
location: ItemLocation.SERVER
|
||||
}))
|
||||
const folders: Folder[] = parseByString(html)
|
||||
if (folders.length === 1) {
|
||||
folders[0].isRoot = true
|
||||
return folders[0]
|
||||
} else {
|
||||
folders.forEach(f => f.parentId = '0')
|
||||
return new Folder({id: '0', title: 'root', children: folders, location: ItemLocation.SERVER, isRoot: true})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new HtmlSerializer()
|
||||
|
||||
// The following code is based on https://github.com/hold-baby/bookmark-file-parser
|
||||
// Copyright (c) 2019 hold-baby
|
||||
// MIT License
|
||||
|
||||
export const getRootFolder = (body: cheerio.Cheerio) => {
|
||||
const h3 = body.find('h3').first()
|
||||
|
||||
const isChrome = typeof h3.attr('personal_toolbar_folder') === 'string'
|
||||
|
||||
if (isChrome) {
|
||||
return body.children('dl').first()
|
||||
}
|
||||
|
||||
const isSafari = typeof h3.attr('folded') === 'string'
|
||||
|
||||
if (isSafari) {
|
||||
return body
|
||||
}
|
||||
|
||||
const isIE = typeof h3.attr('item_id') === 'string'
|
||||
|
||||
if (isIE) {
|
||||
return body.children('dl').first()
|
||||
}
|
||||
|
||||
const isFireFox = h3.text() === 'Mozilla Firefox'
|
||||
|
||||
if (isFireFox) {
|
||||
return body.children('dl').first()
|
||||
}
|
||||
|
||||
return body.children('dl').first()
|
||||
}
|
||||
|
||||
export const parseByString = (content: string) => {
|
||||
const $ = load(content, {
|
||||
decodeEntities: false
|
||||
})
|
||||
|
||||
const body = $('body')
|
||||
const root: Folder[] = []
|
||||
const rdt = getRootFolder(body).children('dt')
|
||||
|
||||
const parseNode = (node: cheerio.Cheerio, parentId) => {
|
||||
const eq0 = node.children().eq(0)
|
||||
const title = eq0.html() || ''
|
||||
let type: TItemType = ItemType.BOOKMARK
|
||||
let url = ''
|
||||
let id = eq0.attr('id') || ''
|
||||
let children: TItem[] = []
|
||||
|
||||
switch (eq0[0].name) {
|
||||
case 'h3':
|
||||
// folder
|
||||
const dl = node.children('dl').first()
|
||||
const dts = dl.children()
|
||||
|
||||
const ls = dts.toArray().map((ele) => {
|
||||
if (ele.name !== 'dt') return null
|
||||
return parseNode($(ele), id)
|
||||
})
|
||||
children = ls.filter((item) => item !== null) as TItem[]
|
||||
return new Folder({id, title, parentId, children, location: ItemLocation.SERVER})
|
||||
case 'a':
|
||||
// site
|
||||
url = eq0.attr('href') || ''
|
||||
return new Bookmark({id, title, url, parentId, location: ItemLocation.SERVER})
|
||||
}
|
||||
throw new Error('Failed to parse')
|
||||
}
|
||||
|
||||
rdt.each((_, item) => {
|
||||
const node = $(item)
|
||||
const child = parseNode(node)
|
||||
root.push(child)
|
||||
})
|
||||
|
||||
return root
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче