@@ -1,4 +1,4 @@
1−{-# LANGUAGE PatternGuards, CPP #-}
1+{-# LANGUAGE PatternGuards, CPP, ScopedTypeVariables #-}
22{-
33Copyright (C) 2010 John MacFarlane <jgm@berkeley.edu>
44
@@ -30,7 +30,8 @@ Conversion of 'Pandoc' documents to EPUB.
3030-}
3131module Text.Pandoc.Writers.EPUB ( writeEPUB ) where
3232import Data.IORef
33−import Data.Maybe ( fromMaybe, isNothing )
33+import qualified Data.Map as M
34+import Data.Maybe ( fromMaybe )
3435import Data.List ( isInfixOf, intercalate )
3536import System.Environment ( getEnv )
3637import Text.Printf (printf)
@@ -40,6 +41,7 @@ import qualified Data.ByteString.Lazy.Char8 as B8
4041import Text.Pandoc.UTF8 ( fromStringLazy, toString )
4142import Text.Pandoc.SelfContained ( makeSelfContained )
4243import Codec.Archive.Zip
44+import Control.Applicative ((<$>))
4345import Data.Time.Clock.POSIX
4446import Data.Time
4547import System.Locale
@@ -70,6 +72,216 @@ import Text.Blaze.Html.Renderer.Utf8 (renderHtml)
7072-- in filenames, chapter0003.xhtml.
7173data Chapter = Chapter (Maybe [Int]) [Block]
7274
75+data EPUBMetadata = EPUBMetadata{
76+ epubIdentifier :: [Identifier]
77+ , epubTitle :: [Title]
78+ , epubDate :: String
79+ , epubLanguage :: String
80+ , epubCreator :: [Creator]
81+ , epubContributor :: [Creator]
82+ , epubSubject :: [String]
83+ , epubDescription :: Maybe String
84+ , epubType :: Maybe String
85+ , epubFormat :: Maybe String
86+ , epubPublisher :: Maybe String
87+ , epubSource :: Maybe String
88+ , epubRelation :: Maybe String
89+ , epubCoverage :: Maybe String
90+ , epubRights :: Maybe String
91+ , epubCoverImage :: Maybe String
92+ } deriving Show
93+
94+data Creator = Creator{
95+ creatorText :: String
96+ , creatorRole :: Maybe String
97+ , creatorFileAs :: Maybe String
98+ } deriving Show
99+
100+data Identifier = Identifier{
101+ identifierText :: String
102+ , identifierScheme :: Maybe String
103+ } deriving Show
104+
105+data Title = Title{
106+ titleText :: String
107+ , titleFileAs :: Maybe String
108+ , titleType :: Maybe String
109+ } deriving Show
110+
111+dcName :: String -> QName
112+dcName n = QName n Nothing (Just "dc")
113+
114+dcNode :: Node t => String -> t -> Element
115+dcNode = node . dcName
116+
117+opfName :: String -> QName
118+opfName n = QName n Nothing (Just "opf")
119+
120+plainify :: [Inline] -> String
121+plainify t =
122+ trimr $ writePlain def{ writerStandalone = False } $ Pandoc nullMeta [Plain t]
123+
124+getEPUBMetadata :: WriterOptions -> Meta -> IO EPUBMetadata
125+getEPUBMetadata opts meta = do
126+ let md = metadataFromMeta opts meta
127+ let elts = onlyElems $ parseXML $ writerEpubMetadata opts
128+ let md' = foldr addMetadataFromXML md elts
129+ let addIdentifier m =
130+ if null (epubIdentifier m)
131+ then do
132+ randomId <- fmap show getRandomUUID
133+ return $ m{ epubIdentifier = [Identifier randomId Nothing] }
134+ else return m
135+ let addLanguage m =
136+ if null (epubLanguage m)
137+ then case lookup "lang" (writerVariables opts) of
138+ Just x -> return m{ epubLanguage = x }
139+ Nothing -> do
140+ localeLang <- catch (liftM
141+ (map (\c -> if c == '_' then '-' else c) .
142+ takeWhile (/='.')) $ getEnv "LANG")
143+ (\e -> let _ = (e :: SomeException) in return "en-US")
144+ return m{ epubLanguage = localeLang }
145+ else return m
146+ let fixDate m =
147+ if null (epubDate m)
148+ then do
149+ currentTime <- getCurrentTime
150+ return $ m{ epubDate = showDateTimeISO8601 currentTime }
151+ else return m
152+ let addAuthor m =
153+ if any (\c -> creatorRole c == Just "aut") $ epubCreator m
154+ then return m
155+ else do
156+ let authors' = map plainify $ docAuthors meta
157+ let toAuthor name = Creator{ creatorText = name
158+ , creatorRole = Just "aut"
159+ , creatorFileAs = Nothing }
160+ return $ m{ epubCreator = map toAuthor authors' ++ epubCreator m }
161+ addIdentifier md' >>= fixDate >>= addAuthor >>= addLanguage
162+
163+addMetadataFromXML :: Element -> EPUBMetadata -> EPUBMetadata
164+addMetadataFromXML e@(Element (QName name _ (Just "dc")) attrs _ _) md
165+ | name == "identifier" = md{ epubIdentifier =
166+ Identifier{ identifierText = strContent e
167+ , identifierScheme = lookupAttr (opfName "scheme") attrs
168+ } : epubIdentifier md }
169+ | name == "title" = md{ epubTitle =
170+ Title{ titleText = strContent e
171+ , titleFileAs = getAttr "file-as"
172+ , titleType = getAttr "type"
173+ } : epubTitle md }
174+ | name == "date" = md{ epubDate = maybe "" id $ normalizeDate $ strContent e }
175+ | name == "language" = md{ epubLanguage = strContent e }
176+ | name == "creator" = md{ epubCreator =
177+ Creator{ creatorText = strContent e
178+ , creatorRole = getAttr "role"
179+ , creatorFileAs = getAttr "file-as"
180+ } : epubCreator md }
181+ | name == "contributor" = md{ epubContributor =
182+ Creator { creatorText = strContent e
183+ , creatorRole = getAttr "role"
184+ , creatorFileAs = getAttr "file-as"
185+ } : epubContributor md }
186+ | name == "subject" = md{ epubSubject = strContent e : epubSubject md }
187+ | name == "description" = md { epubDescription = Just $ strContent e }
188+ | name == "type" = md { epubType = Just $ strContent e }
189+ | name == "format" = md { epubFormat = Just $ strContent e }
190+ | name == "type" = md { epubType = Just $ strContent e }
191+ | name == "publisher" = md { epubPublisher = Just $ strContent e }
192+ | name == "source" = md { epubSource = Just $ strContent e }
193+ | name == "relation" = md { epubRelation = Just $ strContent e }
194+ | name == "coverage" = md { epubCoverage = Just $ strContent e }
195+ | name == "rights" = md { epubRights = Just $ strContent e }
196+ | otherwise = md
197+ where getAttr n = lookupAttr (opfName n) attrs
198+addMetadataFromXML _ md = md
199+
200+metaValueToString :: MetaValue -> String
201+metaValueToString (MetaString s) = s
202+metaValueToString (MetaInlines ils) = plainify ils
203+metaValueToString (MetaBlocks bs) = plainify $ query (:[]) bs
204+metaValueToString (MetaBool b) = show b
205+metaValueToString _ = ""
206+
207+getList :: String -> Meta -> (MetaValue -> a) -> [a]
208+getList s meta handleMetaValue =
209+ case lookupMeta s meta of
210+ Just (MetaList xs) -> map handleMetaValue xs
211+ Just mv -> [handleMetaValue mv]
212+ Nothing -> []
213+
214+getIdentifier :: Meta -> [Identifier]
215+getIdentifier meta = getList "identifier" meta handleMetaValue
216+ where handleMetaValue (MetaMap m) =
217+ Identifier{ identifierText = maybe "" metaValueToString
218+ $ M.lookup "text" m
219+ , identifierScheme = metaValueToString <$>
220+ M.lookup "scheme" m }
221+ handleMetaValue mv = Identifier (metaValueToString mv) Nothing
222+
223+getTitle :: Meta -> [Title]
224+getTitle meta = getList "title" meta handleMetaValue
225+ where handleMetaValue (MetaMap m) =
226+ Title{ titleText = maybe "" metaValueToString $ M.lookup "text" m
227+ , titleFileAs = metaValueToString <$> M.lookup "file-as" m
228+ , titleType = metaValueToString <$> M.lookup "type" m }
229+ handleMetaValue mv = Title (metaValueToString mv) Nothing Nothing
230+
231+getCreator :: String -> Meta -> [Creator]
232+getCreator s meta = getList s meta handleMetaValue
233+ where handleMetaValue (MetaMap m) =
234+ Creator{ creatorText = maybe "" metaValueToString $ M.lookup "text" m
235+ , creatorFileAs = metaValueToString <$> M.lookup "file-as" m
236+ , creatorRole = metaValueToString <$> M.lookup "role" m }
237+ handleMetaValue mv = Creator (metaValueToString mv) Nothing Nothing
238+
239+simpleList :: String -> Meta -> [String]
240+simpleList s meta =
241+ case lookupMeta s meta of
242+ Just (MetaList xs) -> map metaValueToString xs
243+ Just x -> [metaValueToString x]
244+ Nothing -> []
245+
246+metadataFromMeta :: WriterOptions -> Meta -> EPUBMetadata
247+metadataFromMeta opts meta = EPUBMetadata{
248+ epubIdentifier = identifiers
249+ , epubTitle = titles
250+ , epubDate = date
251+ , epubLanguage = language
252+ , epubCreator = creators
253+ , epubContributor = contributors
254+ , epubSubject = subjects
255+ , epubDescription = description
256+ , epubType = epubtype
257+ , epubFormat = format
258+ , epubPublisher = publisher
259+ , epubSource = source
260+ , epubRelation = relation
261+ , epubCoverage = coverage
262+ , epubRights = rights
263+ , epubCoverImage = coverImage
264+ }
265+ where identifiers = getIdentifier meta
266+ titles = getTitle meta
267+ date = maybe "" id $
268+ (metaValueToString <$> lookupMeta "date" meta) >>= normalizeDate
269+ language = maybe "" metaValueToString $
270+ lookupMeta "language" meta `mplus` lookupMeta "lang" meta
271+ creators = getCreator "creator" meta
272+ contributors = getCreator "contributor" meta
273+ subjects = simpleList "subject" meta
274+ description = metaValueToString <$> lookupMeta "description" meta
275+ epubtype = metaValueToString <$> lookupMeta "type" meta
276+ format = metaValueToString <$> lookupMeta "format" meta
277+ publisher = metaValueToString <$> lookupMeta "publisher" meta
278+ source = metaValueToString <$> lookupMeta "source" meta
279+ relation = metaValueToString <$> lookupMeta "relation" meta
280+ coverage = metaValueToString <$> lookupMeta "coverage" meta
281+ rights = metaValueToString <$> lookupMeta "rights" meta
282+ coverImage = fmap (const "cover-image") $
283+ lookup "epub-cover-image" $ writerVariables opts
284+
73285-- | Produce an EPUB file from a Pandoc document.
74286writeEPUB :: WriterOptions -- ^ Writer options
75287 -> Pandoc -- ^ Document to convert
@@ -192,18 +404,6 @@ writeEPUB opts doc@(Pandoc meta _) = do
192404 let containsMathML ent = "<math" `isInfixOf` (B8.unpack $ fromEntry ent)
193405
194406 -- contents.opf
195− localeLang <- catch (liftM (map (\c -> if c == '_' then '-' else c) .
196− takeWhile (/='.')) $ getEnv "LANG")
197− (\e -> let _ = (e :: SomeException) in return "en-US")
198− let lang = case lookup "lang" (writerVariables opts') of
199− Just x -> x
200− Nothing -> localeLang
201− let userNodes = onlyElems $ parseXML $ writerEpubMetadata opts'
202− let mbIdent = findElement (QName "identifier" Nothing (Just "dc"))
203− $ unode "dummy" ! [] $ userNodes
204− uuid <- case mbIdent of
205− Just id' -> return $ trim $ strContent id'
206− Nothing -> fmap show getRandomUUID
207407 let chapterNode ent = unode "item" !
208408 ([("id", takeBaseName $ eRelativePath ent),
209409 ("href", eRelativePath ent),
@@ -221,22 +421,23 @@ writeEPUB opts doc@(Pandoc meta _) = do
221421 [("id", takeBaseName $ eRelativePath ent),
222422 ("href", eRelativePath ent),
223423 ("media-type", maybe "" id $ getMimeType $ eRelativePath ent)] $ ()
224− let plainify t = trimr $
225− writePlain opts'{ writerStandalone = False } $
226− Pandoc meta [Plain t]
227− let plainTitle = plainify $ docTitle meta
228− let plainAuthors = map plainify $ docAuthors meta
424+ metadata <- getEPUBMetadata opts' meta
425+ let plainTitle = case docTitle meta of
426+ [] -> case epubTitle metadata of
427+ [] -> "UNTITLED"
428+ (x:_) -> titleText x
429+ x -> plainify x
430+ let uuid = case epubIdentifier metadata of
431+ (x:_) -> identifierText x -- use first identifier as UUID
432+ [] -> error "epubIdentifier is null" -- shouldn't happen
229433 currentTime <- getCurrentTime
230− let plainDate = maybe (showDateTimeISO8601 currentTime) id
231− $ normalizeDate $ stringify $ docDate meta
232434 let contentsData = fromStringLazy $ ppTopElement $
233435 unode "package" ! [("version", case version of
234436 EPUB2 -> "2.0"
235437 EPUB3 -> "3.0")
236438 ,("xmlns","http://www.idpf.org/2007/opf")
237− ,("unique-identifier","BookId")] $
238− [ metadataElement version userNodes
239− uuid lang plainTitle plainAuthors plainDate currentTime mbCoverImage
439+ ,("unique-identifier","epub-id-1")] $
440+ [ metadataElement version metadata currentTime
240441 , unode "manifest" $
241442 [ unode "item" ! [("id","ncx"), ("href","toc.ncx")
242443 ,("media-type","application/x-dtbncx+xml")] $ ()
@@ -266,7 +467,8 @@ writeEPUB opts doc@(Pandoc meta _) = do
266467 map chapterRefNode chapterEntries)
267468 , unode "guide" $
268469 [ unode "reference" !
269− [("type","toc"),("title",plainTitle),("href","nav.xhtml")] $ ()
470+ [("type","toc"),("title",plainTitle),
471+ ("href","nav.xhtml")] $ ()
270472 ] ++
271473 [ unode "reference" !
272474 [("type","cover"),("title","Cover"),("href","cover.xhtml")] $ () | mbCoverImage /= Nothing
@@ -390,32 +592,96 @@ writeEPUB opts doc@(Pandoc meta _) = do
390592 (picEntries ++ cpicEntry ++ cpgEntry ++ chapterEntries ++ fontEntries))
391593 return $ fromArchive archive
392594
393−metadataElement :: EPUBVersion -> [Element] -> String -> String -> String -> [String]
394− -> String -> UTCTime -> Maybe a -> Element
395−metadataElement version userNodes uuid lang title authors date currentTime mbCoverImage =
396− let elt = unode "metadata" ! [("xmlns:dc","http://purl.org/dc/elements/1.1/")
397− ,("xmlns:opf","http://www.idpf.org/2007/opf")] $
398− filter isMetadataElement userNodes
399− dublinElements = ["contributor","coverage","creator","date",
400− "description","format","identifier","language","publisher",
401− "relation","rights","source","subject","title","type"]
402− isMetadataElement e = (qPrefix (elName e) == Just "dc" &&
403− qName (elName e) `elem` dublinElements) ||
404− (qPrefix (elName e) == Nothing &&
405− qName (elName e) `elem` ["link","meta"])
406− contains e n = not (null (findElements (QName n Nothing (Just "dc")) e))
407− newNodes = [ unode "dc:title" title | not (elt `contains` "title") ] ++
408− [ unode "dc:language" lang | not (elt `contains` "language") ] ++
409− [ unode "dc:identifier" ! [("id","BookId")] $ uuid |
410− not (elt `contains` "identifier") ] ++
411− [ unode "dc:creator" ! [("opf:role","aut") | version == EPUB2]
412− $ a | a <- authors, not (elt `contains` "creator") ] ++
413− [ unode "dc:date" date | not (elt `contains` "date") ] ++
414− [ unode "meta" ! [("property", "dcterms:modified")] $
415− (showDateTimeISO8601 currentTime) | version == EPUB3] ++
416− [ unode "meta" ! [("name","cover"), ("content","cover-image")] $ () |
417− not (isNothing mbCoverImage) ]
418− in elt{ elContent = elContent elt ++ map Elem newNodes }
595+metadataElement :: EPUBVersion -> EPUBMetadata -> UTCTime -> Element
596+metadataElement version md currentTime =
597+ unode "metadata" ! [("xmlns:dc","http://purl.org/dc/elements/1.1/")
598+ ,("xmlns:opf","http://www.idpf.org/2007/opf")] $ mdNodes
599+ where mdNodes = identifierNodes ++ titleNodes ++ dateNodes ++ languageNodes
600+ ++ creatorNodes ++ contributorNodes ++ subjectNodes
601+ ++ descriptionNodes ++ typeNodes ++ formatNodes
602+ ++ publisherNodes ++ sourceNodes ++ relationNodes
603+ ++ coverageNodes ++ rightsNodes ++ coverImageNodes
604+ ++ modifiedNodes
605+ withIds base f = concat . zipWith f (map (\x -> base ++ ('-' : show x))
606+ ([1..] :: [Int]))
607+ identifierNodes = withIds "epub-id" toIdentifierNode $
608+ epubIdentifier md
609+ titleNodes = withIds "epub-title" toTitleNode $ epubTitle md
610+ dateNodes = dcTag' "date" $ epubDate md
611+ languageNodes = [dcTag "language" $ epubLanguage md]
612+ creatorNodes = withIds "epub-creator" (toCreatorNode "creator") $
613+ epubCreator md
614+ contributorNodes = withIds "epub-contributor"
615+ (toCreatorNode "contributor") $ epubContributor md
616+ subjectNodes = map (dcTag "subject") $ epubSubject md
617+ descriptionNodes = maybe [] (dcTag' "description") $ epubDescription md
618+ typeNodes = maybe [] (dcTag' "type") $ epubType md
619+ formatNodes = maybe [] (dcTag' "format") $ epubFormat md
620+ publisherNodes = maybe [] (dcTag' "publisher") $ epubPublisher md
621+ sourceNodes = maybe [] (dcTag' "source") $ epubSource md
622+ relationNodes = maybe [] (dcTag' "relation") $ epubRelation md
623+ coverageNodes = maybe [] (dcTag' "coverage") $ epubCoverage md
624+ rightsNodes = maybe [] (dcTag' "rights") $ epubRights md
625+ coverImageNodes = maybe []
626+ (\ci -> [unode "meta" ! [("name","cover"), ("content",ci)] $ ()])
627+ $ epubCoverImage md
628+ modifiedNodes = [ unode "meta" ! [("property", "dcterms:modified")] $
629+ (showDateTimeISO8601 currentTime) | version == EPUB3 ]
630+ dcTag n s = unode ("dc:" ++ n) s
631+ dcTag' n s = [dcTag n s]
632+ toIdentifierNode id' (Identifier txt scheme)
633+ | version == EPUB2 = [dcNode "identifier" !
634+ ([("id",id')] ++ maybe [] (\x -> [("opf:scheme", x)]) scheme) $
635+ txt]
636+ | otherwise = [dcNode "identifier" ! [("id",id')] $ txt] ++
637+ maybe [] (\x -> [unode "meta" !
638+ [("refines",'#':id'),("property","identifier-type"),
639+ ("scheme","onix:codelist5")] $ x])
640+ (schemeToOnix `fmap` scheme)
641+ toCreatorNode s id' creator
642+ | version == EPUB2 = [dcNode s !
643+ ([("id",id')] ++
644+ maybe [] (\x -> [("opf:file-as",x)]) (creatorFileAs creator) ++
645+ maybe [] (\x -> [("opf:role",x)])
646+ (creatorRole creator >>= toRelator)) $ creatorText creator]
647+ | otherwise = [dcNode s ! [("id",id')] $ creatorText creator] ++
648+ maybe [] (\x -> [unode "meta" !
649+ [("refines",'#':id'),("property","file-as")] $ x])
650+ (creatorFileAs creator) ++
651+ maybe [] (\x -> [unode "meta" !
652+ [("refines",'#':id'),("property","role"),
653+ ("scheme","marc:relators")] $ x])
654+ (creatorRole creator >>= toRelator)
655+ toTitleNode id' title
656+ | version == EPUB2 = [dcNode "title" !
657+ ([("id",id')] ++
658+ maybe [] (\x -> [("opf:file-as",x)]) (titleFileAs title) ++
659+ maybe [] (\x -> [("opf:title-type",x)]) (titleType title)) $
660+ titleText title]
661+ | otherwise = [dcNode "title" ! [("id",id')] $ titleText title]
662+ ++
663+ maybe [] (\x -> [unode "meta" !
664+ [("refines",'#':id'),("property","file-as")] $ x])
665+ (titleFileAs title) ++
666+ maybe [] (\x -> [unode "meta" !
667+ [("refines",'#':id'),("property","title-type")] $ x])
668+ (titleType title)
669+ schemeToOnix "ISBN-10" = "02"
670+ schemeToOnix "GTIN-13" = "03"
671+ schemeToOnix "UPC" = "04"
672+ schemeToOnix "ISMN-10" = "05"
673+ schemeToOnix "DOI" = "06"
674+ schemeToOnix "LCCN" = "13"
675+ schemeToOnix "GTIN-14" = "14"
676+ schemeToOnix "ISBN-13" = "15"
677+ schemeToOnix "Legal deposit number" = "17"
678+ schemeToOnix "URN" = "22"
679+ schemeToOnix "OCLC" = "23"
680+ schemeToOnix "ISMN-13" = "25"
681+ schemeToOnix "ISBN-A" = "26"
682+ schemeToOnix "JP" = "27"
683+ schemeToOnix "OLCC" = "28"
684+ schemeToOnix _ = "01"
419685
420686showDateTimeISO8601 :: UTCTime -> String
421687showDateTimeISO8601 = formatTime defaultTimeLocale "%FT%TZ"
@@ -527,3 +793,277 @@ replaceRefs refTable = walk replaceOneRef
527793 Just url -> Link lab (url,tit)
528794 Nothing -> x
529795 replaceOneRef x = x
796+
797+toRelator :: String -> Maybe String
798+toRelator x
799+ | x `elem` relators = Just x
800+ | otherwise = lookup (map toLower x) relatorMap
801+
802+relators :: [String]
803+relators = map snd relatorMap
804+
805+relatorMap :: [(String, String)]
806+relatorMap =
807+ [("abridger", "abr")
808+ ,("actor", "act")
809+ ,("adapter", "adp")
810+ ,("addressee", "rcp")
811+ ,("analyst", "anl")
812+ ,("animator", "anm")
813+ ,("annotator", "ann")
814+ ,("appellant", "apl")
815+ ,("appellee", "ape")
816+ ,("applicant", "app")
817+ ,("architect", "arc")
818+ ,("arranger", "arr")
819+ ,("art copyist", "acp")
820+ ,("art director", "adi")
821+ ,("artist", "art")
822+ ,("artistic director", "ard")
823+ ,("assignee", "asg")
824+ ,("associated name", "asn")
825+ ,("attributed name", "att")
826+ ,("auctioneer", "auc")
827+ ,("author", "aut")
828+ ,("author in quotations or text abstracts", "aqt")
829+ ,("author of afterword, colophon, etc.", "aft")
830+ ,("author of dialog", "aud")
831+ ,("author of introduction, etc.", "aui")
832+ ,("autographer", "ato")
833+ ,("bibliographic antecedent", "ant")
834+ ,("binder", "bnd")
835+ ,("binding designer", "bdd")
836+ ,("blurb writer", "blw")
837+ ,("book designer", "bkd")
838+ ,("book producer", "bkp")
839+ ,("bookjacket designer", "bjd")
840+ ,("bookplate designer", "bpd")
841+ ,("bookseller", "bsl")
842+ ,("braille embosser", "brl")
843+ ,("broadcaster", "brd")
844+ ,("calligrapher", "cll")
845+ ,("cartographer", "ctg")
846+ ,("caster", "cas")
847+ ,("censor", "cns")
848+ ,("choreographer", "chr")
849+ ,("cinematographer", "cng")
850+ ,("client", "cli")
851+ ,("collection registrar", "cor")
852+ ,("collector", "col")
853+ ,("collotyper", "clt")
854+ ,("colorist", "clr")
855+ ,("commentator", "cmm")
856+ ,("commentator for written text", "cwt")
857+ ,("compiler", "com")
858+ ,("complainant", "cpl")
859+ ,("complainant-appellant", "cpt")
860+ ,("complainant-appellee", "cpe")
861+ ,("composer", "cmp")
862+ ,("compositor", "cmt")
863+ ,("conceptor", "ccp")
864+ ,("conductor", "cnd")
865+ ,("conservator", "con")
866+ ,("consultant", "csl")
867+ ,("consultant to a project", "csp")
868+ ,("contestant", "cos")
869+ ,("contestant-appellant", "cot")
870+ ,("contestant-appellee", "coe")
871+ ,("contestee", "cts")
872+ ,("contestee-appellant", "ctt")
873+ ,("contestee-appellee", "cte")
874+ ,("contractor", "ctr")
875+ ,("contributor", "ctb")
876+ ,("copyright claimant", "cpc")
877+ ,("copyright holder", "cph")
878+ ,("corrector", "crr")
879+ ,("correspondent", "crp")
880+ ,("costume designer", "cst")
881+ ,("court governed", "cou")
882+ ,("court reporter", "crt")
883+ ,("cover designer", "cov")
884+ ,("creator", "cre")
885+ ,("curator", "cur")
886+ ,("dancer", "dnc")
887+ ,("data contributor", "dtc")
888+ ,("data manager", "dtm")
889+ ,("dedicatee", "dte")
890+ ,("dedicator", "dto")
891+ ,("defendant", "dfd")
892+ ,("defendant-appellant", "dft")
893+ ,("defendant-appellee", "dfe")
894+ ,("degree granting institution", "dgg")
895+ ,("delineator", "dln")
896+ ,("depicted", "dpc")
897+ ,("depositor", "dpt")
898+ ,("designer", "dsr")
899+ ,("director", "drt")
900+ ,("dissertant", "dis")
901+ ,("distribution place", "dbp")
902+ ,("distributor", "dst")
903+ ,("donor", "dnr")
904+ ,("draftsman", "drm")
905+ ,("dubious author", "dub")
906+ ,("editor", "edt")
907+ ,("editor of compilation", "edc")
908+ ,("editor of moving image work", "edm")
909+ ,("electrician", "elg")
910+ ,("electrotyper", "elt")
911+ ,("enacting jurisdiction", "enj")
912+ ,("engineer", "eng")
913+ ,("engraver", "egr")
914+ ,("etcher", "etr")
915+ ,("event place", "evp")
916+ ,("expert", "exp")
917+ ,("facsimilist", "fac")
918+ ,("field director", "fld")
919+ ,("film director", "fmd")
920+ ,("film distributor", "fds")
921+ ,("film editor", "flm")
922+ ,("film producer", "fmp")
923+ ,("filmmaker", "fmk")
924+ ,("first party", "fpy")
925+ ,("forger", "frg")
926+ ,("former owner", "fmo")
927+ ,("funder", "fnd")
928+ ,("geographic information specialist", "gis")
929+ ,("honoree", "hnr")
930+ ,("host", "hst")
931+ ,("host institution", "his")
932+ ,("illuminator", "ilu")
933+ ,("illustrator", "ill")
934+ ,("inscriber", "ins")
935+ ,("instrumentalist", "itr")
936+ ,("interviewee", "ive")
937+ ,("interviewer", "ivr")
938+ ,("inventor", "inv")
939+ ,("issuing body", "isb")
940+ ,("judge", "jud")
941+ ,("jurisdiction governed", "jug")
942+ ,("laboratory", "lbr")
943+ ,("laboratory director", "ldr")
944+ ,("landscape architect", "lsa")
945+ ,("lead", "led")
946+ ,("lender", "len")
947+ ,("libelant", "lil")
948+ ,("libelant-appellant", "lit")
949+ ,("libelant-appellee", "lie")
950+ ,("libelee", "lel")
951+ ,("libelee-appellant", "let")
952+ ,("libelee-appellee", "lee")
953+ ,("librettist", "lbt")
954+ ,("licensee", "lse")
955+ ,("licensor", "lso")
956+ ,("lighting designer", "lgd")
957+ ,("lithographer", "ltg")
958+ ,("lyricist", "lyr")
959+ ,("manufacture place", "mfp")
960+ ,("manufacturer", "mfr")
961+ ,("marbler", "mrb")
962+ ,("markup editor", "mrk")
963+ ,("metadata contact", "mdc")
964+ ,("metal-engraver", "mte")
965+ ,("moderator", "mod")
966+ ,("monitor", "mon")
967+ ,("music copyist", "mcp")
968+ ,("musical director", "msd")
969+ ,("musician", "mus")
970+ ,("narrator", "nrt")
971+ ,("onscreen presenter", "osp")
972+ ,("opponent", "opn")
973+ ,("organizer of meeting", "orm")
974+ ,("originator", "org")
975+ ,("other", "oth")
976+ ,("owner", "own")
977+ ,("panelist", "pan")
978+ ,("papermaker", "ppm")
979+ ,("patent applicant", "pta")
980+ ,("patent holder", "pth")
981+ ,("patron", "pat")
982+ ,("performer", "prf")
983+ ,("permitting agency", "pma")
984+ ,("photographer", "pht")
985+ ,("plaintiff", "ptf")
986+ ,("plaintiff-appellant", "ptt")
987+ ,("plaintiff-appellee", "pte")
988+ ,("platemaker", "plt")
989+ ,("praeses", "pra")
990+ ,("presenter", "pre")
991+ ,("printer", "prt")
992+ ,("printer of plates", "pop")
993+ ,("printmaker", "prm")
994+ ,("process contact", "prc")
995+ ,("producer", "pro")
996+ ,("production company", "prn")
997+ ,("production designer", "prs")
998+ ,("production manager", "pmn")
999+ ,("production personnel", "prd")
1000+ ,("production place", "prp")
1001+ ,("programmer", "prg")
1002+ ,("project director", "pdr")
1003+ ,("proofreader", "pfr")
1004+ ,("provider", "prv")
1005+ ,("publication place", "pup")
1006+ ,("publisher", "pbl")
1007+ ,("publishing director", "pbd")
1008+ ,("puppeteer", "ppt")
1009+ ,("radio director", "rdd")
1010+ ,("radio producer", "rpc")
1011+ ,("recording engineer", "rce")
1012+ ,("recordist", "rcd")
1013+ ,("redaktor", "red")
1014+ ,("renderer", "ren")
1015+ ,("reporter", "rpt")
1016+ ,("repository", "rps")
1017+ ,("research team head", "rth")
1018+ ,("research team member", "rtm")
1019+ ,("researcher", "res")
1020+ ,("respondent", "rsp")
1021+ ,("respondent-appellant", "rst")
1022+ ,("respondent-appellee", "rse")
1023+ ,("responsible party", "rpy")
1024+ ,("restager", "rsg")
1025+ ,("restorationist", "rsr")
1026+ ,("reviewer", "rev")
1027+ ,("rubricator", "rbr")
1028+ ,("scenarist", "sce")
1029+ ,("scientific advisor", "sad")
1030+ ,("screenwriter", "aus")
1031+ ,("scribe", "scr")
1032+ ,("sculptor", "scl")
1033+ ,("second party", "spy")
1034+ ,("secretary", "sec")
1035+ ,("seller", "sll")
1036+ ,("set designer", "std")
1037+ ,("setting", "stg")
1038+ ,("signer", "sgn")
1039+ ,("singer", "sng")
1040+ ,("sound designer", "sds")
1041+ ,("speaker", "spk")
1042+ ,("sponsor", "spn")
1043+ ,("stage director", "sgd")
1044+ ,("stage manager", "stm")
1045+ ,("standards body", "stn")
1046+ ,("stereotyper", "str")
1047+ ,("storyteller", "stl")
1048+ ,("supporting host", "sht")
1049+ ,("surveyor", "srv")
1050+ ,("teacher", "tch")
1051+ ,("technical director", "tcd")
1052+ ,("television director", "tld")
1053+ ,("television producer", "tlp")
1054+ ,("thesis advisor", "ths")
1055+ ,("transcriber", "trc")
1056+ ,("translator", "trl")
1057+ ,("type designer", "tyd")
1058+ ,("typographer", "tyg")
1059+ ,("university place", "uvp")
1060+ ,("videographer", "vdg")
1061+ ,("witness", "wit")
1062+ ,("wood engraver", "wde")
1063+ ,("woodcutter", "wdc")
1064+ ,("writer of accompanying material", "wam")
1065+ ,("writer of added commentary", "wac")
1066+ ,("writer of added lyrics", "wal")
1067+ ,("writer of added text", "wat")
1068+ ]
1069+