Coverage for crateweb/extra/salutation.py: 15%

40 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-08-27 10:34 -0500

1""" 

2crate_anon/crateweb/extra/salutation.py 

3 

4=============================================================================== 

5 

6 Copyright (C) 2015, University of Cambridge, Department of Psychiatry. 

7 Created by Rudolf Cardinal (rnc1001@cam.ac.uk). 

8 

9 This file is part of CRATE. 

10 

11 CRATE is free software: you can redistribute it and/or modify 

12 it under the terms of the GNU General Public License as published by 

13 the Free Software Foundation, either version 3 of the License, or 

14 (at your option) any later version. 

15 

16 CRATE is distributed in the hope that it will be useful, 

17 but WITHOUT ANY WARRANTY; without even the implied warranty of 

18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

19 GNU General Public License for more details. 

20 

21 You should have received a copy of the GNU General Public License 

22 along with CRATE. If not, see <https://www.gnu.org/licenses/>. 

23 

24=============================================================================== 

25 

26**Converts names to salutations and parses names into component parts.** 

27 

28""" 

29 

30from typing import Optional, Tuple 

31 

32 

33# ============================================================================= 

34# Salutation and other forms of name/title generation 

35# ============================================================================= 

36 

37 

38def title_forename_surname( 

39 title: Optional[str], 

40 forename: Optional[str], 

41 surname: Optional[str], 

42 always_title: bool = False, 

43 sex: str = "", 

44 assume_dr: bool = False, 

45) -> str: 

46 """ 

47 Used when reporting names. Returns a string of the format ``Title Forename 

48 Surname``, as far as we can work it out. 

49 

50 Args: 

51 title: title 

52 forename: forename 

53 surname: surname 

54 always_title: if we don't know the title, guess one? 

55 sex: ``"M"`` or ``"F"`` or other/unknown 

56 assume_dr: assume the person has the title "Dr"? 

57 

58 Returns: 

59 str: a string of a format like 

60 

61 .. code-block:: none 

62 

63 Prof. John Smith 

64 John Smith 

65 Prof. Smith 

66 

67 etc. 

68 

69 """ 

70 if always_title and not title: 

71 title = salutation_default_title(sex, assume_dr) 

72 return " ".join(filter(None, [title, forename, surname])) 

73 

74 

75def forename_surname(forename: Optional[str], surname: Optional[str]) -> str: 

76 """ 

77 For use when reporting names. 

78 

79 Args: 

80 forename: forename 

81 surname: surname 

82 

83 Returns: 

84 str: a string of the style ``Forename Surname`` 

85 

86 """ 

87 return " ".join(filter(None, [forename, surname])) 

88 

89 

90def salutation_default_title(sex: str = "", assume_dr: bool = False) -> str: 

91 """ 

92 Returns a guess as to someone's title. 

93 

94 Args: 

95 sex: ``"M"`` or ``"F"`` or other/unknown 

96 assume_dr: assume the person has the title "Dr"? 

97 

98 Returns: 

99 a title 

100 

101 """ 

102 if assume_dr: 

103 return "Dr" 

104 if sex.upper() == "M": 

105 return "Mr" 

106 if sex.upper() == "F": 

107 return "Ms" 

108 # really stuck now 

109 # https://en.wikipedia.org/wiki/Gender_neutral_title 

110 return "Mx" 

111 

112 

113def salutation( 

114 title: Optional[str], 

115 forename: Optional[str], 

116 surname: Optional[str], 

117 sex: str = "", 

118 assume_dr: bool = False, 

119) -> str: 

120 """ 

121 For salutations: "Dear ..." 

122 

123 Args: 

124 title: title 

125 forename: forename 

126 surname: surname 

127 sex: ``"M"`` or ``"F"`` or other/unknown 

128 assume_dr: assume the person has the title "Dr"? 

129 

130 Returns: 

131 a salutation like ``Prof. Smith`` 

132 

133 """ 

134 if not title: 

135 title = salutation_default_title(sex, assume_dr) 

136 if title.lower() == "sir": # frivolous! 

137 return " ".join([title, forename]) 

138 return " ".join([title, surname]) 

139 

140 

141# ============================================================================= 

142# String parsing 

143# ============================================================================= 

144 

145 

146def get_initial_surname_tuple_from_string(s: str) -> Tuple[str, str]: 

147 """ 

148 Parses a name-like string into plausible parts. Try: 

149 

150 .. code-block:: python 

151 

152 get_initial_surname_tuple_from_string("AJ VAN DEN BERG") 

153 get_initial_surname_tuple_from_string("VAN DEN BERG AJ") 

154 get_initial_surname_tuple_from_string("J Smith") 

155 get_initial_surname_tuple_from_string("J. Smith") 

156 get_initial_surname_tuple_from_string("Smith J.") 

157 get_initial_surname_tuple_from_string("Smith JKC") 

158 get_initial_surname_tuple_from_string("Dr Bob Smith") 

159 get_initial_surname_tuple_from_string("LINTON H C (PL)") 

160 

161 Returns: 

162 tuple: ``initial, surname`` 

163 """ 

164 parts = s.split() if s else [] 

165 nparts = len(parts) 

166 if nparts == 0: 

167 return "", "" 

168 elif "(" in s: 

169 # something v. odd like "Linton H C (PL)", for Linton Health Centre 

170 # partners or similar. We can't fix it, but... 

171 return "", parts[0] 

172 elif nparts == 1: 

173 # hmm... assume "Smith" 

174 return "", parts[0] 

175 elif nparts == 2: 

176 if len(parts[0]) < len(parts[1]): 

177 # probably "J Smith" 

178 return parts[0][0], parts[1] 

179 else: 

180 # probably "Smith JKC" 

181 return parts[1][0], parts[0] 

182 else: 

183 # Lots of parts. 

184 if parts[0].lower() == "dr": 

185 parts = parts[1:] 

186 nparts -= 1 

187 if len(parts[0]) < len(parts[-1]): 

188 # probably "AJ VAN DEN BERG" 

189 return parts[0][0], " ".join(parts[1:]) 

190 else: 

191 # probably "VAN DEN BERG AJ" 

192 return parts[-1][0], " ".join(parts[:-1])