Coverage for app / helpers / utilities.py: 96%

39 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-06 04:49 +0000

1""" 

2This module provides utilities for building library search URLs, rendering icons, URL parsing, 

3and computing navigation paths. It includes helper functions for escaping user input, mapping 

4values to icons, and handling request referrer URLs. 

5""" 

6from urllib.parse import quote_plus, urlparse 

7 

8import bleach 

9from flask import url_for 

10from markupsafe import Markup 

11 

12PLACEHOLDER = '<span class="placeholder-icon"></span>' 

13 

14 

15_SEARCH_TEMPLATES = { 

16 "Los Gatos Library": 

17 "https://losgatos.aspendiscovery.org/Search/Results?join=AND&bool0[]=AND&lookfor0[]=" + 

18 "{title}&type0[]=Title&lookfor1[]={author}&type1[]=Author&submit=Find", 

19 "Santa Clara Country Library": 

20 "https://sccl.bibliocommons.com/v2/search?custom_edit=false&query=(title%3A({title})" + 

21 "%20AND%20contributor%3A({author}))&searchType=bl&suppress=true", 

22 "San Diego Library": 

23 "https://sandiego.bibliocommons.com/v2/search?custom_edit=false&query=(title%3A({title})" + 

24 "%20AND%20contributor%3A({author})%20)&searchType=bl&suppress=true", 

25 "San Diego County Library": 

26 "https://sdcl.bibliocommons.com/v2/search?custom_edit=false&query=(anywhere%3A({title})" + 

27 "%20AND%20anywhere%3A({author})%20)&searchType=bl&suppress=true", 

28 "Placer County Library": 

29 "https://placer.polarislibrary.com/polaris/search/searchresults.aspx?type=Advanced" + 

30 "&term={title}&relation=ALL&by=TI&term2={author}&relation2=ALL&by2=AU&bool1=AND" + 

31 "&limit=TOM=*&sort=RELEVANCE&page=0", 

32 "Nevada County Library": 

33 "https://library.nevadacountyca.gov/polaris/search/searchresults.aspx?ctx=1.1033.0.0.1" + 

34 "&type=Advanced&term={title}&relation=ALL&by=TI&term2={author}&relation2=ALL&by2=AU" + 

35 "&bool1=AND&bool4=AND&limit=(TOM=*%20AND%20OWN=1)&sort=RELEVANCE&page=0&searchid=1" 

36} 

37 

38 

39def _strip_subtitles(title: str) -> str: 

40 """ 

41 Removes subtitles or additional information from a given title string. 

42 

43 This function takes a title string and removes parts following a colon (:) or parentheses (), 

44 returning the cleaned main title. 

45 

46 Args: 

47 title (str): The title string to clean. 

48 

49 Returns: 

50 str: The main title without subtitles or additional information. 

51 """ 

52 returned_title = title 

53 if ':' in returned_title: 

54 returned_title = returned_title.split(':')[0].strip() 

55 if '(' in returned_title: 55 ↛ 56line 55 didn't jump to line 56 because the condition on line 55 was never true

56 returned_title = returned_title.split('(')[0].strip() 

57 return returned_title 

58 

59 

60def build_library_search_urls(author, title) -> dict[str, str]: 

61 """ 

62 Builds library search URLs using the given author and title. 

63 

64 This function takes the author's name and the title of a work, escapes 

65 them to make them URL-safe, and substitutes them into predefined search 

66 URL templates. The result is a dictionary of search URLs for external 

67 library systems or online catalog platforms. 

68 

69 :param author: The name of the author to search for (must be a string). 

70 :type author: str 

71 :param title: The title of the work to search for (must be a string). 

72 :type title: str 

73 :return: A dictionary where the keys are search platform identifiers 

74 and the values are URLs with the provided author and title 

75 embedded within them. 

76 :rtype: dict 

77 """ 

78 escaped_title = quote_plus(_strip_subtitles(title), safe="") 

79 escaped_author = quote_plus(author, safe="") 

80 search_urls = { 

81 key: value.replace("{title}", escaped_title).replace("{author}", escaped_author) 

82 for key, value in _SEARCH_TEMPLATES.items() 

83 } 

84 return search_urls 

85 

86 

87def render_icon(item, icon_mapping, span_id) -> Markup: 

