htmlGen.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. ########################################################################
  2. # Searx-Qt - Lightweight desktop application for Searx.
  3. # Copyright (C) 2020-2022 CYBERDEViL
  4. #
  5. # This file is part of Searx-Qt.
  6. #
  7. # Searx-Qt is free software: you can redistribute it and/or modify
  8. # it under the terms of the GNU General Public License as published by
  9. # the Free Software Foundation, either version 3 of the License, or
  10. # (at your option) any later version.
  11. #
  12. # Searx-Qt is distributed in the hope that it will be useful,
  13. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. # GNU General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU General Public License
  18. # along with this program. If not, see <https://www.gnu.org/licenses/>.
  19. #
  20. ########################################################################
  21. import html
  22. from urllib.parse import quote
  23. from searxqt.utils.string import formatFileSize, formatFileCount
  24. from searxqt.translations import _
  25. class HtmlGen:
  26. def wrapHtml(bodyStr, styleStr=""):
  27. return f"""
  28. <html>
  29. <head>
  30. <style>{styleStr}</style>
  31. </head>
  32. <body>
  33. {bodyStr}
  34. </body>
  35. </html>"""
  36. class FailedResponsesHtml:
  37. def create(results, message, css):
  38. """
  39. @param result: List with failed search result object (searxqt.core.requests.Result).
  40. @type result: list
  41. @param message: TODO
  42. @param css: Optionaly set a stylesheet, it should be the content only;
  43. so do not include the <style> tags!
  44. @type css: str
  45. """
  46. body = f"<p id=\"top-message\">{message}</p>" \
  47. f"<h3>{_('Failed requests')}:</h3>" \
  48. "<table class=\"fail\">"
  49. lastResult = results[-1]
  50. for result in results:
  51. url = result.url()
  52. error = html.escape(result.error())
  53. content = html.escape(result.text())
  54. body += "<tr>" \
  55. "<td colspan=\"2\" class=\"fail-instance\">" \
  56. f"<a href=\"{url}\">{url}</a>" \
  57. "</td></tr>" \
  58. "<tr>" \
  59. f"<td class=\"fail-prefix\">{_('Error')}</td>" \
  60. f"<td class=\"fail-value\">{error}</td>" \
  61. "</tr>" \
  62. "<tr>" \
  63. f"<td class=\"fail-prefix\">{_('Content')}</td>" \
  64. f"<td class=\"fail-value\">{content}</td>" \
  65. "</tr>"
  66. if result != lastResult:
  67. body += "<tr><td colspan=\"2\"><hr></td></tr>"
  68. body += "</table>"
  69. return HtmlGen.wrapHtml(body, css)
  70. class ResultsHtml:
  71. """ Create HTML from a valid search response.
  72. """
  73. def create(jsonResult, css):
  74. """
  75. @param jsonResult: The josn result of a search response.
  76. @type jsonResult: json or dict
  77. @param css: Optionaly set a stylesheet, it should be the content only;
  78. so do not include the <style> tags!
  79. @type css: str
  80. """
  81. elemStr = ""
  82. # Search query suggestions
  83. data = jsonResult.get('suggestions', [])
  84. if data:
  85. elemStr += ResultsHtml.createHrefListElement(
  86. 'search', _('Suggestions'), data
  87. )
  88. elemStr += "<hr>"
  89. # Search query corrections
  90. data = jsonResult.get('corrections', [])
  91. if data:
  92. elemStr += ResultsHtml.createHrefListElement(
  93. 'search', _('Corrections'), data
  94. )
  95. elemStr += "<hr>"
  96. # Unresponsive engines
  97. data = jsonResult.get('unresponsive_engines', [])
  98. if data:
  99. elemStr += ResultsHtml.createUnresponsiveEnginesElement(data)
  100. elemStr += "<hr>"
  101. # Infoboxes
  102. data = jsonResult.get('infoboxes', [])
  103. if data:
  104. elemStr += "<p id=\"infoboxes_header\">Infoboxes</p>"
  105. for infoboxData in data:
  106. elemStr += ResultsHtml.createInfobox(infoboxData)
  107. # Answers
  108. data = jsonResult.get('answers', [])
  109. if data:
  110. elemStr += ResultsHtml.createStrListElement(_('Answers'), data)
  111. elemStr += "<hr>"
  112. # Results
  113. data = jsonResult.get('results', [])
  114. if data:
  115. for resultData in data:
  116. elemStr += ResultsHtml.createResultElement(resultData)
  117. return HtmlGen.wrapHtml(elemStr, css)
  118. def createStrListElement(title, data):
  119. elemStr = " | ".join([html.escape(entry) for entry in data])
  120. return ResultsHtml.createListContainer(title, elemStr)
  121. def createHrefListElement(scheme, title, data):
  122. elemStr = ""
  123. scheme += ":"
  124. for entry in data:
  125. if elemStr:
  126. elemStr += " | "
  127. url = scheme + quote(entry)
  128. elemStr += f"<a href=\"{url}\">{html.escape(entry)}</a>"
  129. return ResultsHtml.createListContainer(title, elemStr)
  130. def createListContainer(title, elems):
  131. return f"<div class=\"{title}\"><b>{title}</b>: {elems}</div>"
  132. def createUnresponsiveEnginesElement(data):
  133. elemStr = ""
  134. for name, error in data:
  135. if elemStr:
  136. elemStr += " | "
  137. elemStr += f"{html.escape(name)} <i>({html.escape(error)})</i>"
  138. return ResultsHtml.createListContainer(_('Unresponsive engines'), elemStr)
  139. def createInfobox(infoboxData):
  140. # Create new infobox
  141. elemStr = "<div class=\"infobox\">"
  142. # Title
  143. data = html.escape(infoboxData.get("infobox", "-"))
  144. elemStr += "<center><div class=\"infobox_title\">" \
  145. f"{data}</div></center>"
  146. # ID
  147. data = infoboxData.get("id", None)
  148. if data:
  149. data = html.escape(data)
  150. elemStr += "<center><div class=\"infobox_id\">" \
  151. f"<a href=\"{data}\">{data}</a></div></center>"
  152. # Content
  153. data = infoboxData.get("content", None)
  154. if data:
  155. data = html.escape(data)
  156. elemStr += f"<div class=\"infobox_content\">{data}</div>"
  157. # Attributes
  158. data = infoboxData.get("attributes", None)
  159. if data:
  160. elemStr += "<p class=\"infobox_attr_head\">Attributes</p>"
  161. elemStr += "<table class=\"infobox_attr_table\">"
  162. for attributeData in data:
  163. # TODO for now skip images ..
  164. if "value" not in attributeData:
  165. continue
  166. # New row
  167. elemStr += "<tr>"
  168. value = html.escape(attributeData.get('label', ''))
  169. elemStr += f"<td class=\"infobox_label\">{value}</td>"
  170. value = html.escape(attributeData.get('value', ''))
  171. elemStr += f"<td class=\"infobox_value\">{value}</td>"
  172. elemStr += "</tr>"
  173. elemStr += "</table>"
  174. # Urls list
  175. data = infoboxData.get("urls", None)
  176. if data:
  177. elemStr += "<p class=\"infobox_links_head\">Links</p>"
  178. elemStr += "<ul class=\"infobox_link_list\">"
  179. for urlData in data:
  180. url = html.escape(urlData.get("url"))
  181. title = html.escape(urlData.get("title", "-"))
  182. elemStr += "<li class=\"infobox_link_list_item\">" \
  183. f"<a href=\"{url}\" title=\"{url}\">{title}</a>" \
  184. "</li>"
  185. elemStr += "</ul>"
  186. # Engines
  187. data = infoboxData.get("engines", None)
  188. if data:
  189. elemStr += ResultsHtml.createStrListElement(_('Engines'), data)
  190. # Closing tag of the new infobox element.
  191. elemStr += "</div>"
  192. elemStr += "<hr>"
  193. return elemStr
  194. def createResultElement(data):
  195. # Create general elements
  196. title=html.escape(data.get('title', ''))
  197. url=html.escape(data.get('url', ''))
  198. content=html.escape(data.get('content', ''))
  199. engine = ''
  200. for e in data.get('engines', []):
  201. engine += f"{e} "
  202. engine = engine.rstrip()
  203. elemStr = "<div class=\"results\">" \
  204. f"<h4 class=\"result-title\"><i>{engine}: </i>" \
  205. f"<a href=\"{url}\">{title}</a></h4>" \
  206. "<div style=\"margin-left: 10px;\">" \
  207. f"<p class=\"result-description\">{content}</p>" \
  208. f"<p class=\"result-url\">{url}</p>"
  209. # Add file data elements
  210. elemStr += ResultsHtml.createFileSection(data)
  211. elemStr += "</div></div>"
  212. return elemStr
  213. def createFileSection(data):
  214. elemStr = ""
  215. value = data.get('magnetlink', '')
  216. if value:
  217. value = html.escape(value)
  218. elemStr += f"<a href=\"{value}\">Magnet</a> "
  219. value = data.get('torrentfile', '')
  220. if value:
  221. value = html.escape(value)
  222. elemStr += f"<a href=\"{value}\">Torrent</a> "
  223. value = data.get('filesize', 0)
  224. if value:
  225. elemStr += formatFileSize(value) + " "
  226. value = data.get('files', 0)
  227. if value:
  228. elemStr += formatFileCount(value) + " "
  229. value = data.get('seed', None)
  230. if value is not None:
  231. value = html.escape(str(value))
  232. elemStr += f"seeders: {value} "
  233. value = data.get('leech', None)
  234. if value is not None:
  235. value = html.escape(str(value))
  236. elemStr += f"leechers: {value} "
  237. value = data.get('img_format', "")
  238. if value:
  239. value = html.escape(value)
  240. elemStr += f"format: {value} "
  241. if elemStr:
  242. elemStr = "<p class=\"result-file\">" + elemStr + "</p>"
  243. return elemStr