{- | Module : Network.URI.Template Copyright : (c) Sigbjorn Finne, 2008 License : BSD3 Maintainer : sof@forkio.com Stability : provisional Portability : portable URI templates and their expansion. For details (and up-to-date specs), see http://bitworking.org/projects/ Also implements the OpenSearch 1.1 style with '?' suffix chars + support for namespace prefixes {xmlns:foo?} => xmlns:bar -} module Network.URI.Template ( TemplateEnv , newEnv -- :: TemplateEnv , addToEnv -- :: TemplateEnv -> String -> String -> TemplateEnv , addListToEnv -- :: TemplateEnv -> String -> [String] -> TemplateEnv , URITemplate -- String synonym , URIString -- ditto. , expand -- :: TemplateEnv -> URITemplate -> URIString ) where import Data.Map as M import Data.List import Codec.Binary.UTF8.String as UTF8 import Codec.Web.Percent type URIString = String type URITemplate = String -- | @TemplateEnv@ holds the key,value mapping for the expansion -- context for a URI template. newtype TemplateEnv = TemplateEnv { tenv :: Map String TempValue } data TempValue = ValStr URIString | ValList [URIString] -- | Construct a new, empty 'TemplateEnv'. newEnv :: TemplateEnv newEnv = TemplateEnv{tenv=M.empty} -- | UTF-8ifies the RHS, followed by percent encoding it. normalize :: String -> String normalize s = getEncodedString (UTF8.encodeString s) -- | @addToEnv key value env@ augments @env@ with a new -- key,value pair. addToEnv :: String -> String -> TemplateEnv -> TemplateEnv addToEnv k v env = TemplateEnv{tenv=M.insert k (ValStr (normalize v)) (tenv env)} -- | @addListToEnv key vals env@ expands the template environment @env@ -- with a list-valued key,value(s) pair. addListToEnv :: String -> [String] -> TemplateEnv -> TemplateEnv addListToEnv k vs env = TemplateEnv{tenv=M.insert k (ValList (Data.List.map normalize vs)) (tenv env)} -- | @expand tenv tpl@ performs template expansion on URL template @tpl@ wrt -- @tenv@. The result contains the expansion, _but_ leaving unbound template -- variables intact. expand :: TemplateEnv -> URITemplate -> URIString expand _ "" = "" expand env ('{':xs) = -- look for the sentinel.. case break (=='}') xs of (_,[]) -> '{' : expand env xs ('-':str,_:bs) -> case wordsBy "|" str of [fun,arg,vars] -> case Data.List.lookup fun funEnv of Just f -> (f env arg vars) ++ expand env bs -- unknown/ill-formed, but just ignored. Nothing -> expand env bs -- ditto. _ -> expand env bs (str,_:bs) -> case break (=='=') str of (k,_:def) -> let (def',_isOpt) = if last def == '?' then (init def, True) else (def,False) in case M.lookup k (tenv env) of Nothing -> def' ++ expand env bs Just (ValStr v) -> v ++ expand env bs Just (ValList vs) -> concat (intersperse "," vs) ++ expand env bs _ -> let -- check for OpenSearch-style opt suffixes.. (k0,_isOpt) | last str == '?' = (init str, True) | otherwise = (str,False) -- ..and its NS-prefixed forms (k,pre) = case break (==':') k0 of (as,_:rs) -> (rs,as++":") _ -> (k0,"") in case M.lookup k (tenv env) of Nothing -> expand env bs -- ToDo: flag an error if not optional. Just (ValStr v) -> pre ++ v ++ expand env bs Just (ValList vs) -> concat (intersperse "," $ Data.List.map (pre++) vs) ++ expand env bs expand env (x:xs) = x : expand env xs type TemplateFun = TemplateEnv -> String -> String -> String funEnv :: [(String, TemplateFun)] funEnv = [ ("opt", optExp False) , ("neg", optExp True) , ("prefix", preExp) , ("suffix", sufExp) , ("join", joinExp) , ("list", listExp) ] optExp :: Bool -> TemplateFun optExp isNeg env sep vs | all isEmpty vals = if isNeg then sep else "" | otherwise = if isNeg then "" else sep where vals = wordsBy "," vs isEmpty x = case M.lookup x (tenv env) of Nothing -> True Just (ValList []) -> True _ -> False preExp :: TemplateFun preExp env sep vs = case wordsBy "," vs of [_] -> case M.lookup vs (tenv env) of Just (ValStr x) -> sep ++ x Just (ValList xs) -> sep ++ concat (intersperse sep xs) _ -> "" _ -> "" -- an error condition, really. sufExp :: TemplateFun sufExp env sep vs = case wordsBy "," vs of [_] -> case M.lookup vs (tenv env) of Just (ValStr x) -> x ++ sep Just (ValList xs) -> concat (intersperse sep xs) ++ sep _ -> "" _ -> "" -- an error condition, really. joinExp :: TemplateFun joinExp env sep vs = concat (intersperse sep (go (wordsBy "," vs))) where go [] = [] go (k:ks) = case M.lookup k (tenv env) of Nothing -> go ks Just ValList{} -> go ks -- an error, really. Just (ValStr s) -> (k ++ '=':s ) : go ks listExp :: TemplateFun listExp env sep vs = case wordsBy "," vs of [_] -> case M.lookup vs (tenv env) of Nothing -> "" Just (ValList []) -> "" Just (ValList as) -> concat (intersperse sep as) _ -> "" -- an error, really. _ -> "" -- helpers wordsBy :: String -> String -> [String] wordsBy _ "" = [] wordsBy sep ls = case spanUntil (sep `isPrefixOf`) ls of (as,[]) -> [as] (as,bs) -> as : wordsBy sep (drop (length sep) bs) spanUntil :: ([a] -> Bool) -> [a] -> ([a],[a]) spanUntil _ [] = ([],[]) spanUntil p ls@(x:xs) | p ls = ([],ls) | otherwise = case spanUntil p xs of (as,bs) -> (x:as,bs)