{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeSynonymInstances #-} module Text.XML.Hexml.Lens ( -- * Nodes _children , XML(..) , node , multiple ) where import Control.Arrow import Control.Lens hiding (children) import qualified Data.ByteString as Strict import qualified Data.ByteString.Internal as Strict import qualified Data.ByteString.Lazy as Lazy import Data.ByteString.Lens import Data.Functor.Contravariant import Data.Profunctor.Unsafe import Data.String import qualified Data.Text as Strict import qualified Data.Text.Encoding as Strict import qualified Data.Text.Lazy as Lazy import qualified Data.Text.Lazy.Encoding as Lazy import Data.Text.Lens import qualified Foundation as F import qualified Foundation.Array.Internal as F import qualified Foundation.String as F import Text.XML.Hexml -- | Getter for the element children _children :: Getter Node [Node] _children = to children class XML s where -- | A prism for parsing and unparsing XML. -- -- unparsing is provided by 'outer'. -- -- >>> "" ^? _XML -- Just Node "" -- -- Nameless nodes are inserted for trees with >1 root. -- -- >>> "" ^? _XML.to name -- Just "" -- -- >>> "" ^? _XML._children.ix(0) -- Just Node "" -- -- >>> "" ^? _XML._children.ix(1) -- Just Node "" -- -- If the tree has only 1 root, no nameless nodes are inserted. -- -- >>> "" ^? _XML.re(_XML @String)._XML.to name -- Just "foo" -- -- The law @x ^? re _XML . _XML == x@ doesn't hold for the nameless nodes -- injected by 'parse'. -- -- >>> parse "" ^? _Right.to name -- Just "" -- >>> parse "" ^? _Right.re(_XML @String)._XML.to name -- Just "foo" _XML :: Prism' s Node -- | Fold over all the children (text and element) _contents :: Fold Node (Either s Node) -- | Getter for the 'inner' contents of a node _inner :: Getter Node s -- | Getter for the 'inner' contents of a node _outer :: Getter Node s -- | Fold for accessing the text contents of a node textContents :: Fold Node s -- | Fold for accessing attributes by name. _Attribute :: s -> Getter Node (Maybe s) -- | Name-Indexed fold over the attribute values iattributes :: IndexedFold String Node s -- | A getter for accessing named children nodes -- This is a more efficient version of -- -- > nodes foo = _children . to (filter (\n -> name n == foo)) nodes :: s -> Getter Node [Node] -- | > node n = nodes n . folded node :: XML s => s -> Fold Node Node node n = nodes n . folded instance XML String where _XML = strictUtf8 . _XML @ Strict.ByteString _contents = _contents . firsting (from strictUtf8) _inner = _inner . from strictUtf8 _outer = _outer . from strictUtf8 textContents = textContents @Strict.ByteString . from strictUtf8 _Attribute n = _Attribute(n ^. packedChars).mapping(from strictUtf8) iattributes = iattributes @ Strict.ByteString . unpackedChars nodes name_ = nodes ( name_ ^. strictUtf8) instance XML F.String where _contents = _contents . lefting (foundation F.UTF8) _inner = _inner . foundation F.UTF8 _outer = _outer . foundation F.UTF8 textContents = textContents . foundation F.UTF8 _Attribute n = pre $ to (`attributeBy` (F.toList n ^. packedChars)) . folded . to attributeValue . foundation F.UTF8 iattributes = iattributes . to fromString nodes name_ = nodes ( F.toList name_ ^. strictUtf8) instance XML Strict.Text where _XML = strictTextUtf8 . _XML _contents = _contents . firsting (from strictTextUtf8) _inner = _inner . from strictTextUtf8 _outer = _outer . from strictTextUtf8 textContents = textContents . from strictTextUtf8 _Attribute n = _Attribute(n ^. strictTextUtf8).mapping(from strictTextUtf8) iattributes = iattributes . packed nodes name_ = nodes ( name_ ^. strictTextUtf8 ) instance XML Lazy.Text where _XML = lazyTextUtf8 . _XML _contents = _contents . firsting lazy _inner = _inner . lazy _outer = _outer . lazy textContents = textContents . from lazyTextUtf8 _Attribute n = _Attribute(n ^. lazyTextUtf8).mapping(from lazyTextUtf8) iattributes = iattributes . packed nodes name_ = nodes ( name_ ^. lazyTextUtf8 ) instance XML Strict.ByteString where _XML = prism' outer doParse where doParse x = case parse x of Right n -> Just $ case children n of [y] -> y ; _ -> n Left _ -> Nothing _contents = folding contents _inner = to inner _outer = to outer textContents = folding contents . _Left _Attribute n = pre $ to(`attributeBy` n).folded.to(attributeValue) iattributes = ifolding (map (\ (Attribute n v) -> (n^.from strictUtf8, v)) . attributes ) nodes name_ = to $ flip childrenBy name_ instance XML Lazy.ByteString where _XML = strict . _XML @ Strict.ByteString _contents = _contents . firsting lazy _inner = _inner . lazy _outer = _outer . lazy textContents = textContents . lazy _Attribute n = _Attribute(n^.strict).mapping(lazy) iattributes = iattributes.lazy nodes name_ = nodes (name_ ^. strict) lazyTextUtf8 :: Iso' Lazy.Text Lazy.ByteString lazyTextUtf8 = iso Lazy.encodeUtf8 Lazy.decodeUtf8 strictTextUtf8 :: Iso' Strict.Text Strict.ByteString strictTextUtf8 = iso Strict.encodeUtf8 Strict.decodeUtf8 strictUtf8 :: Iso' String Strict.ByteString strictUtf8 = packed . strictTextUtf8 foundation :: F.Encoding -> Getter Strict.ByteString F.String foundation encoding = to (F.fromBytes encoding . fromByteString) . {-. filtered (hasn't (_2.folded))-} _1 where fromByteString = F.fromForeignPtr . Strict.toForeignPtr -- | A more restricted version of 'firsting' which works on 'Fold's lefting :: Fold l l' -> Fold (Either l a) (Either l' a) lefting fold = runFold (left $ Fold fold) multiple :: Getting [a] s a -> IndexPreservingGetter s [a] multiple l = dimap (getConst #. l (Const #. (:[]))) phantom -- Test setup -- --------------------------------------------------------------------------------- -- $setup -- >>> import Test.QuickCheck -- >>> :set -XTypeApplications -- >>> :set -XOverloadedStrings