module Text.Proton.Xml (
Element(..),
Attribute(..),
ElementType(..),
RenderCallbackFn(..),
containsAttribute,
copyElement,
copyElements,
findAttribute,
getAttributes,
getChildren,
parseXmlFile,
parseAttributes,
render,
render'
) where
import qualified Data.Map as Map
import Text.Proton.XmlTypes
import Text.Proton.XmlInternal
containsAttribute :: String -> [Attribute] -> Bool
containsAttribute _ [] = False
containsAttribute name (x:xs) = do
let aname = attname x
(aname == name) || containsAttribute name xs
copyElement :: Element -> Element
copyElement (Element elemtype s atts xs) = Element elemtype s atts (copyElements xs)
copyElements :: [Element] -> [Element]
copyElements = map copyElement
findAttribute :: String -> [Attribute] -> Attribute
findAttribute _ [] = NoAttribute
findAttribute name (x:xs) = do
let aname = attname x
if aname == name then x
else findAttribute name xs
getChildren :: Element -> [Element]
getChildren (Element _ _ _ xs) = xs
getAttributes :: Element -> [Attribute]
getAttributes (Element _ _ atts _) = atts
parseAttributes :: String -> [Attribute]
parseAttributes "" = []
parseAttributes ">" = []
parseAttributes " />" = []
parseAttributes "/>" = []
parseAttributes s = do
let news = dropWhile (matches " \"") s
let (name, maybeValue) = splitOn '=' news
let (value, rest) = splitUntilClose maybeValue
Attribute name value 1 : (if rest /= ""
then parseAttributes (tail rest)
else [])
parseTag :: String -> (String, String)
parseTag s = do
let (_, remainder) = span (matches "</") s
break (matches " >/") remainder
parse :: [String] -> ([Element], [String])
parse [] = ([], [])
parse (x:xs) = do
let first = head x
let sec = head (tail x)
let seclst = last (init x)
let lst = last x
case (first, sec, seclst, lst) of
('<', '?', _, _) -> do
let (parsed, remaining) = parse xs
(Element Raw x [] [] : parsed, remaining)
('<', '!', _, _) -> do
let (parsed, remaining) = parse xs
(Element Raw x [] [] : parsed, remaining)
('<', _, '/', '>') -> do
let (tag, tagcontent) = parseTag x
let attributes = parseAttributes tagcontent
let (parsed, remaining) = parse xs
(Element Closed tag attributes [] : parsed, remaining)
('<', '/', _, '>') -> ([], xs)
('<', _, _, '>') -> do
let (tag, tagcontent) = parseTag x
let attributes = parseAttributes tagcontent
let (children, siblings) = parse xs
let (parsed, remaining) = parse siblings
(Element Open tag attributes children : parsed, remaining)
(_, _, _, _) -> do
let (parsed, remaining) = parse xs
(Element Raw x [] [] : parsed, remaining)
parseXmlFile :: String -> IO Element
parseXmlFile fname = do
file <- readFile fname
let sp = splitText file
let (parsed, _) = parse sp
return (Element Root "" [] parsed)
getData :: (RenderCallbackFn (String, [Attribute], [Element]) b) -> (String, [Attribute], [Element])
getData (RenderCallbackFn a _) = do
let (tag, atts, xs) = a
(tag, atts, xs)
getFn :: RenderCallbackFn a b -> b -> RenderCallbackFn a b
getFn (RenderCallbackFn _ b) = b
renderNoop :: (String, [Attribute], [Element]) -> RenderCallbackFn (String, [Attribute], [Element]) (String, [Attribute], [Element])
renderNoop (s, atts, xs) = RenderCallbackFn (s, atts, xs) renderNoop
render :: Element -> String
render e = render' e renderNoop
render' :: Element -> ((String, [Attribute], [Element]) -> RenderCallbackFn (String, [Attribute], [Element]) (String, [Attribute], [Element])) -> String
render' e fn = do
let (newe, _) = preprocessElement e Map.empty
renderElement newe fn
incrementOccurrences :: [Attribute] -> Map.Map String Integer -> ([Attribute], Map.Map String Integer)
incrementOccurrences [] occurrences = ([], occurrences)
incrementOccurrences (a:as) occurrences = do
let (Attribute name val _) = a
if name == "eid" || name == "aid"
then do
let key = name ++ "/" ++ val
let count = Map.findWithDefault 0 key occurrences + 1
let newoccurrences = Map.insert key count occurrences
let (newatts, newoccurrences2) = incrementOccurrences as newoccurrences
(Attribute name val count : newatts, newoccurrences2)
else do
let (newatts, newoccurrences) = incrementOccurrences as occurrences
(a : newatts, newoccurrences)
preprocessElement :: Element -> Map.Map String Integer -> (Element, Map.Map String Integer)
preprocessElement e occurrences = do
let (Element elemtype s atts xs) = e
let (newatts, newoccurrences) = incrementOccurrences atts occurrences
let (newxs, newoccurrences2) = preprocessElement' xs newoccurrences
(Element elemtype s newatts newxs, newoccurrences2)
preprocessElement' :: [Element] -> Map.Map String Integer -> ([Element], Map.Map String Integer)
preprocessElement' [] occurrences = ([], occurrences)
preprocessElement' (e:es) occurrences = do
let (newe, newoccurrences) = preprocessElement e occurrences
let (newes, newoccurrences2) = preprocessElement' es newoccurrences
(newe : newes, newoccurrences2)
renderElement :: Element -> ((String, [Attribute], [Element]) -> RenderCallbackFn (String, [Attribute], [Element]) (String, [Attribute], [Element])) -> String
renderElement (Element elemtype s atts xs) fn =
case elemtype of
(Raw) -> s
(Closed) -> renderClosed s atts fn
(Open) -> renderOpen s atts xs fn
(Root) -> renderList xs fn
renderClosed :: String -> [Attribute] -> ((String, [Attribute], [Element]) -> RenderCallbackFn (String, [Attribute], [Element]) (String, [Attribute], [Element])) -> String
renderClosed s atts fn = do
let fnres = fn (s, atts, [Element Raw "" [] []])
let (newtag, newatts, _) = getData fnres
"<" ++ newtag ++ renderAttributeList newatts ++ " />"
renderOpen :: String -> [Attribute] -> [Element] -> ((String, [Attribute], [Element]) -> RenderCallbackFn (String, [Attribute], [Element]) (String, [Attribute], [Element])) -> String
renderOpen s atts xs fn = do
let fnres = fn (s, atts, xs)
let (newtag, newatts, newxs) = getData fnres
let newfn = getFn fnres
"<" ++ newtag ++ renderAttributeList newatts ++ ">" ++ renderList newxs newfn ++ "</" ++ newtag ++ ">"
renderList :: [Element] -> ((String, [Attribute], [Element]) -> RenderCallbackFn (String, [Attribute], [Element]) (String, [Attribute], [Element])) -> String
renderList xs fn = foldr (\ x -> (++) (renderElement x fn)) "" xs
renderAttribute :: Attribute -> String
renderAttribute NoAttribute = ""
renderAttribute (Attribute name val _) =
if name == "rid" || name == "eid" || name == "aid"
then ""
else " " ++ name ++ "=\"" ++ val ++ "\""
renderAttributeList :: [Attribute] -> String
renderAttributeList = foldr ((++) . renderAttribute) ""