{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
module Text.Pandoc.XML ( escapeCharForXML,
escapeStringForXML,
escapeNCName,
inTags,
selfClosingTag,
inTagsSimple,
inTagsIndented,
toEntities,
toHtml5Entities,
fromEntities,
html4Attributes,
html5Attributes,
rdfaAttributes ) where
import Data.Char (isAscii, isSpace, ord, isLetter, isDigit)
import Data.Text (Text)
import qualified Data.Text as T
import Text.HTML.TagSoup.Entity (lookupEntity, htmlEntities)
import Text.DocLayout
import Text.Printf (printf)
import qualified Data.Map as M
import Data.String
import qualified Data.Set as Set
escapeCharForXML :: Char -> Text
escapeCharForXML :: Char -> Text
escapeCharForXML x :: Char
x = case Char
x of
'&' -> "&"
'<' -> "<"
'>' -> ">"
'"' -> """
c :: Char
c -> Char -> Text
T.singleton Char
c
escapeStringForXML :: Text -> Text
escapeStringForXML :: Text -> Text
escapeStringForXML = (Char -> Text) -> Text -> Text
T.concatMap Char -> Text
escapeCharForXML (Text -> Text) -> (Text -> Text) -> Text -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Char -> Bool) -> Text -> Text
T.filter Char -> Bool
isLegalXMLChar
where isLegalXMLChar :: Char -> Bool
isLegalXMLChar c :: Char
c = Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== '\t' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== '\n' Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== '\r' Bool -> Bool -> Bool
||
(Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
>= '\x20' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= '\xD7FF') Bool -> Bool -> Bool
||
(Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
>= '\xE000' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= '\xFFFD') Bool -> Bool -> Bool
||
(Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
>= '\x10000' Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= '\x10FFFF')
escapeNls :: Text -> Text
escapeNls :: Text -> Text
escapeNls = (Char -> Text) -> Text -> Text
T.concatMap ((Char -> Text) -> Text -> Text) -> (Char -> Text) -> Text -> Text
forall a b. (a -> b) -> a -> b
$ \case
'\n' -> " "
c :: Char
c -> Char -> Text
T.singleton Char
c
attributeList :: (HasChars a, IsString a) => [(Text, Text)] -> Doc a
attributeList :: [(Text, Text)] -> Doc a
attributeList = [Doc a] -> Doc a
forall a. [Doc a] -> Doc a
hcat ([Doc a] -> Doc a)
-> ([(Text, Text)] -> [Doc a]) -> [(Text, Text)] -> Doc a
forall b c a. (b -> c) -> (a -> b) -> a -> c
. ((Text, Text) -> Doc a) -> [(Text, Text)] -> [Doc a]
forall a b. (a -> b) -> [a] -> [b]
map
(\(a :: Text
a, b :: Text
b) -> String -> Doc a
forall a. HasChars a => String -> Doc a
text (Text -> String
T.unpack (Text -> String) -> Text -> String
forall a b. (a -> b) -> a -> b
$ " " Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
escapeStringForXML Text
a Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "=\"" Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
Text -> Text
escapeNls (Text -> Text
escapeStringForXML Text
b) Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> "\""))
inTags :: (HasChars a, IsString a)
=> Bool -> Text -> [(Text, Text)] -> Doc a -> Doc a
inTags :: Bool -> Text -> [(Text, Text)] -> Doc a -> Doc a
inTags isIndented :: Bool
isIndented tagType :: Text
tagType attribs :: [(Text, Text)]
attribs contents :: Doc a
contents =
let openTag :: Doc a
openTag = Char -> Doc a
forall a. HasChars a => Char -> Doc a
char '<' Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> String -> Doc a
forall a. HasChars a => String -> Doc a
text (Text -> String
T.unpack Text
tagType) Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> [(Text, Text)] -> Doc a
forall a. (HasChars a, IsString a) => [(Text, Text)] -> Doc a
attributeList [(Text, Text)]
attribs Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<>
Char -> Doc a
forall a. HasChars a => Char -> Doc a
char '>'
closeTag :: Doc a
closeTag = String -> Doc a
forall a. HasChars a => String -> Doc a
text "</" Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> String -> Doc a
forall a. HasChars a => String -> Doc a
text (Text -> String
T.unpack Text
tagType) Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> Char -> Doc a
forall a. HasChars a => Char -> Doc a
char '>'
in if Bool
isIndented
then Doc a
openTag Doc a -> Doc a -> Doc a
forall a. Doc a -> Doc a -> Doc a
$$ Int -> Doc a -> Doc a
forall a. IsString a => Int -> Doc a -> Doc a
nest 2 Doc a
contents Doc a -> Doc a -> Doc a
forall a. Doc a -> Doc a -> Doc a
$$ Doc a
closeTag
else Doc a
openTag Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> Doc a
contents Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> Doc a
closeTag
selfClosingTag :: (HasChars a, IsString a)
=> Text -> [(Text, Text)] -> Doc a
selfClosingTag :: Text -> [(Text, Text)] -> Doc a
selfClosingTag tagType :: Text
tagType attribs :: [(Text, Text)]
attribs =
Char -> Doc a
forall a. HasChars a => Char -> Doc a
char '<' Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> String -> Doc a
forall a. HasChars a => String -> Doc a
text (Text -> String
T.unpack Text
tagType) Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> [(Text, Text)] -> Doc a
forall a. (HasChars a, IsString a) => [(Text, Text)] -> Doc a
attributeList [(Text, Text)]
attribs Doc a -> Doc a -> Doc a
forall a. Semigroup a => a -> a -> a
<> String -> Doc a
forall a. HasChars a => String -> Doc a
text " />"
inTagsSimple :: (HasChars a, IsString a)
=> Text -> Doc a -> Doc a
inTagsSimple :: Text -> Doc a -> Doc a
inTagsSimple tagType :: Text
tagType = Bool -> Text -> [(Text, Text)] -> Doc a -> Doc a
forall a.
(HasChars a, IsString a) =>
Bool -> Text -> [(Text, Text)] -> Doc a -> Doc a
inTags Bool
False Text
tagType []
inTagsIndented :: (HasChars a, IsString a)
=> Text -> Doc a -> Doc a
inTagsIndented :: Text -> Doc a -> Doc a
inTagsIndented tagType :: Text
tagType = Bool -> Text -> [(Text, Text)] -> Doc a -> Doc a
forall a.
(HasChars a, IsString a) =>
Bool -> Text -> [(Text, Text)] -> Doc a -> Doc a
inTags Bool
True Text
tagType []
toEntities :: Text -> Text
toEntities :: Text -> Text
toEntities = (Char -> Text) -> Text -> Text
T.concatMap Char -> Text
go
where go :: Char -> Text
go c :: Char
c | Char -> Bool
isAscii Char
c = Char -> Text
T.singleton Char
c
| Bool
otherwise = String -> Text
T.pack (String -> Int -> String
forall r. PrintfType r => String -> r
printf "&#x%X;" (Char -> Int
ord Char
c))
toHtml5Entities :: Text -> Text
toHtml5Entities :: Text -> Text
toHtml5Entities = (Char -> Text) -> Text -> Text
T.concatMap Char -> Text
go
where go :: Char -> Text
go c :: Char
c | Char -> Bool
isAscii Char
c = Char -> Text
T.singleton Char
c
| Bool
otherwise =
case Char -> Map Char Text -> Maybe Text
forall k a. Ord k => k -> Map k a -> Maybe a
M.lookup Char
c Map Char Text
html5EntityMap of
Just t :: Text
t -> Char -> Text
T.singleton '&' Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text
t Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Char -> Text
T.singleton ';'
Nothing -> String -> Text
T.pack ("&#" String -> String -> String
forall a. [a] -> [a] -> [a]
++ Int -> String
forall a. Show a => a -> String
show (Char -> Int
ord Char
c) String -> String -> String
forall a. [a] -> [a] -> [a]
++ ";")
html5EntityMap :: M.Map Char Text
html5EntityMap :: Map Char Text
html5EntityMap = ((String, String) -> Map Char Text -> Map Char Text)
-> Map Char Text -> [(String, String)] -> Map Char Text
forall (t :: * -> *) a b.
Foldable t =>
(a -> b -> b) -> b -> t a -> b
foldr (String, String) -> Map Char Text -> Map Char Text
forall k. Ord k => (String, [k]) -> Map k Text -> Map k Text
go Map Char Text
forall a. Monoid a => a
mempty [(String, String)]
htmlEntities
where go :: (String, [k]) -> Map k Text -> Map k Text
go (ent :: String
ent, s :: [k]
s) entmap :: Map k Text
entmap =
case [k]
s of
[c :: k
c] -> (Text -> Text -> Text) -> k -> Text -> Map k Text -> Map k Text
forall k a. Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a
M.insertWith
(\new :: Text
new old :: Text
old -> if Text -> Int
T.length Text
new Int -> Int -> Bool
forall a. Ord a => a -> a -> Bool
> Text -> Int
T.length Text
old
then Text
old
else Text
new) k
c Text
ent' Map k Text
entmap
where ent' :: Text
ent' = (Char -> Bool) -> Text -> Text
T.takeWhile (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
/=';') (String -> Text
T.pack String
ent)
_ -> Map k Text
entmap
escapeNCName :: Text -> Text
escapeNCName :: Text -> Text
escapeNCName t :: Text
t = case Text -> Maybe (Char, Text)
T.uncons Text
t of
Nothing -> Text
T.empty
Just (c :: Char
c, cs :: Text
cs) -> Char -> Text
escapeStartChar Char
c Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> (Char -> Text) -> Text -> Text
T.concatMap Char -> Text
escapeNCNameChar Text
cs
where
escapeStartChar :: Char -> Text
escapeStartChar :: Char -> Text
escapeStartChar c :: Char
c = if Char -> Bool
isLetter Char
c Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== '_'
then Char -> Text
T.singleton Char
c
else Char -> Text
escapeChar Char
c
escapeNCNameChar :: Char -> Text
escapeNCNameChar :: Char -> Text
escapeNCNameChar c :: Char
c = if Char -> Bool
isNCNameChar Char
c
then Char -> Text
T.singleton Char
c
else Char -> Text
escapeChar Char
c
isNCNameChar :: Char -> Bool
isNCNameChar :: Char -> Bool
isNCNameChar c :: Char
c = Char -> Bool
isLetter Char
c Bool -> Bool -> Bool
|| Char
c Char -> String -> Bool
forall (t :: * -> *) a. (Foldable t, Eq a) => a -> t a -> Bool
`elem` ("_-.·" :: String) Bool -> Bool -> Bool
|| Char -> Bool
isDigit Char
c
Bool -> Bool -> Bool
|| '\x0300' Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
c Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= '\x036f'
Bool -> Bool -> Bool
|| '\x203f' Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
c Bool -> Bool -> Bool
&& Char
c Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= '\x2040'
escapeChar :: Char -> Text
escapeChar :: Char -> Text
escapeChar = String -> Text
T.pack (String -> Text) -> (Char -> String) -> Char -> Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. String -> Int -> String
forall r. PrintfType r => String -> r
printf "U%04X" (Int -> String) -> (Char -> Int) -> Char -> String
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Int
ord
fromEntities :: Text -> Text
fromEntities :: Text -> Text
fromEntities t :: Text
t
= let (x :: Text
x, y :: Text
y) = (Char -> Bool) -> Text -> (Text, Text)
T.break (Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== '&') Text
t
in if Text -> Bool
T.null Text
y
then Text
t
else Text
x Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<>
let (ent :: Text
ent, rest :: Text
rest) = (Char -> Bool) -> Text -> (Text, Text)
T.break (\c :: Char
c -> Char -> Bool
isSpace Char
c Bool -> Bool -> Bool
|| Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== ';') Text
y
rest' :: Text
rest' = case Text -> Maybe (Char, Text)
T.uncons Text
rest of
Just (';',ys :: Text
ys) -> Text
ys
_ -> Text
rest
ent' :: Text
ent' = Int -> Text -> Text
T.drop 1 Text
ent Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> ";"
in case String -> Text
T.pack (String -> Text) -> Maybe String -> Maybe Text
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> String -> Maybe String
lookupEntity (Text -> String
T.unpack Text
ent') of
Just c :: Text
c -> Text
c Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
fromEntities Text
rest'
Nothing -> Text
ent Text -> Text -> Text
forall a. Semigroup a => a -> a -> a
<> Text -> Text
fromEntities Text
rest
html5Attributes :: Set.Set Text
html5Attributes :: Set Text
html5Attributes = [Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList
[ "abbr"
, "accept"
, "accept-charset"
, "accesskey"
, "action"
, "allow"
, "allowfullscreen"
, "allowpaymentrequest"
, "allowusermedia"
, "alt"
, "as"
, "async"
, "autocapitalize"
, "autocomplete"
, "autofocus"
, "autoplay"
, "charset"
, "checked"
, "cite"
, "class"
, "color"
, "cols"
, "colspan"
, "content"
, "contenteditable"
, "controls"
, "coords"
, "crossorigin"
, "data"
, "datetime"
, "decoding"
, "default"
, "defer"
, "dir"
, "dirname"
, "disabled"
, "download"
, "draggable"
, "enctype"
, "enterkeyhint"
, "for"
, "form"
, "formaction"
, "formenctype"
, "formmethod"
, "formnovalidate"
, "formtarget"
, "headers"
, "height"
, "hidden"
, "high"
, "href"
, "hreflang"
, "http-equiv"
, "id"
, "imagesizes"
, "imagesrcset"
, "inputmode"
, "integrity"
, "is"
, "ismap"
, "itemid"
, "itemprop"
, "itemref"
, "itemscope"
, "itemtype"
, "kind"
, "label"
, "lang"
, "list"
, "loading"
, "loop"
, "low"
, "manifest"
, "max"
, "maxlength"
, "media"
, "method"
, "min"
, "minlength"
, "multiple"
, "muted"
, "name"
, "nomodule"
, "nonce"
, "novalidate"
, "onabort"
, "onafterprint"
, "onauxclick"
, "onbeforeprint"
, "onbeforeunload"
, "onblur"
, "oncancel"
, "oncanplay"
, "oncanplaythrough"
, "onchange"
, "onclick"
, "onclose"
, "oncontextmenu"
, "oncopy"
, "oncuechange"
, "oncut"
, "ondblclick"
, "ondrag"
, "ondragend"
, "ondragenter"
, "ondragexit"
, "ondragleave"
, "ondragover"
, "ondragstart"
, "ondrop"
, "ondurationchange"
, "onemptied"
, "onended"
, "onerror"
, "onfocus"
, "onhashchange"
, "oninput"
, "oninvalid"
, "onkeydown"
, "onkeypress"
, "onkeyup"
, "onlanguagechange"
, "onload"
, "onloadeddata"
, "onloadedmetadata"
, "onloadend"
, "onloadstart"
, "onmessage"
, "onmessageerror"
, "onmousedown"
, "onmouseenter"
, "onmouseleave"
, "onmousemove"
, "onmouseout"
, "onmouseover"
, "onmouseup"
, "onoffline"
, "ononline"
, "onpagehide"
, "onpageshow"
, "onpaste"
, "onpause"
, "onplay"
, "onplaying"
, "onpopstate"
, "onprogress"
, "onratechange"
, "onrejectionhandled"
, "onreset"
, "onresize"
, "onscroll"
, "onsecuritypolicyviolation"
, "onseeked"
, "onseeking"
, "onselect"
, "onstalled"
, "onstorage"
, "onsubmit"
, "onsuspend"
, "ontimeupdate"
, "ontoggle"
, "onunhandledrejection"
, "onunload"
, "onvolumechange"
, "onwaiting"
, "onwheel"
, "open"
, "optimum"
, "pattern"
, "ping"
, "placeholder"
, "playsinline"
, "poster"
, "preload"
, "readonly"
, "referrerpolicy"
, "rel"
, "required"
, "reversed"
, "role"
, "rows"
, "rowspan"
, "sandbox"
, "scope"
, "selected"
, "shape"
, "size"
, "sizes"
, "slot"
, "span"
, "spellcheck"
, "src"
, "srcdoc"
, "srclang"
, "srcset"
, "start"
, "step"
, "style"
, "tabindex"
, "target"
, "title"
, "translate"
, "type"
, "typemustmatch"
, "updateviacache"
, "usemap"
, "value"
, "width"
, "workertype"
, "wrap"
]
rdfaAttributes :: Set.Set Text
rdfaAttributes :: Set Text
rdfaAttributes = [Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList
[ "about"
, "rel"
, "rev"
, "src"
, "href"
, "resource"
, "property"
, "content"
, "datatype"
, "typeof"
, "vocab"
, "prefix"
]
html4Attributes :: Set.Set Text
html4Attributes :: Set Text
html4Attributes = [Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList
[ "abbr"
, "accept"
, "accept-charset"
, "accesskey"
, "action"
, "align"
, "alink"
, "alt"
, "archive"
, "axis"
, "background"
, "bgcolor"
, "border"
, "cellpadding"
, "cellspacing"
, "char"
, "charoff"
, "charset"
, "checked"
, "cite"
, "class"
, "classid"
, "clear"
, "code"
, "codebase"
, "codetype"
, "color"
, "cols"
, "colspan"
, "compact"
, "content"
, "coords"
, "data"
, "datetime"
, "declare"
, "defer"
, "dir"
, "disabled"
, "enctype"
, "face"
, "for"
, "frame"
, "frameborder"
, "headers"
, "height"
, "href"
, "hreflang"
, "hspace"
, "http-equiv"
, "id"
, "ismap"
, "label"
, "lang"
, "language"
, "link"
, "longdesc"
, "marginheight"
, "marginwidth"
, "maxlength"
, "media"
, "method"
, "multiple"
, "name"
, "nohref"
, "noresize"
, "noshade"
, "nowrap"
, "object"
, "onblur"
, "onchange"
, "onclick"
, "ondblclick"
, "onfocus"
, "onkeydown"
, "onkeypress"
, "onkeyup"
, "onload"
, "onmousedown"
, "onmousemove"
, "onmouseout"
, "onmouseover"
, "onmouseup"
, "onreset"
, "onselect"
, "onsubmit"
, "onunload"
, "profile"
, "prompt"
, "readonly"
, "rel"
, "rev"
, "rows"
, "rowspan"
, "rules"
, "scheme"
, "scope"
, "scrolling"
, "selected"
, "shape"
, "size"
, "span"
, "src"
, "standby"
, "start"
, "style"
, "summary"
, "tabindex"
, "target"
, "text"
, "title"
, "usemap"
, "valign"
, "value"
, "valuetype"
, "version"
, "vlink"
, "vspace"
, "width"
]