Add support for reading and writing <kbd> elements

* Text.Pandoc.Shared: export `htmlSpanLikeElements` [API change]

This commit also introduces a mapping of HTML span like elements that
are internally represented as a Span with a single class, but that are
converted back to the original element by the html writer. As of now,
only the kbd element is handled this way. Ideally these elements should
be handled as plain AST values, but since that would be a breaking
change with a large impact, we revert to this stop-gap solution.

Fixes https://github.com/jgm/pandoc/issues/5796.
This commit is contained in:
Daniele D'Orazio 2019-10-09 10:10:08 +02:00 committed by John MacFarlane
parent a1977dd2d6
commit 1425bf9a65
4 changed files with 74 additions and 26 deletions

View file

@ -62,7 +62,8 @@ import Text.Pandoc.Options (
extensionEnabled) extensionEnabled)
import Text.Pandoc.Parsing hiding ((<|>)) import Text.Pandoc.Parsing hiding ((<|>))
import Text.Pandoc.Shared (addMetaField, blocksToInlines', crFilter, escapeURI, import Text.Pandoc.Shared (addMetaField, blocksToInlines', crFilter, escapeURI,
extractSpaces, onlySimpleTableCells, safeRead, underlineSpan) extractSpaces, htmlSpanLikeElements,
onlySimpleTableCells, safeRead, underlineSpan)
import Text.Pandoc.Walk import Text.Pandoc.Walk
import Text.Parsec.Error import Text.Parsec.Error
import Text.TeXMath (readMathML, writeTeX) import Text.TeXMath (readMathML, writeTeX)
@ -643,6 +644,7 @@ inline = choice
, pStrong , pStrong
, pSuperscript , pSuperscript
, pSubscript , pSubscript
, pSpanLike
, pSmall , pSmall
, pStrikeout , pStrikeout
, pUnderline , pUnderline
@ -707,6 +709,12 @@ pSuperscript = pInlinesInTags "sup" B.superscript
pSubscript :: PandocMonad m => TagParser m Inlines pSubscript :: PandocMonad m => TagParser m Inlines
pSubscript = pInlinesInTags "sub" B.subscript pSubscript = pInlinesInTags "sub" B.subscript
pSpanLike :: PandocMonad m => TagParser m Inlines
pSpanLike = Set.foldr
(\tag acc -> acc <|> pInlinesInTags tag (B.spanWith ("",[T.unpack tag],[])))
mzero
htmlSpanLikeElements
pSmall :: PandocMonad m => TagParser m Inlines pSmall :: PandocMonad m => TagParser m Inlines
pSmall = pInlinesInTags "small" (B.spanWith ("",["small"],[])) pSmall = pInlinesInTags "small" (B.spanWith ("",["small"],[]))

View file

