|
| 1 | +{-# LANGUAGE DeriveDataTypeable #-} |
| 2 | +{-# LANGUAGE ExtendedDefaultRules #-} |
| 3 | +{-# LANGUAGE FlexibleContexts #-} |
| 4 | +{-# LANGUAGE FlexibleInstances #-} |
| 5 | +{-# LANGUAGE FunctionalDependencies #-} |
| 6 | +{-# LANGUAGE GeneralizedNewtypeDeriving #-} |
| 7 | +{-# LANGUAGE MultiParamTypeClasses #-} |
| 8 | +{-# LANGUAGE OverloadedStrings #-} |
| 9 | +{-# LANGUAGE TypeSynonymInstances #-} |
| 10 | + |
| 11 | +module Commonmark.Pandoc |
| 12 | + ( CmPandoc(..) |
| 13 | + , CmRangedPandoc(..) |
| 14 | + ) |
| 15 | + |
| 16 | +where |
| 17 | + |
| 18 | +import Data.Semigroup (Semigroup, (<>)) |
| 19 | +import Text.Pandoc.Definition |
| 20 | +import Commonmark.Types |
| 21 | +import Commonmark.Extensions.Math |
| 22 | +import Commonmark.Extensions.PipeTable |
| 23 | +import Commonmark.Extensions.Strikethrough |
| 24 | +import Commonmark.Extensions.Footnote |
| 25 | + |
| 26 | +newtype CmPandoc = CmPandoc { unCmPandoc :: Pandoc } |
| 27 | + deriving (Show, Semigroup, Monoid) |
| 28 | + |
| 29 | +newtype CmRangedPandoc = CmRangedPandoc { unCmRangedPandoc :: Pandoc } |
| 30 | + deriving (Show, Semigroup, Monoid) |
| 31 | + |
| 32 | +{- |
| 33 | +instance IsInline CmPandoc where |
| 34 | + lineBreak = Html5 $ br_ [] <> "\n" |
| 35 | + softBreak = Html5 $ "\n" |
| 36 | + str t = Html5 $ toHtml t |
| 37 | + entity t |
| 38 | + | illegalCodePoint t = Html5 $ toHtml ("\xFFFD" :: Text) |
| 39 | + | otherwise = Html5 $ toHtmlRaw t |
| 40 | + escapedChar c = Html5 $ toHtml (T.singleton c) |
| 41 | + emph ils = Html5 $ em_ $ unHtml5 ils |
| 42 | + strong ils = Html5 $ strong_ $ unHtml5 ils |
| 43 | + link target title ils = Html5 $ |
| 44 | + a_ (href_ (escapeURI target) : [title_ title | not (T.null title)]) |
| 45 | + $ unHtml5 ils |
| 46 | + image target title ils = Html5 $ |
| 47 | + img_ ([src_ (escapeURI target), |
| 48 | + alt_ (renderAlt $ unHtml5 ils)] ++ |
| 49 | + [title_ title | not (T.null title)]) |
| 50 | + code t = Html5 $ code_ (toHtml t) |
| 51 | + rawInline f t |
| 52 | + | f == Format "html" = Html5 $ toHtmlRaw t |
| 53 | + | otherwise = Html5 $ mempty |
| 54 | +
|
| 55 | +escapeURI :: Text -> Text |
| 56 | +escapeURI = T.pack . escapeURIString |
| 57 | + (\c -> isAllowedInURI c && c /= '[' && c /= ']') . T.unpack |
| 58 | +
|
| 59 | +renderAlt :: Html () -> Text |
| 60 | +renderAlt = mconcat . map textOrAlt . parseTags . TL.toStrict . renderText |
| 61 | + where textOrAlt (TagText t) = t |
| 62 | + textOrAlt tag@(TagOpen "img" _) = fromAttrib "alt" tag |
| 63 | + textOrAlt _ = mempty |
| 64 | +
|
| 65 | +illegalCodePoint :: Text -> Bool |
| 66 | +illegalCodePoint t = |
| 67 | + "&#" `T.isPrefixOf` t && |
| 68 | + let t' = T.drop 2 $ T.filter (/=';') t |
| 69 | + badvalue (n, r) = not (T.null r) || |
| 70 | + n < 1 || |
| 71 | + n > (0x10FFFF :: Integer) |
| 72 | + in |
| 73 | + case T.uncons t' of |
| 74 | + Nothing -> True |
| 75 | + Just (x, rest) |
| 76 | + | x == 'x' || x == 'X' |
| 77 | + -> either (const True) badvalue (TR.hexadecimal rest) |
| 78 | + | otherwise |
| 79 | + -> either (const True) badvalue (TR.decimal t') |
| 80 | +
|
| 81 | +instance IsBlock Html5 Html5 where |
| 82 | + paragraph ils = Html5 $ p_ (unHtml5 ils) <> nl |
| 83 | + plain ils = Html5 $ unHtml5 ils <> nl |
| 84 | + thematicBreak = Html5 $ hr_ [] <> nl |
| 85 | + blockQuote bs = Html5 $ blockquote_ (nl <> unHtml5 bs) <> nl |
| 86 | + codeBlock info t = Html5 $ pre_ (with code_ attr (toHtml t)) <> nl |
| 87 | + where attr = [class_ ("language-" <> lang) | not (T.null info)] |
| 88 | + lang = T.takeWhile (not . isSpace) info |
| 89 | + header level ils = Html5 $ h (unHtml5 ils) <> nl |
| 90 | + where h = case level of |
| 91 | + 1 -> h1_ |
| 92 | + 2 -> h2_ |
| 93 | + 3 -> h3_ |
| 94 | + 4 -> h4_ |
| 95 | + 5 -> h5_ |
| 96 | + 6 -> h6_ |
| 97 | + _ -> p_ |
| 98 | + rawBlock f t |
| 99 | + | f == Format "html" = Html5 $ toHtmlRaw t |
| 100 | + | otherwise = Html5 $ mempty |
| 101 | + referenceLinkDefinition _ _ = Html5 $ mempty |
| 102 | + list (BulletList _) lSpacing items = Html5 $ ul_ |
| 103 | + (nl <> mconcat (map (li . unHtml5) items)) <> nl |
| 104 | + where li x = if lSpacing == TightList |
| 105 | + then li_ x <> nl |
| 106 | + else li_ (nl <> x) <> nl |
| 107 | + list (OrderedList startnum _) lSpacing items = Html5 $ with ol_ attr |
| 108 | + (nl <> mconcat (map (li . unHtml5) items)) <> nl |
| 109 | + where li x = if lSpacing == TightList |
| 110 | + then li_ x <> nl |
| 111 | + else li_ (nl <> x) <> nl |
| 112 | + attr = [start_ (T.pack (show startnum)) | startnum /= 1] |
| 113 | +
|
| 114 | +nl :: Html () |
| 115 | +nl = toHtmlRaw ("\n" :: Text) |
| 116 | +
|
| 117 | +newtype RangedHtml5 = RangedHtml5 {unRangedHtml5 :: Html ()} |
| 118 | + deriving (Show, Semigroup, Monoid) |
| 119 | +
|
| 120 | +instance IsInline RangedHtml5 where |
| 121 | + lineBreak = RangedHtml5 $ br_ [] <> "\n" |
| 122 | + softBreak = RangedHtml5 "\n" |
| 123 | + str t = RangedHtml5 $ span_ $ toHtml t |
| 124 | + entity t |
| 125 | + | illegalCodePoint t = RangedHtml5 $ span_ $ toHtml ("\xFFFD" :: Text) |
| 126 | + | otherwise = RangedHtml5 $ span_ $ toHtmlRaw t |
| 127 | + escapedChar c = RangedHtml5 $ span_ $ toHtml (T.singleton c) |
| 128 | + emph ils = RangedHtml5 $ em_ $ unRangedHtml5 ils |
| 129 | + strong ils = RangedHtml5 $ strong_ $ unRangedHtml5 ils |
| 130 | + link target title ils = RangedHtml5 $ |
| 131 | + a_ (href_ (escapeURI target) : [title_ title | not (T.null title)]) |
| 132 | + $ unRangedHtml5 ils |
| 133 | + image target title ils = RangedHtml5 $ |
| 134 | + img_ ([src_ (escapeURI target), alt_ (renderAlt $ unRangedHtml5 ils)] ++ |
| 135 | + [title_ title | not (T.null title)]) |
| 136 | + code t = RangedHtml5 $ code_ (toHtml t) |
| 137 | + rawInline f t |
| 138 | + | f == Format "html" = RangedHtml5 $ span_ $ toHtmlRaw t |
| 139 | + | otherwise = mempty |
| 140 | +
|
| 141 | +instance IsBlock RangedHtml5 RangedHtml5 where |
| 142 | + paragraph ils = RangedHtml5 $ p_ (unRangedHtml5 ils) <> nl |
| 143 | + plain ils = RangedHtml5 $ unRangedHtml5 ils <> nl |
| 144 | + thematicBreak = RangedHtml5 $ hr_ [] <> nl |
| 145 | + blockQuote bs = RangedHtml5 $ blockquote_ (nl <> unRangedHtml5 bs) <> nl |
| 146 | + codeBlock info t = RangedHtml5 $ pre_ (with code_ attr (toHtml t)) <> nl |
| 147 | + where attr = [class_ ("language-" <> lang) | not (T.null info)] |
| 148 | + lang = T.takeWhile (not . isSpace) info |
| 149 | + header level ils = RangedHtml5 $ h (unRangedHtml5 ils) <> nl |
| 150 | + where h = case level of |
| 151 | + 1 -> h1_ |
| 152 | + 2 -> h2_ |
| 153 | + 3 -> h3_ |
| 154 | + 4 -> h4_ |
| 155 | + 5 -> h5_ |
| 156 | + 6 -> h6_ |
| 157 | + _ -> p_ |
| 158 | + rawBlock f t |
| 159 | + | f == Format "html" = RangedHtml5 $ toHtmlRaw t |
| 160 | + | otherwise = mempty |
| 161 | + referenceLinkDefinition _ _ = mempty |
| 162 | + list (BulletList _) lSpacing items = RangedHtml5 $ ul_ |
| 163 | + (nl <> mconcat (map (li . unRangedHtml5) items)) <> nl |
| 164 | + where li x = if lSpacing == TightList |
| 165 | + then li_ x <> nl |
| 166 | + else li_ (nl <> x) <> nl |
| 167 | + list (OrderedList startnum _) lSpacing items = RangedHtml5 $ with ol_ attr |
| 168 | + (nl <> mconcat (map (li . unRangedHtml5) items)) <> nl |
| 169 | + where li x = if lSpacing == TightList |
| 170 | + then li_ x <> nl |
| 171 | + else li_ (nl <> x) <> nl |
| 172 | + attr = [start_ (T.pack (show startnum)) | startnum /= 1] |
| 173 | +
|
| 174 | +instance Rangeable RangedHtml5 where |
| 175 | + ranged r (RangedHtml5 x) = |
| 176 | + RangedHtml5 $ with x [data_ "sourcepos" (T.pack (show r))] |
| 177 | +
|
| 178 | +instance Rangeable Html5 where |
| 179 | + ranged _ x = x |
| 180 | +
|
| 181 | +instance HasMath Html5 where |
| 182 | + inlineMath t = Html5 $ |
| 183 | + span_ [class_ ("math inline")] ("\\(" <> toHtml t <> "\\)") |
| 184 | + displayMath t = Html5 $ |
| 185 | + span_ [class_ ("math display")] ("\\[" <> toHtml t <> "\\]") |
| 186 | +
|
| 187 | +instance HasMath RangedHtml5 where |
| 188 | + inlineMath t = RangedHtml5 (unHtml5 $ inlineMath t) |
| 189 | + displayMath t = RangedHtml5 (unHtml5 $ displayMath t) |
| 190 | +
|
| 191 | +instance HasPipeTable Html5 Html5 where |
| 192 | + pipeTable aligns headerCells rows = Html5 $ do |
| 193 | + let alignToAttr LeftAlignedCol = [style_ "text-align: left;"] |
| 194 | + alignToAttr CenterAlignedCol = [style_ "text-align: center;"] |
| 195 | + alignToAttr RightAlignedCol = [style_ "text-align: right;"] |
| 196 | + alignToAttr DefaultAlignedCol = [] |
| 197 | + let toCell constructor align cell = do |
| 198 | + with constructor (alignToAttr align) (unHtml5 cell) |
| 199 | + "\n" |
| 200 | + table_ $ do |
| 201 | + "\n" |
| 202 | + thead_ $ do |
| 203 | + "\n" |
| 204 | + tr_ $ do |
| 205 | + "\n" |
| 206 | + zipWithM_ (toCell th_) aligns headerCells |
| 207 | + "\n" |
| 208 | + "\n" |
| 209 | + unless (null rows) $ do |
| 210 | + tbody_ $ do |
| 211 | + "\n" |
| 212 | + mapM_ ((>> "\n") . tr_ . ("\n" >>) . |
| 213 | + zipWithM_ (toCell td_) aligns) rows |
| 214 | + "\n" |
| 215 | + "\n" |
| 216 | +
|
| 217 | +instance HasPipeTable RangedHtml5 RangedHtml5 where |
| 218 | + pipeTable aligns headerCells rows = |
| 219 | + RangedHtml5 $ unHtml5 $ pipeTable aligns |
| 220 | + (map (Html5 . unRangedHtml5) headerCells) |
| 221 | + (map (map (Html5 . unRangedHtml5)) rows) |
| 222 | +
|
| 223 | +instance HasStrikethrough Html5 where |
| 224 | + strikethrough ils = Html5 $ del_ (unHtml5 ils) |
| 225 | +
|
| 226 | +instance HasStrikethrough RangedHtml5 where |
| 227 | + strikethrough (RangedHtml5 ils) = RangedHtml5 $ del_ ils |
| 228 | +-} |
0 commit comments