Skip to content

Commit 6ba71ed

Browse files
committed
fix: inline image title location mapping and macro titles
Fixes issue #111.
1 parent ecee040 commit 6ba71ed

File tree

5 files changed

+83
-73
lines changed

5 files changed

+83
-73
lines changed
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
image::sunset.jpg[alt=Sunset,width=300,height=400]
1+
.A *beautiful* sunset
2+
image::sunset.jpg[alt=Sunset,width=300,height=400]

acdc-parser/fixtures/tests/document_with_image_block.json

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,35 @@
77
"type": "block",
88
"form": "macro",
99
"target": "sunset.jpg",
10+
"title": [
11+
{
12+
"name":"text",
13+
"type":"string",
14+
"value":"A ",
15+
"location":[{"line":1,"col":2},{"line":1,"col":3}]
16+
},
17+
{
18+
"name":"span",
19+
"type":"inline",
20+
"variant":"strong",
21+
"form":"constrained",
22+
"inlines":[
23+
{
24+
"name":"text",
25+
"type":"string",
26+
"value":"beautiful",
27+
"location":[{"line":1,"col":4},{"line":1,"col":12}]
28+
}
29+
],
30+
"location":[{"line":1,"col":4},{"line":1,"col":14}]
31+
},
32+
{
33+
"name":"text",
34+
"type":"string",
35+
"value":" sunset",
36+
"location":[{"line":1,"col":15},{"line":1,"col":21}]
37+
}
38+
],
1039
"metadata": {
1140
"attributes": {
1241
"alt": "Sunset",
@@ -20,7 +49,7 @@
2049
"col": 1
2150
},
2251
{
23-
"line": 1,
52+
"line": 2,
2453
"col": 50
2554
}
2655
]
@@ -32,7 +61,7 @@
3261
"col": 1
3362
},
3463
{
35-
"line": 1,
64+
"line": 2,
3665
"col": 50
3766
}
3867
]

acdc-parser/fixtures/tests/inline_image.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
"location": [
1818
{
1919
"line": 1,
20-
"col": 1
20+
"col": 23
2121
},
2222
{
2323
"line": 1,
24-
"col": 11
24+
"col": 33
2525
}
2626
]
2727
}

acdc-parser/src/grammar/document.rs

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ pub(crate) struct BlockParsingMetadata {
6969
pub(crate) metadata: BlockMetadata,
7070
title: Vec<InlineNode>,
7171
parent_section_level: Option<SectionLevel>,
72+
pub(crate) attribute_positions: std::collections::HashMap<String, (usize, usize)>,
7273
}
7374

7475
#[derive(Debug)]
@@ -422,6 +423,7 @@ peg::parser! {
422423
metadata,
423424
title,
424425
parent_section_level,
426+
attribute_positions: std::collections::HashMap::new(),
425427
}
426428
}
427429

@@ -1007,10 +1009,10 @@ peg::parser! {
10071009
}))
10081010
}
10091011

