Org reader: put tree parsing code into dedicated module
This commit is contained in:
parent
af4bf91c59
commit
a27e2e8a4e
3 changed files with 263 additions and 210 deletions
|
@ -445,6 +445,7 @@ Library
|
||||||
Text.Pandoc.Readers.Odt.Arrows.Utils,
|
Text.Pandoc.Readers.Odt.Arrows.Utils,
|
||||||
Text.Pandoc.Readers.Org.BlockStarts,
|
Text.Pandoc.Readers.Org.BlockStarts,
|
||||||
Text.Pandoc.Readers.Org.Blocks,
|
Text.Pandoc.Readers.Org.Blocks,
|
||||||
|
Text.Pandoc.Readers.Org.DocumentTree,
|
||||||
Text.Pandoc.Readers.Org.ExportSettings,
|
Text.Pandoc.Readers.Org.ExportSettings,
|
||||||
Text.Pandoc.Readers.Org.Inlines,
|
Text.Pandoc.Readers.Org.Inlines,
|
||||||
Text.Pandoc.Readers.Org.Meta,
|
Text.Pandoc.Readers.Org.Meta,
|
||||||
|
|
|
@ -33,6 +33,7 @@ module Text.Pandoc.Readers.Org.Blocks
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Text.Pandoc.Readers.Org.BlockStarts
|
import Text.Pandoc.Readers.Org.BlockStarts
|
||||||
|
import Text.Pandoc.Readers.Org.DocumentTree (headline, headlineToBlocks)
|
||||||
import Text.Pandoc.Readers.Org.Inlines
|
import Text.Pandoc.Readers.Org.Inlines
|
||||||
import Text.Pandoc.Readers.Org.Meta (metaExport, metaKey, metaLine)
|
import Text.Pandoc.Readers.Org.Meta (metaExport, metaKey, metaLine)
|
||||||
import Text.Pandoc.Readers.Org.ParserState
|
import Text.Pandoc.Readers.Org.ParserState
|
||||||
|
@ -54,196 +55,6 @@ import Data.List (foldl', isPrefixOf)
|
||||||
import Data.Maybe (fromMaybe, isNothing)
|
import Data.Maybe (fromMaybe, isNothing)
|
||||||
import Data.Monoid ((<>))
|
import Data.Monoid ((<>))
|
||||||
|
|
||||||
--
|
|
||||||
-- Org headers
|
|
||||||
--
|
|
||||||
newtype Tag = Tag { fromTag :: String }
|
|
||||||
deriving (Show, Eq)
|
|
||||||
|
|
||||||
-- | Create a tag containing the given string.
|
|
||||||
toTag :: String -> Tag
|
|
||||||
toTag = Tag
|
|
||||||
|
|
||||||
-- | The key (also called name or type) of a property.
|
|
||||||
newtype PropertyKey = PropertyKey { fromKey :: String }
|
|
||||||
deriving (Show, Eq, Ord)
|
|
||||||
|
|
||||||
-- | Create a property key containing the given string. Org mode keys are
|
|
||||||
-- case insensitive and are hence converted to lower case.
|
|
||||||
toPropertyKey :: String -> PropertyKey
|
|
||||||
toPropertyKey = PropertyKey . map toLower
|
|
||||||
|
|
||||||
-- | The value assigned to a property.
|
|
||||||
newtype PropertyValue = PropertyValue { fromValue :: String }
|
|
||||||
|
|
||||||
-- | Create a property value containing the given string.
|
|
||||||
toPropertyValue :: String -> PropertyValue
|
|
||||||
toPropertyValue = PropertyValue
|
|
||||||
|
|
||||||
-- | Check whether the property value is non-nil (i.e. truish).
|
|
||||||
isNonNil :: PropertyValue -> Bool
|
|
||||||
isNonNil p = map toLower (fromValue p) `notElem` ["()", "{}", "nil"]
|
|
||||||
|
|
||||||
-- | Key/value pairs from a PROPERTIES drawer
|
|
||||||
type Properties = [(PropertyKey, PropertyValue)]
|
|
||||||
|
|
||||||
-- | Org mode headline (i.e. a document subtree).
|
|
||||||
data Headline = Headline
|
|
||||||
{ headlineLevel :: Int
|
|
||||||
, headlineTodoMarker :: Maybe TodoMarker
|
|
||||||
, headlineText :: Inlines
|
|
||||||
, headlineTags :: [Tag]
|
|
||||||
, headlineProperties :: Properties
|
|
||||||
, headlineContents :: Blocks
|
|
||||||
, headlineChildren :: [Headline]
|
|
||||||
}
|
|
||||||
|
|
||||||
--
|
|
||||||
-- Parsing headlines and subtrees
|
|
||||||
--
|
|
||||||
|
|
||||||
-- | Read an Org mode headline and its contents (i.e. a document subtree).
|
|
||||||
-- @lvl@ gives the minimum acceptable level of the tree.
|
|
||||||
headline :: PandocMonad m => Int -> OrgParser m (F Headline)
|
|
||||||
headline lvl = try $ do
|
|
||||||
level <- headerStart
|
|
||||||
guard (lvl <= level)
|
|
||||||
todoKw <- optionMaybe todoKeyword
|
|
||||||
title <- trimInlinesF . mconcat <$> manyTill inline endOfTitle
|
|
||||||
tags <- option [] headerTags
|
|
||||||
newline
|
|
||||||
properties <- option mempty propertiesDrawer
|
|
||||||
contents <- blocks
|
|
||||||
children <- many (headline (level + 1))
|
|
||||||
return $ do
|
|
||||||
title' <- title
|
|
||||||
contents' <- contents
|
|
||||||
children' <- sequence children
|
|
||||||
return $ Headline
|
|
||||||
{ headlineLevel = level
|
|
||||||
, headlineTodoMarker = todoKw
|
|
||||||
, headlineText = title'
|
|
||||||
, headlineTags = tags
|
|
||||||
, headlineProperties = properties
|
|
||||||
, headlineContents = contents'
|
|
||||||
, headlineChildren = children'
|
|
||||||
}
|
|
||||||
where
|
|
||||||
endOfTitle :: Monad m => OrgParser m ()
|
|
||||||
endOfTitle = void . lookAhead $ optional headerTags *> newline
|
|
||||||
|
|
||||||
headerTags :: Monad m => OrgParser m [Tag]
|
|
||||||
headerTags = try $
|
|
||||||
let tag = many1 (alphaNum <|> oneOf "@%#_") <* char ':'
|
|
||||||
in map toTag <$> (skipSpaces *> char ':' *> many1 tag <* skipSpaces)
|
|
||||||
|
|
||||||
-- | Convert an Org mode headline (i.e. a document tree) into pandoc's Blocks
|
|
||||||
headlineToBlocks :: Monad m => Headline -> OrgParser m Blocks
|
|
||||||
headlineToBlocks hdln@(Headline {..}) = do
|
|
||||||
maxHeadlineLevels <- getExportSetting exportHeadlineLevels
|
|
||||||
case () of
|
|
||||||
_ | any isNoExportTag headlineTags -> return mempty
|
|
||||||
_ | any isArchiveTag headlineTags -> archivedHeadlineToBlocks hdln
|
|
||||||
_ | isCommentTitle headlineText -> return mempty
|
|
||||||
_ | headlineLevel >= maxHeadlineLevels -> headlineToHeaderWithList hdln
|
|
||||||
_ | otherwise -> headlineToHeaderWithContents hdln
|
|
||||||
|
|
||||||
isNoExportTag :: Tag -> Bool
|
|
||||||
isNoExportTag = (== toTag "noexport")
|
|
||||||
|
|
||||||
isArchiveTag :: Tag -> Bool
|
|
||||||
isArchiveTag = (== toTag "ARCHIVE")
|
|
||||||
|
|
||||||
-- | Check if the title starts with COMMENT.
|
|
||||||
-- FIXME: This accesses builder internals not intended for use in situations
|
|
||||||
-- like these. Replace once keyword parsing is supported.
|
|
||||||
isCommentTitle :: Inlines -> Bool
|
|
||||||
isCommentTitle (B.toList -> (Str "COMMENT":_)) = True
|
|
||||||
isCommentTitle _ = False
|
|
||||||
|
|
||||||
archivedHeadlineToBlocks :: Monad m => Headline -> OrgParser m Blocks
|
|
||||||
archivedHeadlineToBlocks hdln = do
|
|
||||||
archivedTreesOption <- getExportSetting exportArchivedTrees
|
|
||||||
case archivedTreesOption of
|
|
||||||
ArchivedTreesNoExport -> return mempty
|
|
||||||
ArchivedTreesExport -> headlineToHeaderWithContents hdln
|
|
||||||
ArchivedTreesHeadlineOnly -> headlineToHeader hdln
|
|
||||||
|
|
||||||
headlineToHeaderWithList :: Monad m => Headline -> OrgParser m Blocks
|
|
||||||
headlineToHeaderWithList hdln@(Headline {..}) = do
|
|
||||||
maxHeadlineLevels <- getExportSetting exportHeadlineLevels
|
|
||||||
header <- headlineToHeader hdln
|
|
||||||
listElements <- sequence (map headlineToBlocks headlineChildren)
|
|
||||||
let listBlock = if null listElements
|
|
||||||
then mempty
|
|
||||||
else B.orderedList listElements
|
|
||||||
let headerText = if maxHeadlineLevels == headlineLevel
|
|
||||||
then header
|
|
||||||
else flattenHeader header
|
|
||||||
return $ headerText <> headlineContents <> listBlock
|
|
||||||
where
|
|
||||||
flattenHeader :: Blocks -> Blocks
|
|
||||||
flattenHeader blks =
|
|
||||||
case B.toList blks of
|
|
||||||
(Header _ _ inlns:_) -> B.para (B.fromList inlns)
|
|
||||||
_ -> mempty
|
|
||||||
|
|
||||||
headlineToHeaderWithContents :: Monad m => Headline -> OrgParser m Blocks
|
|
||||||
headlineToHeaderWithContents hdln@(Headline {..}) = do
|
|
||||||
header <- headlineToHeader hdln
|
|
||||||
childrenBlocks <- mconcat <$> sequence (map headlineToBlocks headlineChildren)
|
|
||||||
return $ header <> headlineContents <> childrenBlocks
|
|
||||||
|
|
||||||
headlineToHeader :: Monad m => Headline -> OrgParser m Blocks
|
|
||||||
headlineToHeader (Headline {..}) = do
|
|
||||||
exportTodoKeyword <- getExportSetting exportWithTodoKeywords
|
|
||||||
let todoText = if exportTodoKeyword
|
|
||||||
then case headlineTodoMarker of
|
|
||||||
Just kw -> todoKeywordToInlines kw <> B.space
|
|
||||||
Nothing -> mempty
|
|
||||||
else mempty
|
|
||||||
let text = tagTitle (todoText <> headlineText) headlineTags
|
|
||||||
let propAttr = propertiesToAttr headlineProperties
|
|
||||||
attr <- registerHeader propAttr headlineText
|
|
||||||
return $ B.headerWith attr headlineLevel text
|
|
||||||
|
|
||||||
todoKeyword :: Monad m => OrgParser m TodoMarker
|
|
||||||
todoKeyword = try $ do
|
|
||||||
taskStates <- activeTodoMarkers <$> getState
|
|
||||||
let kwParser tdm = try $ (tdm <$ string (todoMarkerName tdm) <* spaceChar)
|
|
||||||
choice (map kwParser taskStates)
|
|
||||||
|
|
||||||
todoKeywordToInlines :: TodoMarker -> Inlines
|
|
||||||
todoKeywordToInlines tdm =
|
|
||||||
let todoText = todoMarkerName tdm
|
|
||||||
todoState = map toLower . show $ todoMarkerState tdm
|
|
||||||
classes = [todoState, todoText]
|
|
||||||
in B.spanWith (mempty, classes, mempty) (B.str todoText)
|
|
||||||
|
|
||||||
propertiesToAttr :: Properties -> Attr
|
|
||||||
propertiesToAttr properties =
|
|
||||||
let
|
|
||||||
toStringPair prop = (fromKey (fst prop), fromValue (snd prop))
|
|
||||||
customIdKey = toPropertyKey "custom_id"
|
|
||||||
classKey = toPropertyKey "class"
|
|
||||||
unnumberedKey = toPropertyKey "unnumbered"
|
|
||||||
specialProperties = [customIdKey, classKey, unnumberedKey]
|
|
||||||
id' = fromMaybe mempty . fmap fromValue . lookup customIdKey $ properties
|
|
||||||
cls = fromMaybe mempty . fmap fromValue . lookup classKey $ properties
|
|
||||||
kvs' = map toStringPair . filter ((`notElem` specialProperties) . fst)
|
|
||||||
$ properties
|
|
||||||
isUnnumbered =
|
|
||||||
fromMaybe False . fmap isNonNil . lookup unnumberedKey $ properties
|
|
||||||
in
|
|
||||||
(id', words cls ++ (if isUnnumbered then ["unnumbered"] else []), kvs')
|
|
||||||
|
|
||||||
tagTitle :: Inlines -> [Tag] -> Inlines
|
|
||||||
tagTitle title tags = title <> (mconcat $ map tagToInline tags)
|
|
||||||
|
|
||||||
tagToInline :: Tag -> Inlines
|
|
||||||
tagToInline t = B.spanWith ("", ["tag"], [("data-tag-name", fromTag t)]) mempty
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- parsing blocks
|
-- parsing blocks
|
||||||
--
|
--
|
||||||
|
@ -252,7 +63,7 @@ tagToInline t = B.spanWith ("", ["tag"], [("data-tag-name", fromTag t)]) mempty
|
||||||
blockList :: PandocMonad m => OrgParser m [Block]
|
blockList :: PandocMonad m => OrgParser m [Block]
|
||||||
blockList = do
|
blockList = do
|
||||||
initialBlocks <- blocks
|
initialBlocks <- blocks
|
||||||
headlines <- sequence <$> manyTill (headline 1) eof
|
headlines <- sequence <$> manyTill (headline blocks inline 1) eof
|
||||||
st <- getState
|
st <- getState
|
||||||
headlineBlocks <- fmap mconcat . sequence . map headlineToBlocks $ runF headlines st
|
headlineBlocks <- fmap mconcat . sequence . map headlineToBlocks $ runF headlines st
|
||||||
return . B.toList $ (runF initialBlocks st) <> headlineBlocks
|
return . B.toList $ (runF initialBlocks st) <> headlineBlocks
|
||||||
|
@ -631,25 +442,6 @@ drawerEnd :: Monad m => OrgParser m String
|
||||||
drawerEnd = try $
|
drawerEnd = try $
|
||||||
skipSpaces *> stringAnyCase ":END:" <* skipSpaces <* newline
|
skipSpaces *> stringAnyCase ":END:" <* skipSpaces <* newline
|
||||||
|
|
||||||
-- | Read a :PROPERTIES: drawer and return the key/value pairs contained
|
|
||||||
-- within.
|
|
||||||
propertiesDrawer :: Monad m => OrgParser m Properties
|
|
||||||
propertiesDrawer = try $ do
|
|
||||||
drawerType <- drawerStart
|
|
||||||
guard $ map toUpper drawerType == "PROPERTIES"
|
|
||||||
manyTill property (try drawerEnd)
|
|
||||||
where
|
|
||||||
property :: Monad m => OrgParser m (PropertyKey, PropertyValue)
|
|
||||||
property = try $ (,) <$> key <*> value
|
|
||||||
|
|
||||||
key :: Monad m => OrgParser m PropertyKey
|
|
||||||
key = fmap toPropertyKey . try $
|
|
||||||
skipSpaces *> char ':' *> many1Till nonspaceChar (char ':')
|
|
||||||
|
|
||||||
value :: Monad m => OrgParser m PropertyValue
|
|
||||||
value = fmap toPropertyValue . try $
|
|
||||||
skipSpaces *> manyTill anyChar (try $ skipSpaces *> newline)
|
|
||||||
|
|
||||||
|
|
||||||
--
|
--
|
||||||
-- Figures
|
-- Figures
|
||||||
|
|
260
src/Text/Pandoc/Readers/Org/DocumentTree.hs
Normal file
260
src/Text/Pandoc/Readers/Org/DocumentTree.hs
Normal file
|
@ -0,0 +1,260 @@
|
||||||
|
{-
|
||||||
|
Copyright (C) 2014-2017 Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation; either version 2 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
{-# LANGUAGE ViewPatterns #-}
|
||||||
|
{- |
|
||||||
|
Module : Text.Pandoc.Readers.Org.DocumentTree
|
||||||
|
Copyright : Copyright (C) 2014-2017 Albert Krewinkel
|
||||||
|
License : GNU GPL, version 2 or above
|
||||||
|
|
||||||
|
Maintainer : Albert Krewinkel <tarleb+pandoc@moltkeplatz.de>
|
||||||
|
|
||||||
|
Parsers for org-mode headlines and document subtrees
|
||||||
|
-}
|
||||||
|
module Text.Pandoc.Readers.Org.DocumentTree
|
||||||
|
( headline
|
||||||
|
, headlineToBlocks
|
||||||
|
) where
|
||||||
|
|
||||||
|
import Control.Monad (guard, void)
|
||||||
|
import Data.Char (toLower, toUpper)
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import Data.Monoid ((<>))
|
||||||
|
import Text.Pandoc.Builder (Blocks, Inlines)
|
||||||
|
import Text.Pandoc.Class (PandocMonad)
|
||||||
|
import Text.Pandoc.Definition
|
||||||
|
import Text.Pandoc.Readers.Org.BlockStarts
|
||||||
|
import Text.Pandoc.Readers.Org.Parsing
|
||||||
|
import Text.Pandoc.Readers.Org.ParserState
|
||||||
|
|
||||||
|
import qualified Text.Pandoc.Builder as B
|
||||||
|
|
||||||
|
--
|
||||||
|
-- Org headers
|
||||||
|
--
|
||||||
|
newtype Tag = Tag { fromTag :: String }
|
||||||
|
deriving (Show, Eq)
|
||||||
|
|
||||||
|
-- | Create a tag containing the given string.
|
||||||
|
toTag :: String -> Tag
|
||||||
|
toTag = Tag
|
||||||
|
|
||||||
|
-- | The key (also called name or type) of a property.
|
||||||
|
newtype PropertyKey = PropertyKey { fromKey :: String }
|
||||||
|
deriving (Show, Eq, Ord)
|
||||||
|
|
||||||
|
-- | Create a property key containing the given string. Org mode keys are
|
||||||
|
-- case insensitive and are hence converted to lower case.
|
||||||
|
toPropertyKey :: String -> PropertyKey
|
||||||
|
toPropertyKey = PropertyKey . map toLower
|
||||||
|
|
||||||
|
-- | The value assigned to a property.
|
||||||
|
newtype PropertyValue = PropertyValue { fromValue :: String }
|
||||||
|
|
||||||
|
-- | Create a property value containing the given string.
|
||||||
|
toPropertyValue :: String -> PropertyValue
|
||||||
|
toPropertyValue = PropertyValue
|
||||||
|
|
||||||
|
-- | Check whether the property value is non-nil (i.e. truish).
|
||||||
|
isNonNil :: PropertyValue -> Bool
|
||||||
|
isNonNil p = map toLower (fromValue p) `notElem` ["()", "{}", "nil"]
|
||||||
|
|
||||||
|
-- | Key/value pairs from a PROPERTIES drawer
|
||||||
|
type Properties = [(PropertyKey, PropertyValue)]
|
||||||
|
|
||||||
|
-- | Org mode headline (i.e. a document subtree).
|
||||||
|
data Headline = Headline
|
||||||
|
{ headlineLevel :: Int
|
||||||
|
, headlineTodoMarker :: Maybe TodoMarker
|
||||||
|
, headlineText :: Inlines
|
||||||
|
, headlineTags :: [Tag]
|
||||||
|
, headlineProperties :: Properties
|
||||||
|
, headlineContents :: Blocks
|
||||||
|
, headlineChildren :: [Headline]
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | Read an Org mode headline and its contents (i.e. a document subtree).
|
||||||
|
-- @lvl@ gives the minimum acceptable level of the tree.
|
||||||
|
headline :: PandocMonad m
|
||||||
|
=> OrgParser m (F Blocks)
|
||||||
|
-> OrgParser m (F Inlines)
|
||||||
|
-> Int
|
||||||
|
-> OrgParser m (F Headline)
|
||||||
|
headline blocks inline lvl = try $ do
|
||||||
|
level <- headerStart
|
||||||
|
guard (lvl <= level)
|
||||||
|
todoKw <- optionMaybe todoKeyword
|
||||||
|
title <- trimInlinesF . mconcat <$> manyTill inline endOfTitle
|
||||||
|
tags <- option [] headerTags
|
||||||
|
newline
|
||||||
|
properties <- option mempty propertiesDrawer
|
||||||
|
contents <- blocks
|
||||||
|
children <- many (headline blocks inline (level + 1))
|
||||||
|
return $ do
|
||||||
|
title' <- title
|
||||||
|
contents' <- contents
|
||||||
|
children' <- sequence children
|
||||||
|
return $ Headline
|
||||||
|
{ headlineLevel = level
|
||||||
|
, headlineTodoMarker = todoKw
|
||||||
|
, headlineText = title'
|
||||||
|
, headlineTags = tags
|
||||||
|
, headlineProperties = properties
|
||||||
|
, headlineContents = contents'
|
||||||
|
, headlineChildren = children'
|
||||||
|
}
|
||||||
|
where
|
||||||
|
endOfTitle :: Monad m => OrgParser m ()
|
||||||
|
endOfTitle = void . lookAhead $ optional headerTags *> newline
|
||||||
|
|
||||||
|
headerTags :: Monad m => OrgParser m [Tag]
|
||||||
|
headerTags = try $
|
||||||
|
let tag = many1 (alphaNum <|> oneOf "@%#_") <* char ':'
|
||||||
|
in map toTag <$> (skipSpaces *> char ':' *> many1 tag <* skipSpaces)
|
||||||
|
|
||||||
|
-- | Convert an Org mode headline (i.e. a document tree) into pandoc's Blocks
|
||||||
|
headlineToBlocks :: Monad m => Headline -> OrgParser m Blocks
|
||||||
|
headlineToBlocks hdln@(Headline {..}) = do
|
||||||
|
maxHeadlineLevels <- getExportSetting exportHeadlineLevels
|
||||||
|
case () of
|
||||||
|
_ | any isNoExportTag headlineTags -> return mempty
|
||||||
|
_ | any isArchiveTag headlineTags -> archivedHeadlineToBlocks hdln
|
||||||
|
_ | isCommentTitle headlineText -> return mempty
|
||||||
|
_ | headlineLevel >= maxHeadlineLevels -> headlineToHeaderWithList hdln
|
||||||
|
_ | otherwise -> headlineToHeaderWithContents hdln
|
||||||
|
|
||||||
|
isNoExportTag :: Tag -> Bool
|
||||||
|
isNoExportTag = (== toTag "noexport")
|
||||||
|
|
||||||
|
isArchiveTag :: Tag -> Bool
|
||||||
|
isArchiveTag = (== toTag "ARCHIVE")
|
||||||
|
|
||||||
|
-- | Check if the title starts with COMMENT.
|
||||||
|
-- FIXME: This accesses builder internals not intended for use in situations
|
||||||
|
-- like these. Replace once keyword parsing is supported.
|
||||||
|
isCommentTitle :: Inlines -> Bool
|
||||||
|
isCommentTitle (B.toList -> (Str "COMMENT":_)) = True
|
||||||
|
isCommentTitle _ = False
|
||||||
|
|
||||||
|
archivedHeadlineToBlocks :: Monad m => Headline -> OrgParser m Blocks
|
||||||
|
archivedHeadlineToBlocks hdln = do
|
||||||
|
archivedTreesOption <- getExportSetting exportArchivedTrees
|
||||||
|
case archivedTreesOption of
|
||||||
|
ArchivedTreesNoExport -> return mempty
|
||||||
|
ArchivedTreesExport -> headlineToHeaderWithContents hdln
|
||||||
|
ArchivedTreesHeadlineOnly -> headlineToHeader hdln
|
||||||
|
|
||||||
|
headlineToHeaderWithList :: Monad m => Headline -> OrgParser m Blocks
|
||||||
|
headlineToHeaderWithList hdln@(Headline {..}) = do
|
||||||
|
maxHeadlineLevels <- getExportSetting exportHeadlineLevels
|
||||||
|
header <- headlineToHeader hdln
|
||||||
|
listElements <- sequence (map headlineToBlocks headlineChildren)
|
||||||
|
let listBlock = if null listElements
|
||||||
|
then mempty
|
||||||
|
else B.orderedList listElements
|
||||||
|
let headerText = if maxHeadlineLevels == headlineLevel
|
||||||
|
then header
|
||||||
|
else flattenHeader header
|
||||||
|
return $ headerText <> headlineContents <> listBlock
|
||||||
|
where
|
||||||
|
flattenHeader :: Blocks -> Blocks
|
||||||
|
flattenHeader blks =
|
||||||
|
case B.toList blks of
|
||||||
|
(Header _ _ inlns:_) -> B.para (B.fromList inlns)
|
||||||
|
_ -> mempty
|
||||||
|
|
||||||
|
headlineToHeaderWithContents :: Monad m => Headline -> OrgParser m Blocks
|
||||||
|
headlineToHeaderWithContents hdln@(Headline {..}) = do
|
||||||
|
header <- headlineToHeader hdln
|
||||||
|
childrenBlocks <- mconcat <$> sequence (map headlineToBlocks headlineChildren)
|
||||||
|
return $ header <> headlineContents <> childrenBlocks
|
||||||
|
|
||||||
|
headlineToHeader :: Monad m => Headline -> OrgParser m Blocks
|
||||||
|
headlineToHeader (Headline {..}) = do
|
||||||
|
exportTodoKeyword <- getExportSetting exportWithTodoKeywords
|
||||||
|
let todoText = if exportTodoKeyword
|
||||||
|
then case headlineTodoMarker of
|
||||||
|
Just kw -> todoKeywordToInlines kw <> B.space
|
||||||
|
Nothing -> mempty
|
||||||
|
else mempty
|
||||||
|
let text = tagTitle (todoText <> headlineText) headlineTags
|
||||||
|
let propAttr = propertiesToAttr headlineProperties
|
||||||
|
attr <- registerHeader propAttr headlineText
|
||||||
|
return $ B.headerWith attr headlineLevel text
|
||||||
|
|
||||||
|
todoKeyword :: Monad m => OrgParser m TodoMarker
|
||||||
|
todoKeyword = try $ do
|
||||||
|
taskStates <- activeTodoMarkers <$> getState
|
||||||
|
let kwParser tdm = try $ (tdm <$ string (todoMarkerName tdm) <* spaceChar)
|
||||||
|
choice (map kwParser taskStates)
|
||||||
|
|
||||||
|
todoKeywordToInlines :: TodoMarker -> Inlines
|
||||||
|
todoKeywordToInlines tdm =
|
||||||
|
let todoText = todoMarkerName tdm
|
||||||
|
todoState = map toLower . show $ todoMarkerState tdm
|
||||||
|
classes = [todoState, todoText]
|
||||||
|
in B.spanWith (mempty, classes, mempty) (B.str todoText)
|
||||||
|
|
||||||
|
propertiesToAttr :: Properties -> Attr
|
||||||
|
propertiesToAttr properties =
|
||||||
|
let
|
||||||
|
toStringPair prop = (fromKey (fst prop), fromValue (snd prop))
|
||||||
|
customIdKey = toPropertyKey "custom_id"
|
||||||
|
classKey = toPropertyKey "class"
|
||||||
|
unnumberedKey = toPropertyKey "unnumbered"
|
||||||
|
specialProperties = [customIdKey, classKey, unnumberedKey]
|
||||||
|
id' = fromMaybe mempty . fmap fromValue . lookup customIdKey $ properties
|
||||||
|
cls = fromMaybe mempty . fmap fromValue . lookup classKey $ properties
|
||||||
|
kvs' = map toStringPair . filter ((`notElem` specialProperties) . fst)
|
||||||
|
$ properties
|
||||||
|
isUnnumbered =
|
||||||
|
fromMaybe False . fmap isNonNil . lookup unnumberedKey $ properties
|
||||||
|
in
|
||||||
|
(id', words cls ++ (if isUnnumbered then ["unnumbered"] else []), kvs')
|
||||||
|
|
||||||
|
tagTitle :: Inlines -> [Tag] -> Inlines
|
||||||
|
tagTitle title tags = title <> (mconcat $ map tagToInline tags)
|
||||||
|
|
||||||
|
-- | Convert
|
||||||
|
tagToInline :: Tag -> Inlines
|
||||||
|
tagToInline t = B.spanWith ("", ["tag"], [("data-tag-name", fromTag t)]) mempty
|
||||||
|
|
||||||
|
-- | Read a :PROPERTIES: drawer and return the key/value pairs contained
|
||||||
|
-- within.
|
||||||
|
propertiesDrawer :: Monad m => OrgParser m Properties
|
||||||
|
propertiesDrawer = try $ do
|
||||||
|
drawerType <- drawerStart
|
||||||
|
guard $ map toUpper drawerType == "PROPERTIES"
|
||||||
|
manyTill property (try endOfDrawer)
|
||||||
|
where
|
||||||
|
property :: Monad m => OrgParser m (PropertyKey, PropertyValue)
|
||||||
|
property = try $ (,) <$> key <*> value
|
||||||
|
|
||||||
|
key :: Monad m => OrgParser m PropertyKey
|
||||||
|
key = fmap toPropertyKey . try $
|
||||||
|
skipSpaces *> char ':' *> many1Till nonspaceChar (char ':')
|
||||||
|
|
||||||
|
value :: Monad m => OrgParser m PropertyValue
|
||||||
|
value = fmap toPropertyValue . try $
|
||||||
|
skipSpaces *> manyTill anyChar (try $ skipSpaces *> newline)
|
||||||
|
|
||||||
|
endOfDrawer :: Monad m => OrgParser m String
|
||||||
|
endOfDrawer = try $
|
||||||
|
skipSpaces *> stringAnyCase ":END:" <* skipSpaces <* newline
|
||||||
|
|
Loading…
Reference in a new issue