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