@ -67,6 +67,7 @@ module Text.Pandoc.Shared (
makeMeta, makeMeta,
eastAsianLineBreakFilter, eastAsianLineBreakFilter,
underlineSpan, underlineSpan,
htmlSpanLikeElements,
splitSentences, splitSentences,
filterIpynbOutput, filterIpynbOutput,
-- * TagSoup HTML handling -- * TagSoup HTML handling
@ -694,6 +695,11 @@ eastAsianLineBreakFilter = bottomUp go
underlineSpan :: Inlines -> Inlines underlineSpan :: Inlines -> Inlines
underlineSpan = B.spanWith ("", ["underline"], []) underlineSpan = B.spanWith ("", ["underline"], [])
-- | Set of HTML elements that are represented as Span with a class equal as
-- the element tag itself.
htmlSpanLikeElements :: Set.Set T.Text
htmlSpanLikeElements = Set.fromList [T.pack "kbd"]
-- | Returns the first sentence in a list of inlines, and the rest. -- | Returns the first sentence in a list of inlines, and the rest.
breakSentence :: [Inline] -> ([Inline], [Inline]) breakSentence :: [Inline] -> ([Inline], [Inline])
breakSentence [] = ([],[]) breakSentence [] = ([],[])

View file

@ -1,9 +1,9 @@
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE MultiWayIf #-}
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-} {-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE MultiWayIf #-}
{- | {- |
Module : Text.Pandoc.Writers.HTML Module : Text.Pandoc.Writers.HTML
Copyright : Copyright (C) 2006-2019 John MacFarlane Copyright : Copyright (C) 2006-2019 John MacFarlane
@ -28,27 +28,28 @@ module Text.Pandoc.Writers.HTML (
writeRevealJs, writeRevealJs,
tagWithAttributes tagWithAttributes
) where ) where
import Prelude
import Control.Monad.State.Strict import Control.Monad.State.Strict
import Data.Char (ord, toLower) import Data.Char (ord, toLower)
import Data.List (intercalate, intersperse, isPrefixOf, partition) import Data.List (intercalate, intersperse, isPrefixOf, partition)
import Data.List.Split (splitWhen)
import Data.Maybe (fromMaybe, isJust, isNothing, mapMaybe) import Data.Maybe (fromMaybe, isJust, isNothing, mapMaybe)
import qualified Data.Set as Set import qualified Data.Set as Set
import Data.String (fromString) import Data.String (fromString)
import Data.List.Split (splitWhen)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy as TL
import Text.DocTemplates (FromContext(lookupContext))
import Network.HTTP (urlEncode) import Network.HTTP (urlEncode)
import Network.URI (URI (..), parseURIReference) import Network.URI (URI (..), parseURIReference)
import Numeric (showHex) import Numeric (showHex)
import Text.Blaze.Internal (customLeaf, customParent, MarkupM(Empty)) import Prelude
import Text.Blaze.Internal (MarkupM (Empty), customLeaf, customParent)
import Text.DocTemplates (FromContext (lookupContext))
#if MIN_VERSION_blaze_markup(0,6,3) #if MIN_VERSION_blaze_markup(0,6,3)
#else #else
import Text.Blaze.Internal (preEscapedString, preEscapedText) import Text.Blaze.Internal (preEscapedString, preEscapedText)
#endif #endif
import Text.Blaze.Html hiding (contents) import Text.Blaze.Html hiding (contents)
import Text.DocTemplates (Context (..))
import Text.Pandoc.Definition import Text.Pandoc.Definition
import Text.Pandoc.Highlighting (formatHtmlBlock, formatHtmlInline, highlight, import Text.Pandoc.Highlighting (formatHtmlBlock, formatHtmlInline, highlight,
styleToCss) styleToCss)
@ -57,7 +58,6 @@ import Text.Pandoc.Options
import Text.Pandoc.Shared import Text.Pandoc.Shared
import Text.Pandoc.Slides import Text.Pandoc.Slides
import Text.Pandoc.Templates (renderTemplate) import Text.Pandoc.Templates (renderTemplate)
import Text.DocTemplates (Context(..))
import Text.Pandoc.Walk import Text.Pandoc.Walk
import Text.Pandoc.Writers.Math import Text.Pandoc.Writers.Math
import Text.Pandoc.Writers.Shared import Text.Pandoc.Writers.Shared
@ -412,7 +412,7 @@ tableOfContents opts sects = do
_ -> opts _ -> opts
case toTableOfContents opts sects of case toTableOfContents opts sects of
bl@(BulletList (_:_)) -> Just <$> blockToHtml opts' bl bl@(BulletList (_:_)) -> Just <$> blockToHtml opts' bl
_ -> return Nothing _ -> return Nothing
-- | Convert list of Note blocks to a footnote <div>. -- | Convert list of Note blocks to a footnote <div>.
-- Assumes notes are sorted. -- Assumes notes are sorted.
@ -650,7 +650,7 @@ blockToHtml opts (Div (ident, "section":dclasses, dkvs)
isSec (Div _ zs) = any isSec zs isSec (Div _ zs) = any isSec zs
isSec _ = False isSec _ = False
let isPause (Para [Str ".",Space,Str ".",Space,Str "."]) = True let isPause (Para [Str ".",Space,Str ".",Space,Str "."]) = True
isPause _ = False isPause _ = False
let fragmentClass = case slideVariant of let fragmentClass = case slideVariant of
RevealJsSlides -> "fragment" RevealJsSlides -> "fragment"
_ -> "incremental" _ -> "incremental"
@ -907,9 +907,9 @@ tableRowToHtml :: PandocMonad m
tableRowToHtml opts aligns rownum cols' = do tableRowToHtml opts aligns rownum cols' = do
let mkcell = if rownum == 0 then H.th else H.td let mkcell = if rownum == 0 then H.th else H.td
let rowclass = case rownum of let rowclass = case rownum of
0 -> "header" 0 -> "header"
x | x `rem` 2 == 1 -> "odd" x | x `rem` 2 == 1 -> "odd"
_ -> "even" _ -> "even"
cols'' <- zipWithM cols'' <- zipWithM
(\alignment item -> tableItemToHtml opts mkcell alignment item) (\alignment item -> tableItemToHtml opts mkcell alignment item)
aligns cols' aligns cols'
@ -980,7 +980,7 @@ inlineToHtml :: PandocMonad m
inlineToHtml opts inline = do inlineToHtml opts inline = do
html5 <- gets stHtml5 html5 <- gets stHtml5
case inline of case inline of
(Str str) -> return $ strToHtml str (Str str) -> return $ strToHtml str
Space -> return $ strToHtml " " Space -> return $ strToHtml " "
SoftBreak -> return $ case writerWrapText opts of SoftBreak -> return $ case writerWrapText opts of
WrapNone -> preEscapedString " " WrapNone -> preEscapedString " "
@ -989,22 +989,36 @@ inlineToHtml opts inline = do
LineBreak -> return $ do LineBreak -> return $ do
if html5 then H5.br else H.br if html5 then H5.br else H.br
strToHtml "\n" strToHtml "\n"
(Span (id',classes,kvs) ils)
-> inlineListToHtml opts ils >>= (Span (id',classes,kvs) ils) ->
addAttrs opts attr' . H.span let spanLikeTag = case classes of
where attr' = (id',classes',kvs') [c] -> do
classes' = filter (`notElem` ["csl-no-emph", let c' = T.pack c
"csl-no-strong", guard (c' `Set.member` htmlSpanLikeElements)
"csl-no-smallcaps"]) classes pure $ customParent (textTag c')
_ -> Nothing
in case spanLikeTag of
Just tag -> tag <$> inlineListToHtml opts ils
Nothing -> do
h <- inlineListToHtml opts ils
addAttrs opts (id',classes',kvs') (H.span h)
where
styles = ["font-style:normal;"
| "csl-no-emph" `elem` classes]
++ ["font-weight:normal;"
| "csl-no-strong" `elem` classes]
++ ["font-variant:normal;"
| "csl-no-smallcaps" `elem` classes]
kvs' = if null styles kvs' = if null styles
then kvs then kvs
else ("style", concat styles) : kvs else ("style", concat styles) : kvs
styles = ["font-style:normal;" classes' = [ c | c <- classes
| "csl-no-emph" `elem` classes] , c `notElem` [ "csl-no-emph"
++ ["font-weight:normal;" , "csl-no-strong"
| "csl-no-strong" `elem` classes] , "csl-no-smallcaps"
++ ["font-variant:normal;" ]
| "csl-no-smallcaps" `elem` classes] ]
(Emph lst) -> inlineListToHtml opts lst >>= return . H.em (Emph lst) -> inlineListToHtml opts lst >>= return . H.em
(Strong lst) -> inlineListToHtml opts lst >>= return . H.strong (Strong lst) -> inlineListToHtml opts lst >>= return . H.strong
(Code attr str) -> case hlCode of (Code attr str) -> case hlCode of

20
test/command/5805.md Normal file
View file

@ -0,0 +1,20 @@
```
% pandoc -f html -t html
<kbd>Ctrl-C</kbd>
^D
<kbd>Ctrl-C</kbd>
```
```
% pandoc -f html -t native
<kbd>Ctrl-C</kbd>
^D
[Plain [Span ("",["kbd"],[]) [Str "Ctrl-C"]]]
```
```
% pandoc -f native -t html
[Plain [Span ("",["kbd"],[]) [Str "Ctrl-C"]]]
^D
<kbd>Ctrl-C</kbd>
```