1010-
rule image(start: usize, offset: usize, _block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
1012+
rule image(start: usize, offset: usize, block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
10111013
= "image::" source:source() attributes:attributes() end:position!()
10121014
{
1013-
let (_discrete, metadata) = attributes;
1015+
let (_discrete, metadata, _title_position) = attributes;
10141016
let mut metadata = metadata.clone();
10151017
if let Some(style) = metadata.style {
10161018
metadata.style = None; // Clear style to avoid confusion
@@ -1024,22 +1026,22 @@ peg::parser! {
10241026
}
10251027
metadata.move_positional_attributes_to_attributes();
10261028
Ok(Block::Image(Image {
1027-
title: Vec::new(), // TODO(nlopes): Handle image titles
1029+
title: block_metadata.title.clone(),
10281030
source,
10291031
metadata: metadata.clone(),
10301032
location: state.create_location(start+offset, (end+offset).saturating_sub(1)),
10311033

10321034
}))
10331035
}
10341036

1035-
rule audio(start: usize, offset: usize, _block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
1037+
rule audio(start: usize, offset: usize, block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
10361038
= "audio::" source:source() attributes:attributes() end:position!()
10371039
{
1038-
let (_discrete, metadata) = attributes;
1040+
let (_discrete, metadata, _title_position) = attributes;
10391041
let mut metadata = metadata.clone();
10401042
metadata.move_positional_attributes_to_attributes();
10411043
Ok(Block::Audio(Audio {
1042-
title: Vec::new(), // TODO(nlopes): Handle audio titles
1044+
title: block_metadata.title.clone(),
10431045
source,
10441046
metadata,
10451047
location: state.create_location(start+offset, (end+offset).saturating_sub(1)),
@@ -1049,10 +1051,10 @@ peg::parser! {
10491051
// The video block is similar to the audio and image blocks, but it supports
10501052
// multiple sources. This is for example to allow passing multiple youtube video
10511053
// ids to form a playlist.
1052-
rule video(start: usize, offset: usize, _block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
1054+
rule video(start: usize, offset: usize, block_metadata: &BlockParsingMetadata) -> Result<Block, Error>
10531055
= "video::" sources:(source() ** comma()) attributes:attributes() end:position!()
10541056
{
1055-
let (_discrete, metadata) = attributes;
1057+
let (_discrete, metadata, _title_position) = attributes;
10561058
let mut metadata = metadata.clone();
10571059
if let Some(style) = metadata.style {
10581060
metadata.style = None;
@@ -1073,7 +1075,7 @@ peg::parser! {
10731075
}
10741076
metadata.move_positional_attributes_to_attributes();
10751077
Ok(Block::Video(Video {
1076-
title: Vec::new(), // TODO(nlopes): Handle video titles
1078+
title: block_metadata.title.clone(),
10771079
sources,
10781080
metadata,
10791081
location: state.create_location(start+offset, (end+offset).saturating_sub(1)),
@@ -1361,7 +1363,7 @@ peg::parser! {
13611363
rule inline_icon(offset: usize, _block_metadata: &BlockParsingMetadata) -> InlineNode
13621364
= start:position() "icon:" source:source() attributes:attributes() end:position!()
13631365
{
1364-
let (_discrete, metadata) = attributes;
1366+
let (_discrete, metadata, _title_position) = attributes;
13651367
let mut metadata = metadata.clone();
13661368
metadata.move_positional_attributes_to_attributes();
13671369
InlineNode::Macro(InlineMacro::Icon(Icon {
@@ -1375,7 +1377,7 @@ peg::parser! {
13751377
rule inline_image(offset: usize, block_metadata: &BlockParsingMetadata) -> InlineNode
13761378
= start:position() "image:" source:source() attributes:attributes() end:position!()
13771379
{
1378-
let (_discrete, metadata) = attributes;
1380+
let (_discrete, metadata, title_position) = attributes;
13791381
let mut metadata = metadata.clone();
13801382
let mut title = Vec::new();
13811383
if let Some(style) = metadata.style {
@@ -1390,7 +1392,17 @@ peg::parser! {
13901392
}
13911393
metadata.move_positional_attributes_to_attributes();
13921394
if let Some(AttributeValue::String(content)) = metadata.attributes.get("title") {
1393-
title = process_inlines(state, block_metadata, start.offset, &start, end, offset, content).unwrap().0;
1395+
if let Some((title_start, title_end)) = title_position {
1396+
// Use the captured position from the named_attribute rule
1397+
let title_start_pos = Position {
1398+
offset: title_start,
1399+
position: state.line_map.offset_to_position(title_start),
1400+
};
1401+
title = process_inlines(state, block_metadata, title_start, &title_start_pos, title_end, offset, content).unwrap().0;
1402+
} else {
1403+
// Fallback to the old behavior if we don't have the position
1404+
title = process_inlines(state, block_metadata, start.offset, &start, end, offset, content).unwrap().0;
1405+
}
13941406
metadata.attributes.remove("title");
13951407
}
13961408

@@ -1871,11 +1883,11 @@ peg::parser! {
18711883

18721884
pub(crate) rule attributes_line() -> (bool, BlockMetadata)
18731885
= attributes:attributes() eol() {
1874-
let (discrete, metadata) = attributes;
1886+
let (discrete, metadata, _title_position) = attributes;
18751887
(discrete, metadata)
18761888
}
18771889

1878-
pub(crate) rule attributes() -> (bool, BlockMetadata)
1890+
pub(crate) rule attributes() -> (bool, BlockMetadata, Option<(usize, usize)>)
18791891
= start:position!() open_square_bracket() content:(
18801892
// The case in which we keep the style empty
18811893
attributes:(comma() att:attribute() { att })+ {
@@ -1921,6 +1933,7 @@ peg::parser! {
19211933
metadata.options.push(option);
19221934
}
19231935
}
1936+
let mut title_position = None;
19241937
for (i, (k, v, pos)) in attributes.into_iter().flatten().enumerate() {
19251938
if k == RESERVED_NAMED_ATTRIBUTE_ID && metadata.id.is_none() {
19261939
let (id_start, id_end) = pos.unwrap_or((start, end));
@@ -1952,13 +1965,21 @@ peg::parser! {
19521965
}
19531966
}
19541967
} else if let AttributeValue::String(v) = v {
1968+
// We special case the "title" attribute to capture its position.
1969+
// An example where this is needed is in the inline image macro.
1970+
//
1971+
// I really don't like how this flows and one day I'll probably
1972+
// refactor this.
1973+
if k == "title" && let Some(title_pos) = pos {
1974+
title_position = Some(title_pos);
1975+
}
19551976
metadata.attributes.insert(k.to_string(), AttributeValue::String(v));
19561977
} else if v == AttributeValue::None && pos.is_none() {
19571978
metadata.positional_attributes.push(k);
19581979
tracing::warn!("Unexpected attribute value type: {:?}", v);
19591980
}
19601981
}
1961-
(discrete, metadata)
1982+
(discrete, metadata, title_position)
19621983
}
19631984

19641985
rule open_square_bracket() = "["
@@ -1999,8 +2020,8 @@ peg::parser! {
19992020
{ Some((RESERVED_NAMED_ATTRIBUTE_ROLE.to_string(), AttributeValue::String(role.to_string()), None)) }
20002021
/ ("options" / "opts") "=" option:option()
20012022
{ Some((RESERVED_NAMED_ATTRIBUTE_OPTIONS.to_string(), AttributeValue::String(option.to_string()), None)) }
2002-
/ name:attribute_name() "=" value:named_attribute_value()
2003-
{ Some((name.to_string(), AttributeValue::String(value), None)) }
2023+
/ name:attribute_name() "=" start:position!() value:named_attribute_value() end:position!()
2024+
{ Some((name.to_string(), AttributeValue::String(value), Some((start, end)))) }
20042025

20052026
// The block style is a positional attribute that is used to set the style of a block element.
20062027
//
@@ -2454,7 +2475,8 @@ Lorn_Kismet R. Lee <kismet@asciidoctor.org>; Norberto M. Lopes <nlopesml@gmail.c
24542475
fn test_document_empty_attribute_list() {
24552476
let input = "[]";
24562477
let mut state = ParserState::new(input);
2457-
let (discrete, metadata) = document_parser::attributes(input, &mut state).unwrap();
2478+
let (discrete, metadata, _title_position) =
2479+
document_parser::attributes(input, &mut state).unwrap();
24582480
assert!(!discrete); // Not discrete
24592481
assert_eq!(metadata.id, None);
24602482
assert_eq!(metadata.style, None);
@@ -2468,7 +2490,8 @@ Lorn_Kismet R. Lee <kismet@asciidoctor.org>; Norberto M. Lopes <nlopesml@gmail.c
24682490
fn test_document_empty_attribute_list_with_discrete() {
24692491
let input = "[discrete]";
24702492
let mut state = ParserState::new(input);
2471-
let (discrete, metadata) = document_parser::attributes(input, &mut state).unwrap();
2493+
let (discrete, metadata, _title_position) =
2494+
document_parser::attributes(input, &mut state).unwrap();
24722495
assert!(discrete); // Should be discrete
24732496
assert_eq!(metadata.id, None);
24742497
assert_eq!(metadata.style, None);
@@ -2481,7 +2504,8 @@ Lorn_Kismet R. Lee <kismet@asciidoctor.org>; Norberto M. Lopes <nlopesml@gmail.c
24812504
fn test_document_attribute_with_id() {
24822505
let input = "[id=my-id,role=admin,options=read,options=write]";
24832506
let mut state = ParserState::new(input);
2484-
let (discrete, metadata) = document_parser::attributes(input, &mut state).unwrap();
2507+
let (discrete, metadata, _title_position) =
2508+
document_parser::attributes(input, &mut state).unwrap();
24852509
assert!(!discrete); // Not discrete
24862510
assert_eq!(
24872511
metadata.id,
@@ -2510,7 +2534,8 @@ Lorn_Kismet R. Lee <kismet@asciidoctor.org>; Norberto M. Lopes <nlopesml@gmail.c
25102534
fn test_document_attribute_with_id_mixed() {
25112535
let input = "[astyle#myid.admin,options=read,options=write]";
25122536
let mut state = ParserState::new(input);
2513-
let (discrete, metadata) = document_parser::attributes(input, &mut state).unwrap();
2537+
let (discrete, metadata, _title_position) =
2538+
document_parser::attributes(input, &mut state).unwrap();
25142539
assert!(!discrete); // Not discrete
25152540
assert_eq!(
25162541
metadata.id,
@@ -2539,7 +2564,8 @@ Lorn_Kismet R. Lee <kismet@asciidoctor.org>; Norberto M. Lopes <nlopesml@gmail.c
25392564
fn test_document_attribute_with_id_mixed_with_quotes() {
25402565
let input = "[astyle#myid.admin,options=\"read,write\"]";
25412566
let mut state = ParserState::new(input);
2542-
let (discrete, metadata) = document_parser::attributes(input, &mut state).unwrap();
2567+
let (discrete, metadata, _title_position) =
2568+
document_parser::attributes(input, &mut state).unwrap();
25432569
assert!(!discrete); // Not discrete
25442570
assert_eq!(
25452571
metadata.id,

acdc-parser/src/lib.rs

Lines changed: 0 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -198,50 +198,4 @@ mod tests {
198198
tracing::warn!("no test file found for {:?}", path);
199199
}
200200
}
201-
202-
// #[test]
203-
// #[tracing_test::traced_test]
204-
// fn test_something() {
205-
// let options = Options {
206-
// safe_mode: SafeMode::Unsafe,
207-
// timings: false,
208-
// document_attributes: DocumentAttributes::default(),
209-
// };
210-
// let result = parse(
211-
// "Setext-style headers for +<h1>+ and +<h2>+ are created by",
212-
// &options,
213-
// )
214-
// .unwrap();
215-
// dbg!(&result);
216-
// assert!(matches!(
217-
// &result.blocks[0],
218-
// Block::Paragraph(Paragraph { content, .. }) if matches!(&content[0], InlineNode::PlainText(Plain { content, location }) if content.starts_with("Setext-style headers for <h1> and <h2> are created by") && location.start.line == 1 && location.end.line == 1 && location.start.column == 1 && location.end.column == 58)
219-
// ));
220-
// }
221-
//
222-
// #[test]
223-
// #[tracing_test::traced_test]
224-
// fn test_something() {
225-
// let options = Options {
226-
// safe_mode: SafeMode::Unsafe,
227-
// timings: false,
228-
// document_attributes: DocumentAttributes::default(),
229-
// };
230-
// let result = parse_file("../test_something1.adoc", &options).unwrap();
231-
// dbg!(&result);
232-
// panic!();
233-
// }
234-
235-
// #[test]
236-
// #[tracing_test::traced_test]
237-
// fn test_mdbasics_adoc() {
238-
// let options = Options {
239-
// safe_mode: SafeMode::Unsafe,
240-
// timings: false,
241-
// document_attributes: DocumentAttributes::default(),
242-
// };
243-
// let result = parse_file("fixtures/samples/mdbasics/mdbasics.adoc", &options).unwrap();
244-
// dbg!(&result);
245-
// panic!()
246-
// }
247201
}

0 commit comments

Comments
 (0)