Blame src/Text/Pandoc/Readers/Textile.hs

Packit Service d2f85f
{-
Packit Service d2f85f
Copyright (C) 2010-2012 Paul Rivier <paul*rivier#demotera*com> | tr '*#' '.@'
Packit Service d2f85f
              2010-2017 John MacFarlane
Packit Service d2f85f
Packit Service d2f85f
This program is free software; you can redistribute it and/or modify
Packit Service d2f85f
it under the terms of the GNU General Public License as published by
Packit Service d2f85f
the Free Software Foundation; either version 2 of the License, or
Packit Service d2f85f
(at your option) any later version.
Packit Service d2f85f
Packit Service d2f85f
This program is distributed in the hope that it will be useful,
Packit Service d2f85f
but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service d2f85f
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service d2f85f
GNU General Public License for more details.
Packit Service d2f85f
Packit Service d2f85f
You should have received a copy of the GNU General Public License
Packit Service d2f85f
along with this program; if not, write to the Free Software
Packit Service d2f85f
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Packit Service d2f85f
-}
Packit Service d2f85f
Packit Service d2f85f
{- |
Packit Service d2f85f
   Module      : Text.Pandoc.Readers.Textile
Packit Service d2f85f
   Copyright   : Copyright (C) 2010-2012 Paul Rivier
Packit Service d2f85f
                               2010-2017 John MacFarlane
Packit Service d2f85f
   License     : GNU GPL, version 2 or above
Packit Service d2f85f
Packit Service d2f85f
   Maintainer  : Paul Rivier <paul*rivier#demotera*com>
Packit Service d2f85f
   Stability   : alpha
Packit Service d2f85f
   Portability : portable
Packit Service d2f85f
Packit Service d2f85f
Conversion from Textile to 'Pandoc' document, based on the spec
Packit Service d2f85f
available at http://redcloth.org/textile.
Packit Service d2f85f
Packit Service d2f85f
Implemented and parsed:
Packit Service d2f85f
 - Paragraphs
Packit Service d2f85f
 - Code blocks
Packit Service d2f85f
 - Lists
Packit Service d2f85f
 - blockquote
Packit Service d2f85f
 - Inlines : strong, emph, cite, code, deleted, superscript,
Packit Service d2f85f
   subscript, links
Packit Service d2f85f
 - footnotes
Packit Service d2f85f
 - HTML-specific and CSS-specific attributes on headers
Packit Service d2f85f
Packit Service d2f85f
Left to be implemented:
Packit Service d2f85f
 - dimension sign
Packit Service d2f85f
 - all caps
Packit Service d2f85f
 - continued blocks (ex bq..)
Packit Service d2f85f
Packit Service d2f85f
TODO : refactor common patterns across readers :
Packit Service d2f85f
 - more ...
Packit Service d2f85f
Packit Service d2f85f
-}
Packit Service d2f85f
Packit Service d2f85f
Packit Service d2f85f
module Text.Pandoc.Readers.Textile ( readTextile) where
Packit Service d2f85f
import Control.Monad (guard, liftM)
Packit Service d2f85f
import Control.Monad.Except (throwError)
Packit Service d2f85f
import Data.Char (digitToInt, isUpper)
Packit Service d2f85f
import Data.List (intercalate, intersperse, transpose)
Packit Service d2f85f
import Data.Monoid ((<>))
Packit Service d2f85f
import Data.Text (Text)
Packit Service d2f85f
import qualified Data.Text as T
Packit Service d2f85f
import Text.HTML.TagSoup (Tag (..), fromAttrib)
Packit Service d2f85f
import Text.HTML.TagSoup.Match
Packit Service d2f85f
import Text.Pandoc.Builder (Blocks, Inlines, trimInlines)
Packit Service d2f85f
import qualified Text.Pandoc.Builder as B
Packit Service d2f85f
import Text.Pandoc.Class (PandocMonad (..))
Packit Service d2f85f
import Text.Pandoc.CSS
Packit Service d2f85f
import Text.Pandoc.Definition
Packit Service d2f85f
import Text.Pandoc.Options
Packit Service d2f85f
import Text.Pandoc.Parsing
Packit Service d2f85f
import Text.Pandoc.Readers.HTML (htmlTag, isBlockTag, isInlineTag)
Packit Service d2f85f
import Text.Pandoc.Readers.LaTeX (rawLaTeXBlock, rawLaTeXInline)
Packit Service d2f85f
import Text.Pandoc.Shared (crFilter, trim, underlineSpan)
Packit Service d2f85f
Packit Service d2f85f
-- | Parse a Textile text and return a Pandoc document.
Packit Service d2f85f
readTextile :: PandocMonad m
Packit Service d2f85f
            => ReaderOptions -- ^ Reader options
Packit Service d2f85f
            -> Text          -- ^ String to parse (assuming @'\n'@ line endings)
Packit Service d2f85f
            -> m Pandoc
Packit Service d2f85f
readTextile opts s = do
Packit Service d2f85f
  parsed <- readWithM parseTextile def{ stateOptions = opts }
Packit Service d2f85f
                (T.unpack (crFilter s) ++ "\n\n")
Packit Service d2f85f
  case parsed of
Packit Service d2f85f
     Right result -> return result
Packit Service d2f85f
     Left e       -> throwError e
Packit Service d2f85f
Packit Service d2f85f
Packit Service d2f85f
-- | Generate a Pandoc ADT from a textile document
Packit Service d2f85f
parseTextile :: PandocMonad m => ParserT [Char] ParserState m Pandoc
Packit Service d2f85f
parseTextile = do
Packit Service d2f85f
  many blankline
Packit Service d2f85f
  startPos <- getPosition
Packit Service d2f85f
  -- go through once just to get list of reference keys and notes
