git.iko.soy/typeable/pandoc
on master help sign in
Org reader: Enable internal links

Internal links in Org are possible by using an anchor-name as the target of a link: [[some-anchor][This]] is an internal link. It links <<some-anchor>> here.

commit2eec20d92fd0f498da5b66ac03cf6f8159392323 authorAlbert Krewinkel <tarleb@moltkeplatz.de> dateFri 25 Apr 2014, 15:29 (13 years ago) parent2f724aa
2 files changed+63−12
src/Text/Pandoc/Readers/Org.hs+38−12 tests/Tests/Readers/Org.hs+25
src/Text/Pandoc/Readers/Org.hs+38−12
@@ -79,6 +79,7 @@ type OrgBlockAttributes = M.Map String String
7979-- | Org-mode parser state
8080data OrgParserState = OrgParserState
8181 { orgStateOptions :: ReaderOptions
82+ , orgStateAnchorIds :: [String]
8283 , orgStateBlockAttributes :: OrgBlockAttributes
8384 , orgStateEmphasisCharStack :: [Char]
8485 , orgStateEmphasisNewlines :: Maybe Int
@@ -105,6 +106,7 @@ instance Default OrgParserState where
105106defaultOrgParserState :: OrgParserState
106107defaultOrgParserState = OrgParserState
107108 { orgStateOptions = def
109+ , orgStateAnchorIds = []
108110 , orgStateBlockAttributes = M.empty
109111 , orgStateEmphasisCharStack = []
110112 , orgStateEmphasisNewlines = Nothing
@@ -116,6 +118,10 @@ defaultOrgParserState = OrgParserState
116118 , orgStateNotes' = []
117119 }
118120
121+recordAnchorId :: String -> OrgParser ()
122+recordAnchorId i = updateState $ \s ->
123+ s{ orgStateAnchorIds = i : (orgStateAnchorIds s) }
124+
119125addBlockAttribute :: String -> String -> OrgParser ()
120126addBlockAttribute key val = updateState $ \s ->
121127 let attrs = orgStateBlockAttributes s
@@ -848,17 +854,14 @@ explicitOrImageLink = try $ do
848854 title <- enclosedRaw (char '[') (char ']')
849855 title' <- parseFromString (mconcat <$> many inline) title
850856 char ']'
851− return $ B.link src "" <$>
852− if isImageFilename src && isImageFilename title
853− then return $ B.image title mempty mempty
854− else title'
857+ return $ if isImageFilename src && isImageFilename title
858+ then pure $ B.link src "" $ B.image title mempty mempty
859+ else linkToInlinesF src =<< title'
855860
856861selflinkOrImage :: OrgParser (F Inlines)
857862selflinkOrImage = try $ do
858863 src <- char '[' *> linkTarget <* char ']'
859− returnF $ if isImageFilename src
860− then B.image src "" ""
861− else B.link src "" (B.str src)
864+ return $ linkToInlinesF src (B.str src)
862865
863866plainLink :: OrgParser (F Inlines)
864867plainLink = try $ do
@@ -878,6 +881,26 @@ selfTarget = try $ char '[' *> linkTarget <* char ']'
878881linkTarget :: OrgParser String
879882linkTarget = enclosed (char '[') (char ']') (noneOf "\n\r[]")
880883
884+linkToInlinesF :: String -> Inlines -> F Inlines
885+linkToInlinesF s@('#':_) = pure . B.link s ""
886+linkToInlinesF s
887+ | isImageFilename s = const . pure $ B.image s "" ""
888+ | isUri s = pure . B.link s ""
889+ | isRelativeUrl s = pure . B.link s ""
890+linkToInlinesF s = \title -> do
891+ anchorB <- (s `elem`) <$> asksF orgStateAnchorIds
892+ if anchorB
893+ then pure $ B.link ('#':s) "" title
894+ else pure $ B.emph title
895+
896+isRelativeUrl :: String -> Bool
897+isRelativeUrl s = (':' `notElem` s) && ("./" `isPrefixOf` s)
898+
899+isUri :: String -> Bool
900+isUri s = let (scheme, path) = break (== ':') s
901+ in all (\c -> isAlphaNum c || c `elem` ".-") scheme
902+ && not (null path)
903+
881904isImageFilename :: String -> Bool
882905isImageFilename filename =
883906 any (\x -> ('.':x) `isSuffixOf` filename) imageExtensions &&
@@ -894,12 +917,15 @@ isImageFilename filename =
894917-- an anchor.
895918
896919anchor :: OrgParser (F Inlines)
897−anchor = try $ pure <$> (B.spanWith <$> attributes <*> pure mempty)
920+anchor = try $ do
921+ anchorId <- parseAnchor
922+ recordAnchorId anchorId
923+ returnF $ B.spanWith (solidify anchorId, [], []) mempty
898924 where
899− name = string "<<"
900− *> many1 (noneOf "\t\n\r<>\"' ")
901− <* string ">>"
902− attributes = name >>= \n -> return (solidify n, [], [])
925+ parseAnchor = string "<<"
926+ *> many1 (noneOf "\t\n\r<>\"' ")
927+ <* string ">>"
928+ <* skipSpaces
903929
904930-- | Replace every char but [a-zA-Z0-9_.-:] with a hypen '-'. This mirrors
905931-- the org function @org-export-solidify-link-text@.
tests/Tests/Readers/Org.hs+25
@@ -202,6 +202,11 @@ tests =
202202 , link "http://moltkeplatz.de" "" "http://moltkeplatz.de"
203203 , "for", "fnords."
204204 ])
205+
206+ , "Anchor" =:
207+ "<<anchor>> Link here later." =?>
208+ (para $ spanWith ("anchor", [], []) mempty <>
209+ "Link" <> space <> "here" <> space <> "later.")
205210 ]
206211
207212 , testGroup "Meta Information" $
@@ -279,6 +284,26 @@ tests =
279284 , ":END:"
280285 ] =?>
281286 para (":FOO:" <> space <> ":END:")
287+
288+ , "Anchor reference" =:
289+ unlines [ "<<link-here>> Target."
290+ , ""
291+ , "[[link-here][See here!]]"
292+ ] =?>
293+ (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
294+ para (link "#link-here" "" ("See" <> space <> "here!")))
295+
296+ , "Search links are read as emph" =:
297+ "[[Wally][Where's Wally?]]" =?>
298+ (para (emph $ "Where's" <> space <> "Wally?"))
299+
300+ , "Link to nonexistent anchor" =:
301+ unlines [ "<<link-here>> Target."
302+ , ""
303+ , "[[link$here][See here!]]"
304+ ] =?>
305+ (para (spanWith ("link-here", [], []) mempty <> "Target.") <>
306+ para (emph ("See" <> space <> "here!")))
282307 ]
283308
284309 , testGroup "Basic Blocks" $