I'm attaching dtmconv.hs.

With this, you should be able to use the command in the example to
duplicate.

-- John

-- arch-tag: DTM conversion program

{-

TODO: categories
CHECK: can rid be eliminated? (palm uses it, so it doesn't seem to harm 
anything)

   Copyright (c) 2005 John Goerzen

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA

-}

import Text.XML.HaXml
import System.Posix.Time(epochTime)
import System.Time
import Text.Regex
import Data.List
import System.IO.Unsafe

-- Get an attribute value from an element.

attrofelem :: String -> Content -> AttValue
attrofelem attrname (CElem (Elem name al _)) =
    case lookup attrname al of
       Just x -> x
       Nothing -> error $ "attrofelem: no " ++ attrname ++ " in " ++ name
attrofelem _ _ =
    error "attrofelem: called on something other than a CElem"

--Render an attribute value as a string.

showattv :: AttValue -> String
showattv (AttValue v) = worker v
                        where worker [] = []
                              worker (Left x:xs) = x ++ worker xs
                              worker (Right x:xs) = worker xs

-- Parse stdin.

parse :: IO Content
parse =
    do c <- getContents
       return $ getContent $ xmlParse "(stdin)" c
    where getContent (Document _ _ e) = CElem e
       
-- Render a Cnotent.

xml2str :: [Content] -> String
xml2str =
    render . ppContent
    where 
        ppContent [CElem e] = element e
        ppContent []  = error "produced no output"
        ppContent _   = error "produced more than one output"

-- Split a date.  Returns Just (date, time) or Nothing if the input
-- was NULL or otherwise unparsable.
splitdate :: String -> Maybe (String, String)
splitdate x = 
    case break (== 'T') x of
      (_, "") -> Nothing
      (date, time) -> Just (date, tail time)

-- Convert a tag to calendar time.
tag2ct :: String -> Content -> Maybe CalendarTime
tag2ct x y = date2ct $ strof x y

-- Convert CT to epoch time.
ct2epoch :: CalendarTime -> Maybe Integer
ct2epoch ct = 
    if ctYear ct < 1971 || ctHour ct > 24 then Nothing
       else case toClockTime ct of
                 TOD x _ -> Just x

-- Convert a date to a generic calendar time object.
-- Direct conversion.  Must adjust tz in calendar time object if necessary.
date2ct :: String -> Maybe CalendarTime
date2ct d =
    case matchRegexAll dregex d of
      Just (_, _, _, [year, month, day, hour, min, sec]) ->
          Just $ CalendarTime
                   {ctYear = read year,
                    ctMonth = toEnum ((read month) - 1),
                    ctDay = read day,
                    ctHour = read hour,
                    ctMin = read min,
                    ctSec = read sec,
                    ctPicosec = 0,
                    ctWDay = Sunday,
                    ctYDay = 0,
                    ctTZName = "",
                    ctTZ = 0,
                    ctIsDST = False}
      Nothing -> Nothing
      Just (_, _, _, x) -> error $ "Strange result: " ++ (show x)
    where
    dregex = mkRegex 
"^([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])T([0-9][0-9])([0-9][0-9])([0-9][0-9])"


-- Program entry point
main :: IO ()
main = do time <- epochTime
          tzoffset <- getTZOffset
          -- UIDs start from a negative timestamp and decrease from there
          let uid = (fromIntegral time) * (-1)
          doc <- parse
          let (addressdata, lastrid, lastuid) = getAddresses uid doc
          writeFile "addressbook.xml" (xml2str addressdata)
          putStrLn $ "Wrote addressbook.xml, rid 1 to " ++ (show lastrid) ++
                     ", uid " ++ (show uid) ++ " to " ++ (show lastuid)
          let (tododata, lastuidtodo) = getTodos (lastuid - 1) doc
          writeFile "todolist.xml" (xml2str tododata)
          putStrLn $ "Wrote todolist.xml, uid " ++ (show (lastuid - 1)) ++
                     " to " ++ (show lastuidtodo)
          let (dbdata, lastuiddb) = getDB tzoffset (lastuidtodo - 1) doc
          writeFile "datebook.xml" (xml2str dbdata)
          putStrLn $ "Wrote datebook.xml, uid " ++ (show (lastuidtodo - 1)) ++
                     " to " ++ (show lastuiddb)
          putStrLn " *** Conversion completed successfully! ***"
    where getTZOffset :: IO Int
          getTZOffset = do t <- getClockTime
                           cal <- toCalendarTime t
                           return $ ctTZ cal

-- Finds the literal children of the named tag, and returns it/them
tagof :: String -> CFilter
tagof x = keep /> tag x /> txt

