@@ -9,6 +9,7 @@ use regex::Regex;
99use pulldown_cmark:: { html, CodeBlockKind , CowStr , Event , Options , Parser , Tag } ;
1010
1111use std:: borrow:: Cow ;
12+ use std:: collections:: HashMap ;
1213use std:: fmt:: Write ;
1314use std:: path:: Path ;
1415
@@ -44,6 +45,8 @@ pub fn normalize_id(content: &str) -> String {
4445
4546/// Generate an ID for use with anchors which is derived from a "normalised"
4647/// string.
48+ // This function should be made private when the deprecation expires.
49+ #[ deprecated( since = "0.4.16" , note = "use unique_id_from_content instead" ) ]
4750pub fn id_from_content ( content : & str ) -> String {
4851 let mut content = content. to_string ( ) ;
4952
@@ -59,10 +62,30 @@ pub fn id_from_content(content: &str) -> String {
5962
6063 // Remove spaces and hashes indicating a header
6164 let trimmed = content. trim ( ) . trim_start_matches ( '#' ) . trim ( ) ;
62-
6365 normalize_id ( trimmed)
6466}
6567
68+ /// Generate an ID for use with anchors which is derived from a "normalised"
69+ /// string.
70+ ///
71+ /// Each ID returned will be unique, if the same `id_counter` is provided on
72+ /// each call.
73+ pub fn unique_id_from_content ( content : & str , id_counter : & mut HashMap < String , usize > ) -> String {
74+ let id = {
75+ #[ allow( deprecated) ]
76+ id_from_content ( content)
77+ } ;
78+
79+ // If we have headers with the same normalized id, append an incrementing counter
80+ let id_count = id_counter. entry ( id. clone ( ) ) . or_insert ( 0 ) ;
81+ let unique_id = match * id_count {
82+ 0 => id,
83+ id_count => format ! ( "{}-{}" , id, id_count) ,
84+ } ;
85+ * id_count += 1 ;
86+ unique_id
87+ }
88+
6689/// Fix links to the correct location.
6790///
6891/// This adjusts links, such as turning `.md` extensions to `.html`.
@@ -332,8 +355,9 @@ more text with spaces
332355 }
333356 }
334357
335- mod html_munging {
336- use super :: super :: { id_from_content, normalize_id} ;
358+ #[ allow( deprecated) ]
359+ mod id_from_content {
360+ use super :: super :: id_from_content;
337361
338362 #[ test]
339363 fn it_generates_anchors ( ) {
@@ -361,6 +385,10 @@ more text with spaces
361385 ) ;
362386 assert_eq ! ( id_from_content( "## Über" ) , "Über" ) ;
363387 }
388+ }
389+
390+ mod html_munging {
391+ use super :: super :: { normalize_id, unique_id_from_content} ;
364392
365393 #[ test]
366394 fn it_normalizes_ids ( ) {
@@ -379,5 +407,28 @@ more text with spaces
379407 assert_eq ! ( normalize_id( "한국어" ) , "한국어" ) ;
380408 assert_eq ! ( normalize_id( "" ) , "" ) ;
381409 }
410+
411+ #[ test]
412+ fn it_generates_unique_ids_from_content ( ) {
413+ // Same id if not given shared state
414+ assert_eq ! (
415+ unique_id_from_content( "## 中文標題 CJK title" , & mut Default :: default ( ) ) ,
416+ "中文標題-cjk-title"
417+ ) ;
418+ assert_eq ! (
419+ unique_id_from_content( "## 中文標題 CJK title" , & mut Default :: default ( ) ) ,
420+ "中文標題-cjk-title"
421+ ) ;
422+
423+ // Different id if given shared state
424+ let mut id_counter = Default :: default ( ) ;
425+ assert_eq ! ( unique_id_from_content( "## Über" , & mut id_counter) , "Über" ) ;
426+ assert_eq ! (
427+ unique_id_from_content( "## 中文標題 CJK title" , & mut id_counter) ,
428+ "中文標題-cjk-title"
429+ ) ;
430+ assert_eq ! ( unique_id_from_content( "## Über" , & mut id_counter) , "Über-1" ) ;
431+ assert_eq ! ( unique_id_from_content( "## Über" , & mut id_counter) , "Über-2" ) ;
432+ }
382433 }
383434}
0 commit comments