Skip to content

Commit 479d5f1

Browse files
committed
handle nested transclusion
1 parent 128848d commit 479d5f1

File tree

3 files changed

+278
-14
lines changed

3 files changed

+278
-14
lines changed

src/Text/Pandoc/Readers/Markdown.hs

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ import Text.Pandoc.Error
4646
import Safe.Foldable (maximumBounded)
4747
import Text.Pandoc.Logging
4848
import Text.Pandoc.Options
49-
import Text.Pandoc.Walk (walk)
49+
import Text.Pandoc.Walk (walk, query)
5050
import Text.Pandoc.Parsing hiding (tableCaption)
5151
import Text.Pandoc.Readers.HTML (htmlInBalanced, htmlTag, isBlockTag,
5252
isInlineTag, isTextTag)
@@ -2406,16 +2406,20 @@ extractBlockById targetId blocks =
24062406
Just blk -> blocksToInlines' [blk]
24072407
Nothing -> mempty
24082408

2409-
-- | Find a block with a specific ID in a list of blocks
2409+
-- | Find a block with a specific ID in a list of blocks (including nested blocks)
24102410
findBlockById :: Text -> [Block] -> Maybe Block
2411-
findBlockById targetId = go
2411+
findBlockById targetId blocks =
2412+
case query findBlock blocks of
2413+
(blk:_) -> Just blk
2414+
[] -> Nothing
24122415
where
2413-
go [] = Nothing
2414-
go (blk:rest) =
2415-
case blk of
2416-
Div (bid, _, _) _ | bid == targetId -> Just blk
2417-
Header _ (bid, _, _) _ | bid == targetId -> Just blk
2418-
_ -> go rest
2416+
findBlock :: Block -> [Block]
2417+
findBlock blk@(Div (bid, _, _) _) | bid == targetId = [blk]
2418+
findBlock blk@(Header _ (bid, _, _) _) | bid == targetId = [blk]
2419+
findBlock blk@(Table (bid, _, _) _ _ _ _ _) | bid == targetId = [blk]
2420+
findBlock blk@(CodeBlock (bid, _, _) _) | bid == targetId = [blk]
2421+
findBlock blk@(Figure (bid, _, _) _ _) | bid == targetId = [blk]
2422+
findBlock _ = []
24192423

24202424
-- | Extract content under a specific heading from a list of blocks
24212425
extractHeadingById :: Text -> Blocks -> Inlines
@@ -2443,9 +2447,25 @@ parseHeadingTransclusion headingText = do
24432447
return $ fmap (extractHeadingById headingText) blocks
24442448

24452449
-- | Extract all content under a heading until the next heading of same or higher level
2450+
-- Handles nested headings by using query to find the target heading anywhere in the document
24462451
extractContentUnderHeading :: Text -> [Block] -> [Block]
2447-
extractContentUnderHeading targetHeading = go False 0
2452+
extractContentUnderHeading targetHeading blocks =
2453+
-- Check if target heading exists anywhere in the document
2454+
case query findTargetHeading blocks of
2455+
[] -> [] -- Target heading not found
2456+
((targetLevel, _):_) ->
2457+
-- Target heading found, extract content using the traditional approach
2458+
-- but with the level information from the nested search
2459+
go False targetLevel blocks
24482460
where
2461+
-- Find the target heading anywhere in the document structure
2462+
findTargetHeading :: Block -> [(Int, [Inline])]
2463+
findTargetHeading (Header lvl _ ils)
2464+
| stringify ils == targetHeading = [(lvl, ils)]
2465+
findTargetHeading _ = []
2466+
2467+
-- Extract content after finding the target heading (handles top-level only for now)
2468+
go :: Bool -> Int -> [Block] -> [Block]
24492469
go _found _level [] = []
24502470
go found level (blk:rest) =
24512471
case blk of
@@ -2460,14 +2480,29 @@ extractContentUnderHeading targetHeading = go False 0
24602480
-- Collecting content under target heading
24612481
blk : go True level rest
24622482
| otherwise ->
2463-
-- Haven't found target heading yet
2464-
go False level rest
2483+
-- Haven't found target heading yet, check if it's nested in this block
2484+
case extractFromNestedBlock blk of
2485+
[] -> go False level rest
2486+
nestedContent -> nestedContent ++ go False level rest
24652487
_ | found ->
24662488
-- Collecting content under target heading
24672489
blk : go True level rest
24682490
| otherwise ->
2469-
-- Haven't found target heading yet
2470-
go False level rest
2491+
-- Haven't found target heading yet, check if it's nested in this block
2492+
case extractFromNestedBlock blk of
2493+
[] -> go False level rest
2494+
nestedContent -> nestedContent ++ go False level rest
2495+
2496+
-- Extract content from blocks that might contain the target heading
2497+
extractFromNestedBlock :: Block -> [Block]
2498+
extractFromNestedBlock blk =
2499+
case blk of
2500+
Div _ nestedBlocks -> extractContentUnderHeading targetHeading nestedBlocks
2501+
BlockQuote nestedBlocks -> extractContentUnderHeading targetHeading nestedBlocks
2502+
BulletList items -> concatMap (extractContentUnderHeading targetHeading) items
2503+
OrderedList _ items -> concatMap (extractContentUnderHeading targetHeading) items
2504+
DefinitionList items -> concatMap (\(_, defs) -> concatMap (extractContentUnderHeading targetHeading) defs) items
2505+
_ -> []
24712506

24722507
blockId :: PandocMonad m => MarkdownParser m (F Inlines)
24732508
blockId = try $ do