-- Retruns the literal string that tagof would fine
strof :: String -> Content -> String
strof x y = verbatim $ tagof x $ y

{- Takes a list of (OldName, NewName) pairs.  Returns a list of (NewName,
CFilter) pairs that will yield the content from calling tagof on the oldname.
-}
mapattrs :: [(String, String)] -> Content -> [(String, CFilter)]
mapattrs [] _ = []
mapattrs (x:xs) doc = 
        case strof (fst x) doc of
           "" -> mapattrs xs doc  -- Omit this tag if the content is empty
           _ -> ((snd x), tagof (fst x)) : mapattrs xs doc

{- Like HaXml's numbered function, but instead of starting with 1 and
incrementing by 1, takes a start and a next. -}
versanumbered :: (Enum a, Show a) => a -> a -> CFilter -> LabelFilter String
versanumbered start next f = zip (map show [start,next..]) . f

----------------------------------------------------------------------
-- TODO LIST
------------------------------------------------------------

getTodos :: Integer -> Content -> ([Content], Integer)
getTodos startuid doc =
    (tasks `o` inputTop $ doc,
           startuid - count)
    where
    -- The top-level of the input
    inputTop :: CFilter
    inputTop = tag "Tasks" `o` children `o` tag "DTM"

    -- The top-level of the output
    tasks :: CFilter
    tasks = mkElem "Tasks"
            [row_task `oo` task_attrs]
            
    count = genericLength $ children `o` inputTop $ doc

    -- Each row if the input
    task_attrs :: LabelFilter String
    task_attrs = versanumbered startuid (startuid - 1)
                             (tag "Task" `o` children)

    -- Each row of the output
    row_task :: String -> CFilter
    row_task uid inp = mkElemAttr "Task" rowattrs [] inp
                   where
                   rowattrs = mapattrs todomap inp
                              ++ [("Uid", literal uid),
                                  ("Completed",
                                              if (strof "MARK" inp) == "0"
                                                 then literal "1"
                                                      else literal "0"
                                  )]
                              ++ case splitdate . strof "ETDY" $ inp of
                                     Nothing -> []
                                     Just (date, _) -> [("StartDate",
                                                         literal date)]
                              ++ case splitdate . strof "FNDY" $ inp of
                                     Nothing -> []
                                     Just (date, _) -> [("CompletedDate",
                                                         literal date)]
                              ++ case splitdate . strof "LTDY" $ inp of
                                     Nothing -> [("HasDate", literal "0")]
                                     Just (date, _) ->
                                         [("HasDate", literal "1")
                                         ,("DateYear", literal year)
                                         ,("DateMonth", literal month)
                                         ,("DateDay", literal day)]
                                         where (year, yr) = splitAt 4 date
                                               (month, mr) = splitAt 2 yr
                                               day = mr
                   todomap = [("TITL", "Summary")
                             ,("MEM1", "Description")
                             ,("PRTY", "Priority")
                             ]


----------------------------------------------------------------------
-- ADDRESS BOOK
----------------------------------------------------------------------

