packages feed

XML-0.0.1.0: Data/XML.hs

{-# LANGUAGE ViewPatterns, RecordWildCards #-}
{-# LANGUAGE PatternSynonyms #-}

module Data.XML where

import Control.Arrow
import Data.Fix
import Data.Functor.Classes
import Data.Functor.Compose
import Data.List.NonEmpty (NonEmpty (..))
import Data.Text
import qualified Data.Text.Lazy as L
import qualified Data.Vector.Generic as V
import Data.Vector.Multi (Vector)
import Util

data ElementF a = ElementF
  { nameF :: !Text
  , attrsF :: !(Vector (Text, Text))
  , contentF :: !a }
  deriving (Functor, Foldable, Traversable)

instance Eq1 ElementF where
    liftEq eq x y = nameF x == nameF y && V.toList (attrsF x) == V.toList (attrsF y) && contentF x `eq` contentF y

instance Eq a => Eq (ElementF a) where (==) = eq1

data ContentF a = ContentF !L.Text !(Vector (NonEmpty a, L.Text))
instance Functor ContentF where fmap f (ContentF xs cs) = Content xs $ V.map (fmap f *** id) cs
instance Foldable ContentF where foldMap f (ContentF _ cs) = foldMap (foldMap f . fst) (V.toList cs)
instance Traversable ContentF where traverse f (ContentF xs cs) = Content xs . V.fromList <$> traverse (traverse f *=* pure) (V.toList cs)
instance Eq1 ContentF where
    liftEq eq (ContentF xs cs) (ContentF ys ds) = xs == ys && liftEq (liftEq2 (liftEq eq) (==)) (V.toList cs) (V.toList ds)

instance Eq a => Eq (ContentF a) where (==) = eq1

instance Semigroup (ContentF a) where
    ContentF xs cs <> ContentF ys ds = case V.unsnoc cs of
        Nothing -> ContentF (xs <> ys) ds
        Just (cs, (es, zs)) -> ContentF xs (cs V.++ V.cons (es, zs <> ys) ds)

instance Monoid (ContentF a) where mempty = ContentF mempty V.empty

type Element = Fix (Compose ElementF ContentF)
type Content = ContentF Element

pattern Element { name, attrs, content } = Fix (Compose (ElementF { nameF = name, attrsF = attrs, contentF = content }))
{-# COMPLETE Element #-}

pattern Content :: L.Text -> Vector (NonEmpty a, L.Text) -> ContentF a
pattern Content xs cs = ContentF xs cs
{-# COMPLETE Content #-}

showElement :: Element -> L.Text
showElement (Element {..}) =
    mconcat
    ["<",
     intercalate " " . fmap L.fromStrict $
     name :| ((\ (a, b) -> a <> "=\"" <> b <> "\"") <$> V.toList attrs),
     case content of
         Content "" (V.null -> True) -> "/>"
         Content xs cs -> ">" <> xs <>
                          foldMap (\ (es, xs) -> foldMap showElement es <> xs) (V.toList cs) <>
                          "</" <> L.fromStrict name <> ">"]