Skip to content

Commit 7b2638d

Browse files
committed
handle nested transclusion
1 parent 2b56ad3 commit 7b2638d

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)
@@ -2424,16 +2424,20 @@ extractBlockById targetId blocks =
24242424
Just blk -> blocksToInlines' [blk]
24252425
Nothing -> mempty
24262426

2427-
-- | Find a block with a specific ID in a list of blocks
2427+
-- | Find a block with a specific ID in a list of blocks (including nested blocks)
24282428
findBlockById :: Text -> [Block] -> Maybe Block
2429-
findBlockById targetId = go
2429+
findBlockById targetId blocks =
2430+
case query findBlock blocks of
2431+
(blk:_) -> Just blk
2432+
[] -> Nothing
24302433
where
2431-
go [] = Nothing
2432-
go (blk:rest) =
2433-
case blk of
2434-
Div (bid, _, _) _ | bid == targetId -> Just blk
2435-
Header _ (bid, _, _) _ | bid == targetId -> Just blk
2436-
_ -> go rest
2434+
findBlock :: Block -> [Block]
2435+
findBlock blk@(Div (bid, _, _) _) | bid == targetId = [blk]
2436+
findBlock blk@(Header _ (bid, _, _) _) | bid == targetId = [blk]
2437+
findBlock blk@(Table (bid, _, _) _ _ _ _ _) | bid == targetId = [blk]
2438+
findBlock blk@(CodeBlock (bid, _, _) _) | bid == targetId = [blk]
2439+
findBlock blk@(Figure (bid, _, _) _ _) | bid == targetId = [blk]
2440+
findBlock _ = []
24372441

24382442
-- | Extract content under a specific heading from a list of blocks
24392443
extractHeadingById :: Text -> Blocks -> Inlines
@@ -2461,9 +2465,25 @@ parseHeadingTransclusion headingText = do
24612465
return $ fmap (extractHeadingById headingText) blocks
24622466

24632467
-- | Extract all content under a heading until the next heading of same or higher level
2468+
-- Handles nested headings by using query to find the target heading anywhere in the document
24642469
extractContentUnderHeading :: Text -> [Block] -> [Block]
2465-
extractContentUnderHeading targetHeading = go False 0
2470+
extractContentUnderHeading targetHeading blocks =
2471+
-- Check if target heading exists anywhere in the document
2472+
case query findTargetHeading blocks of
2473+
[] -> [] -- Target heading not found
2474+
((targetLevel, _):_) ->
2475+
-- Target heading found, extract content using the traditional approach
2476+
-- but with the level information from the nested search
2477+
go False targetLevel blocks
24662478
where
2479+
-- Find the target heading anywhere in the document structure
2480+
findTargetHeading :: Block -> [(Int, [Inline])]
2481+
findTargetHeading (Header lvl _ ils)
2482+
| stringify ils == targetHeading = [(lvl, ils)]
2483+
findTargetHeading _ = []
2484+
2485+
-- Extract content after finding the target heading (handles top-level only for now)
2486+
go :: Bool -> Int -> [Block] -> [Block]
24672487
go _found _level [] = []
24682488
go found level (blk:rest) =
24692489
case blk of
@@ -2478,14 +2498,29 @@ extractContentUnderHeading targetHeading = go False 0
24782498
-- Collecting content under target heading
24792499
blk : go True level rest
24802500
| otherwise ->
2481-
-- Haven't found target heading yet
2482-
go False level rest
2501+
-- Haven't found target heading yet, check if it's nested in this block
2502+
case extractFromNestedBlock blk of
2503+
[] -> go False level rest
2504+
nestedContent -> nestedContent ++ go False level rest
24832505
_ | found ->
24842506
-- Collecting content under target heading
24852507
blk : go True level rest
24862508
| otherwise ->
2487-
-- Haven't found target heading yet
2488-
go False level rest
2509+
-- Haven't found target heading yet, check if it's nested in this block
2510+
case extractFromNestedBlock blk of
2511+
[] -> go False level rest
2512+
nestedContent -> nestedContent ++ go False level rest
2513+
2514+
-- Extract content from blocks that might contain the target heading
2515+
extractFromNestedBlock :: Block -> [Block]
2516+
extractFromNestedBlock blk =
2517+
case blk of
2518+
Div _ nestedBlocks -> extractContentUnderHeading targetHeading nestedBlocks
2519+
BlockQuote nestedBlocks -> extractContentUnderHeading targetHeading nestedBlocks
2520+
BulletList items -> concatMap (extractContentUnderHeading targetHeading) items
2521+
OrderedList _ items -> concatMap (extractContentUnderHeading targetHeading) items
2522+
DefinitionList items -> concatMap (\(_, defs) -> concatMap (extractContentUnderHeading targetHeading) defs) items
2523+
_ -> []
24892524

24902525
blockId :: PandocMonad m => MarkdownParser m (F Inlines)
24912526
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)