packages feed

jammittools 0.4.1.1 → 0.5

raw patch · 6 files changed

+313/−121 lines, 6 filesdep +textdep +xmldep −property-listdep ~jammittoolsPVP ok

version bump matches the API change (PVP)

Dependencies added: text, xml

Dependencies removed: property-list

Dependency ranges changed: jammittools

API changes (from Hackage documentation)

+ Sound.Jammit.Base: Beat :: Bool -> Bool -> Double -> Beat
+ Sound.Jammit.Base: ManySkills :: [(Instrument, Integer)] -> SkillLevel
+ Sound.Jammit.Base: OneSkill :: Integer -> SkillLevel
+ Sound.Jammit.Base: Section :: Integer -> Integer -> Integer -> Section
+ Sound.Jammit.Base: data Beat
+ Sound.Jammit.Base: data Section
+ Sound.Jammit.Base: data SkillLevel
+ Sound.Jammit.Base: findAudio :: Track -> FilePath -> IO (Maybe FilePath)
+ Sound.Jammit.Base: findNotation :: Track -> FilePath -> IO [FilePath]
+ Sound.Jammit.Base: findTab :: Track -> FilePath -> IO [FilePath]
+ Sound.Jammit.Base: instance Eq Beat
+ Sound.Jammit.Base: instance Eq Section
+ Sound.Jammit.Base: instance Eq SkillLevel
+ Sound.Jammit.Base: instance Ord Beat
+ Sound.Jammit.Base: instance Ord Section
+ Sound.Jammit.Base: instance Ord SkillLevel
+ Sound.Jammit.Base: instance PropertyListItem Beat
+ Sound.Jammit.Base: instance PropertyListItem Instrument
+ Sound.Jammit.Base: instance PropertyListItem Section
+ Sound.Jammit.Base: instance PropertyListItem SkillLevel
+ Sound.Jammit.Base: instance Read Beat
+ Sound.Jammit.Base: instance Read Section
+ Sound.Jammit.Base: instance Read SkillLevel
+ Sound.Jammit.Base: instance Show Beat
+ Sound.Jammit.Base: instance Show Section
+ Sound.Jammit.Base: instance Show SkillLevel
+ Sound.Jammit.Base: isDownbeat :: Beat -> Bool
+ Sound.Jammit.Base: isGhostBeat :: Beat -> Bool
+ Sound.Jammit.Base: loadBeats :: FilePath -> IO (Maybe [Beat])
+ Sound.Jammit.Base: loadGhost :: FilePath -> IO (Maybe [Beat])
+ Sound.Jammit.Base: loadSections :: FilePath -> IO (Maybe [Section])
+ Sound.Jammit.Base: position :: Beat -> Double
+ Sound.Jammit.Base: sectionBeat :: Section -> Integer
+ Sound.Jammit.Base: sectionNumber :: Section -> Integer
+ Sound.Jammit.Base: sectionType :: Section -> Integer
+ Sound.Jammit.Base: sheetHeight :: Num a => a
+ Sound.Jammit.Base: sheetWidth :: Num a => a
- Sound.Jammit.Base: Info :: String -> String -> String -> String -> Integer -> String -> Bool -> Bool -> String -> Instrument -> String -> Either Integer [(Instrument, Integer)] -> String -> Double -> String -> Integer -> String -> Info
+ Sound.Jammit.Base: Info :: String -> String -> String -> String -> Integer -> String -> Bool -> Bool -> String -> Instrument -> String -> SkillLevel -> String -> Double -> String -> Integer -> String -> Info
- Sound.Jammit.Base: skillLevel :: Info -> Either Integer [(Instrument, Integer)]
+ Sound.Jammit.Base: skillLevel :: Info -> SkillLevel

Files

