|
1 # -*- coding: utf-8 -*- |
|
2 |
|
3 # Copyright (c) 2016 - 2021 Detlev Offenbach <detlev@die-offenbachs.de> |
|
4 # |
|
5 |
|
6 """ |
|
7 Module implementing tool functions for the web browser. |
|
8 """ |
|
9 |
|
10 import os |
|
11 import re |
|
12 import mimetypes |
|
13 |
|
14 from PyQt5.QtCore import ( |
|
15 QFile, QByteArray, QUrl, QCoreApplication, QBuffer, QIODevice |
|
16 ) |
|
17 from PyQt5.QtGui import QPixmap |
|
18 |
|
19 |
|
20 WebBrowserDataDirectory = { |
|
21 "html": os.path.join(os.path.dirname(__file__), "..", "data", "html"), |
|
22 "icons": os.path.join(os.path.dirname(__file__), "..", "data", "icons"), |
|
23 "js": os.path.join(os.path.dirname(__file__), "..", "data", "javascript"), |
|
24 } |
|
25 |
|
26 |
|
27 def readAllFileContents(filename): |
|
28 """ |
|
29 Function to read the string contents of the given file. |
|
30 |
|
31 @param filename name of the file |
|
32 @type str |
|
33 @return contents of the file |
|
34 @rtype str |
|
35 """ |
|
36 return str(readAllFileByteContents(filename), encoding="utf-8") |
|
37 |
|
38 |
|
39 def readAllFileByteContents(filename): |
|
40 """ |
|
41 Function to read the bytes contents of the given file. |
|
42 |
|
43 @param filename name of the file |
|
44 @type str |
|
45 @return contents of the file |
|
46 @rtype str |
|
47 """ |
|
48 dataFile = QFile(filename) |
|
49 if filename and dataFile.open(QFile.ReadOnly): |
|
50 contents = dataFile.readAll() |
|
51 dataFile.close() |
|
52 return contents |
|
53 |
|
54 return QByteArray() |
|
55 |
|
56 |
|
57 def containsSpace(string): |
|
58 """ |
|
59 Function to check, if a string contains whitespace characters. |
|
60 |
|
61 @param string string to be checked |
|
62 @type str |
|
63 @return flag indicating the presence of at least one whitespace character |
|
64 @rtype bool |
|
65 """ |
|
66 return any(ch.isspace() for ch in string) |
|
67 |
|
68 |
|
69 def ensureUniqueFilename(name, appendFormat="({0})"): |
|
70 """ |
|
71 Module function to generate an unique file name based on a pattern. |
|
72 |
|
73 @param name desired file name (string) |
|
74 @param appendFormat format pattern to be used to make the unique name |
|
75 (string) |
|
76 @return unique file name |
|
77 """ |
|
78 if not os.path.exists(name): |
|
79 return name |
|
80 |
|
81 tmpFileName = name |
|
82 i = 1 |
|
83 while os.path.exists(tmpFileName): |
|
84 tmpFileName = name |
|
85 index = tmpFileName.rfind(".") |
|
86 |
|
87 appendString = appendFormat.format(i) |
|
88 if index == -1: |
|
89 tmpFileName += appendString |
|
90 else: |
|
91 tmpFileName = ( |
|
92 tmpFileName[:index] + appendString + tmpFileName[index:] |
|
93 ) |
|
94 i += 1 |
|
95 |
|
96 return tmpFileName |
|
97 |
|
98 |
|
99 def getFileNameFromUrl(url): |
|
100 """ |
|
101 Module function to generate a file name based on the given URL. |
|
102 |
|
103 @param url URL (QUrl) |
|
104 @return file name (string) |
|
105 """ |
|
106 fileName = url.toString( |
|
107 QUrl.UrlFormattingOption.RemoveFragment | |
|
108 QUrl.UrlFormattingOption.RemoveQuery | |
|
109 QUrl.UrlFormattingOption.RemoveScheme | |
|
110 QUrl.UrlFormattingOption.RemovePort |
|
111 ) |
|
112 if fileName.find("/") != -1: |
|
113 pos = fileName.rfind("/") |
|
114 fileName = fileName[pos:] |
|
115 fileName = fileName.replace("/", "") |
|
116 |
|
117 fileName = filterCharsFromFilename(fileName) |
|
118 |
|
119 if not fileName: |
|
120 fileName = filterCharsFromFilename(url.host().replace(".", "_")) |
|
121 |
|
122 return fileName |
|
123 |
|
124 |
|
125 def filterCharsFromFilename(name): |
|
126 """ |
|
127 Module function to filter illegal characters. |
|
128 |
|
129 @param name name to be sanitized (string) |
|
130 @return sanitized name (string) |
|
131 """ |
|
132 return ( |
|
133 name |
|
134 .replace("/", "_") |
|
135 .replace("\\", "") |
|
136 .replace(":", "") |
|
137 .replace("*", "") |
|
138 .replace("?", "") |
|
139 .replace('"', "") |
|
140 .replace("<", "") |
|
141 .replace(">", "") |
|
142 .replace("|", "") |
|
143 ) |
|
144 |
|
145 |
|
146 def pixmapFromByteArray(data): |
|
147 """ |
|
148 Module function to convert a byte array to a pixmap. |
|
149 |
|
150 @param data data for the pixmap |
|
151 @type bytes or QByteArray |
|
152 @return extracted pixmap |
|
153 @rtype QPixmap |
|
154 """ |
|
155 pixmap = QPixmap() |
|
156 barray = QByteArray.fromBase64(data) |
|
157 pixmap.loadFromData(barray) |
|
158 |
|
159 return pixmap |
|
160 |
|
161 |
|
162 def pixmapToByteArray(pixmap): |
|
163 """ |
|
164 Module function to convert a pixmap to a byte array containing the pixmap |
|
165 as a PNG encoded as base64. |
|
166 |
|
167 @param pixmap pixmap to be converted |
|
168 @type QPixmap |
|
169 @return byte array containing the pixmap |
|
170 @rtype QByteArray |
|
171 """ |
|
172 byteArray = QByteArray() |
|
173 buffer = QBuffer(byteArray) |
|
174 buffer.open(QIODevice.OpenModeFlag.WriteOnly) |
|
175 if pixmap.save(buffer, "PNG"): |
|
176 return buffer.buffer().toBase64() |
|
177 |
|
178 return QByteArray() |
|
179 |
|
180 |
|
181 def pixmapToDataUrl(pixmap, mimetype="image/png"): |
|
182 """ |
|
183 Module function to convert a pixmap to a data: URL. |
|
184 |
|
185 @param pixmap pixmap to be converted |
|
186 @type QPixmap |
|
187 @param mimetype MIME type to be used |
|
188 @type str |
|
189 @return data: URL |
|
190 @rtype QUrl |
|
191 """ |
|
192 data = bytes(pixmapToByteArray(pixmap)).decode() |
|
193 if data: |
|
194 return QUrl("data:{0};base64,{1}".format(mimetype, data)) |
|
195 else: |
|
196 return QUrl() |
|
197 |
|
198 |
|
199 def pixmapFileToDataUrl(pixmapFile, asString=False): |
|
200 """ |
|
201 Module function to load a pixmap file and convert the pixmap to a |
|
202 data: URL. |
|
203 |
|
204 Note: If the given pixmap file path is not absolute, it is assumed to |
|
205 denote a pixmap file in the icons data directory. |
|
206 |
|
207 @param pixmapFile file name of the pixmap file |
|
208 @type str |
|
209 @param asString flag indicating a string representation is requested |
|
210 @type bool |
|
211 @return data: URL |
|
212 @rtype QUrl or str |
|
213 """ |
|
214 if not os.path.isabs(pixmapFile): |
|
215 pixmapFile = os.path.join(WebBrowserDataDirectory["icons"], pixmapFile) |
|
216 |
|
217 mime = mimetypes.guess_type(pixmapFile, strict=False)[0] |
|
218 if mime is None: |
|
219 # assume PNG file |
|
220 mime = "image/png" |
|
221 url = pixmapToDataUrl(QPixmap(pixmapFile), mimetype=mime) |
|
222 |
|
223 if asString: |
|
224 return url.toString() |
|
225 else: |
|
226 return url |
|
227 |
|
228 |
|
229 def getWebEngineVersions(): |
|
230 """ |
|
231 Module function to extract the web engine version from the default user |
|
232 agent string. |
|
233 |
|
234 @return tuple containing the Chrome version and the QtWebEngine version |
|
235 @rtype tuple of str |
|
236 """ |
|
237 from PyQt5.QtWebEngineWidgets import QWebEngineProfile |
|
238 |
|
239 useragent = QWebEngineProfile.defaultProfile().httpUserAgent() |
|
240 match = re.search(r"""Chrome/([\d.]+)""", useragent) |
|
241 chromeVersion = ( |
|
242 match.group(1) |
|
243 if match else |
|
244 QCoreApplication.translate("WebBrowserTools", "<unknown>") |
|
245 ) |
|
246 match = re.search(r"""QtWebEngine/([\d.]+)""", useragent) |
|
247 webengineVersion = ( |
|
248 match.group(1) |
|
249 if match else |
|
250 QCoreApplication.translate("WebBrowserTools", "<unknown>") |
|
251 ) |
|
252 return (chromeVersion, webengineVersion) |
|
253 |
|
254 |
|
255 def getHtmlPage(pageFileName): |
|
256 """ |
|
257 Module function to load a HTML page. |
|
258 |
|
259 Note: If the given HTML file path is not absolute, it is assumed to |
|
260 denote a HTML file in the html data directory. |
|
261 |
|
262 @param pageFileName file name of the HTML file |
|
263 @type str |
|
264 @return HTML page |
|
265 @rtype str |
|
266 """ |
|
267 if not os.path.isabs(pageFileName): |
|
268 pageFileName = os.path.join( |
|
269 WebBrowserDataDirectory["html"], pageFileName) |
|
270 |
|
271 return readAllFileContents(pageFileName) |
|
272 |
|
273 |
|
274 def getJavascript(jsFileName): |
|
275 """ |
|
276 Module function to load a JavaScript source file. |
|
277 |
|
278 Note: If the given JavaScript source file path is not absolute, it is |
|
279 assumed to denote a JavaScript source file in the javascript data |
|
280 directory. |
|
281 |
|
282 @param jsFileName file name of the JavaScript source file |
|
283 @type str |
|
284 @return JavaScript source |
|
285 @rtype str |
|
286 """ |
|
287 if not os.path.isabs(jsFileName): |
|
288 jsFileName = os.path.join( |
|
289 WebBrowserDataDirectory["js"], jsFileName) |
|
290 |
|
291 return readAllFileContents(jsFileName) |