Packit Service d2f85f
  -- docMinusKeys is the raw document with blanks where the keys/notes were...
Packit Service d2f85f
  let firstPassParser = noteBlock <|> lineClump
Packit Service d2f85f
  manyTill firstPassParser eof >>= setInput . concat
Packit Service d2f85f
  setPosition startPos
Packit Service d2f85f
  st' <- getState
Packit Service d2f85f
  let reversedNotes = stateNotes st'
Packit Service d2f85f
  updateState $ \s -> s { stateNotes = reverse reversedNotes }
Packit Service d2f85f
  -- now parse it for real...
Packit Service d2f85f
  blocks <- parseBlocks
Packit Service d2f85f
  return $ Pandoc nullMeta (B.toList blocks) -- FIXME
Packit Service d2f85f
Packit Service d2f85f
noteMarker :: PandocMonad m => ParserT [Char] ParserState m [Char]
Packit Service d2f85f
noteMarker = skipMany spaceChar >> string "fn" >> manyTill digit (char '.')
Packit Service d2f85f
Packit Service d2f85f
noteBlock :: PandocMonad m => ParserT [Char] ParserState m [Char]
Packit Service d2f85f
noteBlock = try $ do
Packit Service d2f85f
  startPos <- getPosition
Packit Service d2f85f
  ref <- noteMarker
Packit Service d2f85f
  optional blankline
Packit Service d2f85f
  contents <- fmap unlines $ many1Till anyLine (blanklines <|> noteBlock)
Packit Service d2f85f
  endPos <- getPosition
Packit Service d2f85f
  let newnote = (ref, contents ++ "\n")
Packit Service d2f85f
  st <- getState
Packit Service d2f85f
  let oldnotes = stateNotes st
Packit Service d2f85f
  updateState $ \s -> s { stateNotes = newnote : oldnotes }
Packit Service d2f85f
  -- return blanks so line count isn't affected
Packit Service d2f85f
  return $ replicate (sourceLine endPos - sourceLine startPos) '\n'
Packit Service d2f85f
Packit Service d2f85f
-- | Parse document blocks
Packit Service d2f85f
parseBlocks :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
parseBlocks = mconcat <$> manyTill block eof
Packit Service d2f85f
Packit Service d2f85f
-- | Block parsers list tried in definition order
Packit Service d2f85f
blockParsers :: PandocMonad m => [ParserT [Char] ParserState m Blocks]
Packit Service d2f85f
blockParsers = [ codeBlock
Packit Service d2f85f
               , header
Packit Service d2f85f
               , blockQuote
Packit Service d2f85f
               , hrule
Packit Service d2f85f
               , commentBlock
Packit Service d2f85f
               , anyList
Packit Service d2f85f
               , rawHtmlBlock
Packit Service d2f85f
               , rawLaTeXBlock'
Packit Service d2f85f
               , table
Packit Service d2f85f
               , maybeExplicitBlock "p" para
Packit Service d2f85f
               , mempty <$ blanklines
Packit Service d2f85f
               ]
Packit Service d2f85f
Packit Service d2f85f
-- | Any block in the order of definition of blockParsers
Packit Service d2f85f
block :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
block = do
Packit Service d2f85f
  res <- choice blockParsers  "block"
Packit Service d2f85f
  trace (take 60 $ show $ B.toList res)
Packit Service d2f85f
  return res
Packit Service d2f85f
Packit Service d2f85f
commentBlock :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
commentBlock = try $ do
Packit Service d2f85f
  string "###."
Packit Service d2f85f
  manyTill anyLine blanklines
Packit Service d2f85f
  return mempty
Packit Service d2f85f
Packit Service d2f85f
codeBlock :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
codeBlock = codeBlockBc <|> codeBlockPre
Packit Service d2f85f
Packit Service d2f85f
codeBlockBc :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
codeBlockBc = try $ do
Packit Service d2f85f
  string "bc."
Packit Service d2f85f
  extended <- option False (True <$ char '.')
Packit Service d2f85f
  char ' '
Packit Service d2f85f
  let starts = ["p", "table", "bq", "bc", "h1", "h2", "h3",
Packit Service d2f85f
                "h4", "h5", "h6", "pre", "###", "notextile"]
Packit Service d2f85f
  let ender = choice $ map explicitBlockStart starts
Packit Service d2f85f
  contents <- if extended
Packit Service d2f85f
                 then do
Packit Service d2f85f
                   f <- anyLine
Packit Service d2f85f
                   rest <- many (notFollowedBy ender *> anyLine)
Packit Service d2f85f
                   return (f:rest)
Packit Service d2f85f
                 else manyTill anyLine blanklines
Packit Service d2f85f
  return $ B.codeBlock (trimTrailingNewlines (unlines contents))
Packit Service d2f85f
Packit Service d2f85f
trimTrailingNewlines :: String -> String
Packit Service d2f85f
trimTrailingNewlines = reverse . dropWhile (=='\n') . reverse
Packit Service d2f85f
Packit Service d2f85f
-- | Code Blocks in Textile are between 
 and 
Packit Service d2f85f
codeBlockPre :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
codeBlockPre = try $ do
Packit Service d2f85f
  (t@(TagOpen _ attrs),_) <- htmlTag (tagOpen (=="pre") (const True))
Packit Service d2f85f
  result' <- manyTill anyChar (htmlTag (tagClose (=="pre")))
Packit Service d2f85f
  -- drop leading newline if any
Packit Service d2f85f
  let result'' = case result' of
Packit Service d2f85f
                      '\n':xs -> xs
Packit Service d2f85f
                      _       -> result'
Packit Service d2f85f
  -- drop trailing newline if any
