1""" 2User name to file name conversion based on the UFO 3 spec: 3http://unifiedfontobject.org/versions/ufo3/conventions/ 4 5The code was copied from: 6https://github.com/unified-font-object/ufoLib/blob/8747da7/Lib/ufoLib/filenames.py 7 8Author: Tal Leming 9Copyright (c) 2005-2016, The RoboFab Developers: 10 Erik van Blokland 11 Tal Leming 12 Just van Rossum 13""" 14from __future__ import unicode_literals 15from fontTools.misc.py23 import basestring, unicode 16 17 18illegalCharacters = "\" * + / : < > ? [ \ ] | \0".split(" ") 19illegalCharacters += [chr(i) for i in range(1, 32)] 20illegalCharacters += [chr(0x7F)] 21reservedFileNames = "CON PRN AUX CLOCK$ NUL A:-Z: COM1".lower().split(" ") 22reservedFileNames += "LPT1 LPT2 LPT3 COM2 COM3 COM4".lower().split(" ") 23maxFileNameLength = 255 24 25 26class NameTranslationError(Exception): 27 pass 28 29 30def userNameToFileName(userName, existing=[], prefix="", suffix=""): 31 """ 32 existing should be a case-insensitive list 33 of all existing file names. 34 35 >>> userNameToFileName("a") == "a" 36 True 37 >>> userNameToFileName("A") == "A_" 38 True 39 >>> userNameToFileName("AE") == "A_E_" 40 True 41 >>> userNameToFileName("Ae") == "A_e" 42 True 43 >>> userNameToFileName("ae") == "ae" 44 True 45 >>> userNameToFileName("aE") == "aE_" 46 True 47 >>> userNameToFileName("a.alt") == "a.alt" 48 True 49 >>> userNameToFileName("A.alt") == "A_.alt" 50 True 51 >>> userNameToFileName("A.Alt") == "A_.A_lt" 52 True 53 >>> userNameToFileName("A.aLt") == "A_.aL_t" 54 True 55 >>> userNameToFileName(u"A.alT") == "A_.alT_" 56 True 57 >>> userNameToFileName("T_H") == "T__H_" 58 True 59 >>> userNameToFileName("T_h") == "T__h" 60 True 61 >>> userNameToFileName("t_h") == "t_h" 62 True 63 >>> userNameToFileName("F_F_I") == "F__F__I_" 64 True 65 >>> userNameToFileName("f_f_i") == "f_f_i" 66 True 67 >>> userNameToFileName("Aacute_V.swash") == "A_acute_V_.swash" 68 True 69 >>> userNameToFileName(".notdef") == "_notdef" 70 True 71 >>> userNameToFileName("con") == "_con" 72 True 73 >>> userNameToFileName("CON") == "C_O_N_" 74 True 75 >>> userNameToFileName("con.alt") == "_con.alt" 76 True 77 >>> userNameToFileName("alt.con") == "alt._con" 78 True 79 """ 80 # the incoming name must be a unicode string 81 if not isinstance(userName, unicode): 82 raise ValueError("The value for userName must be a unicode string.") 83 # establish the prefix and suffix lengths 84 prefixLength = len(prefix) 85 suffixLength = len(suffix) 86 # replace an initial period with an _ 87 # if no prefix is to be added 88 if not prefix and userName[0] == ".": 89 userName = "_" + userName[1:] 90 # filter the user name 91 filteredUserName = [] 92 for character in userName: 93 # replace illegal characters with _ 94 if character in illegalCharacters: 95 character = "_" 96 # add _ to all non-lower characters 97 elif character != character.lower(): 98 character += "_" 99 filteredUserName.append(character) 100 userName = "".join(filteredUserName) 101 # clip to 255 102 sliceLength = maxFileNameLength - prefixLength - suffixLength 103 userName = userName[:sliceLength] 104 # test for illegal files names 105 parts = [] 106 for part in userName.split("."): 107 if part.lower() in reservedFileNames: 108 part = "_" + part 109 parts.append(part) 110 userName = ".".join(parts) 111 # test for clash 112 fullName = prefix + userName + suffix 113 if fullName.lower() in existing: 114 fullName = handleClash1(userName, existing, prefix, suffix) 115 # finished 116 return fullName 117 118def handleClash1(userName, existing=[], prefix="", suffix=""): 119 """ 120 existing should be a case-insensitive list 121 of all existing file names. 122 123 >>> prefix = ("0" * 5) + "." 124 >>> suffix = "." + ("0" * 10) 125 >>> existing = ["a" * 5] 126 127 >>> e = list(existing) 128 >>> handleClash1(userName="A" * 5, existing=e, 129 ... prefix=prefix, suffix=suffix) == ( 130 ... '00000.AAAAA000000000000001.0000000000') 131 True 132 133 >>> e = list(existing) 134 >>> e.append(prefix + "aaaaa" + "1".zfill(15) + suffix) 135 >>> handleClash1(userName="A" * 5, existing=e, 136 ... prefix=prefix, suffix=suffix) == ( 137 ... '00000.AAAAA000000000000002.0000000000') 138 True 139 140 >>> e = list(existing) 141 >>> e.append(prefix + "AAAAA" + "2".zfill(15) + suffix) 142 >>> handleClash1(userName="A" * 5, existing=e, 143 ... prefix=prefix, suffix=suffix) == ( 144 ... '00000.AAAAA000000000000001.0000000000') 145 True 146 """ 147 # if the prefix length + user name length + suffix length + 15 is at 148 # or past the maximum length, silce 15 characters off of the user name 149 prefixLength = len(prefix) 150 suffixLength = len(suffix) 151 if prefixLength + len(userName) + suffixLength + 15 > maxFileNameLength: 152 l = (prefixLength + len(userName) + suffixLength + 15) 153 sliceLength = maxFileNameLength - l 154 userName = userName[:sliceLength] 155 finalName = None 156 # try to add numbers to create a unique name 157 counter = 1 158 while finalName is None: 159 name = userName + str(counter).zfill(15) 160 fullName = prefix + name + suffix 161 if fullName.lower() not in existing: 162 finalName = fullName 163 break 164 else: 165 counter += 1 166 if counter >= 999999999999999: 167 break 168 # if there is a clash, go to the next fallback 169 if finalName is None: 170 finalName = handleClash2(existing, prefix, suffix) 171 # finished 172 return finalName 173 174def handleClash2(existing=[], prefix="", suffix=""): 175 """ 176 existing should be a case-insensitive list 177 of all existing file names. 178 179 >>> prefix = ("0" * 5) + "." 180 >>> suffix = "." + ("0" * 10) 181 >>> existing = [prefix + str(i) + suffix for i in range(100)] 182 183 >>> e = list(existing) 184 >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == ( 185 ... '00000.100.0000000000') 186 True 187 188 >>> e = list(existing) 189 >>> e.remove(prefix + "1" + suffix) 190 >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == ( 191 ... '00000.1.0000000000') 192 True 193 194 >>> e = list(existing) 195 >>> e.remove(prefix + "2" + suffix) 196 >>> handleClash2(existing=e, prefix=prefix, suffix=suffix) == ( 197 ... '00000.2.0000000000') 198 True 199 """ 200 # calculate the longest possible string 201 maxLength = maxFileNameLength - len(prefix) - len(suffix) 202 maxValue = int("9" * maxLength) 203 # try to find a number 204 finalName = None 205 counter = 1 206 while finalName is None: 207 fullName = prefix + str(counter) + suffix 208 if fullName.lower() not in existing: 209 finalName = fullName 210 break 211 else: 212 counter += 1 213 if counter >= maxValue: 214 break 215 # raise an error if nothing has been found 216 if finalName is None: 217 raise NameTranslationError("No unique name could be found.") 218 # finished 219 return finalName 220 221if __name__ == "__main__": 222 import doctest 223 import sys 224 sys.exit(doctest.testmod().failed) 225