336 lines
10 KiB
Python
336 lines
10 KiB
Python
|
import string
|
||
|
from otp.otpbase import OTPLocalizer
|
||
|
from direct.directnotify import DirectNotifyGlobal
|
||
|
from pandac.PandaModules import NSError
|
||
|
from pandac.PandaModules import TextEncoder, TextNode
|
||
|
notify = DirectNotifyGlobal.directNotify.newCategory('NameCheck')
|
||
|
|
||
|
def filterString(str, filter):
|
||
|
result = ''
|
||
|
for char in str:
|
||
|
if char in filter:
|
||
|
result = result + char
|
||
|
|
||
|
return result
|
||
|
|
||
|
|
||
|
def justLetters(str):
|
||
|
letters = ''
|
||
|
for c in str:
|
||
|
if c.isalpha():
|
||
|
letters = letters + c
|
||
|
|
||
|
return letters
|
||
|
|
||
|
|
||
|
def justUpper(str):
|
||
|
upperCaseLetters = ''
|
||
|
for c in str:
|
||
|
if c.upper() != c.lower():
|
||
|
if c == c.upper():
|
||
|
upperCaseLetters = upperCaseLetters + c
|
||
|
|
||
|
return upperCaseLetters
|
||
|
|
||
|
|
||
|
def wordList(str):
|
||
|
words = str.split()
|
||
|
result = []
|
||
|
for word in words:
|
||
|
subWords = word.split('-')
|
||
|
for sw in subWords:
|
||
|
if sw:
|
||
|
result.append(sw)
|
||
|
|
||
|
return result
|
||
|
|
||
|
|
||
|
def checkName(name, otherCheckFuncs = [], font = None):
|
||
|
|
||
|
def longEnough(name):
|
||
|
if len(name) < 2:
|
||
|
notify.info('name is too short')
|
||
|
return OTPLocalizer.NCTooShort
|
||
|
|
||
|
def emptyName(name):
|
||
|
if name.strip() == '':
|
||
|
notify.info('name is empty')
|
||
|
return OTPLocalizer.NCTooShort
|
||
|
|
||
|
def printableChars(name):
|
||
|
for char in name:
|
||
|
if ord(char) < 128 and char not in string.printable:
|
||
|
notify.info('name contains non-printable char #%s' % ord(char))
|
||
|
return OTPLocalizer.NCGeneric
|
||
|
|
||
|
validAsciiChars = set(".,'-" + string.letters + string.whitespace)
|
||
|
|
||
|
def _validCharacter(c, validAsciiChars = validAsciiChars, font = font):
|
||
|
if c in validAsciiChars:
|
||
|
return True
|
||
|
if c.isalpha() or c.isspace():
|
||
|
return True
|
||
|
return False
|
||
|
|
||
|
def badCharacters(name, _validCharacter = _validCharacter):
|
||
|
for char in name:
|
||
|
if not _validCharacter(char):
|
||
|
if char in string.digits:
|
||
|
notify.info('name contains digits')
|
||
|
return OTPLocalizer.NCNoDigits
|
||
|
else:
|
||
|
notify.info('name contains bad char: %s' % TextEncoder().encodeWtext(char))
|
||
|
return OTPLocalizer.NCBadCharacter % TextEncoder().encodeWtext(char)
|
||
|
|
||
|
def fontHasCharacters(name, font = font):
|
||
|
if font:
|
||
|
tn = TextNode('NameCheck')
|
||
|
tn.setFont(font)
|
||
|
for c in name:
|
||
|
if not tn.hasCharacter(c.decode('utf-8')):
|
||
|
notify.info('name contains bad char: %s' % TextEncoder().encodeWtext(c))
|
||
|
return OTPLocalizer.NCBadCharacter % TextEncoder().encodeWtext(c)
|
||
|
|
||
|
def hasLetters(name):
|
||
|
words = wordList(name)
|
||
|
for word in words:
|
||
|
letters = justLetters(word)
|
||
|
if len(letters) == 0:
|
||
|
notify.info('word "%s" has no letters' % TextEncoder().encodeWtext(word))
|
||
|
return OTPLocalizer.NCNeedLetters
|
||
|
|
||
|
def hasVowels(name):
|
||
|
|
||
|
def perWord(word):
|
||
|
if '.' in word:
|
||
|
return None
|
||
|
for char in word:
|
||
|
if ord(char) >= 128:
|
||
|
return None
|
||
|
|
||
|
letters = filterString(word, string.letters)
|
||
|
if len(letters) > 2:
|
||
|
vowels = filterString(letters, 'aeiouyAEIOUY')
|
||
|
if len(vowels) == 0:
|
||
|
notify.info('word "%s" has no vowels' % TextEncoder().encodeWtext(word))
|
||
|
return OTPLocalizer.NCNeedVowels
|
||
|
return None
|
||
|
|
||
|
for word in wordList(name):
|
||
|
problem = perWord(word)
|
||
|
if problem:
|
||
|
return problem
|
||
|
|
||
|
def monoLetter(name):
|
||
|
|
||
|
def perWord(word):
|
||
|
word = word
|
||
|
letters = justLetters(word)
|
||
|
if len(letters) > 2:
|
||
|
letters = TextEncoder().decodeText(TextEncoder.lower(TextEncoder().encodeWtext(letters)))
|
||
|
filtered = filterString(letters, letters[0])
|
||
|
if filtered == letters:
|
||
|
notify.info('word "%s" uses only one letter' % TextEncoder().encodeWtext(word))
|
||
|
return OTPLocalizer.NCGeneric
|
||
|
|
||
|
for word in wordList(name):
|
||
|
problem = perWord(word)
|
||
|
if problem:
|
||
|
return problem
|
||
|
|
||
|
def checkDashes(name):
|
||
|
def validDash(index, name=name):
|
||
|
if index == 0 or i == len(name)-1:
|
||
|
return 0
|
||
|
if not name[i-1].isalpha():
|
||
|
return 0
|
||
|
if not name[i+1].isalpha():
|
||
|
return 0
|
||
|
return 1
|
||
|
|
||
|
i=0
|
||
|
while 1:
|
||
|
i = name.find('-', i, len(name))
|
||
|
if i < 0:
|
||
|
return None
|
||
|
if not validDash(i):
|
||
|
notify.info('name makes invalid use of dashes')
|
||
|
return OTPLocalizer.NCDashUsage
|
||
|
i += 1
|
||
|
|
||
|
def checkCommas(name):
|
||
|
def validComma(index, name=name):
|
||
|
if index == 0 or i == len(name)-1:
|
||
|
return OTPLocalizer.NCCommaEdge
|
||
|
if name[i-1].isspace():
|
||
|
return OTPLocalizer.NCCommaAfterWord
|
||
|
if not name[i+1].isspace():
|
||
|
return OTPLocalizer.NCCommaUsage
|
||
|
return None
|
||
|
|
||
|
i=0
|
||
|
while 1:
|
||
|
i = name.find(',', i, len(name))
|
||
|
if i < 0:
|
||
|
return None
|
||
|
problem = validComma(i)
|
||
|
if problem:
|
||
|
notify.info('name makes invalid use of commas')
|
||
|
return problem
|
||
|
i += 1
|
||
|
|
||
|
def checkPeriods(name):
|
||
|
words = wordList(name)
|
||
|
for word in words:
|
||
|
if word[-1] == ',':
|
||
|
word = word[:-1]
|
||
|
numPeriods = word.count('.')
|
||
|
if not numPeriods:
|
||
|
continue
|
||
|
letters = justLetters(word)
|
||
|
numLetters = len(letters)
|
||
|
if word[-1] != '.':
|
||
|
notify.info('word "%s" does not end in a period' % TextEncoder().encodeWtext(word))
|
||
|
return OTPLocalizer.NCPeriodUsage
|
||
|
if numPeriods > 2:
|
||
|
notify.info('word "%s" has too many periods' % TextEncoder().encodeWtext(word))
|
||
|
return OTPLocalizer.NCPeriodUsage
|
||
|
if numPeriods == 2:
|
||
|
if not (word[1] == '.' and word[3] == '.'):
|
||
|
notify.info('word "%s" does not fit the J.T. pattern' % TextEncoder().encodeWtext(word))
|
||
|
return OTPLocalizer.NCPeriodUsage
|
||
|
|
||
|
return None
|
||
|
|
||
|
def checkApostrophes(name):
|
||
|
words = wordList(name)
|
||
|
for word in words:
|
||
|
numApos = word.count("'")
|
||
|
if numApos > 2:
|
||
|
notify.info('word "%s" has too many apostrophes.' % TextEncoder().encodeWtext(word))
|
||
|
return OTPLocalizer.NCApostrophes
|
||
|
|
||
|
numApos = name.count("'")
|
||
|
if numApos > 3:
|
||
|
notify.info('name has too many apostrophes.')
|
||
|
return OTPLocalizer.NCApostrophes
|
||
|
|
||
|
def tooManyWords(name):
|
||
|
if len(wordList(name)) > 4:
|
||
|
notify.info('name has too many words')
|
||
|
return OTPLocalizer.NCTooManyWords
|
||
|
|
||
|
def allCaps(name):
|
||
|
letters = justLetters(name)
|
||
|
if len(letters) > 2:
|
||
|
upperLetters = TextEncoder().decodeText(TextEncoder.upper(TextEncoder().encodeWtext(letters)))
|
||
|
for i in xrange(len(upperLetters)):
|
||
|
if not upperLetters[0].isupper():
|
||
|
return
|
||
|
|
||
|
if upperLetters == letters:
|
||
|
notify.info('name is all caps')
|
||
|
return OTPLocalizer.NCAllCaps
|
||
|
|
||
|
def mixedCase(name):
|
||
|
words = wordList(name)
|
||
|
for word in words:
|
||
|
if len(word) > 2:
|
||
|
capitals = justUpper(word)
|
||
|
if len(capitals) > 2:
|
||
|
notify.info('name has mixed case')
|
||
|
return OTPLocalizer.NCMixedCase
|
||
|
|
||
|
def checkJapanese(name):
|
||
|
asciiSpace = range(32, 33)
|
||
|
asciiDigits = range(48, 64)
|
||
|
hiragana = range(12353, 12448)
|
||
|
katakana = range(12449, 12544)
|
||
|
halfwidthKatakana = range(65381, 65440)
|
||
|
halfwidthCharacter = set(asciiSpace + halfwidthKatakana)
|
||
|
allowedUtf8 = set(asciiSpace + hiragana + katakana + halfwidthKatakana)
|
||
|
|
||
|
te = TextEncoder()
|
||
|
dc = 0.0
|
||
|
|
||
|
for char in (ord(char) for char in te.decodeText(name)):
|
||
|
if char not in allowedUtf8:
|
||
|
if char in asciiDigits:
|
||
|
notify.info('name contains not allowed ascii digits')
|
||
|
return OTPLocalizer.NCNoDigits
|
||
|
else:
|
||
|
notify.info('name contains not allowed utf8 char: 0x%04x' % char)
|
||
|
return OTPLocalizer.NCBadCharacter % te.encodeWtext(unichr(char))
|
||
|
elif char in halfwidthCharacter:
|
||
|
dc += 0.5
|
||
|
else:
|
||
|
dc += 1
|
||
|
|
||
|
if dc < 2:
|
||
|
notify.info('name is too short: %0.1f' % dc)
|
||
|
return OTPLocalizer.NCTooShort
|
||
|
elif dc > 8:
|
||
|
notify.info('name has been occupied more than eight display cells: %0.1f' % dc)
|
||
|
return OTPLocalizer.NCGeneric
|
||
|
|
||
|
def repeatedChars(name):
|
||
|
count = 1
|
||
|
lastChar = None
|
||
|
i = 0
|
||
|
while i < len(name):
|
||
|
char = name[i]
|
||
|
i += 1
|
||
|
if char == lastChar:
|
||
|
count += 1
|
||
|
else:
|
||
|
count = 1
|
||
|
lastChar = char
|
||
|
if count > 2:
|
||
|
notify.info('character %s is repeated too many times' % TextEncoder().encodeWtext(char))
|
||
|
return OTPLocalizer.NCRepeatedChar % TextEncoder().encodeWtext(char)
|
||
|
|
||
|
return
|
||
|
|
||
|
checks = [printableChars,
|
||
|
badCharacters,
|
||
|
fontHasCharacters,
|
||
|
longEnough,
|
||
|
emptyName,
|
||
|
hasLetters,
|
||
|
hasVowels,
|
||
|
monoLetter,
|
||
|
checkDashes,
|
||
|
checkCommas,
|
||
|
checkPeriods,
|
||
|
checkApostrophes,
|
||
|
tooManyWords,
|
||
|
allCaps,
|
||
|
mixedCase,
|
||
|
repeatedChars] + otherCheckFuncs
|
||
|
symmetricChecks = []
|
||
|
name = TextEncoder().decodeText(name)
|
||
|
notify.info('checking name "%s"...' % TextEncoder().encodeWtext(name))
|
||
|
for check in checks:
|
||
|
problem = check(name[:])
|
||
|
if not problem and check in symmetricChecks:
|
||
|
nName = name[:]
|
||
|
bName.reverse()
|
||
|
problem = check(bName)
|
||
|
print 'problem = %s' % problem
|
||
|
if problem:
|
||
|
return problem
|
||
|
|
||
|
return None
|
||
|
|
||
|
|
||
|
severity = notify.getSeverity()
|
||
|
notify.setSeverity(NSError)
|
||
|
for i in xrange(32):
|
||
|
pass
|
||
|
|
||
|
for c in '!"#$%&()*+/:;<=>?@[\\]^_`{|}~':
|
||
|
pass
|
||
|
|
||
|
notify.setSeverity(severity)
|
||
|
del severity
|