Packit Service d2f85f
  let result''' = case reverse result'' of
Packit Service d2f85f
                       '\n':_ -> init result''
Packit Service d2f85f
                       _      -> result''
Packit Service d2f85f
  let classes = words $ fromAttrib "class" t
Packit Service d2f85f
  let ident = fromAttrib "id" t
Packit Service d2f85f
  let kvs = [(k,v) | (k,v) <- attrs, k /= "id" && k /= "class"]
Packit Service d2f85f
  return $ B.codeBlockWith (ident,classes,kvs) result'''
Packit Service d2f85f
Packit Service d2f85f
-- | Header of the form "hN. content" with N in 1..6
Packit Service d2f85f
header :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
header = try $ do
Packit Service d2f85f
  char 'h'
Packit Service d2f85f
  level <- digitToInt <$> oneOf "123456"
Packit Service d2f85f
  attr <- attributes
Packit Service d2f85f
  char '.'
Packit Service d2f85f
  lookAhead whitespace
Packit Service d2f85f
  name <- trimInlines . mconcat <$> many inline
Packit Service d2f85f
  attr' <- registerHeader attr name
Packit Service d2f85f
  return $ B.headerWith attr' level name
Packit Service d2f85f
Packit Service d2f85f
-- | Blockquote of the form "bq. content"
Packit Service d2f85f
blockQuote :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
blockQuote = try $ do
Packit Service d2f85f
  string "bq" >> attributes >> char '.' >> whitespace
Packit Service d2f85f
  B.blockQuote <$> para
Packit Service d2f85f
Packit Service d2f85f
-- Horizontal rule
Packit Service d2f85f
Packit Service d2f85f
hrule :: PandocMonad m => ParserT [Char] st m Blocks
Packit Service d2f85f
hrule = try $ do
Packit Service d2f85f
  skipSpaces
Packit Service d2f85f
  start <- oneOf "-*"
Packit Service d2f85f
  count 2 (skipSpaces >> char start)
Packit Service d2f85f
  skipMany (spaceChar <|> char start)
Packit Service d2f85f
  newline
Packit Service d2f85f
  optional blanklines
Packit Service d2f85f
  return B.horizontalRule
Packit Service d2f85f
Packit Service d2f85f
-- Lists handling
Packit Service d2f85f
Packit Service d2f85f
-- | Can be a bullet list or an ordered list. This implementation is
Packit Service d2f85f
-- strict in the nesting, sublist must start at exactly "parent depth
Packit Service d2f85f
-- plus one"
Packit Service d2f85f
anyList :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
anyList = try $ anyListAtDepth 1 <* blanklines
Packit Service d2f85f
Packit Service d2f85f
-- | This allow one type of list to be nested into an other type,
Packit Service d2f85f
-- provided correct nesting
Packit Service d2f85f
anyListAtDepth :: PandocMonad m => Int -> ParserT [Char] ParserState m Blocks
Packit Service d2f85f
anyListAtDepth depth = choice [ bulletListAtDepth depth,
Packit Service d2f85f
                                orderedListAtDepth depth,
Packit Service d2f85f
                                definitionList ]
Packit Service d2f85f
Packit Service d2f85f
-- | Bullet List of given depth, depth being the number of leading '*'
Packit Service d2f85f
bulletListAtDepth :: PandocMonad m => Int -> ParserT [Char] ParserState m Blocks
Packit Service d2f85f
bulletListAtDepth depth = try $ B.bulletList  <$> many1 (bulletListItemAtDepth depth)
Packit Service d2f85f
Packit Service d2f85f
-- | Bullet List Item of given depth, depth being the number of
Packit Service d2f85f
-- leading '*'
Packit Service d2f85f
bulletListItemAtDepth :: PandocMonad m => Int -> ParserT [Char] ParserState m Blocks
Packit Service d2f85f
bulletListItemAtDepth = genericListItemAtDepth '*'
Packit Service d2f85f
Packit Service d2f85f
-- | Ordered List of given depth, depth being the number of
Packit Service d2f85f
-- leading '#'
Packit Service d2f85f
orderedListAtDepth :: PandocMonad m => Int -> ParserT [Char] ParserState m Blocks
Packit Service d2f85f
orderedListAtDepth depth = try $ do
Packit Service d2f85f
  items <- many1 (orderedListItemAtDepth depth)
Packit Service d2f85f
  return $ B.orderedList items
Packit Service d2f85f
Packit Service d2f85f
-- | Ordered List Item of given depth, depth being the number of
Packit Service d2f85f
-- leading '#'
Packit Service d2f85f
orderedListItemAtDepth :: PandocMonad m => Int -> ParserT [Char] ParserState m Blocks
Packit Service d2f85f
orderedListItemAtDepth = genericListItemAtDepth '#'
Packit Service d2f85f
Packit Service d2f85f
-- | Common implementation of list items
Packit Service d2f85f
genericListItemAtDepth :: PandocMonad m => Char -> Int -> ParserT [Char] ParserState m Blocks
Packit Service d2f85f
genericListItemAtDepth c depth = try $ do
Packit Service d2f85f
  count depth (char c) >> attributes >> whitespace
Packit Service d2f85f
  contents <- mconcat <$> many ((B.plain . mconcat <$> many1 inline) <|>
Packit Service d2f85f
                                try (newline >> codeBlockPre))
Packit Service d2f85f
  newline
Packit Service d2f85f
  sublist <- option mempty (anyListAtDepth (depth + 1))
Packit Service d2f85f
  return $ contents <> sublist
Packit Service d2f85f
Packit Service d2f85f
-- | A definition list is a set of consecutive definition items
Packit Service d2f85f
definitionList :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
definitionList = try $ B.definitionList <$> many1 definitionListItem
Packit Service d2f85f
Packit Service d2f85f
-- | List start character.
Packit Service d2f85f
listStart :: PandocMonad m => ParserT [Char] ParserState m ()
Packit Service d2f85f
listStart = genericListStart '*'
Packit Service d2f85f
        <|> () <$ genericListStart '#'
