]> git.phdru.name Git - bookmarks_db.git/blob - bkmk_objects.py
d672bcfc7f2850e0e7c98d5fec7adf7148d50b16
[bookmarks_db.git] / bkmk_objects.py
1 """Objects to represent bookmarks.html structure
2
3 This file is a part of Bookmarks database and Internet robot.
4
5 """
6
7 __author__ = "Oleg Broytman <phd@phdru.name>"
8 __copyright__ = "Copyright (C) 2000-2024 PhiloSoft Design"
9 __license__ = "GNU GPL"
10
11 __all__ = ['Folder', 'Bookmark', 'Ruler', 'Walker', 'Writer', 'Robot',
12            'InverseLinker', 'Linear', 'make_linear', 'make_tree', 'break_tree',
13            'quote_title', 'unquote_title', 'parse_params', 'set_params',
14            ]
15
16
17 from urllib.parse import urlsplit, quote, unquote
18 import os
19
20 BKMK_FORMAT = os.environ.get("BKMK_FORMAT", "MOZILLA")
21
22
23 class Folder(list):
24     isFolder = 1
25     isBookmark = 0
26
27     def __init__(self, add_date=None, comment='', last_modified=None):
28         super(Folder, self).__init__()
29         self.comment = comment
30         self.add_date = add_date
31         self.last_modified = last_modified
32
33     def walk_depth(self, walker, level=0):
34         if hasattr(self, "header"):  # root folder
35             prune = 0
36             walker.root_folder(self)
37         else:
38             prune = walker.prune_folder(self)
39             if not prune:
40                 walker.start_folder(self, level)
41
42         if not prune:
43             for object in self:
44                 if object.isFolder:
45                     object.walk_depth(walker, level+1)
46                 elif object.isBookmark:
47                     walker.bookmark(object, level)
48                 else:
49                     walker.ruler(object, level)
50
51             walker.end_folder(self, level)
52
53
54 class Bookmark(object):
55     isFolder = 0
56     isBookmark = 1
57
58     def __init__(self, href, add_date, last_visit=None, last_modified=None,
59                  keyword=None, comment='', icon_href=None, icon=None,
60                  charset=None, parser_charset=None):
61         split_results = urlsplit(href)
62         protocol, netloc, path, query, tag = split_results
63         user = split_results.username
64         password = split_results.password
65         host = split_results.hostname
66         port = split_results.port
67
68         if protocol == 'place':
69             href = protocol + ":"
70         else:
71             href = protocol + "://"
72         if user:
73             href += quote(user)
74             if password:
75                 href += ':' + quote(password)
76             href += '@'
77         if host:
78             href += host.encode('idna').decode('ascii')
79             if port:
80                 href += ':%d' % port
81         if path:
82             href += path
83
84         self.href = href
85         self.add_date = add_date
86         self.last_visit = last_visit
87         self.last_modified = last_modified
88         self.keyword = keyword
89         self.comment = comment
90         self.icon_href = icon_href
91         self.icon = icon
92         self.charset = charset
93
94
95 class Ruler(object):
96     isFolder = 0
97     isBookmark = 0
98
99
100 class Walker(object):
101     """
102        Interface class. Any instance that will be passed to Folder.walk_depth
103        may be derived from this class. It is not mandatory - unlike Java
104        Python does not require interface classes; but it is convenient to have
105        some methods predefined to no-op, in case you do not want to
106        provide end_folder etc.
107     """
108
109     def root_folder(self, r):
110         pass
111
112     def start_folder(self, f, level):
113         pass
114
115     def end_folder(self, f, level):
116         pass
117
118     def bookmark(self, b, level):
119         pass
120
121     def ruler(self, r, level):
122         pass
123
124     def prune_folder(self, folder):
125         return 0
126
127
128 class Writer(Walker):
129     def __init__(self, outfile, prune=None):
130         self.outfile = outfile
131         self.prune = prune
132
133     def prune_folder(self, folder):
134         return self.prune == folder.name
135
136
137 class Robot(object):
138     def __init__(self, log):
139         self.log = log
140
141     def stop(self):
142         pass  # Nothing to do on cleanup
143
144
145 # Helper class to make inverese links (nodes linked to their parent)
146 class InverseLinker(Walker):
147     def root_folder(self, r):
148         self.parent_stack = [r]
149
150     def start_folder(self, f, level):
151         f.parent = self.parent_stack[-1]
152         # Push the folder onto the stack of parents
153         self.parent_stack.append(f)
154
155     def end_folder(self, f, level):
156         del self.parent_stack[-1]   # Pop off the stack
157
158     def bookmark(self, b, level):
159         b.parent = self.parent_stack[-1]
160
161     def ruler(self, r, level):
162         r.parent = self.parent_stack[-1]
163
164
165 # Helper class to make linear represenatation of the tree
166 class Linear(Walker):
167     def root_folder(self, r):
168         r.linear = [r]
169         self.linear = r.linear
170
171     def add_object(self, object):
172         self.linear.append(object)
173
174     def start_folder(self, f, level):
175         self.add_object(f)
176
177     def bookmark(self, b, level):
178         self.add_object(b)
179
180     def ruler(self, r, level):
181         self.add_object(r)
182
183
184 # Helper - make linked linear represenatation of the tree,
185 # suitable to be stored in sequential storage.
186 def make_linear(root_folder):
187     linker = InverseLinker()
188     root_folder.walk_depth(linker)
189
190     linear = Linear()
191     root_folder.walk_depth(linear)
192
193
194 # Helper, opposite of make_linear -
195 # make a tree from the linked linear representation.
196 def make_tree(linear):
197     root_folder = linear[0]
198     del linear[0]
199
200     for object in linear:
201         object.parent.append(object)
202
203     return root_folder
204
205
206 def break_tree(linear):
207     del linear[0]
208
209     for object in linear:
210         del object.parent
211
212
213 def quote_title(title):
214     if BKMK_FORMAT == "MOZILLA":
215         title = title.replace("'", "&#39;")
216     return title
217
218
219 def unquote_title(title):
220     if BKMK_FORMAT == "MOZILLA":
221         try:
222             from HTMLParser import HTMLParser
223         except ImportError:
224             from html import unescape
225         else:
226             unescape = HTMLParser().unescape
227         title = unescape(
228             title.replace("&amp;", '&'))
229         title = title.replace("&#39;", "'")
230     return title
231
232
233 def parse_params(param_str):
234     params = param_str.split(':')
235     main_param = params.pop(0)
236     param_list = {}
237     for param in params:
238         key, value = param.split('=', 1)
239         param_list[key] = unquote(value)
240     return main_param, param_list
241
242
243 def set_params(obj, params):
244     if hasattr(params, "items"):
245         params = params.items()
246     for key, value in params:
247         setattr(obj, key, value)