uri-template-0.2: Network/URI/Template.hs
{- |
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)