Packit Service d2f85f
        <|> () <$ definitionListStart
Packit Service d2f85f
Packit Service d2f85f
genericListStart :: PandocMonad m => Char -> ParserT [Char] st m ()
Packit Service d2f85f
genericListStart c = () <$ try (many1 (char c) >> whitespace)
Packit Service d2f85f
Packit Service d2f85f
basicDLStart :: PandocMonad m => ParserT [Char] ParserState m ()
Packit Service d2f85f
basicDLStart = do
Packit Service d2f85f
  char '-'
Packit Service d2f85f
  whitespace
Packit Service d2f85f
  notFollowedBy newline
Packit Service d2f85f
Packit Service d2f85f
definitionListStart :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
definitionListStart = try $ do
Packit Service d2f85f
  basicDLStart
Packit Service d2f85f
  trimInlines . mconcat <$>
Packit Service d2f85f
    many1Till inline
Packit Service d2f85f
     (  try (newline *> lookAhead basicDLStart)
Packit Service d2f85f
    <|> try (lookAhead (() <$ string ":="))
Packit Service d2f85f
     )
Packit Service d2f85f
Packit Service d2f85f
-- | A definition list item in textile begins with '- ', followed by
Packit Service d2f85f
-- the term defined, then spaces and ":=". The definition follows, on
Packit Service d2f85f
-- the same single line, or spaned on multiple line, after a line
Packit Service d2f85f
-- break.
Packit Service d2f85f
definitionListItem :: PandocMonad m => ParserT [Char] ParserState m (Inlines, [Blocks])
Packit Service d2f85f
definitionListItem = try $ do
Packit Service d2f85f
  term <- (mconcat . intersperse B.linebreak) <$> many1 definitionListStart
Packit Service d2f85f
  def' <- string ":=" *> optional whitespace *> (multilineDef <|> inlineDef)
