imbib-1.2.5: lib/TypedBibData.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE RecordWildCards, TupleSections #-}
module TypedBibData where
import Control.Applicative hiding ((<|>),many)
import Control.Monad
import Control.Monad.Trans
import Data.Char
import Data.List
import Data.List.Split
import Data.Maybe
import Data.Tree
import Text.BibTeX.Entry as Entry
import Text.BibTeX.Parse
import Data.Function
import Text.ParserCombinators.Parsek as Parsek
-------------------------------------
-- Bib Data manipulation
data Entry = Entry {
kind :: String,
authors :: [(String,String)],
files :: [(String,String)], -- ^ name, type
seeAlso :: [(String,String)],
otherFields :: [(String,String)]
} deriving (Show, Eq, Ord)
renderTex = filter (`notElem` "{}")
sanitizeIdent = filter (\c -> (c >= 'a' && c <= 'z') || isDigit c || c `elem` ".-_?+") .
map ((\c -> if isSpace c then '_' else c) . toLower)
parseBib = skippingSpace $ skippingLeadingSpace Text.BibTeX.Parse.file
-- >>> test
-- Left [([("\"{\"",Just '\n')],"satisfy"),([("\"\\\\\"",Just '\n')],"satisfy"),([],"satisfy"),([("\"}\"",Just '\n')],"satisfy")]
test = parse (pAuthBlock True) longestResult
"{students of the\nUtrecht University Generic Programming class}"
-- "Ba, Jimmy"
pAuthLastFirst = do
lst <- pAuthName
spaces
_ <- string ","
spaces
frst <- pAuthName
return (frst,lst)
pAuthFirstLast = do
frst <- pAuthName
_ <- some space
lst <- pAuthNamePart
return (frst,lst)
pAuthLastOnly = do
lst <- pAuthNamePart
return ("",lst)
pAuth :: Parser Char ([Char], [Char])
pAuth = pAuthFirstLast <|> pAuthLastFirst <|> pAuthLastOnly
pAuthors :: Parser Char [([Char], [Char])]
pAuthors = pAuth `Parsek.sepBy1` (some space >> string "and" >> some space)
<|> return [("","UnknownAuthor")]
pAuthName :: Parser Char [Char]
pAuthName = intercalate " " <$> (pAuthNamePart `sepBy1` some space)
pAuthNamePart :: Parser Char [Char]
pAuthNamePart =
do n <- concat <$> some (pAuthBlock False)
when (n == "and") (fail "and is not a name")
return n
pAuthBlock :: Bool -> Parser Char String
pAuthBlock allowSpace =
(\open body close -> open : body ++ close : []) <$> (Parsek.char '{') <*> (concat <$> many (pAuthBlock True)) <*> (Parsek.char '}') <|>
sequence
[Parsek.char '\\',
Parsek.oneOf "{}'`^&%\".,~# " <|> Parsek.letter] <|>
munch1 (not . (`elem` ((if allowSpace then "" else "\n\t " ) ++ "{},")))
-- | When searching ignore special characters
project :: String -> String
project = map toLower . filter isAlphaNum
-- | Does a node contain a string (for search)
contains :: Entry -> String -> Bool
contains t needle = or [needle `isInfixOf` project txt | txt <- findTitle t : map snd (authors t)]
matchSearch :: Entry -> String -> Bool
matchSearch entry pattern = all (contains entry) (map project $ words pattern)
findCiteAuth :: Entry -> String
findCiteAuth Entry {..} = renderTex $ case map snd authors of
[] -> "????"
[a] -> a
[a,b] -> a ++ " and " ++ b
[a,b,c] -> a ++ ", " ++ b ++ " and " ++ c
(a:_) -> a ++ " et al."
findYear :: Entry -> String
findYear = findField "year"
findTitle :: Entry -> String
findTitle = findField "title"
findField :: String -> Entry -> String
findField f t = findField' f t ?? "????"
eqField :: [Char] -> [Char] -> Bool
eqField = (==) `on` (map toLower)
findField' :: String -> Entry -> [String]
findField' f Entry {..} = map snd (filter ((eqField f) . fst) otherFields)
findFirstAuthor :: Entry -> String
findFirstAuthor Entry{..} = map snd authors ?? "UnknownAuthor"
findCite t = findCiteAuth t ++ " " ++ findYear t
findNiceKey t = findField' "forcedkey" t ??
(intercalate "_" $ map sanitizeIdent $ [findFirstAuthor t, title, findYear t])
where title = ((filter ((> 2) . length) . map sanitizeIdent . words . findTitle $ t)
\\ ["de","am","for","le","an","to","be","on","make","the","how","why","its","from","towards","does"])
?? "????"
findFullText Entry {..} = map fst . filter ((`elem` ["pdf","ps"]) . snd) $ files
addFile f (Entry {..})= Entry {files = f:files,..}
partitions :: [a -> Bool] -> [a] -> [[a]]
partitions [] l = [l]
partitions (x:xs) l = yes : partitions xs no
where (yes,no) = partition x l
entryToTree :: Entry.T -> Either String Entry
entryToTree Entry.Cons{..} =
do authors <- authorsToTree (concatMap snd auths)
return Entry {..}
where
[auths,fils,seeAlsos,otherFields] = partitions (map (\k -> (eqField k) . fst) ["author","file","see"]) fields
kind = entryType
ident = identifier
files = [fileToTree f | (_,fs) <- fils, f <- splitOn ";" fs]
seeAlso = [seeAlsoToTree r | (_,rs) <- seeAlsos, not $ null rs, r <- splitOn ";" rs ]
treeToEntry :: Entry -> Entry.T
treeToEntry t@Entry {..} = Entry.Cons{..}
where fields = ("author", intercalate " and " [first ++ " " ++ last | (first,last) <- authors]) :
[("file",intercalate ";" [":" ++ f ++ ":" ++ t | (f,t) <- files]) | not $ null files] ++
otherFields ++
[("see",intercalate ";" [how ++ ":" ++ what | (how,what) <- seeAlso]) | not $ null seeAlso]
entryType = kind
identifier = findNiceKey t
fileToTree :: [Char] -> ([Char], [Char])
fileToTree = \case
(':':fs) -> (f, t)
where (f,':':t) = break (== ':') fs
other -> (error $ "fileToTree: unexpected: " ++ show (take 10 other))
authorsToTree :: String -> Either String [(String,String)]
authorsToTree s = case parse (pAuthors <* spaces) longestResultWithLeftover s of
Right (r,[]) -> Right r
_ -> Left ("parse error in authors name: " ++ s)
seeAlsoToTree r = (how,what)
where (how,':':what) = break (== ':') r
l ?? b = head $ l ++ [b]
bibToForest :: [T] -> Either String [Entry]
bibToForest xs = mapM entryToTree xs
formatEntry :: Entry.T -> String
formatEntry (Entry.Cons entryType bibId items) =
let formatItem (name, value) =
"\t"++name++" = {"++value++"}"
in "@" ++ entryType ++ "{" ++ bibId ++ ",\n" ++
intercalate ",\n" (map formatItem items) ++
"\n}\n\n"
e1 `isSeeAlso` e2 = findNiceKey e1 `elem` (map snd (seeAlso e2))
areRelated e1 e2 = e1 `isSeeAlso` e2 || e2 `isSeeAlso` e1