chore: names with non-ascii prefix get a ExamOccurrenceMappingSpecial

This commit is contained in:
Wolfgang Witt 2021-02-18 17:22:06 +01:00 committed by Wolfgang Witt
parent d60f93561f
commit 5480e2d7b7
2 changed files with 69 additions and 51 deletions

View File

@ -1,4 +1,5 @@
{-# OPTIONS_GHC -fno-warn-incomplete-uni-patterns #-}
{-# OPTIONS_GHC -Wwarn #-}
module Handler.Utils.Exam
( fetchExamAux
@ -540,60 +541,68 @@ examAutoOccurrence (hash -> seed) rule ExamAutoOccurrenceConfig{..} occurrences
rangeAlphabet :: [CI Char]
rangeAlphabet = case rule of
ExamRoomSurname -> map CI.mk ['A'..'Z']
-- ExamRoomSurname -> map CI.mk [c | c <- universeF, isPrint c] -- all printable unicode characters
ExamRoomMatriculation-> map CI.mk ['0'..'9']
_rule -> []
resultAscList :: Map ExamOccurrenceId (Set ExamOccurrenceMappingDescription)
resultAscList = case fromNullable rangeAlphabet of
Nothing -> Map.empty
(Just alphabet) -> Map.map Set.singleton $ Map.fromList $ go (singleton $ head alphabet) [] result
(Just alphabet) -> Map.fromList $ go (singleton $ head alphabet) 1 [] result
where
go :: NonNull [CI Char] -> [(ExamOccurrenceId, ExamOccurrenceMappingDescription)] -> [(ExamOccurrenceId, [[CI Char]])] -> [(ExamOccurrenceId, ExamOccurrenceMappingDescription)]
go _start acc [] = acc
go :: NonNull [CI Char]
-> Int
-> [(ExamOccurrenceId, Set ExamOccurrenceMappingDescription)]
-> [(ExamOccurrenceId, [[CI Char]])]
-> [(ExamOccurrenceId, Set ExamOccurrenceMappingDescription)]
go _start _borderLength acc [] = acc
-- special case necessary, so ranges always end on last alphabet
go start acc [(_occurrenceId, [])] = case acc of
go start _borderLength acc [(_occurrenceId, [])] = case acc of
[] -> []
((occurrenceId, mappingDescription):t) -> (occurrenceId, mappingDescription {eaomrEnd}) : t
((occurrenceId, mappingDescription):t) -> (occurrenceId, Set.map extendEnd mappingDescription) : t
where
extendEnd :: ExamOccurrenceMappingDescription -> ExamOccurrenceMappingDescription
extendEnd ExamOccurrenceMappingRange {eaomrStart} = ExamOccurrenceMappingRange {eaomrStart, eaomrEnd}
extendEnd examOccurrenceMappingSpecial = examOccurrenceMappingSpecial
eaomrEnd :: [CI Char]
eaomrEnd = replicate (length start) $ last alphabet
go start acc ((_occurrenceId, []):t) = go start acc t
go start acc ((occurrenceId, userTags):t)
go start borderLength acc ((_occurrenceId, []):t) = go start borderLength acc t
go start borderLength acc ((occurrenceId, userTags):t)
| matchMappingDescription mappingDescription userTags
= go nextStart ((occurrenceId, mappingDescription) : acc) t
| length start < maxTagLength
= go (impureNonNull $ replicate (succ $ length start) $ head alphabet) [] result
= go nextStart borderLength ((occurrenceId, mappingDescription) : acc) t
| borderLength < maxTagLength
= go (singleton $ head alphabet) (succ borderLength) [] result
| otherwise
= []
where
mappingDescription :: ExamOccurrenceMappingDescription
mappingDescription = ExamOccurrenceMappingRange (toNullable start) end
-- | pre/suffix of larges user tag
end :: [CI Char]
-- userTags is guaranteed nonNull
end = case t of
[] -> replicate (length start) $ last alphabet
_nonEmpty
| length biggestTag < length start
-- add padding, to keep equal length
-> biggestTag ++ replicate (length start - length biggestTag) paddingChar
| otherwise -> biggestTag
where
biggestTag :: [CI Char]
biggestTag = maximum $ impureNonNull $ map (transformTag start) userTags
paddingChar :: CI Char
paddingChar = CI.mk ' '
mappingDescription :: Set ExamOccurrenceMappingDescription
mappingDescription = Set.fromList $ case maybeEnd of
(Just end) -> ExamOccurrenceMappingRange (toNullable start) end : specialMapping
Nothing -> specialMapping
specialMapping :: [ExamOccurrenceMappingDescription]
specialMapping = [ExamOccurrenceMappingSpecial $ transformTag borderLength tag | tag <- specialTags]
alphabetTags, specialTags :: [[CI Char]]
(alphabetTags, specialTags) = partition (all (`elem` alphabet) . take (length start)) userTags
-- | pre/suffix of largest user tag
maybeEnd :: Maybe [CI Char]
maybeEnd = case t of
[] -> Just $ replicate borderLength $ last alphabet
_nonEmpty -> transformTag borderLength . maximum <$> fromNullable alphabetTags
nextStart :: NonNull [CI Char]
-- end is guaranteed nonNull, all empty tags are filtered out in users'
nextStart = impureNonNull $ reverse $ increase $ reverse end
nextStart
| Nothing <- maybeEnd
= start
| length start < borderLength
= start <> impureNonNull [head alphabet]
| (Just end) <- maybeEnd
= impureNonNull $ reverse $ increase $ reverse end
alphabetCycle :: [CI Char]
alphabetCycle = List.cycle $ toNullable alphabet
increase :: [CI Char] -> [CI Char]
increase [] = []
increase (c:cs)
| c < head alphabet
= head alphabet : cs
| nextChar == head alphabet
= nextChar : increase cs
| otherwise
@ -603,25 +612,25 @@ examAutoOccurrence (hash -> seed) rule ExamAutoOccurrenceConfig{..} occurrences
nextChar
| c `elem` alphabet
= dropWhile (/= c) alphabetCycle List.!! 1
| otherwise -- includes padding char
= head alphabet
-- TODO what if the border is between to non-ascii characters?
| otherwise -- shouldn't happen, simply use head alphabet
= error $ "uncaught non-alphabet char: " ++ show c --TODO head alphabet
transformTag :: (MonoFoldable f) => f -> [CI Char] -> [CI Char]
transformTag (length -> l) tag = case rule of
transformTag :: Int -> [CI Char] -> [CI Char]
transformTag l tag = case rule of
ExamRoomMatriculation -> drop (max 0 $ length tag - l) tag
_rule -> take l tag
matchMappingDescription :: ExamOccurrenceMappingDescription -> [[CI Char]] -> Bool
matchMappingDescription ExamOccurrenceMappingRange {eaomrStart, eaomrEnd} = all $ \tag ->
(eaomrStart <= transformTag eaomrStart tag) && (transformTag eaomrEnd tag <= eaomrEnd)
matchMappingDescription ExamOccurrenceMappingSpecial {eaomrSpecial} = all $ checkSpecial eaomrSpecial
where
checkSpecial :: [CI Char] -> [CI Char] -> Bool
checkSpecial = case rule of
ExamRoomMatriculation -> isSuffixOf
_rule -> isPrefixOf
matchMappingDescription :: Set ExamOccurrenceMappingDescription -> [[CI Char]] -> Bool
matchMappingDescription mappingDescription userTags = flip all userTags $ \tag -> flip any mappingDescription $ \case
ExamOccurrenceMappingRange {eaomrStart, eaomrEnd}
-- non-rangeAlphabet-chars get a special mapping, so <= is fine here
-> (eaomrStart <= transformTag (length eaomrStart) tag) && (transformTag (length eaomrEnd) tag <= eaomrEnd)
ExamOccurrenceMappingSpecial {eaomrSpecial} -> checkSpecial eaomrSpecial tag
where
checkSpecial :: [CI Char] -> [CI Char] -> Bool
checkSpecial = case rule of
ExamRoomMatriculation -> isSuffixOf
_rule -> isPrefixOf
resultUsers :: Map UserId (Maybe ExamOccurrenceId)
resultUsers = Map.fromList $ do

View File

@ -1,4 +1,5 @@
{-# OPTIONS_GHC -Wno-redundant-constraints #-}
{-# OPTIONS_GHC -Wwarn #-}
module Handler.Utils.ExamSpec (spec) where
@ -15,6 +16,8 @@ import qualified Data.Set as Set
import qualified Data.Text as Text
import qualified Data.CaseInsensitive as CI
import qualified Data.RFC5051 as RFC5051
import Handler.Utils.Exam
@ -244,15 +247,21 @@ spec = do
| otherwise -> userMatrikelnummer
_rule -> Nothing
fitsInRange :: ExamOccurrenceMappingDescription -> Bool
fitsInRange ExamOccurrenceMappingRange {eaomrStart, eaomrEnd} = case ciTag of
Nothing -> True
(Just tag) -> (eaomrStart <= transformTag eaomrStart tag) && (transformTag eaomrEnd tag <= eaomrEnd)
fitsInRange ExamOccurrenceMappingSpecial {}
= True -- FIXME what is the meaning of special?
fitsInRange mappingDescription = case (ciTag, mappingDescription) of
(Nothing, _mappingDescription) -> True
(Just tag, ExamOccurrenceMappingRange {eaomrStart=(pack . map CI.foldedCase -> start), eaomrEnd=(pack . map CI.foldedCase-> end)})
-> (RFC5051.compareUnicode start (pack $ map CI.foldedCase $ transformTag start tag) /= GT)
&& (RFC5051.compareUnicode end (pack $ map CI.foldedCase $ transformTag end tag) /= LT)
(Just tag, ExamOccurrenceMappingSpecial {eaomrSpecial})
-> checkSpecial eaomrSpecial tag
transformTag :: (MonoFoldable f) => f -> [CI Char] -> [CI Char]
transformTag (length -> rangeLength) = case rule of
ExamRoomMatriculation -> reverse . take rangeLength . reverse
_rule -> take rangeLength
checkSpecial :: [CI Char] -> [CI Char] -> Bool
checkSpecial = case rule of
ExamRoomMatriculation -> isSuffixOf
_rule -> isPrefixOf
_otherwise -> (rule /= ExamRoomSurname) && (rule /= ExamRoomMatriculation)
{-
-- | Is mapping impossible?