1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
|
{-# LANGUAGE OverloadedStrings #-}
-- Copyright (C) 2010-2011 John Millikin <jmillikin@gmail.com>
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
module Network.Protocol.XMPP.XML
( module Data.XML.Types
-- * Constructors
, element
-- * Misc
, contentText
, escape
, serialiseElement
, readEvents
-- * libxml-sax-0.4 API imitation
, Parser
, newParser
, parse
, eventsToElement
) where
import Control.Monad (when)
import Data.ByteString (ByteString)
import qualified Data.Text
import Data.Text (Text)
import Data.XML.Types
import Data.IORef (IORef, newIORef, readIORef, writeIORef)
import qualified Text.XML.LibXML.SAX as SAX
contentText :: Content -> Text
contentText (ContentText t) = t
contentText (ContentEntity e) = Data.Text.concat ["&", e, ";"]
escape :: Text -> Text
escape = Data.Text.concatMap escapeChar where
escapeChar c = case c of
'&' -> "&"
'<' -> "<"
'>' -> ">"
'"' -> """
'\'' -> "'"
_ -> Data.Text.singleton c
escapeContent :: Content -> Text
escapeContent (ContentText t) = escape t
escapeContent (ContentEntity e) = Data.Text.concat ["&", escape e, ";"]
element :: Name -> [(Name, Text)] -> [Node] -> Element
element name attrs children = Element name attrs' children where
attrs' = map (uncurry mkattr) attrs
mkattr :: Name -> Text -> (Name, [Content])
mkattr n val = (n, [ContentText val])
-- A somewhat primitive serialisation function
--
-- TODO: better namespace / prefix handling
serialiseElement :: Element -> Text
serialiseElement e = text where
text = Data.Text.concat ["<", eName, " ", attrs, ">", contents, "</", eName, ">"]
eName = formatName (elementName e)
formatName = escape . nameLocalName
attrs = Data.Text.intercalate " " (map attr (elementAttributes e ++ nsattr))
attr (n, c) = Data.Text.concat ([formatName n, "=\""] ++ map escapeContent c ++ ["\""])
nsattr = case nameNamespace $ elementName e of
Nothing -> []
Just ns -> [mkattr "xmlns" ns]
contents = Data.Text.concat (map serialiseNode (elementNodes e))
serialiseNode (NodeElement e') = serialiseElement e'
serialiseNode (NodeContent c) = escape (contentText c)
serialiseNode (NodeComment _) = ""
serialiseNode (NodeInstruction _) = ""
-- quick-and-dirty imitation of libxml-sax-0.4 API; later, this should
-- probably be rewritten to use ST and discard the list parsing
data Parser = Parser (SAX.Parser IO) (IORef (Either Text [Event]))
newParser :: IO Parser
newParser = do
ref <- newIORef (Right [])
p <- SAX.newParserIO Nothing
let addEvent e = do
x <- readIORef ref
case x of
Left _ -> return ()
Right es -> writeIORef ref (Right (e:es))
return True
SAX.setCallback p SAX.parsedBeginElement (\name attrs -> addEvent (EventBeginElement name attrs))
SAX.setCallback p SAX.parsedEndElement (addEvent . EventEndElement)
SAX.setCallback p SAX.parsedCharacters (addEvent . EventContent . ContentText)
SAX.setCallback p SAX.parsedComment (addEvent . EventComment)
SAX.setCallback p SAX.parsedInstruction (addEvent . EventInstruction)
SAX.setCallback p SAX.reportError (\err -> writeIORef ref (Left err) >> return False)
return (Parser p ref)
parse :: Parser -> ByteString -> Bool -> IO (Either Text [Event])
parse (Parser p ref) bytes finish = do
writeIORef ref (Right [])
SAX.parseBytes p bytes
when finish (SAX.parseComplete p)
eitherEvents <- readIORef ref
return $ case eitherEvents of
Left err -> Left err
Right events -> Right (reverse events)
readEvents :: Monad m
=> (Integer -> Event -> Bool)
-> m [Event]
-> m [Event]
readEvents done nextEvents = readEvents' 0 [] where
readEvents' depth acc = do
events <- nextEvents
let (done', depth', acc') = step events depth acc
if done'
then return acc'
else readEvents' depth' acc'
step [] depth acc = (False, depth, acc)
step (e:es) depth acc = let
depth' = depth + case e of
(EventBeginElement _ _) -> 1
(EventEndElement _) -> (- 1)
_ -> 0
acc' = e : acc
in if done depth' e
then (True, depth', reverse acc')
else step es depth' acc'
-- | Convert a list of events to a single 'Element'. If the events do not
-- contain at least one valid element, 'Nothing' will be returned instead.
eventsToElement :: [Event] -> Maybe Element
eventsToElement es = case eventsToNodes es >>= isElement of
(e:_) -> Just e
_ -> Nothing
eventsToNodes :: [Event] -> [Node]
eventsToNodes = concatMap blockToNodes . splitBlocks
-- Split event list into a sequence of "blocks", which are the events including
-- and between a pair of tags. <start><start2/></start> and <start/> are both
-- single blocks.
splitBlocks :: [Event] -> [[Event]]
splitBlocks es = ret where
(_, _, ret) = foldl splitBlocks' (0, [], []) es
splitBlocks' (depth, accum, allAccum) e = split where
split = if depth' == 0
then (depth', [], allAccum ++ [accum'])
else (depth', accum', allAccum)
accum' = accum ++ [e]
depth' :: Integer
depth' = depth + case e of
(EventBeginElement _ _) -> 1
(EventEndElement _) -> (- 1)
_ -> 0
blockToNodes :: [Event] -> [Node]
blockToNodes [] = []
blockToNodes (begin:rest) = nodes where
end = last rest
nodes = case (begin, end) of
(EventBeginElement name attrs, EventEndElement _) -> [node name attrs]
(EventContent c, _) -> [NodeContent c]
_ -> []
node n as = NodeElement (Element n as (eventsToNodes (init rest)))
|