Main.hs view
@@ -127,7 +127,7 @@  getPageLines :: Integer -> Args -> Int getPageLines systemHeight args = let-  pageHeight   = 724 / 8.5 * 11 :: Double+  pageHeight   = sheetWidth / 8.5 * 11 :: Double   defaultLines = round $ pageHeight / fromIntegral systemHeight   in max 1 $ fromMaybe defaultLines $ pageLines args 
README.md view
@@ -6,6 +6,8 @@ It should go without saying, but please do not distribute content from songs you have purchased -- it is for your use only! +Download the latest Windows/Mac binaries from the [releases page](https://github.com/mtolly/jammittools/releases).+ [Jammit]: https://www.jammit.com/  ## Usage
jammittools.cabal view
@@ -1,5 +1,5 @@ Name:               jammittools-Version:            0.4.1.1+Version:            0.5 Synopsis:           Export sheet music and audio from Windows/Mac app Jammit Description: @@ -30,10 +30,12 @@     Sound.Jammit.Internal.Audio     Sound.Jammit.Internal.Image     Sound.Jammit.Internal.TempIO+    Sound.Jammit.Internal.PropertyList   hs-source-dirs:   src   build-depends:     base            >= 4.6.0.1 && < 5-    , property-list >= 0.1.0.4 && < 0.2+    , xml           >= 1.3.14  && < 1.4+    , text          >= 1.2.1.1 && < 1.3     , directory     >= 1.2.0.1 && < 1.3     , filepath      >= 1.3.0.1 && < 1.5     , containers    >= 0.5.0.0 && < 0.6@@ -59,7 +61,7 @@     , directory     >= 1.2.0.1 && < 1.3     , filepath      >= 1.3.0.1 && < 1.5     , boxes         >= 0.1.3   && < 0.2-    , jammittools   == 0.4.1.1+    , jammittools   == 0.5   ghc-options:      -Wall -O2  source-repository head
src/Sound/Jammit/Base.hs view
@@ -2,6 +2,7 @@ Basic types and functions for dealing with Jammit song packages. -} {-# LANGUAGE CPP #-}+{-# LANGUAGE RecordWildCards #-} module Sound.Jammit.Base ( Instrument(..) , Part(..)@@ -11,44 +12,49 @@ , titleToAudioPart , partToInstrument , audioPartToInstrument-, Info(..)-, Track(..)-, loadInfo-, loadTracks+, Info(..), loadInfo+, Track(..), loadTracks+, SkillLevel(..) , findJammitDir , songSubdirs+, Beat(..), loadBeats, loadGhost+, Section(..), loadSections+, findNotation, findTab, findAudio+, sheetWidth, sheetHeight ) where  #if !MIN_VERSION_base(4,8,0)-import Control.Applicative ((<$>), (<*>))+import Control.Applicative ((<$>)) #endif-import Control.Applicative (liftA2)-import Control.Arrow ((***))-import Control.Exception (evaluate)-import Control.Monad (filterM, guard)-import Data.Char (toLower, toUpper)-import Data.Maybe (catMaybes)+import Control.Applicative ((<|>))+import Control.Monad (filterM, guard, forM)+import Data.Char (toLower) import System.Environment (lookupEnv)-import Text.Read (readMaybe)- import qualified Data.Map as Map-import qualified Data.PropertyList as PL import qualified System.Directory as Dir import System.FilePath ((</>)) import qualified System.Info as Info --- | The Enum instance corresponds to the number used in the "instrument"--- property, and the names (used by Show/Read) are capitalized versions of those--- used in the "skillLevel" property.+import Sound.Jammit.Internal.PropertyList++-- | The 'Enum' instance corresponds to the number used in the 'instrument'+-- property, and the names (used by 'Show' and 'Read') are capitalized versions+-- of those used in the 'skillLevel' property. data Instrument = Guitar | Bass | Drums | Keyboard | Vocal   deriving (Eq, Ord, Show, Read, Enum, Bounded) +-- | Used for both the "instrument" and "skillLevel" properties.+instance PropertyListItem Instrument where+  fromPropertyList pl = plistToEnum pl <|> do+    str <- fromPropertyList pl+    lookup str [ (map toLower $ show inst, inst) | inst <- [minBound .. maxBound] ]+ data Part   = PartGuitar1 -- ^ Used for both Guitar and Guitar 1   | PartGuitar2   | PartBass   | PartDrums1 -- ^ Used for both Drums and Drums 1-  | PartDrums2 -- ^ Rarely used. Seen in \"Space Truckin'\"+  | PartDrums2 -- ^ Rarely used. Seen in \"Space Truckin\'\"   | PartKeys1 -- ^ Used for both Keys and Keys 1   | PartKeys2   | PartPiano -- ^ Rarely used. Seen in \"The Answer Lies Within\" and \"Wait for Sleep\"@@ -59,13 +65,13 @@   deriving (Eq, Ord, Show, Read, Enum, Bounded)  data AudioPart-  = Only Part -- ^ An audio file for a single notated part.+  = Only    Part       -- ^ An audio file for a single notated part.   | Without Instrument -- ^ The backing track for an instrument package.   deriving (Eq, Ord, Show, Read)  data SheetPart   = Notation Part -- ^ For any instrument, the notation sheet music.-  | Tab Part -- ^ For guitar and bass, the tablature sheet music.+  | Tab      Part -- ^ For guitar and bass, the tablature sheet music.   deriving (Eq, Ord, Show, Read)  titleToPart :: String -> Maybe Part@@ -110,6 +116,22 @@ audioPartToInstrument (Only    p) = partToInstrument p audioPartToInstrument (Without i) = i +data SkillLevel+  = OneSkill   Integer+  | ManySkills [(Instrument, Integer)]+  deriving (Eq, Ord, Show, Read)++-- | Can be an integer, or a dict of instrument names to integers.+instance PropertyListItem SkillLevel where+  fromPropertyList pl+    =   do+      OneSkill <$> fromPropertyList pl+    <|> do+      dict <- fromPropertyList pl+      fmap ManySkills $ forM (Map.toList dict) $ \(k, v) -> do+        inst <- fromPropertyList $ String k+        return (inst, v)+ data Info = Info   { album        :: String   , artist       :: String@@ -122,7 +144,7 @@   , genre        :: String   , instrument   :: Instrument   , publishedBy  :: String-  , skillLevel   :: Either Integer [(Instrument, Integer)]+  , skillLevel   :: SkillLevel   , sku          :: String   , slow         :: Double   , title        :: String@@ -130,73 +152,30 @@   , writtenBy    :: String   } deriving (Eq, Ord, Show, Read) -instance PL.PropertyListItem Info where--  fromPropertyList pl = PL.fromPlDict pl >>= \dict -> Info-    <$> (Map.lookup "album"        dict >>= PL.fromPlString)-    <*> (Map.lookup "artist"       dict >>= PL.fromPlString)-    <*> (Map.lookup "bpm"          dict >>= PL.fromPlString)-    <*> (Map.lookup "copyright"    dict >>= PL.fromPlString)-    <*> (Map.lookup "countInBeats" dict >>= PL.fromPlInt   )-    <*> (Map.lookup "courtesyOf"   dict >>= PL.fromPlString)-    <*> (Map.lookup "demo"         dict >>=    fromPlEnum  )-    <*> (Map.lookup "explicit"     dict >>=    fromPlEnum  )-    <*> (Map.lookup "genre"        dict >>= PL.fromPlString)-    <*> (Map.lookup "instrument"   dict >>=    fromPlEnum  )-    <*> (Map.lookup "publishedBy"  dict >>= PL.fromPlString)-    <*> (Map.lookup "skillLevel"   dict >>=    fromPlSkills)-    <*> (Map.lookup "sku"          dict >>= PL.fromPlString)-    <*> (Map.lookup "slow"         dict >>= PL.fromPlReal  )-    <*> (Map.lookup "title"        dict >>= PL.fromPlString)-    <*> (Map.lookup "version"      dict >>= PL.fromPlInt   )-    <*> (Map.lookup "writtenBy"    dict >>= PL.fromPlString)--  toPropertyList info = PL.plDict $ Map.fromList-    [ ("album"       , PL.plString $ album        info)-    , ("artist"      , PL.plString $ artist       info)-    , ("bpm"         , PL.plString $ bpm          info)-    , ("copyright"   , PL.plString $ copyright    info)-    , ("countInBeats", PL.plInt    $ countInBeats info)-    , ("courtesyOf"  , PL.plString $ courtesyOf   info)-    , ("demo"        ,    plEnum   $ demo         info)-    , ("explicit"    ,    plEnum   $ explicit     info)-    , ("genre"       , PL.plString $ genre        info)-    , ("instrument"  ,    plEnum   $ instrument   info)-    , ("publishedBy" , PL.plString $ publishedBy  info)-    , ("skillLevel"  ,    plSkills $ skillLevel   info)-    , ("sku"         , PL.plString $ sku          info)-    , ("slow"        , PL.plReal   $ slow         info)-    , ("title"       , PL.plString $ title        info)-    , ("version"     , PL.plInt    $ version      info)-    , ("writtenBy"   , PL.plString $ writtenBy    info)-    ]--fromPlEnum :: (Enum a) => PL.PropertyList -> Maybe a-fromPlEnum pl = toEnum . fromIntegral <$> PL.fromPlInt pl--plEnum :: (Enum a) => a -> PL.PropertyList-plEnum = PL.plInt . fromIntegral . fromEnum--fromPlSkills-  :: PL.PropertyList -> Maybe (Either Integer [(Instrument, Integer)])-fromPlSkills pl = case (PL.fromPlInt pl, PL.fromPlDict pl) of-  (Nothing, Nothing) -> Nothing-  (Just i , _      ) -> Just $ Left i-  (_      , Just d ) -> let-    getSkill :: (String, PL.PropertyList) -> Maybe (Instrument, Integer)-    getSkill (x, y) = liftA2 (,) (readMaybe $ capitalize x) (PL.fromPlInt y)-    capitalize ""     = ""-    capitalize (c:cs) = toUpper c : map toLower cs-    in fmap Right $ mapM getSkill $ Map.toList d--plSkills :: Either Integer [(Instrument, Integer)] -> PL.PropertyList-plSkills (Left  i ) = PL.plInt i-plSkills (Right sl) = PL.plDict $-  Map.fromList $ map (map toLower . show *** PL.plInt) sl+instance PropertyListItem Info where+  fromPropertyList pl = do+    dict <- fromPropertyList pl+    album        <- fromLookup "album"        dict+    artist       <- fromLookup "artist"       dict+    bpm          <- fromLookup "bpm"          dict+    copyright    <- fromLookup "copyright"    dict+    countInBeats <- fromLookup "countInBeats" dict+    courtesyOf   <- fromLookup "courtesyOf"   dict+    demo         <- fromLookup "demo"         dict+    explicit     <- fromLookup "explicit"     dict+    genre        <- fromLookup "genre"        dict+    instrument   <- fromLookup "instrument"   dict+    publishedBy  <- fromLookup "publishedBy"  dict+    skillLevel   <- fromLookup "skillLevel"   dict+    sku          <- fromLookup "sku"          dict+    slow         <- fromLookup "slow"         dict+    title        <- fromLookup "title"        dict+    version      <- fromLookup "version"      dict+    writtenBy    <- fromLookup "writtenBy"    dict+    return Info{..}  loadInfo :: FilePath -> IO (Maybe Info)-loadInfo dir =-  PL.fromPropertyList <$> readXmlPropertyListFromFile' (dir </> "info.plist")+loadInfo dir = fromPropertyList <$> readPropertyList (dir </> "info.plist")  data Track = Track   { trackClass          :: String@@ -206,33 +185,18 @@   , trackTitle          :: Maybe String   } deriving (Eq, Ord, Show, Read) -instance PL.PropertyListItem Track where--  toPropertyList t = PL.plDict $ Map.fromList $ catMaybes-    [ Just ("class"              , PL.plString $ trackClass          t)-    , Just ("identifier"         , PL.plString $ identifier          t)-    , (\i -> ("scoreSystemHeight"  , PL.plInt    i)) <$> scoreSystemHeight   t-    , (\i -> ("scoreSystemInterval", PL.plInt    i)) <$> scoreSystemInterval t-    , (\s -> ("title"              , PL.plString s)) <$> trackTitle          t-    ]--  fromPropertyList pl = PL.fromPlDict pl >>= \d -> Track-    <$>      (Map.lookup "class"               d >>= PL.fromPlString)-    <*>      (Map.lookup "identifier"          d >>= PL.fromPlString)-    <*> Just (Map.lookup "scoreSystemHeight"   d >>= PL.fromPlInt   )-    <*> Just (Map.lookup "scoreSystemInterval" d >>= PL.fromPlInt   )-    <*> Just (Map.lookup "title"               d >>= PL.fromPlString)+instance PropertyListItem Track where+  fromPropertyList pl = do+    dict <- fromPropertyList pl+    trackClass             <- fromLookup "class"               dict+    identifier             <- fromLookup "identifier"          dict+    let scoreSystemHeight   = fromLookup "scoreSystemHeight"   dict+        scoreSystemInterval = fromLookup "scoreSystemInterval" dict+        trackTitle          = fromLookup "title"               dict+    return Track{..}  loadTracks :: FilePath -> IO (Maybe [Track])-loadTracks dir = PL.listFromPropertyList <$>-  readXmlPropertyListFromFile' (dir </> "tracks.plist")---- | Reads strictly so as not to exhaust our allowed open files.-readXmlPropertyListFromFile' :: FilePath -> IO PL.PropertyList-readXmlPropertyListFromFile' f = do-  str <- readFile f-  _ <- evaluate $ length str-  either fail return $ PL.readXmlPropertyList str+loadTracks dir = fromPropertyList <$> readPropertyList (dir </> "tracks.plist")  -- | Tries to find the top-level Jammit library directory on Windows or -- Mac OS X.@@ -266,3 +230,82 @@   let here = [dir | isSong]   subdirs <- lsAbsolute dir >>= filterM Dir.doesDirectoryExist   (here ++) . concat <$> mapM songSubdirs subdirs++data Beat = Beat+  { isDownbeat  :: Bool+  , isGhostBeat :: Bool+  , position    :: Double+  } deriving (Eq, Ord, Show, Read)++instance PropertyListItem Beat where+  fromPropertyList pl = do+    dict <- fromPropertyList pl+    isDownbeat  <- fromLookup "isDownbeat"  dict+    isGhostBeat <- fromLookup "isGhostBeat" dict+    position    <- fromLookup "position"    dict+    return Beat{..}++loadBeats :: FilePath -> IO (Maybe [Beat])+loadBeats dir = fromPropertyList <$> readPropertyList (dir </> "beats.plist")++loadGhost :: FilePath -> IO (Maybe [Beat])+loadGhost dir = fromPropertyList <$> readPropertyList (dir </> "ghost.plist")++data Section = Section+  { sectionBeat   :: Integer+  , sectionNumber :: Integer+  , sectionType   :: Integer+  } deriving (Eq, Ord, Show, Read)++instance PropertyListItem Section where+  fromPropertyList pl = do+    dict <- fromPropertyList pl+    sectionBeat   <- fromLookup "beat"   dict+    sectionNumber <- fromLookup "number" dict+    sectionType   <- fromLookup "type"   dict+    return Section{..}++loadSections :: FilePath -> IO (Maybe [Section])+loadSections dir = fromPropertyList <$> readPropertyList (dir </> "sections.plist")++{-+Known section types+=== in Erotomania ===+0 pre-song+1 intro+2 verse+4 chorus+7 outro+9 post-song+13 b-section+17 interlude+23 c-section+25 guitar solo+36 transition+-}++findImages :: String -> Track -> FilePath -> IO [FilePath]+findImages suffix trk dir = do+  files <- Dir.getDirectoryContents dir+  let image i = identifier trk ++ "_" ++ suffix ++ "_" ++ showTwo i+      showTwo i = if i < 10 then '0' : show i else show i+  return+    [ dir </> file+    | file <- map image ([0..99] :: [Int])+    , file `elem` files+    ]++findNotation, findTab :: Track -> FilePath -> IO [FilePath]+findNotation = findImages "jcfn"+findTab      = findImages "jcft"++findAudio :: Track -> FilePath -> IO (Maybe FilePath)+findAudio trk dir = let+  file = dir </> identifier trk ++ "_jcfx"+  in do+    b <- Dir.doesFileExist file+    return $ guard b >> Just file++sheetWidth, sheetHeight :: (Num a) => a+sheetWidth  = 724+sheetHeight = 1024
src/Sound/Jammit/Internal/Image.hs view
@@ -90,12 +90,21 @@     else go is x $ y - P.imageHeight i  vertSplit :: Int -> P.Image P.PixelRGBA8 -> [P.Image P.PixelRGBA8]-vertSplit h img = map f $ takeWhile (< P.imageHeight img) [0, h ..] where-  f yoff = P.generateImage-    (g yoff)-    (P.imageWidth img)-    (min h $ P.imageHeight img - yoff)-  g yoff x y = P.pixelAt img x $ y + yoff+vertSplit h img = if P.imageHeight img <= h+  then [img]+  else let+    chunkSize = P.pixelBaseIndex img 0 h+    first = P.Image+      { P.imageWidth  = P.imageWidth img+      , P.imageHeight = h+      , P.imageData   = V.take chunkSize $ P.imageData img+      }+    rest = P.Image+      { P.imageWidth  = P.imageWidth img+      , P.imageHeight = P.imageHeight img - h+      , P.imageData   = V.drop chunkSize $ P.imageData img+      }+    in first : vertSplit h rest  imagePage :: PDF.JpegFile -> PDF.PDF () imagePage jpeg = do
+ src/Sound/Jammit/Internal/PropertyList.hs view
@@ -0,0 +1,136 @@+-- | A very simple, non-robust property list parser.+{-# LANGUAGE CPP #-}+{-# LANGUAGE LambdaCase #-}+{-# LANGUAGE RecordWildCards #-}+{-# LANGUAGE FlexibleInstances #-}+module Sound.Jammit.Internal.PropertyList+( readPropertyList+, PropertyList(..)+, PropertyListItem(..)+, plistToEnum+, fromLookup+) where++import qualified Data.Text.IO as TIO+import Text.XML.Light+import qualified Data.Map as Map+import Text.Read (readMaybe)+import Data.Maybe (mapMaybe, fromJust)+import Data.Char (isSpace)+import Control.Monad (guard)++#if !MIN_VERSION_base(4,8,0)+import Prelude hiding (mapM)+import Data.Traversable (mapM)+import Control.Applicative ((<$>))+#endif++data PropertyList+  = String String+  | Real Double+  | Integer Integer+  | Bool Bool+  | Array [PropertyList]+  | Dict (Map.Map String PropertyList)+  -- not supported: date or data+  deriving (Eq, Ord, Show, Read)++asParent :: Element -> (String, [Element])+asParent Element{ elName = QName{..}, .. } = (qName, getElements elContent)++getElements :: [Content] -> [Element]+getElements = mapMaybe $ \case+  Elem e -> Just e+  _      -> Nothing++asChild :: Element -> Maybe (String, String)+asChild Element{ elName = QName{..}, .. } = case elContent of+  [Text CData{..}] -> Just (qName, cdData)+  _                -> Nothing++plist :: Element -> Maybe PropertyList+plist e = case asParent e of+  ("plist", [x]) -> value x+  _              -> Nothing++value :: Element -> Maybe PropertyList+value e = case asParent e of+  ("array", elts) -> Array <$> mapM value elts+  ("dict" , elts) -> Dict . Map.fromList <$> go elts where+    go (x : y : xs) = do+      ("key", k) <- asChild x+      v <- value y+      ((k, v) :) <$> go xs+    go [] = Just []+    go _  = Nothing+  ("true" , []) -> Just $ Bool True+  ("false", []) -> Just $ Bool False+  _ -> asChild e >>= \case+    ("string" , s) -> Just $ String $ trim s+    ("real"   , s) -> Real    <$> readMaybe s+    ("integer", s) -> Integer <$> readMaybe s+    _              -> Nothing+    where trim = reverse . dropWhile isSpace . reverse . dropWhile isSpace++-- | Reads strictly so as not to exhaust our allowed open files.+readPropertyList :: FilePath -> IO PropertyList+readPropertyList f = do+  txt <- TIO.readFile f+  case parseXMLDoc txt >>= plist of+    Nothing -> error $+      "readPropertyList: failed to read property list from " ++ f+    Just pl -> return pl++-- | Only covers parsing values from property lists.+class PropertyListItem a where+  fromPropertyList :: PropertyList -> Maybe a+  listFromPropertyList :: PropertyList -> Maybe [a]+  listFromPropertyList (Array xs) = mapM fromPropertyList xs+  listFromPropertyList _          = Nothing++instance PropertyListItem PropertyList where+  fromPropertyList = Just++instance PropertyListItem Char where+  fromPropertyList (String [c]) = Just c+  fromPropertyList _            = Nothing+  listFromPropertyList (String s) = Just s+  listFromPropertyList _          = Nothing++instance (PropertyListItem a) => PropertyListItem [a] where+  fromPropertyList = listFromPropertyList++instance PropertyListItem Int where+  fromPropertyList (Integer i) = Just $ fromIntegral i+  fromPropertyList _           = Nothing++instance PropertyListItem Integer where+  fromPropertyList (Integer i) = Just i+  fromPropertyList _           = Nothing++instance PropertyListItem Double where+  fromPropertyList (Real d) = Just d+  fromPropertyList _        = Nothing++instance (PropertyListItem a) => PropertyListItem (Map.Map String a) where+  fromPropertyList (Dict d) = mapM fromPropertyList d+  fromPropertyList _        = Nothing++instance PropertyListItem Bool where+  fromPropertyList (Bool    b) = Just b+  fromPropertyList (Integer 0) = Just False+  fromPropertyList (Integer 1) = Just True+  fromPropertyList _           = Nothing++plistToEnum :: (Enum a, Bounded a) => PropertyList -> Maybe a+plistToEnum pl = let+  minval = fromEnum $ minBound `asTypeOf` fromJust result+  maxval = fromEnum $ maxBound `asTypeOf` fromJust result+  result = do+    n <- fromPropertyList pl+    guard $ minval <= n && n <= maxval+    return $ toEnum n+  in result++fromLookup :: (PropertyListItem a) => String -> Map.Map String PropertyList -> Maybe a+fromLookup s dict = Map.lookup s dict >>= fromPropertyList