Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 38 additions & 5 deletions crates/quarto-markdown-pandoc/src/filters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,21 @@ impl_inline_filterable_terminal!(
Math,
RawInline,
Shortcode,
NoteReference,
Attr
NoteReference
);

// Attr is special because it has two fields (Attr, AttrSourceInfo)
// We need a custom impl that preserves attr_source
// However, filters don't actually work on Attr values directly,
// so this is just a placeholder that should never be called
impl InlineFilterableStructure for (pandoc::Attr, crate::pandoc::attr::AttrSourceInfo) {
fn filter_structure(self, _: &mut Filter) -> Inline {
// Note: This should not be called in practice because Attr inlines
// are stripped during postprocessing before filters run
Inline::Attr(self.0, self.1)
}
}

macro_rules! impl_inline_filterable_simple {
($($variant:ident),*) => {
$(
Expand Down Expand Up @@ -350,6 +361,7 @@ impl InlineFilterableStructure for pandoc::Cite {
mode: cit.mode,
note_num: cit.note_num,
hash: cit.hash,
id_source: cit.id_source,
})
.collect(),
content: topdown_traverse_inlines(self.content, filter),
Expand Down Expand Up @@ -641,8 +653,22 @@ pub fn topdown_traverse_inline(inline: Inline, filter: &mut Filter) -> Inlines {
Inline::NoteReference(note_ref) => {
handle_inline_filter!(NoteReference, note_ref, note_reference, filter)
}
Inline::Attr(attr) => {
handle_inline_filter!(Attr, attr, attr, filter)
Inline::Attr(attr, attr_source) => {
// Special handling for Attr since it has two fields and filters don't actually work on Attr tuples
// Attr inlines should be stripped during postprocessing before filters run
// So this branch should rarely be hit
if let Some(f) = &mut filter.inline {
let inline = Inline::Attr(attr, attr_source);
match f(inline.clone()) {
FilterReturn::Unchanged(_) => vec![inline],
FilterReturn::FilterResult(result, _should_recurse) => result,
}
} else {
vec![traverse_inline_structure(
Inline::Attr(attr, attr_source),
filter,
)]
}
}
Inline::Insert(ins) => {
handle_inline_filter!(Insert, ins, insert, filter)
Expand Down Expand Up @@ -827,6 +853,7 @@ fn traverse_inline_nonterminal(inline: Inline, filter: &mut Filter) -> Inline {
mode: cit.mode,
note_num: cit.note_num,
hash: cit.hash,
id_source: cit.id_source,
})
.collect(),
content: topdown_traverse_inlines(c.content, filter),
Expand All @@ -837,12 +864,16 @@ fn traverse_inline_nonterminal(inline: Inline, filter: &mut Filter) -> Inline {
target: l.target,
content: topdown_traverse_inlines(l.content, filter),
source_info: l.source_info,
attr_source: l.attr_source,
target_source: l.target_source,
}),
Inline::Image(i) => Inline::Image(crate::pandoc::Image {
attr: i.attr,
target: i.target,
content: topdown_traverse_inlines(i.content, filter),
source_info: i.source_info,
attr_source: i.attr_source,
target_source: i.target_source,
}),
Inline::Note(note) => Inline::Note(crate::pandoc::Note {
content: topdown_traverse_blocks(note.content, filter),
Expand All @@ -852,6 +883,7 @@ fn traverse_inline_nonterminal(inline: Inline, filter: &mut Filter) -> Inline {
attr: span.attr,
content: topdown_traverse_inlines(span.content, filter),
source_info: span.source_info,
attr_source: span.attr_source,
}),
_ => panic!("Unsupported inline type: {:?}", inline),
}
Expand All @@ -870,7 +902,7 @@ pub fn traverse_inline_structure(inline: Inline, filter: &mut Filter) -> Inline
// extensions
Inline::Shortcode(_) => inline,
Inline::NoteReference(_) => inline,
Inline::Attr(_) => inline,
Inline::Attr(_, _) => inline,
_ => traverse_inline_nonterminal(inline, filter),
}
}
Expand All @@ -893,6 +925,7 @@ fn traverse_caption(
long: caption
.long
.map(|long| topdown_traverse_blocks(long, filter)),
source_info: caption.source_info,
}
}

Expand Down
49 changes: 49 additions & 0 deletions crates/quarto-markdown-pandoc/src/pandoc/attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Copyright (c) 2025 Posit, PBC
*/

use quarto_source_map::SourceInfo;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

pub fn empty_attr() -> Attr {
Expand All @@ -14,3 +16,50 @@ pub type Attr = (String, Vec<String>, HashMap<String, String>);
pub fn is_empty_attr(attr: &Attr) -> bool {
attr.0.is_empty() && attr.1.is_empty() && attr.2.is_empty()
}

/// Source location information for Attr components.
///
/// Attr is a tuple: (id: String, classes: Vec<String>, attributes: HashMap<String, String>)
/// This struct tracks source locations for each component:
/// - id: Source location of the id string (None if id is empty "")
/// - classes: Source locations for each class string
/// - attributes: Source locations for each key-value pair (both key and value)
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct AttrSourceInfo {
pub id: Option<SourceInfo>,
pub classes: Vec<Option<SourceInfo>>,
pub attributes: Vec<(Option<SourceInfo>, Option<SourceInfo>)>,
}

impl AttrSourceInfo {
/// Creates an empty AttrSourceInfo with no source tracking.
pub fn empty() -> Self {
AttrSourceInfo {
id: None,
classes: Vec::new(),
attributes: Vec::new(),
}
}
}

/// Source location information for Target components.
///
/// Target is a tuple: (url: String, title: String)
/// This struct tracks source locations for each component:
/// - url: Source location of the URL string (None if url is empty "")
/// - title: Source location of the title string (None if title is empty "")
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TargetSourceInfo {
pub url: Option<SourceInfo>,
pub title: Option<SourceInfo>,
}

impl TargetSourceInfo {
/// Creates an empty TargetSourceInfo with no source tracking.
pub fn empty() -> Self {
TargetSourceInfo {
url: None,
title: None,
}
}
}
6 changes: 5 additions & 1 deletion crates/quarto-markdown-pandoc/src/pandoc/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
*/

use crate::pandoc::MetaValueWithSourceInfo;
use crate::pandoc::attr::Attr;
use crate::pandoc::attr::{Attr, AttrSourceInfo};
use crate::pandoc::caption::Caption;
use crate::pandoc::inline::Inlines;
use crate::pandoc::list::ListAttributes;
Expand Down Expand Up @@ -59,6 +59,7 @@ pub struct CodeBlock {
pub attr: Attr,
pub text: String,
pub source_info: quarto_source_map::SourceInfo,
pub attr_source: AttrSourceInfo,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -99,6 +100,7 @@ pub struct Header {
pub attr: Attr,
pub content: Inlines,
pub source_info: quarto_source_map::SourceInfo,
pub attr_source: AttrSourceInfo,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand All @@ -112,13 +114,15 @@ pub struct Figure {
pub caption: Caption,
pub content: Blocks,
pub source_info: quarto_source_map::SourceInfo,
pub attr_source: AttrSourceInfo,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Div {
pub attr: Attr,
pub content: Blocks,
pub source_info: quarto_source_map::SourceInfo,
pub attr_source: AttrSourceInfo,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
Expand Down
1 change: 1 addition & 0 deletions crates/quarto-markdown-pandoc/src/pandoc/caption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ use serde::{Deserialize, Serialize};
pub struct Caption {
pub short: Option<Inlines>,
pub long: Option<Blocks>,
pub source_info: quarto_source_map::SourceInfo,
}
Loading