{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Text.ICalendar.Parser.Components where
import Control.Applicative
import Control.Arrow ((&&&))
import Control.Monad.Error hiding (mapM)
import Control.Monad.RWS (MonadState (get), tell)
import qualified Data.CaseInsensitive as CI
import qualified Data.Foldable as F
import Data.List (partition)
import qualified Data.Map as M
import Data.Maybe
import Data.Set (Set)
import qualified Data.Set as S
import Text.ICalendar.Parser.Common
import Text.ICalendar.Parser.Properties
import Text.ICalendar.Types
parseVCalendar :: Content -> ContentParser VCalendar
parseVCalendar c@(Component _ "VCALENDAR" _) = down c $ do
vcProdId <- reqLine1 "PRODID" (parseSimple ProdId)
vcVersion <- reqLine1 "VERSION" parseVersion
vcScale <- optLine1 "CALSCALE" (parseSimpleI Scale)
vcMethod <- optLine1 "METHOD" (parseSimpleI ((Just .) . Method))
vcTimeZones <- f (tzidValue . vtzId) =<< optCompN "VTIMEZONE" parseVTimeZone
vcEvents <- f (uidValue . veUID &&& recur . veRecurId)
=<< optCompN "VEVENT" (parseVEvent vcMethod)
vcTodos <- f (uidValue . vtUID &&& recur . vtRecurId)
=<< optCompN "VTODO" parseVTodo
vcJournals <- f (uidValue . vjUID &&& recur . vjRecurId)
=<< optCompN "VJOURNAL" parseVJournal
vcFreeBusys <- f (uidValue . vfbUID) =<< optCompN "VFREEBUSY" parseVFreeBusy
vcOtherComps <- otherComponents
vcOther <- otherProperties
return VCalendar {..}
where recur :: Maybe RecurrenceId -> Maybe (Either Date DateTime)
recur Nothing = Nothing
recur (Just (RecurrenceIdDate x _ _)) = Just (Left x)
recur (Just (RecurrenceIdDateTime x _ _)) = Just (Right x)
f :: Ord b => (a -> b) -> Set a -> ContentParser (M.Map b a)
f g = F.foldlM h M.empty
where h m e = let k = g e
in if k `M.member` m
then throwError "Duplicate UID/RecurId/TZID."
else return $ M.insert k e m
parseVCalendar _ = throwError "parseVCalendar: Content given not a VCALENDAR\
\ component."
parseVEvent :: Maybe Method -> Content -> ContentParser VEvent
parseVEvent mmethod (Component _ "VEVENT" _) = do
veDTStamp <- reqLine1 "DTSTAMP" $ parseSimpleUTC DTStamp
veUID <- reqLine1 "UID" $ parseSimple UID
veDTStart <- optLine1 "DTSTART" $
Just .: parseSimpleDateOrDateTime DTStartDateTime DTStartDate
when (isNothing mmethod && isNothing veDTStart) $
throwError "A VEVENT in a VCALENDAR without a METHOD requires a \
\DTSTART property."
veClass <- optLine1 "CLASS" parseClass
veCreated <- optLine1 "CREATED" (Just .: parseCreated)
veDescription <- optLine1 "DESCRIPTION" .
parseAltRepLang $ (((Just .) .) .) . Description
veGeo <- optLine1 "GEO" (Just .: parseGeo)
veLastMod <- optLine1 "LAST-MODIFIED" (Just .: parseLastModified)
veLocation <- optLine1 "LOCATION" .
parseAltRepLang $ (((Just .) .) .) . Location
veOrganizer <- optLine1 "ORGANIZER" (Just .: parseOrganizer)
vePriority <- optLine1 "PRIORITY" (parseSimpleRead Priority)
veSeq <- optLine1 "SEQUENCE" (parseSimpleRead Sequence)
veStatus <- optLine1 "STATUS" (Just .: parseEventStatus)
veSummary <- optLine1 "SUMMARY" .
parseAltRepLang $ (((Just .) .) .) . Summary
veTransp <- optLine1 "TRANSP" parseTransp
veUrl <- optLine1 "URL" (Just .: parseSimpleURI URL)
veRecurId <- optLine1 "RECURRENCE-ID"
$ Just .: parseRecurId veDTStart
veRRule <- optLineN "RRULE" $ parseRRule veDTStart
when (S.size veRRule > 1) $ tell ["SHOULD NOT have multiple RRules."]
veDTEndDuration <- parseXDurationOpt "DTEND" DTEndDateTime DTEndDate
veDTStart
veAttach <- optLineN "ATTACH" parseAttachment
veAttendee <- optLineN "ATTENDEE" parseAttendee
veCategories <- optLineN "CATEGORIES" parseCategories
veComment <- optLineN "COMMENT" $ parseAltRepLang Comment
veContact <- optLineN "CONTACT" $ parseAltRepLang Contact
veExDate <- optLineN "EXDATE" parseExDate
veRStatus <- optLineN "REQUEST-STATUS" parseRequestStatus
veRelated <- optLineN "RELATED-TO" parseRelatedTo
veResources <- optLineN "RESOURCES" $ parseAltRepLangN Resources
veRDate <- optLineN "RDATE" parseRDate
veAlarms <- optCompN "VALARM" parseVAlarm
veOther <- otherProperties
return VEvent {..}
parseVEvent _ x = throwError $ "parseVEvent: " ++ show x
parseVTodo :: Content -> ContentParser VTodo
parseVTodo (Component _ "VTODO" _) = do
vtDTStamp <- reqLine1 "DTSTAMP" $ parseSimpleUTC DTStamp
vtUID <- reqLine1 "UID" $ parseSimple UID
vtClass <- optLine1 "CLASS" parseClass
vtCompleted <- optLine1 "COMPLETED" . parseSimpleDateTime $
(Just .) . Completed
vtCreated <- optLine1 "CREATED" (Just .: parseCreated)
vtDTStart <- optLine1 "DTSTART" $
Just .: parseSimpleDateOrDateTime DTStartDateTime DTStartDate
vtDescription <- optLine1 "DESCRIPTION" .
parseAltRepLang $ (((Just .) .) .) . Description
vtGeo <- optLine1 "GEO" (Just .: parseGeo)
vtLastMod <- optLine1 "LAST-MODIFIED" (Just .: parseLastModified)
vtLocation <- optLine1 "LOCATION" .
parseAltRepLang $ (((Just .) .) .) . Location
vtOrganizer <- optLine1 "ORGANIZER" (Just .: parseOrganizer)
vtPercent <- optLine1 "PERCENT-COMPLETE" $ Just .: parseSimpleRead
PercentComplete
vtPriority <- optLine1 "PRIORITY" $ parseSimpleRead Priority
vtRecurId <- optLine1 "RECURRENCE-ID" (Just .: parseRecurId vtDTStart)
vtSeq <- optLine1 "SEQUENCE" $ parseSimpleRead Sequence
vtStatus <- optLine1 "STATUS" (Just .: parseTodoStatus)
vtSummary <- optLine1 "SUMMARY" .
parseAltRepLang $ (((Just .) .) .) . Summary
vtUrl <- optLine1 "URL" (Just .: parseSimpleURI URL)
vtRRule <- optLineN "RRULE" $ parseRRule vtDTStart
when (S.size vtRRule > 1) $ tell ["SHOULD NOT have multiple RRules."]
vtDueDuration <- parseXDurationOpt "DUE" DueDateTime DueDate vtDTStart
vtAttach <- optLineN "ATTACH" parseAttachment
vtAttendee <- optLineN "ATTENDEE" parseAttendee
vtCategories <- optLineN "CATEGORIES" parseCategories
vtComment <- optLineN "COMMENT" $ parseAltRepLang Comment
vtContact <- optLineN "CONTACT" $ parseAltRepLang Contact
vtExDate <- optLineN "EXDATE" parseExDate
vtRStatus <- optLineN "REQUEST-STATUS" parseRequestStatus
vtRelated <- optLineN "RELATED-TO" parseRelatedTo
vtResources <- optLineN "RESOURCES" $ parseAltRepLangN Resources
vtRDate <- optLineN "RDATE" parseRDate
vtAlarms <- optCompN "VALARM" parseVAlarm
vtOther <- otherProperties
return VTodo {..}
parseVTodo x = throwError $ "parseVTodo: " ++ show x
parseVTimeZone :: Content -> ContentParser VTimeZone
parseVTimeZone (Component _ "VTIMEZONE" _) = do
vtzId <- reqLine1 "TZID" parseTZID
vtzLastMod <- optLine1 "LAST-MODIFIED" (Just .: parseLastModified)
vtzUrl <- optLine1 "TZURL" (Just .: parseSimpleURI TZUrl)
vtzStandardC <- optCompN "STANDARD" parseTZProp
vtzDaylightC <- optCompN "DAYLIGHT" parseTZProp
when (S.size vtzStandardC + S.size vtzDaylightC < 1) .
throwError $ "VTIMEZONE must include at least one of the STANDARD or \
\DAYLIGHT components."
vtzOther <- otherProperties
return VTimeZone {..}
parseVTimeZone x = throwError $ "parseVTimeZone: " ++ show x
parseTZProp :: Content -> ContentParser TZProp
parseTZProp (Component _ n _) | n `elem` ["STANDARD", "DAYLIGHT"] = do
tzpDTStart <- reqLine1 "DTSTART" $
parseSimpleDateOrDateTime DTStartDateTime DTStartDate
tzpTZOffsetTo <- reqLine1 "TZOFFSETTO" parseUTCOffset
tzpTZOffsetFrom <- reqLine1 "TZOFFSETFROM" parseUTCOffset
tzpRRule <- optLineN "RRULE" (parseRRule $ Just tzpDTStart)
when (S.size tzpRRule > 1) $ tell ["SHOULD NOT have multiple RRules."]
tzpComment <- optLineN "COMMENT" (parseAltRepLang Comment)
tzpRDate <- optLineN "RDATE" parseRDate
tzpTZName <- optLineN "TZNAME" parseTZName
tzpOther <- otherProperties
return TZProp {..}
parseTZProp x = throwError $ "parseTZProp: " ++ show x
parseVAlarm :: Content -> ContentParser VAlarm
parseVAlarm (Component _ "VALARM" _) = do
(ao, a') <- reqLine1 "ACTION" (\(ContentLine _ _ o bs) -> return (o, bs))
a <- valueOnlyOne =<< parseText a'
vaTrigger <- reqLine1 "TRIGGER" parseTrigger
let vaActionOther = toO ao
case CI.mk a of
"AUDIO" -> do
(vaRepeat, vaDuration) <- repAndDur
vaAudioAttach <- optLine1 "ATTACH" $ Just .: parseAttachment
vaOther <- otherProperties
return VAlarmAudio {..}
"DISPLAY" -> do
(vaRepeat, vaDuration) <- repAndDur
vaDescription <- reqLine1 "DESCRIPTION" $
parseAltRepLang Description
vaOther <- otherProperties
return VAlarmDisplay {..}
"EMAIL" -> do
(vaRepeat, vaDuration) <- repAndDur
vaDescription <- reqLine1 "DESCRIPTION" $
parseAltRepLang Description
vaSummary <- reqLine1 "SUMMARY" $ parseAltRepLang Summary
vaAttendee <- reqLineN "ATTENDEE" parseAttendee
vaMailAttach <- optLineN "ATTACH" parseAttachment
vaOther <- otherProperties
return VAlarmEmail {..}
vaAction -> do vaOther <- otherProperties
return VAlarmX {..}
where repAndDur = do
rep <- optLine1 "REPEAT" $ parseSimpleRead Repeat
dur <- optLine1 "DURATION" $ Just .: parseDurationProp Nothing
when (repeatValue rep > 0 && isNothing dur) .
throwError $ "parseVAlarm: when REPEAT > 0, DURATION must \
\ be specified."
return (rep, dur)
parseVAlarm x = throwError $ "parseVAlarm: " ++ show x
parseVJournal :: Content -> ContentParser VJournal
parseVJournal (Component _ "VJOURNAL" _) = do
vjDTStamp <- reqLine1 "DTSTAMP" $ parseSimpleUTC DTStamp
vjUID <- reqLine1 "UID" $ parseSimple UID
vjClass <- optLine1 "CLASS" parseClass
vjCreated <- optLine1 "CREATED" (Just .: parseCreated)
vjDTStart <- optLine1 "DTSTART" $
Just .: parseSimpleDateOrDateTime DTStartDateTime DTStartDate
vjDescription <- optLineN "DESCRIPTION" $ parseAltRepLang Description
vjLastMod <- optLine1 "LAST-MODIFIED" (Just .: parseLastModified)
vjOrganizer <- optLine1 "ORGANIZER" (Just .: parseOrganizer)
vjRecurId <- optLine1 "RECURRENCE-ID" (Just .: parseRecurId vjDTStart)
vjSeq <- optLine1 "SEQUENCE" $ parseSimpleRead Sequence
vjStatus <- optLine1 "STATUS" (Just .: parseJournalStatus)
vjSummary <- optLine1 "SUMMARY" .
parseAltRepLang $ (((Just .) .) .) . Summary
vjUrl <- optLine1 "URL" (Just .: parseSimpleURI URL)
vjRRule <- optLineN "RRULE" $ parseRRule vjDTStart
when (S.size vjRRule > 1) $ tell ["SHOULD NOT have multiple RRules."]
vjAttach <- optLineN "ATTACH" parseAttachment
vjAttendee <- optLineN "ATTENDEE" parseAttendee
vjCategories <- optLineN "CATEGORIES" parseCategories
vjComment <- optLineN "COMMENT" $ parseAltRepLang Comment
vjContact <- optLineN "CONTACT" $ parseAltRepLang Contact
vjExDate <- optLineN "EXDATE" parseExDate
vjRStatus <- optLineN "REQUEST-STATUS" parseRequestStatus
vjRelated <- optLineN "RELATED-TO" parseRelatedTo
vjRDate <- optLineN "RDATE" parseRDate
vjOther <- otherProperties
return VJournal {..}
parseVJournal x = throwError $ "parseVJournal: " ++ show x
parseVFreeBusy :: Content -> ContentParser VFreeBusy
parseVFreeBusy (Component _ "VFreeBusy" _) = do
vfbDTStamp <- reqLine1 "DTSTAMP" $ parseSimpleUTC DTStamp
vfbUID <- reqLine1 "UID" $ parseSimple UID
vfbContact <- optLine1 "CONTACT" $ Just .: parseAltRepLang Contact
vfbDTStart <- optLine1 "DTSTART" $
Just .: parseSimpleDateOrDateTime DTStartDateTime DTStartDate
vfbDTEnd <- optLine1 "DTEND" $ Just .: parseSimpleDateOrDateTime
DTEndDateTime DTEndDate
vfbOrganizer <- optLine1 "ORGANIZER" $ Just .: parseOrganizer
vfbAttendee <- optLineN "ATTENDEE" parseAttendee
vfbComment <- optLineN "COMMENT" $ parseAltRepLang Comment
vfbRStatus <- optLineN "REQUEST-STATUS" parseRequestStatus
vfbUrl <- optLine1 "URL" (Just .: parseSimpleURI URL)
vfbFreeBusy <- optLineN "FREEBUSY" parseFreeBusy
vfbOther <- otherProperties
return VFreeBusy {..}
parseVFreeBusy x = throwError $ "parseVFreeBusy: " ++ show x
otherComponents :: ContentParser (Set VOther)
otherComponents = optN parseVOther . partition isComponent =<< snd <$> get
parseVOther :: Content -> ContentParser VOther
parseVOther (Component _ voName _) = do
voProps <- otherProperties
return VOther {..}
parseVOther x = throwError $ "parseVOther: "++ show x