]> git.phdru.name Git - bookmarks_db.git/blob - bkmk_objects.py
Recode hrefs (due to international domain names) to the current charset.
[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 __version__ = "$Revision$"[11:-2]
7 __revision__ = "$Id$"[5:-2]
8 __date__ = "$Date$"[7:-2]
9 __author__ = "Oleg Broytman <phd@phdru.name>"
10 __copyright__ = "Copyright (C) 2000-2011 PhiloSoft Design"
11 __license__ = "GNU GPL"
12
13 __all__ = ['Folder', 'Bookmark', 'Ruler', 'Walker', 'Writer', 'Robot',
14     'InverseLinker', 'Linear', 'make_linear', 'make_tree', 'break_tree',
15     'quote_title', 'unquote_title',
16 ]
17
18
19 import os
20
21 BKMK_FORMAT = os.environ.get("BKMK_FORMAT", "MOZILLA")
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:
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, charset=None):
60       if isinstance(href, str):
61          try:
62             href = href.decode('idna')
63          except UnicodeDecodeError: # Non-ascii href
64             href = href.decode('utf-8')
65       elif not isinstance(href, unicode):
66           raise TypeError("Bookmark's href must be str or unicode, not %r" % type(href))
67       self.href = href
68       self.add_date = add_date
69       self.last_visit = last_visit
70       self.last_modified = last_modified
71       self.keyword = keyword
72       self.comment = comment
73       self.icon_href = icon_href
74       self.icon = icon
75       self.charset = charset
76
77
78 class Ruler:
79    isFolder = 0
80    isBookmark = 0
81
82
83 class Walker:
84    """
85       Interface class. Any instance that will be passed to Folder.walk_depth
86       may be derived from this class. It is not mandatory - unlike Java
87       Python does not require interface classes; but it is convenient to have
88       some methods predefined to no-op, in case you do not want to
89       provide end_folder etc.
90    """
91
92    def root_folder(self, r):
93       pass
94
95    def start_folder(self, f, level):
96       pass
97
98    def end_folder(self, f, level):
99       pass
100
101    def bookmark(self, b, level):
102       pass
103
104    def ruler(self, r, level):
105       pass
106
107    def prune_folder(self, folder):
108       return 0
109
110
111 class Writer(Walker):
112    def __init__(self, outfile, prune=None):
113       self.outfile = outfile
114       self.prune = prune
115
116    def prune_folder(self, folder):
117       return self.prune == folder.name
118
119
120 class Robot:
121    def __init__(self, log):
122       self.log = log
123
124    def stop(self):
125       pass # Nothing to do on cleanup
126
127
128 # Helper class to make inverese links (nodes linked to their parent)
129 class InverseLinker(Walker):
130    def root_folder(self, r):
131       self.parent_stack = [r]
132
133    def start_folder(self, f, level):
134       f.parent = self.parent_stack[-1]
135       self.parent_stack.append(f) # Push the folder onto the stack of parents
136
137    def end_folder(self, f, level):
138       del self.parent_stack[-1]   # Pop off the stack
139
140    def bookmark(self, b, level):
141       b.parent = self.parent_stack[-1]
142
143    def ruler(self, r, level):
144       r.parent = self.parent_stack[-1]
145
146
147 # Helper class to make linear represenatation of the tree
148 class Linear(Walker):
149    def root_folder(self, r):
150       r.linear = [r]
151       self.linear = r.linear
152
153    def add_object(self, object):
154       self.linear.append(object)
155
156    def start_folder(self, f, level):
157       self.add_object(f)
158
159    def bookmark(self, b, level):
160       self.add_object(b)
161
162    def ruler(self, r, level):
163       self.add_object(r)
164
165
166 # Helper - make linked linear represenatation of the tree, suitable to be stored in sequential storage
167 def make_linear(root_folder):
168    linker = InverseLinker()
169    root_folder.walk_depth(linker)
170
171    linear = Linear()
172    root_folder.walk_depth(linear)
173
174
175 # Helper, opposite of make_linear - make a tree from the linked linear representation
176 def make_tree(linear):
177    root_folder = linear[0]
178    del linear[0]
179
180    for object in linear:
181       object.parent.append(object)
182
183    return root_folder
184
185 def break_tree(linear):
186    del linear[0]
187
188    for object in linear:
189       del object.parent
190
191
192 def quote_title(title):
193    if BKMK_FORMAT == "MOZILLA":
194       title = title.replace("'", "&#39;")
195    return title
196
197 def unquote_title(title):
198    if BKMK_FORMAT == "MOZILLA":
199       from HTMLParser import HTMLParser
200       title = HTMLParser().unescape(title.replace("&amp;", '&'))
201       title = title.replace("&#39;", "'")
202    return title