packages feed

ideas-0.7: src/Text/XML/Interface.hs

-----------------------------------------------------------------------------
-- Copyright 2010, Open Universiteit Nederland. This file is distributed 
-- under the terms of the GNU General Public License. For more information, 
-- see the file "LICENSE.txt", which is included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer  :  bastiaan.heeren@ou.nl
-- Stability   :  provisional
-- Portability :  portable (depends on ghc)
--
-- Collection of common operation on XML documents
--
-----------------------------------------------------------------------------
module Text.XML.Interface where

import Control.Arrow
import Text.XML.Document (Name)
import Text.XML.Unicode (decoding)
import Text.XML.Parser (document, extParsedEnt)
import Text.XML.ParseLib (parse)
import Control.Monad.Error ()
import qualified Text.XML.Document as D
import System.FilePath (takeDirectory, pathSeparator)
import Data.Char (chr, ord)
import Data.Maybe

data Element = Element
   { name       :: Name
   , attributes :: Attributes
   , content    :: Content
   } 

instance Show Element where
   show = show . extend

type Content = [Either String Element]

type Attributes = [Attribute]
data Attribute = Name := String

normalize :: D.XMLDoc -> Element
normalize doc = toElement (D.root doc)
 where
   toElement :: D.Element -> Element
   toElement (D.Element n as c) = 
      Element n (map toAttribute as) (toContent c)
   
   toAttribute :: D.Attribute -> Attribute
   toAttribute (n D.:= v) = 
      n := concatMap (either return refToString) v
   
   toContent :: D.Content -> Content
   toContent = merge . concatMap f
    where
      f :: D.XML -> Content
      f (D.Tagged e)    = [Right (toElement e)]
      f (D.CharData s)  = [Left s]
      f (D.CDATA s)     = [Left s]
      f (D.Reference r) = refToContent r
   
   refToString :: D.Reference -> String
   refToString (D.CharRef i)   = [chr i]
   refToString (D.EntityRef _) = "" -- error
   
   refToContent :: D.Reference -> Content
   refToContent (D.CharRef i)   = [Left [chr i]]
   refToContent (D.EntityRef s) = 
      fromJust (lookup s entities)

   entities :: [(String, Content)]
   entities = 
      [ (n, toContent (snd ext)) | (n, ext) <- D.externals doc ] ++ 
      -- predefined entities
      map (second (return . Left . return))
         [("lt",'<'), ("gt",'>'), ("amp",'&'), ("apos",'\''), ("quot",'"')]
   
   merge :: Content -> Content
   merge (Left s:Left t:rest) = merge (Left (s++t) : rest)
   merge (x:xs) = x:merge xs
   merge []     = []
   
extend :: Element -> D.XMLDoc
extend e = D.XMLDoc
   { D.versionInfo = Nothing
   , D.encoding    = Nothing
   , D.standalone  = Nothing
   , D.dtd         = Nothing
   , D.externals   = []
   , D.root        = toElement e
   }
 where
   toElement :: Element -> D.Element
   toElement (Element n as c) =
      D.Element n (map toAttribute as) (concatMap toXML c)
   
   toAttribute :: Attribute -> D.Attribute
   toAttribute (n := s) = (D.:=) n (map Left s)
   
   toXML :: Either String Element -> [D.XML]
   toXML = either fromString (return . D.Tagged . toElement)
   
   fromString :: String -> [D.XML]
   fromString [] = []
   fromString xs@(hd:tl) 
      | null xs1  = D.Reference (D.CharRef (ord hd)) : fromString tl
      | otherwise = D.CharData xs1 : fromString xs2
    where
      (xs1, xs2) = break ((> 127) . ord) xs

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

parseXML :: String -> Either String Element
parseXML xs = do
   input <- decoding xs
   doc   <- parse document input
   return (normalize doc)

parseIO :: String -> IO Element
parseIO baseFile = do
   -- putStrLn $ "Reading " ++ show baseFile
   xs    <- readFile baseFile
   input <- decoding xs
   case parse document input of
      Left err  -> fail err
      Right doc -> do
         let exts = getExternals doc
         rs <- mapM (parseExternal . snd) exts
         let new = doc { D.externals = zip (map fst exts) rs }
         return (normalize new)

 where 
   getExternals :: D.XMLDoc -> [(String, String)]
   getExternals doc =
      case D.dtd doc of 
         Just (D.DTD _ _ decls) ->
            [ (n, s) | D.EntityDecl True n (Right (D.System s, Nothing)) <- decls ]  
         Nothing -> []
 
   parseExternal :: String -> IO D.External
   parseExternal extFile = do
      let full = takeDirectory baseFile ++ [pathSeparator] ++ extFile
      -- putStrLn $ "Reading " ++ show full
      xs    <- readFile full
      input <- decoding xs
      case parse extParsedEnt input of
         Right doc -> return doc
         Left err  -> fail err

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

noAttributes :: Element -> Bool
noAttributes = null . attributes

findAttribute :: Monad m => String -> Element -> m String
findAttribute s (Element _ as _) =
   case [ t | n := t <- as, s==n ] of
      [hd] -> return hd
      _    -> fail $ "Invalid attribute: " ++ show s

findChild :: Monad m => String -> Element -> m Element
findChild s e = 
   case filter ((==s) . name) (children e) of
      [a] -> return a
      _   -> fail $ "Child not found: " ++ show s

children :: Element -> [Element]
children e = [ c | Right c <- content e ]

getData :: Element -> String
getData e = concat [ s | Left s <- content e ]

{-
children :: D.Element -> [D.Element]
children (D.Element _ _ c) = [ e | D.Tagged e <- c ]

getAttributes :: D.Element -> [(String, String)]
getAttributes (D.Element _ as _) = 
   [ (n, concatMap f av) | n D.:= av <- as ]
 where 
   f :: Either Char D.Reference -> String
   f (Left c)              = [c]
   f (Right (D.CharRef n))   = [chr $ fromIntegral n]
   f (Right (D.EntityRef _)) = []

findAttribute :: Monad m => String -> D.Element -> m String
findAttribute n e =
   case lookup n (getAttributes e) of
      Just a  -> return a
      Nothing -> fail $ "Attribute not found: " ++ show n

getData :: D.Element -> String
getData (D.Element _ _ c) = concat [ s | D.CharData s <- c ]
-}