ideas-1.9.1: src/Ideas/Text/XML/Data.hs
-----------------------------------------------------------------------------
-- Copyright 2019, Ideas project team. This file is distributed under the
-- terms of the Apache License 2.0. For more information, see the files
-- "LICENSE.txt" and "NOTICE.txt", which are included in the distribution.
-----------------------------------------------------------------------------
-- |
-- Maintainer : bastiaan.heeren@ou.nl
-- Stability : provisional
-- Portability : portable (depends on ghc)
--
-----------------------------------------------------------------------------
module Ideas.Text.XML.Data
( -- types
XML, xmlRoot, getName
, Content, xmlToContent
, HasContent(..), contentIsEmpty, headIsString, headIsXML
-- pretty-printing
, prettyXML, compactXML
-- processing
, foldXML, trimXML
) where
import Data.Char (isSpace, ord)
import Data.String
import Ideas.Text.XML.Attributes
import Ideas.Text.XML.Document (Name)
import qualified Ideas.Text.XML.Document as D
-------------------------------------------------------------------------------
-- XML types
data XML = Root Name Attributes Content
deriving Eq
instance Show XML where
show = compactXML
data Content = Empty
| CData String
| Cons XML Content
| Mixed String XML Content
deriving Eq
instance Show Content where
showsPrec _ = rec
where
rec Empty = id
rec (CData s) = (s ++)
rec (Cons x c) = (show x ++) . rec c
rec (Mixed s x c) = (s ++) . (show x ++) . rec c
instance Semigroup Content where
Empty <> rest = rest
CData s <> Empty = CData s
CData s <> CData t = CData (s ++ t)
CData s <> Cons x c = Mixed s x c
CData s <> Mixed t x c = Mixed (s ++ t) x c
Cons x c <> rest = Cons x (c <> rest)
Mixed s x c <> rest = Mixed s x (c <> rest)
instance Monoid Content where
mempty = Empty
instance IsString Content where
fromString s = if null s then Empty else CData s
class HasContent a where
getContent :: a -> Content
setContent :: Content -> a -> a
changeContent :: (Content -> Content) -> a -> a
updateContent :: a -> (Content, Content -> a)
{-# MINIMAL (getContent, changeContent) | updateContent #-}
-- default definitions
getContent = fst . updateContent
setContent = changeContent . const
changeContent f = (\(c, g) -> g (f c)) . updateContent
updateContent a = (getContent a, (`setContent` a))
instance HasContent Content where
updateContent a = (a, id)
instance HasContent XML where
updateContent (Root n as c) = (c, xmlRoot n as)
instance HasAttributes XML where
updateAttributes (Root n as c) = (as, \bs -> xmlRoot n bs c)
xmlRoot :: Name -> Attributes -> Content -> XML
xmlRoot = Root
getName :: XML -> Name
getName (Root n _ _) = n
contentIsEmpty :: HasContent a => a -> Bool
contentIsEmpty a =
case getContent a of
Empty -> True
_ -> False
headIsString :: HasContent a => a -> Maybe (String, a)
headIsString a =
case updateContent a of
(CData s, f) -> Just (s, f Empty)
(Mixed s x c, f) -> Just (s, f (Cons x c))
_ -> Nothing
headIsXML :: HasContent a => a -> Maybe (XML, a)
headIsXML a =
case updateContent a of
(Cons x c, f) -> Just (x, f c)
_ -> Nothing
xmlToContent :: XML -> Content
xmlToContent = (`Cons` Empty)
-------------------------------------------------------------------------------
-- Pretty-printing XML
prettyXML :: XML -> String
prettyXML = show . D.prettyElement False . toElement
compactXML :: XML -> String
compactXML = show . D.prettyElement True . toElement
toElement :: XML -> D.Element
toElement = foldXML D.Element mkAttribute mkString (return . D.Tagged)
where
mkAttribute :: Name -> String -> D.Attributes
mkAttribute m s = [(D.:=) m (map Left s)]
mkString :: String -> [D.XML]
mkString [] = []
mkString xs@(hd:tl)
| null xs1 = D.Reference (D.CharRef (ord hd)) : mkString tl
| otherwise = D.CharData xs1 : mkString xs2
where
(xs1, xs2) = break ((> 127) . ord) xs
-------------------------------------------------------------------------------
-- Processing XML
foldXML :: (Monoid a, Monoid c) => (Name -> a -> c -> e) -> (Name -> String -> a) -> (String -> c) -> (e -> c) -> XML -> e
foldXML f fa fs fc = rec
where
rec (Root n as cs) = f n (foldAttributes fa as) (recContent cs)
recContent Empty = mempty
recContent (CData s) = fs s
recContent (Cons x c) = fc (rec x) <> recContent c
recContent (Mixed s x c) = fs s <> fc (rec x) <> recContent c
trimXML :: XML -> XML
trimXML = foldXML xmlRoot f (fromString . trim) xmlToContent
where
f n = attribute n . trim
trim, trimLeft, trimRight :: String -> String
trim = trimLeft . trimRight
trimLeft = dropWhile isSpace
trimRight = reverse . trimLeft . reverse