88 """ 

89 Render an HTML span element with an icon based on the given item and mapping. 

90 

91 This function generates a span element containing a font-awesome icon 

92 corresponding to the provided item. The icon is determined by the 

93 ``icon_mapping`` dictionary. If the item is not found in the ``icon_mapping``, 

94 a default hidden placeholder HTML element is returned as a spacer. 

95 

96 :param item: The enumeration value representing the action (e.g., 'like', 'read') 

97 to map to a corresponding icon. 

98 :type item: str 

99 :param icon_mapping: A dictionary mapping enumeration values to font-awesome icon 

100 classes. 

101 :type icon_mapping: dict 

102 :param span_id: The ID attribute to assign to the span element in the resultant 

103 HTML string. 

104 :type span_id: str 

105 :return: A `Markup` object containing the rendered span element string 

106 with the appropriate icon or a placeholder if the item is not mapped. 

107 :rtype: Markup 

108 """ 

109 # item is the enumeration value (like 'like', 'read'), 

110 # icon_mapping is a dictionary mapping enum values to font-awesome icons 

111 if item in icon_mapping: 

112 return Markup( # nosec B704 

113 f'<span id="{span_id}">' + 

114 f'<i class="fa {icon_mapping[item]}" aria-hidden="true"></i>' + 

115 '</span>' 

116 ) 

117 # not in mapping, just use hidden default as a spacer 

118 return Markup(f'<span id="{span_id}">{PLACEHOLDER}</span>') # nosec B704 

119 

120 

121def compute_next_url(request): 

122 """ 

123 Compute the next URL based on the referrer of the provided request object. 

124 

125 This function examines the `referrer` attribute of the given `request` object 

126 to compute the next URL to navigate to. If the `request` contains a valid `referrer`, 

127 the function extracts the path and appends the query string (if present). If no 

128 `referrer` is available, the function defaults to returning the URL for the 

129 "index" route. 

130 

131 :param request: The request object containing information about the current 

132 HTTP request. Should include a `referrer` attribute to extract navigation 

133 information from. 

134 :type request: flask.Request 

135 :return: The computed next URL string based on the `referrer`, or the default 

136 URL for the "index" route if no `referrer` is present. 

137 :rtype: str 

138 """ 

139 return urlparse(request.referrer).path + ( 

140 '?' + urlparse(request.referrer).query if urlparse(request.referrer).query else '') \ 

141 if request.referrer else url_for("index") 

142 

143 

144# Define a custom function wrapping `urlparse` to parse URLs 

145def parse_url(url): 

146 """ 

147 Parses a given URL string and extracts its components. 

148 

149 This function takes a URL string as input, validates its presence, and 

150 parses it using Python's `urlparse` module. It extracts the path and query 

151 components and provides them along with the full URL in a dictionary format. 

152 

153 :param url: The URL string to be parsed. 

154 :type url: str 

155 :return: A dictionary containing the parsed components: "path", "query", 

156 and the original "full" URL string. Returns None if the input URL is 

157 not provided. 

158 :rtype: dict or None 

159 """ 

160 if not url: 

161 return None 

162 parsed = urlparse(url) 

163 return { 

164 "path": parsed.path, 

165 "query": parsed.query, 

166 "full": url 

167 } 

168 

169 

170def sanitize(content): 

171 """ 

172 Strip all HTML content from the input string. 

173 

174 :param content: The content to sanitize 

175 :return: Plain text with all HTML tags removed 

176 """ 

177 if content is None or not isinstance(content, str): 

178 return content 

179 

180 # Strip all HTML tags by setting allowed_tags to an empty list 

181 return bleach.clean( 

182 content, 

183 tags=[], # No HTML tags allowed 

184 attributes={}, # No attributes allowed 

185 strip=True # Strip all disallowed tags (which is all tags) 

186 ) 

187 

188 

189def sanitize_categories_flat(categories: str): 

190 """ 

191 Clean the component pieces of a falt categories string. 

192 :param categories: 

193 :return: 

194 """ 

195 cat_strings = categories.split(' > ') 

196 sanitize_list = [] 

197 for cat in cat_strings: 

198 sanitize_list.append(sanitize(cat)) 

199 return ' > '.join(sanitize_list)