@@ -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,
0 commit comments