Source code for frostmark.exporter
'''
Export bookmarks from internal database into various formats.
'''
from textwrap import dedent
from ensure import ensure_annotations
from frostmark.common import fetch_bookmark_tree, traverse
from frostmark.models import Folder, Bookmark
[docs]class Exporter:
'''
Interface for retrieving and exporting bookmarks from local storage
to multiple backends.
'''
# pylint: disable=too-few-public-methods
@staticmethod
@ensure_annotations
def export_to(path: str):
'''
Public export function for STDOUT or file.
'''
xml = Exporter.prepare_export()
if path != '-':
with open(path, 'rb') as output:
output.write(xml.encode('utf-8'))
else:
# output to STDOUT
print(xml)
@staticmethod
@ensure_annotations
def prepare_export():
'''
Export bookmarks from internal storage.
'''
xml = dedent('''
<!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>
''')
xml = xml[1:] # strip first \n character
tree = traverse(fetch_bookmark_tree())
tree_len = len(tree)
sep = ' ' * 4
folder_stack = []
for idx, node in enumerate(tree):
if node.node_type == Folder:
# root folder for internal DB
if node.id == 0:
xml += f'{sep * len(folder_stack)}<DL><p>\n'
elif [node.parent_folder_id] == folder_stack[-2:-1]:
# child folder of a second last folder on stack
# stack = [parent, child1]
# F parent
# |-- F child
# +-- F child <- this
# pop the sister folder from stack
# close the previous folder
folder_stack = folder_stack[:-1]
xml += f'{sep * len(folder_stack)}</DL><p>\n'
xml += (
f'{sep * len(folder_stack)}'
f'<DT><H3>{node.folder_name}</H3>\n'
)
xml += f'{sep * len(folder_stack)}<DL><p>\n'
else:
xml += (
f'{sep * len(folder_stack)}'
f'<DT><H3>{node.folder_name}</H3>\n'
)
xml += f'{sep * len(folder_stack)}<DL><p>\n'
# completely new folder, append to stack
folder_stack.append(node.id)
if node.id == 0:
continue
# empty folder, close DL, pop from stack
if not node.children and idx != tree_len - 1:
# pop from stack and close the folder
if node.id in folder_stack:
folder_stack = folder_stack[:-1]
xml += f'{sep * len(folder_stack)}</DL><p>\n'
continue
# last element, in case it's folder close the DL
if idx == tree_len - 1:
folder_stack = folder_stack[:-1]
xml += f'{sep * len(folder_stack)}</DL><p>\n'
continue
elif node.node_type == Bookmark:
# bookmark of a second folder on the stack
# F
# |-- F
# | +-- B
# +-- B <- this
if [node.folder_id] != folder_stack[-1:]:
folder_stack = folder_stack[:-1]
xml += (
f'{sep * len(folder_stack)}</DL><p>\n'
f'{sep * len(folder_stack)}<HR>\n'
)
xml += (
f'{sep * len(folder_stack)}'
f'<DT><A HREF="{node.url}" '
f'ICON="{node.icon.decode("utf-8")}">{node.title}</A>\n'
)
# if there are remaining folders, those don't have
# any children remaining, so the whole tree can be
# closed with multiple dedented DL closings
while folder_stack:
folder_stack = folder_stack[:-1]
xml += f'{sep * len(folder_stack)}</DL><p>\n'
return xml