test/command/obsidian.md

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,3 +411,201 @@ Text with ==highlighted== content.
411411
]
412412
```
413413

414+
```
415+
% pandoc -f obsidian -t native
416+
Nested heading transclusion: ![[command/obsidian/nested-transclusion#Nested Heading in Blockquote]]
417+
^D
418+
[ Para
419+
[ Str "Nested"
420+
, Space
421+
, Str "heading"
422+
, Space
423+
, Str "transclusion:"
424+
, Space
425+
, Str "Nested"
426+
, Space
427+
, Str "Heading"
428+
, Space
429+
, Str "in"
430+
, Space
431+
, Str "Blockquote"
432+
, LineBreak
433+
, Str "This"
434+
, Space
435+
, Str "heading"
436+
, Space
437+
, Str "is"
438+
, Space
439+
, Str "nested"
440+
, Space
441+
, Str "inside"
442+
, Space
443+
, Str "a"
444+
, Space
445+
, Str "blockquote"
446+
, Space
447+
, Str "for"
448+
, Space
449+
, Str "testing"
450+
, Space
451+
, Str "nested"
452+
, Space
453+
, Str "heading"
454+
, Space
455+
, Str "transclusions."
456+
, LineBreak
457+
, Str "Some"
458+
, Space
459+
, Str "content"
460+
, Space
461+
, Str "under"
462+
, Space
463+
, Str "the"
464+
, Space
465+
, Str "nested"
466+
, Space
467+
, Str "heading."
468+
, LineBreak
469+
, Str "A"
470+
, Space
471+
, Str "paragraph"
472+
, Space
473+
, Str "with"
474+
, Space
475+
, Str "a"
476+
, Space
477+
, Str "nested"
478+
, Space
479+
, Str "block"
480+
, Space
481+
, Str "ID."
482+
]
483+
]
484+
```
485+
486+
```
487+
% pandoc -f obsidian -t native
488+
Nested block transclusion: ![[command/obsidian/nested-transclusion#^nested-block-id]]
489+
^D
490+
[ Para
491+
[ Str "Nested"
492+
, Space
493+
, Str "block"
494+
, Space
495+
, Str "transclusion:"
496+
, Space
497+
, Str "A"
498+
, Space
499+
, Str "paragraph"
500+
, Space
501+
, Str "with"
502+
, Space
503+
, Str "a"
504+
, Space
505+
, Str "nested"
506+
, Space
507+
, Str "block"
508+
, Space
509+
, Str "ID."
510+
]
511+
]
512+
```
513+
514+
```
515+
% pandoc -f obsidian -t native
516+
List nested heading transclusion: ![[command/obsidian/nested-transclusion#Heading in List Item]]
517+
^D
518+
[ Para
519+
[ Str "List"
520+
, Space
521+
, Str "nested"
522+
, Space
523+
, Str "heading"
524+
, Space
525+
, Str "transclusion:"
526+
, Space
527+
, Str "Heading"
528+
, Space
529+
, Str "in"
530+
, Space
531+
, Str "List"
532+
, Space
533+
, Str "Item"
534+
, LineBreak
535+
, Str "This"
536+
, Space
537+
, Str "heading"
538+
, Space
539+
, Str "is"
540+
, Space
541+
, Str "nested"
542+
, Space
543+
, Str "inside"
544+
, Space
545+
, Str "a"
546+
, Space
547+
, Str "list"
548+
, Space
549+
, Str "item."
550+
, LineBreak
551+
, Str "Content"
552+
, Space
553+
, Str "under"
554+
, Space
555+
, Str "the"
556+
, Space
557+
, Str "list"
558+
, Space
559+
, Str "heading."
560+
, LineBreak
561+
, Str "A"
562+
, Space
563+
, Str "block"
564+
, Space
565+
, Str "with"
566+
, Space
567+
, Str "an"
568+
, Space
569+
, Str "ID"
570+
, Space
571+
, Str "in"
572+
, Space
573+
, Str "a"
574+
, Space
575+
, Str "list."
576+
]
577+
]
578+
```
579+
580+
```
581+
% pandoc -f obsidian -t native
582+
List nested block transclusion: ![[command/obsidian/nested-transclusion#^list-block-id]]
583+
^D
584+
[ Para
585+
[ Str "List"
586+
, Space
587+
, Str "nested"
588+
, Space
589+
, Str "block"
590+
, Space
591+
, Str "transclusion:"
592+
, Space
593+
, Str "A"
594+
, Space
595+
, Str "block"
596+
, Space
597+
, Str "with"
598+
, Space
599+
, Str "an"
600+
, Space
601+
, Str "ID"
602+
, Space
603+
, Str "in"
604+
, Space
605+
, Str "a"
606+
, Space
607+
, Str "list."
608+
]
609+
]
610+
```
611+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
This is a test file for nested transclusions.
2+
3+
## Main Section
4+
5+
Content before the nested structure.
6+
7+
> ### Nested Heading in Blockquote
8+
>
9+
> This heading is nested inside a blockquote for testing nested heading transclusions.
10+
>
11+
> Some content under the nested heading.
12+
>
13+
> A paragraph with a nested block ID. ^nested-block-id
14+
15+
More content after the blockquote.
16+
17+
## Middle Section
18+
19+
Some content in the middle section.
20+
21+
- ### Heading in List Item
22+
23+
This heading is nested inside a list item.
24+
25+
Content under the list heading.
26+
27+
A block with an ID in a list. ^list-block-id
28+
29+
## Final Section
30+
31+
Regular content at the end.

0 commit comments

Comments
 (0)