CurryInfo: ertools-3.0.0 / Database.ERD.FromXML

classes:

              
documentation:
--------------------------------------------------------------------------
--- module to convert Umbrello 1.5.52 output to datatype ERD 
--------------------------------------------------------------------------
name:
Database.ERD.FromXML
operations:
convert
sourcecode:
{-# OPTIONS_CYMAKE -Wno-incomplete-patterns #-}

module Database.ERD.FromXML ( convert ) where

import Data.Char
import Data.List
import Data.Maybe

import Database.ERD
import Data.Time
import XML

findElements :: [XmlExp] -> [String] -> [XmlExp]
findElements [] _ = []
findElements (x@(XElem t _ _) : xs) [s] 
  | t == s = (x : findElements xs [s])
  | otherwise = findElements xs [s]
findElements ((XElem t _ content) : xs) (z:y:ys) 
  | t == z = findElements content (y:ys)
  | otherwise = findElements xs (z:y:ys)

getContent :: XmlExp -> [XmlExp]
getContent (XElem _ _ content) = content
getContent (XText _) = []

getUMLModel :: [XmlExp] -> String -> [XmlExp]
getUMLModel [] _ = []
getUMLModel (x@(XElem tag _ _):xs) name 
  | tag == "UML:Model" = case (lookup "name" (getAttrs x)) of
      Just d  -> if d == name then getContent x
                 else getUMLModel xs name
      Nothing -> getUMLModel xs name
  | otherwise = getUMLModel xs name

convert :: XmlExp -> ERD
convert xml = 
  let contentxml  = getContent 
                      (head 
                        (findElements 
                           [xml] 
                           ["XMI","XMI.content","UML:Model",
                            "UML:Namespace.ownedElement"]))
      logicalView = getContent 
                      (head 
                        (findElements 
                          (getUMLModel contentxml "Logical View") 
                          ["UML:Namespace.ownedElement"]))
      erm         = getUMLModel contentxml "Entity Relationship Model"
      exml        = findElements erm ["UML:Namespace.ownedElement",
                                      "UML:Entity"]
      rxml        = findElements erm ["UML:Namespace.ownedElement",
                                      "UML:Association"]
      idlist      = (iddata logicalView) ++ (identities exml)
      name        = fromJust 
                      (lookup "name" 
                              (getAttrs 
                                 (head (findElements 
                                          erm 
                                          ["XMI.extension","diagrams",
                                           "diagram"]))))
      es          = map (convertE idlist) exml
      rs          = map (convertR idlist) rxml
  in
  if uniqueNames es rs
  then ERD name es rs
  else error "names (entity, relationship, role) in er-diagramm not unique"

uniqueNames :: [Entity] -> [Relationship] -> Bool
uniqueNames es rs = 
  length es + length rs == length (nub ((map eName es)++(concatMap rNames rs)))

eName :: Entity -> String
eName (Entity n _) = n

rNames :: Relationship -> [String]
rNames (Relationship rn [REnd r1 _ _, REnd r2 _ _]) = [rn, r1, r2]
  

 
iddata :: [XmlExp] -> [(String,String)]
iddata [] = []
iddata (x@(XElem t attrs _):elems) 
      | t == "UML:DataType" || t == "UML:Class" =
          let id = fromJust (lookup "xmi.id" attrs)
              name = fromJust (lookup "name" attrs)
          in
          (id,name) : iddata elems
      | t == "UML:Package" = 
          (iddata (findElements [x] 
                                ["UML:Package","UML:Namespace.ownedElement",
                                 "UML:DataType"]))
          ++ (iddata elems)
      | otherwise = iddata elems


identities :: [XmlExp] -> [(String,String)]
identities [] = []
identities ((XElem t attrs _):elems) 
      | t == "UML:Entity" =
          let id = fromJust (lookup "xmi.id" attrs)
              name = fromJust (lookup "name" attrs)
          in
          (id,name) : identities elems
      | otherwise = identities elems
    
getAttrs :: XmlExp -> [(String, String)]
getAttrs (XElem _ attrs _) = attrs
  



-- convert entity 
convertE :: [(String, String)] -> XmlExp -> Entity
convertE idlist (XElem "UML:Entity" attrs alist) =
  let name = fromJust (lookup "name" attrs)
      attributes = map (convertAttr idlist) alist
  in
  if null alist
  then error ("Entity " ++ name ++ " without attributes")
  else Entity name (map (checkAttr name) attributes)

checkAttr :: String -> Attribute -> Attribute 
checkAttr ename (Attribute name domain key null) =
  let n = if (isLower (head name))
          then (toUpper (head name)):(tail name)
          else name
      v = getValue domain
  in
  if n == "Key" 
  then error ("attribute name Key is not allowed in entity "++ename)
  else if v
       then 
         if null 
         then error ("attribute "++name
                      ++" with default value should not be null in entity "++ename)
         else if key==Unique
              then error ("attribute "++name
                           ++" with unique value should not be null in entity "
                           ++ename)
              else Attribute n domain key null
       else Attribute n domain key null

getValue :: Domain -> Bool
getValue (IntDom Nothing) = False
getValue (IntDom (Just _)) = True
getValue (FloatDom Nothing) = False
getValue (FloatDom (Just _)) = True
getValue (CharDom Nothing) = False
getValue (CharDom (Just _)) = True
getValue (StringDom Nothing) = False
getValue (StringDom (Just _)) = True
getValue (BoolDom Nothing) = False
getValue (BoolDom (Just _)) = True
getValue (DateDom Nothing) = False
getValue (DateDom (Just _)) = True
getValue (UserDefined _ Nothing) = False
getValue (UserDefined _ (Just _)) = True


  

-- convert relationship
convertR :: [(String, String)] -> XmlExp -> Relationship
convertR idlist 
         (XElem "UML:Association" attrs 
                [(XElem "UML:Association.connection" _ [end1, end2])]) =
  let name = fromJust (lookup "name" attrs)
      rends = [convertREnd idlist end1, 
               convertREnd idlist end2]
  in 
  if twoMin rends
  then error ("relationship " ++ name ++ " has two minima")
  else if name==""
       then error "relationship without name"
       else Relationship (toUpper (head name) : tail name) rends
    where
      convertREnd :: [(String, String)] -> XmlExp -> REnd
      convertREnd idl (XElem "UML:AssociationEnd" alist _) =
        let t = fromJust (lookup "type" alist)
            name = fromJust (lookup "name" alist)  
            mult = lookup "multiplicity" alist
        in
        if name==""
        then error "role without name"
        else REnd (fromJust (lookup t idl)) 
                  (toLower (head name) : tail name) 
                  (convertCard mult)
      twoMin [REnd _ _ c1, REnd _ _ c2] = case c1 of
        Between i _ -> if i > 0 
                       then case c2 of Between j _ -> j > 0 
                                       Exactly _   -> False
                       else False
        Exactly _ -> case c2 of Between m _ -> m > 0 
                                Exactly _   -> True

      -- exactly: >0
      -- range: min<max
convertCard :: Maybe String -> Cardinality
convertCard c = case c of
        Nothing -> error "cardinality missing"
        Just "m" -> Between 0 Infinite
        Just "n" -> Between 0 Infinite
        Just ('(':m:ms) -> let (min, (_:max')) = break (== ',') (m:ms) 
                               max = fst (break (== ')') max')
                           in
                           if all isDigit min 
                           then let minimum = read min
                                in
                                if all isDigit max
                                then if minimum == read max then Exactly minimum
                                     else if minimum < read max
                                          then Between minimum (Max (read max))
                                          else error "wrong cardinality"
                                else Between minimum Infinite
                           else error "wrong cardinality (min)"
        Just i   -> if all isDigit i 
                    then let e = read i
                         in
                         if e > 0 then Exactly e else error "cardinality <= 0"
                    else error "wrong cardinality"

-- convert attribute
convertAttr :: [(String, String)] -> XmlExp -> Attribute
convertAttr idlist (XElem "UML:EntityAttribute" alist _) =
  let t = fromJust (lookup "type" alist)
      name = fromJust (lookup "name" alist)
      init = lookup "initialValue" alist  
      d = convertDomain (lookup t idlist) init
      dbindex_type = fromJust (lookup "dbindex_type" alist) 
      pkey = if dbindex_type == "1101"
             then PKey
             else if dbindex_type == "1103"  
                  then Unique
                  else NoKey
      allow_null = fromJust (lookup "allow_null" alist)
      null = if allow_null == "0" 
             then False
             else True
  in 
  Attribute name d pkey null 

-- datatypes:                
int :: [String]
int = ["Int","int"] 

char :: [String]
char = ["Char", "char"]

string :: [String]
string = ["String","string", "text", "varchar"]

float :: [String]
float = ["Float", "float", "Double", "double"]

bool :: [String]
bool = ["Bool", "bool"]

date :: [String]
date = ["Date", "date"]

convertDomain :: Maybe String -> Maybe String -> Domain
convertDomain Nothing _ = error "domain missing"
convertDomain (Just t) Nothing
  | elem t int    = IntDom Nothing
  | elem t float  = FloatDom Nothing
  | elem t char   = CharDom Nothing
  | elem t string = StringDom Nothing
  | elem t bool   = BoolDom Nothing
  | elem t date   = DateDom Nothing
  | otherwise     = UserDefined t Nothing
convertDomain (Just t) (Just d) = 
  if d == "" 
  then convertDomain (Just t) Nothing
  else convertD t d
    where 
      convertD :: String -> String -> Domain
      convertD typ dom 
        | elem typ int    = IntDom (Just (read dom))
        | elem typ float  = FloatDom (Just (read dom))
        | elem typ char   = CharDom (Just (head dom))
        | elem typ string = StringDom (Just dom)
        | elem typ bool   = if dom == "true" 
                            then BoolDom (Just True) 
                            else BoolDom (Just False)
        | elem t date     = DateDom (Just (parseDate dom))
        | otherwise       = UserDefined t (Just d)

       
      -- 01.01.2007 15:16:17 ~> CalendarTime 2007 1 1 15 16 17 0
      parseDate :: String -> CalendarTime
      parseDate s = 
        let (ts,_:cs) = break (== ' ') s
            d1    = break (== '.') ts
            day   = read (fst d1)
            d2    = break (== '.') (tail (snd d1))
            month = read (fst d2)
            year  = read (tail (snd d2))    
            c1    = break (== ':') cs 
            hour  = read (fst c1)
            c2    = break (== ':') (tail (snd c1))
            minute = read (fst c2)
            second = read (tail (snd c2))
        in
        CalendarTime year month day hour minute second 0

-------------------------------------------------------------------------------

{-
(ERD "Uni" 
  [(Entity "Student" 
     [(Attribute "MatrikelNr" (IntDom Nothing) PKey False),
      (Attribute "Name" (StringDom Nothing) NoKey False),
      (Attribute "Vorname" (StringDom Nothing) NoKey False),
      (Attribute "Email" (UserDefined "MyModule.Email" Nothing) NoKey True)]),
   (Entity "Veranstaltung" 
     [(Attribute "Nr" (IntDom Nothing) PKey False),
      (Attribute "Titel" (StringDom Nothing) Unique False),
      (Attribute "SWS" (IntDom (Just 4)) NoKey False)]),
   (Entity "Dozent" 
     [(Attribute "Nr" (IntDom Nothing) PKey False),
      (Attribute "Name" (StringDom Nothing) NoKey False),
      (Attribute "Vorname" (StringDom Nothing) NoKey False)]),
   (Entity "Gruppe" 
     [(Attribute "Termin" (StringDom Nothing) NoKey False)])] 
  [(Relationship "Veranstalten" 
     [(REnd "Dozent" "wird_gehalten" (Exactly 1)),
      (REnd "Veranstaltung" "haelt" (Between 0 Infinite))]),
   (Relationship "Teilnahme" 
     [(REnd "Student" "wird_besucht" (Between 0 Infinite)),
      (REnd "Veranstaltung" "nimmt_teil" (Between 0 Infinite))]),
   (Relationship "Zugehoerigkeit" 
     [(REnd "Student" "besteht_aus" (Exactly 3)),
      (REnd "Gruppe" "ist_in" (Between 0 Infinite))])])
-}
types:

              
unsafe:
safe