|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2009 - 2022 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing a class to read XBEL bookmark files. |
|
8 """ |
|
9 |
|
10 from PyQt6.QtCore import ( |
|
11 QXmlStreamReader, QXmlStreamEntityResolver, QIODevice, QFile, |
|
12 QCoreApplication, QXmlStreamNamespaceDeclaration, QDateTime, Qt |
|
13 ) |
|
14 |
|
15 from .BookmarkNode import BookmarkNode |
|
16 |
|
17 |
|
18 class XmlEntityResolver(QXmlStreamEntityResolver): |
|
19 """ |
|
20 Class implementing an XML entity resolver for bookmark files. |
|
21 """ |
|
22 def resolveUndeclaredEntity(self, entity): |
|
23 """ |
|
24 Public method to resolve undeclared entities. |
|
25 |
|
26 @param entity entity to be resolved (string) |
|
27 @return resolved entity (string) |
|
28 """ |
|
29 if entity == "nbsp": |
|
30 return " " |
|
31 return "" |
|
32 |
|
33 |
|
34 class XbelReader(QXmlStreamReader): |
|
35 """ |
|
36 Class implementing a reader object for XBEL bookmark files. |
|
37 """ |
|
38 def __init__(self): |
|
39 """ |
|
40 Constructor |
|
41 """ |
|
42 super().__init__() |
|
43 |
|
44 self.__resolver = XmlEntityResolver() |
|
45 self.setEntityResolver(self.__resolver) |
|
46 |
|
47 def read(self, fileNameOrDevice): |
|
48 """ |
|
49 Public method to read an XBEL bookmark file. |
|
50 |
|
51 @param fileNameOrDevice name of the file to read (string) |
|
52 or reference to the device to read (QIODevice) |
|
53 @return reference to the root node (BookmarkNode) |
|
54 """ |
|
55 if isinstance(fileNameOrDevice, QIODevice): |
|
56 self.setDevice(fileNameOrDevice) |
|
57 else: |
|
58 f = QFile(fileNameOrDevice) |
|
59 if not f.exists(): |
|
60 return BookmarkNode(BookmarkNode.Root) |
|
61 f.open(QIODevice.OpenModeFlag.ReadOnly) |
|
62 self.setDevice(f) |
|
63 |
|
64 root = BookmarkNode(BookmarkNode.Root) |
|
65 while not self.atEnd(): |
|
66 self.readNext() |
|
67 if self.isStartElement(): |
|
68 version = self.attributes().value("version") |
|
69 if ( |
|
70 self.name() == "xbel" and |
|
71 (not version or version == "1.0") |
|
72 ): |
|
73 self.__readXBEL(root) |
|
74 else: |
|
75 self.raiseError(QCoreApplication.translate( |
|
76 "XbelReader", |
|
77 "The file is not an XBEL version 1.0 file.")) |
|
78 |
|
79 return root |
|
80 |
|
81 def __readXBEL(self, node): |
|
82 """ |
|
83 Private method to read and parse the XBEL file. |
|
84 |
|
85 @param node reference to the node to attach to (BookmarkNode) |
|
86 """ |
|
87 if not self.isStartElement() and self.name() != "xbel": |
|
88 return |
|
89 |
|
90 while not self.atEnd(): |
|
91 self.readNext() |
|
92 if self.isEndElement(): |
|
93 break |
|
94 |
|
95 if self.isStartElement(): |
|
96 if self.name() == "folder": |
|
97 self.__readFolder(node) |
|
98 elif self.name() == "bookmark": |
|
99 self.__readBookmarkNode(node) |
|
100 elif self.name() == "separator": |
|
101 self.__readSeparator(node) |
|
102 else: |
|
103 self.__skipUnknownElement() |
|
104 |
|
105 def __readFolder(self, node): |
|
106 """ |
|
107 Private method to read and parse a folder subtree. |
|
108 |
|
109 @param node reference to the node to attach to (BookmarkNode) |
|
110 """ |
|
111 if not self.isStartElement() and self.name() != "folder": |
|
112 return |
|
113 |
|
114 folder = BookmarkNode(BookmarkNode.Folder, node) |
|
115 folder.expanded = self.attributes().value("folded") == "no" |
|
116 folder.added = QDateTime.fromString( |
|
117 self.attributes().value("added"), Qt.DateFormat.ISODate) |
|
118 |
|
119 while not self.atEnd(): |
|
120 self.readNext() |
|
121 if self.isEndElement(): |
|
122 break |
|
123 |
|
124 if self.isStartElement(): |
|
125 if self.name() == "title": |
|
126 self.__readTitle(folder) |
|
127 elif self.name() == "desc": |
|
128 self.__readDescription(folder) |
|
129 elif self.name() == "folder": |
|
130 self.__readFolder(folder) |
|
131 elif self.name() == "bookmark": |
|
132 self.__readBookmarkNode(folder) |
|
133 elif self.name() == "separator": |
|
134 self.__readSeparator(folder) |
|
135 elif self.name() == "info": |
|
136 self.__readInfo() |
|
137 else: |
|
138 self.__skipUnknownElement() |
|
139 |
|
140 def __readTitle(self, node): |
|
141 """ |
|
142 Private method to read the title element. |
|
143 |
|
144 @param node reference to the bookmark node title belongs to |
|
145 (BookmarkNode) |
|
146 """ |
|
147 if not self.isStartElement() and self.name() != "title": |
|
148 return |
|
149 |
|
150 node.title = self.readElementText() |
|
151 |
|
152 def __readDescription(self, node): |
|
153 """ |
|
154 Private method to read the desc element. |
|
155 |
|
156 @param node reference to the bookmark node desc belongs to |
|
157 (BookmarkNode) |
|
158 """ |
|
159 if not self.isStartElement() and self.name() != "desc": |
|
160 return |
|
161 |
|
162 node.desc = self.readElementText() |
|
163 |
|
164 def __readSeparator(self, node): |
|
165 """ |
|
166 Private method to read a separator element. |
|
167 |
|
168 @param node reference to the bookmark node the separator belongs to |
|
169 (BookmarkNode) |
|
170 """ |
|
171 sep = BookmarkNode(BookmarkNode.Separator, node) |
|
172 sep.added = QDateTime.fromString( |
|
173 self.attributes().value("added"), Qt.DateFormat.ISODate) |
|
174 |
|
175 # empty elements have a start and end element |
|
176 while not self.atEnd(): |
|
177 self.readNext() |
|
178 if self.isEndElement(): |
|
179 break |
|
180 |
|
181 if self.isStartElement(): |
|
182 if self.name() == "info": |
|
183 self.__readInfo() |
|
184 else: |
|
185 self.__skipUnknownElement() |
|
186 |
|
187 def __readBookmarkNode(self, node): |
|
188 """ |
|
189 Private method to read and parse a bookmark subtree. |
|
190 |
|
191 @param node reference to the node to attach to (BookmarkNode) |
|
192 """ |
|
193 if not self.isStartElement() and self.name() != "bookmark": |
|
194 return |
|
195 |
|
196 bookmark = BookmarkNode(BookmarkNode.Bookmark, node) |
|
197 bookmark.url = self.attributes().value("href") |
|
198 bookmark.added = QDateTime.fromString( |
|
199 self.attributes().value("added"), Qt.DateFormat.ISODate) |
|
200 bookmark.modified = QDateTime.fromString( |
|
201 self.attributes().value("modified"), Qt.DateFormat.ISODate) |
|
202 bookmark.visited = QDateTime.fromString( |
|
203 self.attributes().value("visited"), Qt.DateFormat.ISODate) |
|
204 try: |
|
205 bookmark.visitCount = int(self.attributes().value("visitCount")) |
|
206 except ValueError: |
|
207 bookmark.visitCount = 0 |
|
208 |
|
209 while not self.atEnd(): |
|
210 self.readNext() |
|
211 if self.isEndElement(): |
|
212 break |
|
213 |
|
214 if self.isStartElement(): |
|
215 if self.name() == "title": |
|
216 self.__readTitle(bookmark) |
|
217 elif self.name() == "desc": |
|
218 self.__readDescription(bookmark) |
|
219 elif self.name() == "info": |
|
220 self.__readInfo() |
|
221 else: |
|
222 self.__skipUnknownElement() |
|
223 |
|
224 if not bookmark.title: |
|
225 bookmark.title = QCoreApplication.translate( |
|
226 "XbelReader", "Unknown title") |
|
227 |
|
228 def __readInfo(self): |
|
229 """ |
|
230 Private method to read and parse an info subtree. |
|
231 """ |
|
232 self.addExtraNamespaceDeclaration(QXmlStreamNamespaceDeclaration( |
|
233 "bookmark", "http://www.python.org")) |
|
234 self.skipCurrentElement() |
|
235 |
|
236 def __skipUnknownElement(self): |
|
237 """ |
|
238 Private method to skip over all unknown elements. |
|
239 """ |
|
240 self.skipCurrentElement() |