{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE PatternGuards #-}
module Text.CSL.Eval.Names where
import Prelude
import Control.Monad.State
import Data.Char (isLower, isUpper)
import Data.List (intersperse, nub)
import Data.List.Split (wordsBy)
import Data.Maybe (isJust)
import Data.Text (Text)
import qualified Data.Text as T
import Text.CSL.Eval.Common
import Text.CSL.Eval.Output
import Text.CSL.Style
import Text.CSL.Util (headInline, isRange, lastInline, query,
readNum, splitStrWhen, toRead, (<^>))
import qualified Text.Pandoc.Builder as B
import Text.Pandoc.Definition
import Text.Pandoc.Shared (stringify)
evalNames :: Bool -> [Text] -> [Name] -> Text -> State EvalState [Output]
evalNames skipEdTrans ns nl d
| [sa,sb] <- ns, not skipEdTrans
, (sa == "editor" && sb == "translator") ||
(sb == "editor" && sa == "translator") = do
aa <- getAgents' sa
ab <- getAgents' sb
if not (null aa) && aa == ab
then modify (\s -> s { edtrans = True }) >>
evalNames True [sa] nl d
else evalNames True ns nl d
| (s:xs) <- ns = do
resetEtal
ags <- getAgents s
k <- getStringVar "ref-id"
p <- gets (citePosition . cite . env)
ops <- gets (options . env)
aus <- gets authSub
r <- do res <- agents p s ags
st <- get
fb <- agents "subsequent" s ags
put st
if null res
then return []
else let role = if aus == ["author"] then "authorsub" else s
in return . OContrib k role res fb <$> gets etal
r' <- evalNames skipEdTrans xs nl d
num <- gets contNum
return $ if r /= [] && r' /= []
then count num (r ++ [ODel $ delim ops] ++ r')
else count num $ cleanOutput (r ++ r')
| otherwise = return []
where
agents p s a = concatMapM (formatNames (hasEtAl nl) d p s a) nl
delim ops = if T.null d then getOptionVal "names-delimiter" ops else d
resetEtal = modify (\s -> s { etal = [] })
count num x = if hasCount nl && num /= []
then [OContrib "" "" [ONum (length num) emptyFormatting] [] []]
else x
hasCount = or . query hasCount'
hasCount' n
| Name Count _ _ _ _ <- n = [True]
| otherwise = [False]
formatNames :: Bool -> Delimiter -> Text -> Text -> [Agent] -> Name -> State EvalState [Output]
formatNames ea del p s as n
| Name f _ ns _ _ <- n, Count <- f = do
b <- isBib <$> gets mode
o <- mergeOptions ns <$> gets (options . env)
modify $ \st -> st { contNum = nub $ (++) (take (snd $ isEtAl b o p as) as) $ contNum st }
return []
| Name f fm ns d np <- n = do
b <- isBib <$> gets mode
o <- mergeOptions ns <$> gets (options . env)
m <- gets mode
let odel = if del /= "" then del else getOptionVal "name-delimiter" o
del'
| d /= "" = d
| T.null odel = ", "
| otherwise = odel
(_,i) = isEtAl b o p as
form = case f of
NotSet -> case getOptionVal "name-form" o of
"" -> Long
x -> read . T.unpack $ toRead x
_ -> f
genName x = do etal' <- formatEtAl o ea "et-al" fm del' x
if null etal'
then do t <- getTerm False Long "and"
return $ delim t o del'
$ format m o form fm np x
else return $
addDelim del' (format m o form fm np x)
++ etal'
setLastName o $ formatName m False f fm o np (last as)
updateEtal =<< mapM genName [1 + i .. length as]
genName i
| NameLabel f fm pl <- n = when' (isVarSet s) $ do
b <- gets edtrans
res <- formatLabel f fm (isPlural pl $ length as) $
if b then "editortranslator" else s
modify $ \st -> st { edtrans = False }
return res
| EtAl fm t <- n = do
o <- gets (options . env)
et <- gets etal
let i = length as - length et
t' = if T.null t then "et-al" else t
r <- mapM (et_al o False t' fm del) [i .. length as]
let (r',r'') = case r of
(x:xs) -> (x, xs)
[] -> ([],[])
updateEtal r''
return r'
| otherwise = return []
where
isBib (EvalBiblio _) = True
isBib _ = False
updateEtal x = modify $ \st ->
let x' = if length x == 1 then repeat $ head x else x
in st { etal = case etal st of
[] -> x
ys -> zipWith (++) ys x'
}
isWithLastName os
| "true" <- getOptionVal "et-al-use-last" os
, em <- readNum $ getOptionVal "et-al-min" os
, uf <- readNum $ getOptionVal "et-al-use-first" os
, em - uf > 1 = True
| otherwise = False
setLastName os x
| as /= []
, isWithLastName os = modify $ \st -> st { lastName = x}
| otherwise = return ()
format m os f fm np i
| (a:xs) <- take i as = formatName m True f fm os np a ++
concatMap (formatName m False f fm os np) xs
| otherwise = concatMap (formatName m True f fm os np) . take i $ as
delim t os d x
| "always" <- getOptionVal "delimiter-precedes-last" os
, length x == 2 = addDelim d (init x) ++ ODel (d <^> andStr t os) : [last x]
| length x == 2 = addDelim d (init x) ++ ODel (andStr' t d os) : [last x]
| "never" <- getOptionVal "delimiter-precedes-last" os
, length x > 2 = addDelim d (init x) ++ ODel (andStr' t d os) : [last x]
| length x > 2 = addDelim d (init x) ++ ODel (d <^> andStr t os) : [last x]
| otherwise = addDelim d x
andStr t os
| "text" <- getOptionVal "and" os = " " <> t <> " "
| "symbol" <- getOptionVal "and" os = " & "
| otherwise = ""
andStr' t d os = if T.null (andStr t os) then d else andStr t os
formatEtAl o b t fm d i = do
ln <- gets lastName
if isWithLastName o
then case () of
_ | (length as - i) == 1 -> et_al o b t fm d i
| (length as - i) > 1 -> return $ [ODel d, OPan [Str "\x2026"], OSpace] ++ ln
| otherwise -> return []
else et_al o b t fm d i
et_al o b t fm d i
= when' ( not . isSorting <$> gets mode) $
if b || length as <= i
then return []
else do x <- getTerm False Long t
when' (return $ x /= "") $
case getOptionVal "delimiter-precedes-et-al" o of
"never" -> return . (++) [OSpace] $ output fm x
"always" -> return . (++) [ODel d] $ output fm x
_ -> if i > 1 && not (T.null d)
then return . (++) [ODel d] $ output fm x
else return . (++) [OSpace] $ output fm x
isEtAl :: Bool -> [Option] -> Text -> [Agent] -> (Bool, Int)
isEtAl b os p as
| p /= "first"
, isOptionSet "et-al-subsequent-min" os
, isOptionSet "et-al-subsequent-use-first" os
, le <- etAlMin "et-al-subsequent-min"
, le' <- etAlMin "et-al-subsequent-use-first"
, length as >= le
, length as > le' = (,) True le'
| isOptionSet' "et-al-min" "et-al-subsequent-min"
, isOptionSet' "et-al-use-first" "et-al-subsequent-use-first"
, le <- etAlMin' "et-al-min" "et-al-subsequent-min"
, le' <- etAlMin' "et-al-use-first" "et-al-subsequent-use-first"
, length as >= le
, length as > le' = (,) True le'
| isOptionSet' "et-al-min" "et-al-subsequent-min"
, le <- etAlMin' "et-al-min" "et-al-subsequent-min"
, length as >= le
, length as > 1 = (,) True getUseFirst
| otherwise = (,) False $ length as
where
etAlMin x = read . T.unpack $ getOptionVal x os
etAlMin' x y = if b then etAlMin x else read . T.unpack $ getOptionVal' x y
isOptionSet' s1 s2 = if b
then isOptionSet s1 os
else or $ isOptionSet s1 os : [isOptionSet s2 os]
getOptionVal' s1 s2 = if T.null (getOptionVal s1 os)
then getOptionVal s2 os
else getOptionVal s1 os
getUseFirst = let u = if b
then getOptionVal "et-al-use-first" os
else getOptionVal' "et-al-use-first" "et-al-subsequent-min"
in if T.null u then 1 else read (T.unpack u)
formatName :: EvalMode -> Bool -> Form -> Formatting -> [Option] -> [NamePart] -> Agent -> [Output]
formatName m b f fm ops np n
| literal n /= mempty = return $ OName n institution [] fm
| Short <- f = return $ OName n shortName disambdata fm
| otherwise = return $ OName n (longName given) disambdata fm
where
institution = oPan' (unFormatted $ literal n) (form "family")
when_ c o = if c /= mempty then o else mempty
addAffixes (Formatted []) _ [] = []
addAffixes s sf ns = [Output (Output [OPan (unFormatted s)]
(form sf){ prefix = mempty, suffix = mempty} : ns)
emptyFormatting { prefix = prefix (form sf)
, suffix = suffix (form sf)}]
form s = case filter (\(NamePart n' _) -> n' == s) np of
NamePart _ fm':_ -> fm'
_ -> emptyFormatting
hyphenate new [] = new
hyphenate new accum =
if getOptionVal "initialize-with-hyphen" ops == "false"
then new ++ accum
else trimsp new ++ [Str "-"] ++ accum
isInit [Str (T.unpack -> [c])] = isUpper c
isInit _ = False
initial (Formatted x) =
case lookup "initialize-with" ops of
Just iw
| getOptionVal "initialize" ops == "false"
, isInit x -> addIn x $ B.toList $ B.text iw
| getOptionVal "initialize" ops /= "false"
, not (all isLower $ query (:[]) x) -> addIn x $ B.toList $ B.text iw
Nothing
| isInit x -> addIn x [Space]
_ -> Space : x ++ [Space]
addIn x i = foldr (hyphenate . (\z -> Str (maybe "" T.singleton $ headInline z) : i)) []
$ wordsBy (== Str "-")
$ splitStrWhen (=='-') x
sortSep g s = when_ g $ separator ++ addAffixes (g <+> s) "given" mempty
separator = if isByzantineFamily
then [OPan (B.toList (B.text
(getOptionValWithDefault "sort-separator" ", " ops)))]
else []
suff = if commaSuffix n && nameSuffix n /= mempty
then suffCom
else suffNoCom
suffCom = when_ (nameSuffix n) $ separator ++
oPan' (unFormatted $ nameSuffix n) fm
suffNoCom = when_ (nameSuffix n) $ OSpace : oPan' (unFormatted $ nameSuffix n) fm
onlyGiven = givenName n /= mempty && family == mempty
given = if onlyGiven
then givenLong
else when_ (givenName n) . Formatted . trimsp . fixsp . concatMap initial $ givenName n
fixsp (Space:Space:xs) = fixsp (Space:xs)
fixsp (x:xs) = x : fixsp xs
fixsp [] = []
trimsp = reverse . dropWhile (==Space) . reverse . dropWhile (==Space)
givenLong = when_ (givenName n) . mconcat . intersperse (Formatted [Space]) $ givenName n
family = familyName n
dropping = droppingPart n
nondropping = nonDroppingPart n
isByzantine c = c == '-' ||
(c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'z') ||
(c >= 'A' && c <= 'Z') ||
(c >= '\x0e01' && c <= '\x0e5b') ||
(c >= '\x00c0' && c <= '\x017f') ||
(c >= '\x0370' && c <= '\x03ff') ||
(c >= '\x0400' && c <= '\x052f') ||
(c >= '\x0590' && c <= '\x05d4') ||
(c >= '\x05d6' && c <= '\x05ff') ||
(c >= '\x1f00' && c <= '\x1fff') ||
(c >= '\x0600' && c <= '\x06ff') ||
(c >= '\x200c' && c <= '\x200e') ||
(c >= '\x2018' && c <= '\x2019') ||
(c >= '\x021a' && c <= '\x021b') ||
(c >= '\x202a' && c <= '\x202e')
isByzantineFamily = T.any isByzantine (stringify family)
shortName = oPan' (unFormatted $ nondropping <+> family) (form "family")
longName g
| isSorting m = let firstPart = case getOptionVal "demote-non-dropping-particle" ops of
"never" -> nondropping <+> family <+> dropping
_ -> family <+> dropping <+> nondropping
in oPan' (unFormatted firstPart) (form "family") <++> oPan' (unFormatted g) (form "given") <> suffCom
| (b && getOptionVal "name-as-sort-order" ops == "first") ||
getOptionVal "name-as-sort-order" ops == "all" = let (fam,par) = case getOptionVal "demote-non-dropping-particle" ops of
"never" -> (nondropping <+> family, dropping)
"sort-only" -> (nondropping <+> family, dropping)
_ -> (family, dropping <+> nondropping)
in oPan' (unFormatted fam) (form "family") <> sortSep g par <> suffCom
| otherwise = let fam = addAffixes (dropping <+> nondropping <+> family) "family" suff
gvn = oPan' (unFormatted g) (form "given")
in if isByzantineFamily
then gvn <++> fam
else fam <> gvn
disWithGiven = getOptionVal "disambiguate-add-givenname" ops == "true"
initialize = isJust (lookup "initialize-with" ops) && not onlyGiven
isLong = f /= Short && initialize
givenRule = let gr = getOptionVal "givenname-disambiguation-rule" ops
in if T.null gr then "by-cite" else gr
disambdata = case () of
_ | "all-names-with-initials" <- givenRule
, disWithGiven, Short <- f, initialize -> [longName given]
| "primary-name-with-initials" <- givenRule
, disWithGiven, Short <- f, initialize, b -> [longName given]
| disWithGiven, Short <- f, b
, "primary-name" <- givenRule -> [longName given, longName givenLong]
| disWithGiven, Short <- f
, "all-names" <- givenRule -> [longName given, longName givenLong]
| disWithGiven, Short <- f
, "by-cite" <- givenRule -> [longName given, longName givenLong]
| disWithGiven, isLong -> [longName givenLong]
| otherwise -> []
formatTerm :: Form -> Formatting -> Bool -> Text -> Text
-> State EvalState [Output]
formatTerm f fm p refid s = do
plural <- if s `elem` ["page", "volume", "issue"]
then do
varset <- isVarSet s
if varset
then isRange <$> getStringVar s
else return p
else return p
t <- getTerm plural f s
return $
if s == "no date"
then [OYear t refid fm]
else oStr' t fm
formatLabel :: Form -> Formatting -> Bool -> Text -> State EvalState [Output]
formatLabel f fm p s = when' (isVarSet s) go
where
go
| "locator" <- s = when' ( (/=) "" <$> gets (citeLocator . cite . env)) $ do
(l,v) <- getLocVar
form (\fm' -> return . flip OLoc emptyFormatting . output fm') id l (isRange v)
| "page" <- s = checkPlural
| "volume" <- s = checkPlural
| "issue" <- s = checkPlural
| "ibid" <- s = format s p
| isRole s = do a <- getAgents' (if s == "editortranslator"
then "editor"
else s)
if null a
then return []
else form (\fm' x -> [OLabel x fm']) id s p
| otherwise = format s p
isRole = flip elem ["author", "collection-editor", "composer", "container-author"
,"director", "editor", "editorial-director", "editortranslator"
,"illustrator", "interviewer", "original-author", "recipient"
,"reviewed-author", "translator"]
checkPlural = when' (isVarSet s) $ do
v <- getStringVar s
format s (isRange v)
format = form output id
form o g t b = o fm . g . period <$> getTerm (b && p) f t
period = if stripPeriods fm then T.filter (/= '.') else id
(<+>) :: Formatted -> Formatted -> Formatted
Formatted [] <+> ss = ss
s <+> Formatted [] = s
Formatted xs <+> Formatted ys =
case lastInline xs of
Just '’' -> Formatted (xs ++ ys)
Just '-' -> Formatted (xs ++ ys)
_ -> Formatted (xs ++ [Space] ++ ys)
(<++>) :: [Output] -> [Output] -> [Output]
[] <++> o = o
o <++> [] = o
o1 <++> o2 = o1 ++ [OSpace] ++ o2