Packit Service d2f85f
  return (term, def')
Packit Service d2f85f
  where inlineDef :: PandocMonad m => ParserT [Char] ParserState m [Blocks]
Packit Service d2f85f
        inlineDef = liftM (\d -> [B.plain d])
Packit Service d2f85f
                    $ optional whitespace >> (trimInlines . mconcat <$> many inline) <* newline
Packit Service d2f85f
        multilineDef :: PandocMonad m => ParserT [Char] ParserState m [Blocks]
Packit Service d2f85f
        multilineDef = try $ do
Packit Service d2f85f
          optional whitespace >> newline
Packit Service d2f85f
          s <- many1Till anyChar (try (string "=:" >> newline))
Packit Service d2f85f
          -- this ++ "\n\n" does not look very good
Packit Service d2f85f
          ds <- parseFromString' parseBlocks (s ++ "\n\n")
Packit Service d2f85f
          return [ds]
Packit Service d2f85f
Packit Service d2f85f
-- raw content
Packit Service d2f85f
Packit Service d2f85f
-- | A raw Html Block, optionally followed by blanklines
Packit Service d2f85f
rawHtmlBlock :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
rawHtmlBlock = try $ do
Packit Service d2f85f
  skipMany spaceChar
Packit Service d2f85f
  (_,b) <- htmlTag isBlockTag
Packit Service d2f85f
  optional blanklines
Packit Service d2f85f
  return $ B.rawBlock "html" b
Packit Service d2f85f
Packit Service d2f85f
-- | Raw block of LaTeX content
Packit Service d2f85f
rawLaTeXBlock' :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
rawLaTeXBlock' = do
Packit Service d2f85f
  guardEnabled Ext_raw_tex
Packit Service d2f85f
  B.rawBlock "latex" <$> (rawLaTeXBlock <* spaces)
Packit Service d2f85f
Packit Service d2f85f
Packit Service d2f85f
-- | In textile, paragraphs are separated by blank lines.
Packit Service d2f85f
para :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
para = B.para . trimInlines . mconcat <$> many1 inline
Packit Service d2f85f
Packit Service d2f85f
-- Tables
Packit Service d2f85f
Packit Service d2f85f
toAlignment :: Char -> Alignment
Packit Service d2f85f
toAlignment '<' = AlignLeft
Packit Service d2f85f
toAlignment '>' = AlignRight
Packit Service d2f85f
toAlignment '=' = AlignCenter
Packit Service d2f85f
toAlignment _   = AlignDefault
Packit Service d2f85f
Packit Service d2f85f
cellAttributes :: PandocMonad m => ParserT [Char] ParserState m (Bool, Alignment)
Packit Service d2f85f
cellAttributes = try $ do
Packit Service d2f85f
  isHeader <- option False (True <$ char '_')
Packit Service d2f85f
  -- we just ignore colspan and rowspan markers:
Packit Service d2f85f
  optional $ try $ oneOf "/\\" >> many1 digit
Packit Service d2f85f
  -- we pay attention to alignments:
Packit Service d2f85f
  alignment <- option AlignDefault $ toAlignment <$> oneOf "<>="
Packit Service d2f85f
  -- ignore other attributes for now:
Packit Service d2f85f
  _ <- attributes
Packit Service d2f85f
  char '.'
Packit Service d2f85f
  return (isHeader, alignment)
Packit Service d2f85f
Packit Service d2f85f
-- | A table cell spans until a pipe |
Packit Service d2f85f
tableCell :: PandocMonad m => ParserT [Char] ParserState m ((Bool, Alignment), Blocks)
Packit Service d2f85f
tableCell = try $ do
Packit Service d2f85f
  char '|'
Packit Service d2f85f
  (isHeader, alignment) <- option (False, AlignDefault) $ cellAttributes
Packit Service d2f85f
  notFollowedBy blankline
Packit Service d2f85f
  raw <- trim <$>
Packit Service d2f85f
         many (noneOf "|\n" <|> try (char '\n' <* notFollowedBy blankline))
Packit Service d2f85f
  content <- mconcat <$> parseFromString' (many inline) raw
Packit Service d2f85f
  return ((isHeader, alignment), B.plain content)
Packit Service d2f85f
Packit Service d2f85f
-- | A table row is made of many table cells
Packit Service d2f85f
tableRow :: PandocMonad m => ParserT [Char] ParserState m [((Bool, Alignment), Blocks)]
Packit Service d2f85f
tableRow = try $ do
Packit Service d2f85f
  -- skip optional row attributes
Packit Service d2f85f
  optional $ try $ do
Packit Service d2f85f
    _ <- attributes
Packit Service d2f85f
    char '.'
Packit Service d2f85f
    many1 spaceChar
Packit Service d2f85f
  many1 tableCell <* char '|' <* blankline
Packit Service d2f85f
Packit Service d2f85f
-- | A table with an optional header.
Packit Service d2f85f
table :: PandocMonad m => ParserT [Char] ParserState m Blocks
Packit Service d2f85f
table = try $ do
Packit Service d2f85f
  -- ignore table attributes
Packit Service d2f85f
  caption <- option mempty $ try $ do
Packit Service d2f85f
    string "table"
Packit Service d2f85f
    _ <- attributes
Packit Service d2f85f
    char '.'
Packit Service d2f85f
    rawcapt <- trim <$> anyLine
Packit Service d2f85f
    parseFromString' (mconcat <$> many inline) rawcapt
Packit Service d2f85f
  rawrows <- many1 $ skipMany ignorableRow >> tableRow
Packit Service d2f85f
  skipMany ignorableRow
Packit Service d2f85f
  blanklines
Packit Service d2f85f
  let (headers, rows) = case rawrows of
Packit Service d2f85f
                             (toprow:rest) | any (fst . fst) toprow ->
Packit Service d2f85f
                                (toprow, rest)
Packit Service d2f85f
                             _ -> (mempty, rawrows)
Packit Service d2f85f
  let nbOfCols = max (length headers) (length $ head rows)
Packit Service d2f85f
  let aligns = map minimum $ transpose $ map (map (snd . fst)) (headers:rows)
Packit Service d2f85f
  return $ B.table caption
Packit Service d2f85f
    (zip aligns (replicate nbOfCols 0.0))
Packit Service d2f85f
    (map snd headers)
Packit Service d2f85f
    (map (map snd) rows)
Packit Service d2f85f
Packit Service d2f85f
-- | Ignore markers for cols, thead, tfoot.
Packit Service d2f85f
ignorableRow :: PandocMonad m => ParserT [Char] ParserState m ()
Packit Service d2f85f
ignorableRow = try $ do
Packit Service d2f85f
  char '|'
Packit Service d2f85f
  oneOf ":^-~"
Packit Service d2f85f
  _ <- attributes
Packit Service d2f85f
  char '.'
Packit Service d2f85f
  _ <- anyLine
Packit Service d2f85f
  return ()
Packit Service d2f85f
Packit Service d2f85f
explicitBlockStart :: PandocMonad m => String -> ParserT [Char] ParserState m ()
Packit Service d2f85f
explicitBlockStart name = try $ do
Packit Service d2f85f
  string name
Packit Service d2f85f
  attributes
Packit Service d2f85f
  char '.'
Packit Service d2f85f
  optional whitespace
Packit Service d2f85f
  optional endline
Packit Service d2f85f
Packit Service d2f85f
-- | Blocks like 'p' and 'table' do not need explicit block tag.
Packit Service d2f85f
-- However, they can be used to set HTML/CSS attributes when needed.
Packit Service d2f85f
maybeExplicitBlock :: PandocMonad m
Packit Service d2f85f
                   => String  -- ^ block tag name
Packit Service d2f85f
                   -> ParserT [Char] ParserState m Blocks -- ^ implicit block
Packit Service d2f85f
                   -> ParserT [Char] ParserState m Blocks
Packit Service d2f85f
maybeExplicitBlock name blk = try $ do
Packit Service d2f85f
  optional $ explicitBlockStart name
Packit Service d2f85f
  blk
Packit Service d2f85f
Packit Service d2f85f
Packit Service d2f85f
Packit Service d2f85f
----------
Packit Service d2f85f
-- Inlines
Packit Service d2f85f
----------
Packit Service d2f85f
Packit Service d2f85f
Packit Service d2f85f
-- | Any inline element
Packit Service d2f85f
inline :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
inline = choice inlineParsers  "inline"
Packit Service d2f85f
Packit Service d2f85f
-- | Inline parsers tried in order
Packit Service d2f85f
inlineParsers :: PandocMonad m => [ParserT [Char] ParserState m Inlines]
Packit Service d2f85f
inlineParsers = [ str
Packit Service d2f85f
                , whitespace
Packit Service d2f85f
                , endline
Packit Service d2f85f
                , code
Packit Service d2f85f
                , escapedInline
Packit Service d2f85f
                , inlineMarkup
Packit Service d2f85f
                , groupedInlineMarkup
Packit Service d2f85f
                , rawHtmlInline
Packit Service d2f85f
                , rawLaTeXInline'
Packit Service d2f85f
                , note
Packit Service d2f85f
                , link
Packit Service d2f85f
                , image
Packit Service d2f85f
                , mark
Packit Service d2f85f
                , (B.str . (:[])) <$> characterReference
Packit Service d2f85f
                , smartPunctuation inline
Packit Service d2f85f
                , symbol
Packit Service d2f85f
                ]
Packit Service d2f85f
Packit Service d2f85f
-- | Inline markups
Packit Service d2f85f
inlineMarkup :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
inlineMarkup = choice [ simpleInline (string "??") (B.cite [])
Packit Service d2f85f
                      , simpleInline (string "**") B.strong
Packit Service d2f85f
                      , simpleInline (string "__") B.emph
Packit Service d2f85f
                      , simpleInline (char '*') B.strong
Packit Service d2f85f
                      , simpleInline (char '_') B.emph
Packit Service d2f85f
                      , simpleInline (char '+') underlineSpan
Packit Service d2f85f
                      , simpleInline (char '-' <* notFollowedBy (char '-')) B.strikeout
Packit Service d2f85f
                      , simpleInline (char '^') B.superscript
Packit Service d2f85f
                      , simpleInline (char '~') B.subscript
Packit Service d2f85f
                      , simpleInline (char '%') id
Packit Service d2f85f
                      ]
Packit Service d2f85f
Packit Service d2f85f
-- | Trademark, registered, copyright
Packit Service d2f85f
mark :: PandocMonad m => ParserT [Char] st m Inlines
Packit Service d2f85f
mark = try $ char '(' >> (try tm <|> try reg <|> copy)
Packit Service d2f85f
Packit Service d2f85f
reg :: PandocMonad m => ParserT [Char] st m Inlines
Packit Service d2f85f
reg = do
Packit Service d2f85f
  oneOf "Rr"
Packit Service d2f85f
  char ')'
Packit Service d2f85f
  return $ B.str "\174"
Packit Service d2f85f
Packit Service d2f85f
tm :: PandocMonad m => ParserT [Char] st m Inlines
Packit Service d2f85f
tm = do
Packit Service d2f85f
  oneOf "Tt"
Packit Service d2f85f
  oneOf "Mm"
Packit Service d2f85f
  char ')'
Packit Service d2f85f
  return $ B.str "\8482"
Packit Service d2f85f
Packit Service d2f85f
copy :: PandocMonad m => ParserT [Char] st m Inlines
Packit Service d2f85f
copy = do
Packit Service d2f85f
  oneOf "Cc"
Packit Service d2f85f
  char ')'
Packit Service d2f85f
  return $ B.str "\169"
Packit Service d2f85f
Packit Service d2f85f
note :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
note = try $ do
Packit Service d2f85f
  ref <- (char '[' *> many1 digit <* char ']')
Packit Service d2f85f
  notes <- stateNotes <$> getState
Packit Service d2f85f
  case lookup ref notes of
Packit Service d2f85f
    Nothing  -> fail "note not found"
Packit Service d2f85f
    Just raw -> B.note <$> parseFromString' parseBlocks raw
Packit Service d2f85f
Packit Service d2f85f
-- | Special chars
Packit Service d2f85f
markupChars :: [Char]
Packit Service d2f85f
markupChars = "\\*#_@~-+^|%=[]&"
Packit Service d2f85f
Packit Service d2f85f
-- | Break strings on following chars. Space tab and newline break for
Packit Service d2f85f
--  inlines breaking. Open paren breaks for mark. Quote, dash and dot
Packit Service d2f85f
--  break for smart punctuation. Punctuation breaks for regular
Packit Service d2f85f
--  punctuation. Double quote breaks for named links. > and < break
Packit Service d2f85f
--  for inline html.
Packit Service d2f85f
stringBreakers :: [Char]
Packit Service d2f85f
stringBreakers = " \t\n\r.,\"'?!;:<>«»„“”‚‘’()[]"
Packit Service d2f85f
Packit Service d2f85f
wordBoundaries :: [Char]
Packit Service d2f85f
wordBoundaries = markupChars ++ stringBreakers
Packit Service d2f85f
Packit Service d2f85f
-- | Parse a hyphened sequence of words
Packit Service d2f85f
hyphenedWords :: PandocMonad m => ParserT [Char] ParserState m String
Packit Service d2f85f
hyphenedWords = do
Packit Service d2f85f
  x <- wordChunk
Packit Service d2f85f
  xs <-  many (try $ char '-' >> wordChunk)
Packit Service d2f85f
  return $ intercalate "-" (x:xs)
Packit Service d2f85f
Packit Service d2f85f
wordChunk :: PandocMonad m => ParserT [Char] ParserState m String
Packit Service d2f85f
wordChunk = try $ do
Packit Service d2f85f
  hd <- noneOf wordBoundaries
Packit Service d2f85f
  tl <- many ( (noneOf wordBoundaries) <|>
Packit Service d2f85f
               try (notFollowedBy' note *> oneOf markupChars
Packit Service d2f85f
                     <* lookAhead (noneOf wordBoundaries) ) )
Packit Service d2f85f
  return $ hd:tl
Packit Service d2f85f
Packit Service d2f85f
-- | Any string
Packit Service d2f85f
str :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
str = do
Packit Service d2f85f
  baseStr <- hyphenedWords
Packit Service d2f85f
  -- RedCloth compliance : if parsed word is uppercase and immediatly
Packit Service d2f85f
  -- followed by parens, parens content is unconditionally word acronym
Packit Service d2f85f
  fullStr <- option baseStr $ try $ do
Packit Service d2f85f
    guard $ all isUpper baseStr
Packit Service d2f85f
    acro <- enclosed (char '(') (char ')') anyChar'
Packit Service d2f85f
    return $ concat [baseStr, " (", acro, ")"]
Packit Service d2f85f
  updateLastStrPos
Packit Service d2f85f
  return $ B.str fullStr
Packit Service d2f85f
Packit Service d2f85f
-- | Some number of space chars
Packit Service d2f85f
whitespace :: PandocMonad m => ParserT [Char] st m Inlines
Packit Service d2f85f
whitespace = many1 spaceChar >> return B.space  "whitespace"
Packit Service d2f85f
Packit Service d2f85f
-- | In Textile, an isolated endline character is a line break
Packit Service d2f85f
endline :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
endline = try $ do
Packit Service d2f85f
  newline
Packit Service d2f85f
  notFollowedBy blankline
Packit Service d2f85f
  notFollowedBy listStart
Packit Service d2f85f
  notFollowedBy rawHtmlBlock
Packit Service d2f85f
  return B.linebreak
Packit Service d2f85f
Packit Service d2f85f
rawHtmlInline :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
rawHtmlInline = B.rawInline "html" . snd <$> htmlTag isInlineTag
Packit Service d2f85f
Packit Service d2f85f
-- | Raw LaTeX Inline
Packit Service d2f85f
rawLaTeXInline' :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
rawLaTeXInline' = try $ do
Packit Service d2f85f
  guardEnabled Ext_raw_tex
Packit Service d2f85f
  B.rawInline "latex" <$> rawLaTeXInline
Packit Service d2f85f
Packit Service d2f85f
-- | Textile standard link syntax is "label":target. But we
Packit Service d2f85f
-- can also have ["label":target].
Packit Service d2f85f
link :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
link = try $ do
Packit Service d2f85f
  bracketed <- (True <$ char '[') <|> return False
Packit Service d2f85f
  char '"' *> notFollowedBy (oneOf " \t\n\r")
Packit Service d2f85f
  attr <- attributes
Packit Service d2f85f
  name <- trimInlines . mconcat <$>
Packit Service d2f85f
          withQuoteContext InDoubleQuote (many1Till inline (char '"'))
Packit Service d2f85f
  char ':'
Packit Service d2f85f
  let stop = if bracketed
Packit Service d2f85f
                then char ']'
Packit Service d2f85f
                else lookAhead $ space <|> eof' <|>
Packit Service d2f85f
                       try (oneOf "!.,;:" *>
Packit Service d2f85f
                              (space <|> newline <|> eof'))
Packit Service d2f85f
  url <- many1Till nonspaceChar stop
Packit Service d2f85f
  let name' = if B.toList name == [Str "$"] then B.str url else name
Packit Service d2f85f
  return $ if attr == nullAttr
Packit Service d2f85f
              then B.link url "" name'
Packit Service d2f85f
              else B.spanWith attr $ B.link url "" name'
Packit Service d2f85f
Packit Service d2f85f
-- | image embedding
Packit Service d2f85f
image :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
image = try $ do
Packit Service d2f85f
  char '!' >> notFollowedBy space
Packit Service d2f85f
  (ident, cls, kvs) <- attributes
Packit Service d2f85f
  let attr = case lookup "style" kvs of
Packit Service d2f85f
               Just stls -> (ident, cls, pickStylesToKVs ["width", "height"] stls)
Packit Service d2f85f
               Nothing   -> (ident, cls, kvs)
Packit Service d2f85f
  src <- many1 (noneOf " \t\n\r!(")
Packit Service d2f85f
  alt <- option "" $ try $ char '(' *> manyTill anyChar (char ')')
Packit Service d2f85f
  char '!'
Packit Service d2f85f
  return $ B.imageWith attr src alt (B.str alt)
Packit Service d2f85f
Packit Service d2f85f
escapedInline :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
escapedInline = escapedEqs <|> escapedTag
Packit Service d2f85f
Packit Service d2f85f
escapedEqs :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
escapedEqs = B.str <$>
Packit Service d2f85f
  try (string "==" *> manyTill anyChar' (try $ string "=="))
Packit Service d2f85f
Packit Service d2f85f
-- | literal text escaped btw <notextile> tags
Packit Service d2f85f
escapedTag :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
escapedTag = B.str <$>
Packit Service d2f85f
  (try $ string "<notextile>" *>
Packit Service d2f85f
         manyTill anyChar' (try $ string "</notextile>"))
Packit Service d2f85f
Packit Service d2f85f
-- | Any special symbol defined in wordBoundaries
Packit Service d2f85f
symbol :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
symbol = B.str . singleton <$> (notFollowedBy newline *>
Packit Service d2f85f
                                notFollowedBy rawHtmlBlock *>
Packit Service d2f85f
                                oneOf wordBoundaries)
Packit Service d2f85f
Packit Service d2f85f
-- | Inline code
Packit Service d2f85f
code :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
code = code1 <|> code2
Packit Service d2f85f
Packit Service d2f85f
-- any character except a newline before a blank line
Packit Service d2f85f
anyChar' :: PandocMonad m => ParserT [Char] ParserState m Char
Packit Service d2f85f
anyChar' =
Packit Service d2f85f
  satisfy (/='\n') <|> (try $ char '\n' <* notFollowedBy blankline)
Packit Service d2f85f
Packit Service d2f85f
code1 :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
code1 = B.code <$> surrounded (char '@') anyChar'
Packit Service d2f85f
Packit Service d2f85f
code2 :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
code2 = do
Packit Service d2f85f
  htmlTag (tagOpen (=="tt") null)
Packit Service d2f85f
  B.code <$> manyTill anyChar' (try $ htmlTag $ tagClose (=="tt"))
Packit Service d2f85f
Packit Service d2f85f
-- | Html / CSS attributes
Packit Service d2f85f
attributes :: PandocMonad m => ParserT [Char] ParserState m Attr
Packit Service d2f85f
attributes = foldl (flip ($)) ("",[],[]) <$>
Packit Service d2f85f
  try (do special <- option id specialAttribute
Packit Service d2f85f
          attrs <- many attribute
Packit Service d2f85f
          return (special : attrs))
Packit Service d2f85f
Packit Service d2f85f
specialAttribute :: PandocMonad m => ParserT [Char] ParserState m (Attr -> Attr)
Packit Service d2f85f
specialAttribute = do
Packit Service d2f85f
  alignStr <- ("center" <$ char '=') <|>
Packit Service d2f85f
    ("justify" <$ try (string "<>")) <|>
Packit Service d2f85f
    ("right" <$ char '>') <|>
Packit Service d2f85f
    ("left" <$ char '<')
Packit Service d2f85f
  notFollowedBy spaceChar
Packit Service d2f85f
  return $ addStyle ("text-align:" ++ alignStr)
Packit Service d2f85f
Packit Service d2f85f
attribute :: PandocMonad m => ParserT [Char] ParserState m (Attr -> Attr)
Packit Service d2f85f
attribute = try $
Packit Service d2f85f
  (classIdAttr <|> styleAttr <|> langAttr) <* notFollowedBy spaceChar
Packit Service d2f85f
Packit Service d2f85f
classIdAttr :: PandocMonad m => ParserT [Char] ParserState m (Attr -> Attr)
Packit Service d2f85f
classIdAttr = try $ do -- (class class #id)
Packit Service d2f85f
  char '('
Packit Service d2f85f
  ws <- words `fmap` manyTill anyChar' (char ')')
Packit Service d2f85f
  case reverse ws of
Packit Service d2f85f
       []                      -> return $ \(_,_,keyvals) -> ("",[],keyvals)
Packit Service d2f85f
       (('#':ident'):classes') -> return $ \(_,_,keyvals) ->
Packit Service d2f85f
                                             (ident',classes',keyvals)
Packit Service d2f85f
       classes'                -> return $ \(_,_,keyvals) ->
Packit Service d2f85f
                                             ("",classes',keyvals)
Packit Service d2f85f
Packit Service d2f85f
styleAttr :: PandocMonad m => ParserT [Char] ParserState m (Attr -> Attr)
Packit Service d2f85f
styleAttr = do
Packit Service d2f85f
  style <- try $ enclosed (char '{') (char '}') anyChar'
Packit Service d2f85f
  return $ addStyle style
Packit Service d2f85f
Packit Service d2f85f
addStyle :: String -> Attr -> Attr
Packit Service d2f85f
addStyle style (id',classes,keyvals) =
Packit Service d2f85f
  (id',classes,keyvals')
Packit Service d2f85f
  where keyvals' = ("style", style') : [(k,v) | (k,v) <- keyvals, k /= "style"]
Packit Service d2f85f
        style' = style ++ ";" ++ concat [v | ("style",v) <- keyvals]
Packit Service d2f85f
Packit Service d2f85f
langAttr :: PandocMonad m => ParserT [Char] ParserState m (Attr -> Attr)
Packit Service d2f85f
langAttr = do
Packit Service d2f85f
  lang <- try $ enclosed (char '[') (char ']') alphaNum
Packit Service d2f85f
  return $ \(id',classes,keyvals) -> (id',classes,("lang",lang):keyvals)
Packit Service d2f85f
Packit Service d2f85f
-- | Parses material surrounded by a parser.
Packit Service d2f85f
surrounded :: (PandocMonad m, Show t)
Packit Service d2f85f
           => ParserT [Char] st m t   -- ^ surrounding parser
Packit Service d2f85f
           -> ParserT [Char] st m a   -- ^ content parser (to be used repeatedly)
Packit Service d2f85f
           -> ParserT [Char] st m [a]
Packit Service d2f85f
surrounded border =
Packit Service d2f85f
  enclosed (border *> notFollowedBy (oneOf " \t\n\r")) (try border)
Packit Service d2f85f
Packit Service d2f85f
simpleInline :: PandocMonad m
Packit Service d2f85f
             => ParserT [Char] ParserState m t        -- ^ surrounding parser
Packit Service d2f85f
             -> (Inlines -> Inlines)                  -- ^ Inline constructor
Packit Service d2f85f
             -> ParserT [Char] ParserState m Inlines  -- ^ content parser (to be used repeatedly)
Packit Service d2f85f
simpleInline border construct = try $ do
Packit Service d2f85f
  notAfterString
Packit Service d2f85f
  border *> notFollowedBy (oneOf " \t\n\r")
Packit Service d2f85f
  attr <- attributes
Packit Service d2f85f
  body <- trimInlines . mconcat <$>
Packit Service d2f85f
          withQuoteContext InSingleQuote
Packit Service d2f85f
            (manyTill (notFollowedBy newline >> inline)
Packit Service d2f85f
             (try border <* notFollowedBy alphaNum))
Packit Service d2f85f
  return $ construct $
Packit Service d2f85f
        if attr == nullAttr
Packit Service d2f85f
           then body
Packit Service d2f85f
           else B.spanWith attr body
Packit Service d2f85f
Packit Service d2f85f
groupedInlineMarkup :: PandocMonad m => ParserT [Char] ParserState m Inlines
Packit Service d2f85f
groupedInlineMarkup = try $ do
Packit Service d2f85f
    char '['
Packit Service d2f85f
    sp1 <- option mempty $ B.space <$ whitespace
Packit Service d2f85f
    result <- withQuoteContext InSingleQuote inlineMarkup
Packit Service d2f85f
    sp2 <- option mempty $ B.space <$ whitespace
Packit Service d2f85f
    char ']'
Packit Service d2f85f
    return $ sp1 <> result <> sp2
Packit Service d2f85f
Packit Service d2f85f
-- | Create a singleton list
Packit Service d2f85f
singleton :: a -> [a]
Packit Service d2f85f
singleton x = [x]
Packit Service d2f85f
Packit Service d2f85f
eof' :: Monad m => ParserT [Char] s m Char
Packit Service d2f85f
eof' = '\n' <$ eof