-- Main address book processor
getAddresses :: Integer -> Content -> ([Content], Integer, Integer)
getAddresses startuid doc = 
    (addressbook `o` inputTop $ doc,
     count,
     startuid - count)
    where 
        -- The <Contacts> tag -- top-level of the input
        inputTop :: CFilter
        inputTop = tag "Contacts" `o` children `o` tag "DTM"
        
        -- AddressBook -- the top level of the output
        addressbook :: CFilter
        addressbook = mkElem "AddressBook" 
                     [mkElem "RIDMax" [literal (show (count + 1))]
                     ,mkElem "Groups" []
                     ,mkElem "Contacts" [row_contact `oo` contact_attrs]
                     ]
        
        count = genericLength $ children `o` inputTop $ doc

        -- Each row of the input
        contact_attrs :: LabelFilter (String, String)
        contact_attrs = numbered `x` versanumbered startuid (startuid - 1)
                          $ tag "Contact" `o` children

        -- Each row of the output
        row_contact :: (String, String) -> CFilter
        row_contact (rid, uid) inp =
            mkElemAttr "Contact" rowattrs [] inp
            where rowattrs = 
                       [("FileAs", \x -> if (strof "FULL" x) `elem` ["", ",", 
", "]
                                      then tagof "CPNY" x
                                      else tagof "FULL" x)
                       ,("rid", literal rid)
                       ,("Uid", literal uid)
                       ,("rinfo", literal "1")
                       ] ++ mapattrs addrmap inp

        -- The address mapping
        addrmap :: [(String, String)]
        addrmap = [("TITL", "Title"),          ("FNME", "FirstName"),
                   ("MNME", "MiddleName"),     ("LNME", "LastName"),
                   ("SUFX", "Suffix"),
                   --FileAs, Categories, UID handled earlier
                   ("DMAL", "DefaultEmail"),   ("MAL1", "Emails"),
                   ("HSTR", "HomeStreet"),     ("HCTY", "HomeCity"),
                   ("HSTA", "HomeState"),      ("HZIP", "HomeZip"),
                   ("HCTR", "Homecountry"),    ("TEL1", "HomePhone"),
                   ("FAX1", "HomeFax"),        ("CPS1", "HomeMobile"),
                   ("HWEB", "HomeWebPage"),    ("CPNY", "Company"),
                   ("BSTR", "BusinessStreet"), ("BCTY", "BusinessCity"),
                   ("BSTA", "BusinessState"),  ("BZIP", "BusinessZip"),
                   ("BCTR", "BusinessCountry"),("BWEB", "BusinessWebPage"),
                   ("PSTN", "JobTitle"),       ("SCTN", "Department"),
                   ("OFCE", "Office"),         ("TEL2", "BusinessPhone"),
                   ("FAX2", "BusinessFax"),    ("CPS2", "BusinessMobile"),
                   ("BPGR", "BusinessPager"),  ("PRFS", "Profession"),
                   ("ASST", "Assistant"),      ("MNGR", "Manager"),
                   ("SPUS", "Spouse"),         ("CLDR", "Children"),
                   ("GNDR", "Gender"),         ("BRTH", "Birthday"),
                   ("ANIV", "Anniversary"),    ("NCNM", "Nickname"),
                   ("MEM1", "Notes")
                  ]

                                           
----------------------------------------------------------------------
-- DATE BOOK
------------------------------------------------------------

-- Main date book processor
getDB :: Int -> Integer -> Content -> ([Content], Integer)
getDB tzoffset startuid doc = 
    (events `o` inputTop $ doc,
            startuid - count)
    where
    -- Tag to calendar time, considering tz
    tag2cttz :: String -> Content -> Maybe CalendarTime
    tag2cttz x y = case tag2ct x y of
                     Just a -> Just $ a {ctTZ = tzoffset}
                     Nothing -> Nothing

    -- The top-level of the input
    inputTop :: CFilter
    inputTop = tag "Events" `o` children `o` tag "DTM"

    -- The top level of the output
    events :: CFilter
    events = mkElem "events"
             [row_event `oo` event_attrs]

    count = genericLength $ children `o` inputTop $ doc

    -- Each row of the input
    event_attrs :: LabelFilter String
    event_attrs = versanumbered startuid (startuid - 1)
                      (filter corruptfilter . tag "Event" `o` children)
    
    -- Filter out corrupt rows.
    corruptfilter :: Content -> Bool
    corruptfilter inp = if strof "ADAY" inp `elem` ["1", "0"] 
                           then True
                           else False

    -- Each row of the output
    row_event :: String -> CFilter
    row_event uid inp = mkElemAttr "event" rowattrs [] inp
        where
        rowattrs = (mapattrs eventmap inp) ++ customattrs
                   ++ times inp
        times :: Content -> [(String, CFilter)]
        times inp = case strof "ADAY" inp of
                  "1" ->  -- All-day item
                      [("type", literal "AllDay")] ++ 
                        case (do c <- tag2cttz "ALSD" inp
                                 ct <- ct2epoch $
                                        c {ctHour = 0, ctMin = 0, ctSec = 0}
                                 return $ show ct
                                ) of
                          Nothing -> []
                          Just x -> [("start", literal (show x))]
                          ++
                        case (do c <- tag2cttz "ALED" inp
                                 ct <- ct2epoch $ 
                                        c {ctHour = 23, ctMin = 59, ctSec = 0}
                                 return $ show ct
                             ) of
                          Nothing -> []
                          Just x -> [("end", literal (show x))]
                  _ -> -- Non-all-day item
                       case (do c <- tag2ct "TIM1" inp
                                ct <- ct2epoch c
                                return $ show ct
                            ) of
                         Nothing -> []
                         Just x -> [("start", literal x)]
                       ++
                       case (do c <- tag2ct "TIM2" inp
                                ct <- ct2epoch c
                                return $ show ct
                            ) of
                         Nothing -> []
                         Just x -> [("end", literal x)]

        customattrs :: [(String, CFilter)]
        customattrs = 
            [("uid", literal uid)]
        eventmap = [("DSRP", "description"),
                    ("PLCE", "location"),
                    ("MEM1", "note")
                   ]

Reply via email to