html.py 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import re
  2. import nh3
  3. from django.utils.html import escape
  4. from .constants import HTML_ALLOWED_ATTRIBUTES, HTML_ALLOWED_TAGS
  5. __all__ = (
  6. 'clean_html',
  7. 'foreground_color',
  8. 'highlight',
  9. )
  10. def clean_html(html, schemes):
  11. """
  12. Sanitizes HTML based on a whitelist of allowed tags and attributes.
  13. Also takes a list of allowed URI schemes.
  14. """
  15. return nh3.clean(
  16. html,
  17. tags=HTML_ALLOWED_TAGS,
  18. attributes=HTML_ALLOWED_ATTRIBUTES,
  19. url_schemes=set(schemes)
  20. )
  21. def foreground_color(bg_color, dark='000000', light='ffffff'):
  22. """
  23. Return the ideal foreground color (dark or light) for a given background color in hexadecimal RGB format.
  24. :param dark: RBG color code for dark text
  25. :param light: RBG color code for light text
  26. """
  27. THRESHOLD = 150
  28. bg_color = bg_color.strip('#')
  29. r, g, b = [int(bg_color[c:c + 2], 16) for c in (0, 2, 4)]
  30. if r * 0.299 + g * 0.587 + b * 0.114 > THRESHOLD:
  31. return dark
  32. else:
  33. return light
  34. def highlight(value, highlight, trim_pre=None, trim_post=None, trim_placeholder='...'):
  35. """
  36. Highlight a string within a string and optionally trim the pre/post portions of the original string.
  37. Args:
  38. value: The body of text being searched against
  39. highlight: The string of compiled regex pattern to highlight in `value`
  40. trim_pre: Maximum length of pre-highlight text to include
  41. trim_post: Maximum length of post-highlight text to include
  42. trim_placeholder: String value to swap in for trimmed pre/post text
  43. """
  44. # Split value on highlight string
  45. try:
  46. if type(highlight) is re.Pattern:
  47. pre, match, post = highlight.split(value, maxsplit=1)
  48. else:
  49. highlight = re.escape(highlight)
  50. pre, match, post = re.split(fr'({highlight})', value, maxsplit=1, flags=re.IGNORECASE)
  51. except ValueError:
  52. # Match not found
  53. return escape(value)
  54. # Trim pre/post sections to length
  55. if trim_pre and len(pre) > trim_pre:
  56. pre = trim_placeholder + pre[-trim_pre:]
  57. if trim_post and len(post) > trim_post:
  58. post = post[:trim_post] + trim_placeholder
  59. return f'{escape(pre)}<mark>{escape(match)}</mark>{escape(post)}'