2828
2929use std:: cast;
3030use std:: fmt;
31+ use std:: intrinsics;
3132use std:: io;
3233use std:: libc;
34+ use std:: local_data;
3335use std:: mem;
3436use std:: str;
35- use std:: intrinsics;
3637use std:: vec;
38+ use collections:: HashMap ;
3739
3840use html:: highlight;
41+ use html:: escape:: Escape ;
3942
4043/// A unit struct which has the `fmt::Show` trait implemented. When
4144/// formatted, this struct will emit the HTML corresponding to the rendered
@@ -52,8 +55,11 @@ static MKDEXT_STRIKETHROUGH: libc::c_uint = 1 << 4;
5255type sd_markdown = libc:: c_void ; // this is opaque to us
5356
5457struct sd_callbacks {
55- blockcode : extern "C" fn ( * buf , * buf , * buf , * libc:: c_void ) ,
56- other : [ libc:: size_t , ..25 ] ,
58+ blockcode : Option < extern "C" fn ( * buf , * buf , * buf , * libc:: c_void ) > ,
59+ blockquote : Option < extern "C" fn ( * buf , * buf , * libc:: c_void ) > ,
60+ blockhtml : Option < extern "C" fn ( * buf , * buf , * libc:: c_void ) > ,
61+ header : Option < extern "C" fn ( * buf , * buf , libc:: c_int , * libc:: c_void ) > ,
62+ other : [ libc:: size_t , ..22 ] ,
5763}
5864
5965struct html_toc_data {
@@ -115,6 +121,8 @@ fn stripped_filtered_line<'a>(s: &'a str) -> Option<&'a str> {
115121 }
116122}
117123
124+ local_data_key ! ( used_header_map: HashMap <~str , uint>)
125+
118126pub fn render ( w : & mut io:: Writer , s : & str ) -> fmt:: Result {
119127 extern fn block ( ob : * buf , text : * buf , lang : * buf , opaque : * libc:: c_void ) {
120128 unsafe {
@@ -155,6 +163,45 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
155163 }
156164 }
157165
166+ extern fn header ( ob : * buf , text : * buf , level : libc:: c_int ,
167+ _opaque : * libc:: c_void ) {
168+ // sundown does this, we may as well too
169+ "\n " . with_c_str ( |p| unsafe { bufputs ( ob, p) } ) ;
170+
171+ // Extract the text provided
172+ let s = if text. is_null ( ) {
173+ ~""
174+ } else {
175+ unsafe {
176+ str:: raw:: from_buf_len ( ( * text) . data , ( * text) . size as uint )
177+ }
178+ } ;
179+
180+ // Transform the contents of the header into a hyphenated string
181+ let id = s. words ( ) . map ( |s| {
182+ match s. to_ascii_opt ( ) {
183+ Some ( s) => s. to_lower ( ) . into_str ( ) ,
184+ None => s. to_owned ( )
185+ }
186+ } ) . to_owned_vec ( ) . connect ( "-" ) ;
187+
188+ // Make sure our hyphenated ID is unique for this page
189+ let id = local_data:: get_mut ( used_header_map, |map| {
190+ let map = map. unwrap ( ) ;
191+ match map. find_mut ( & id) {
192+ None => { }
193+ Some ( a) => { * a += 1 ; return format ! ( "{}-{}" , id, * a - 1 ) }
194+ }
195+ map. insert ( id. clone ( ) , 1 ) ;
196+ id. clone ( )
197+ } ) ;
198+
199+ // Render the HTML
200+ let text = format ! ( r#"<h{lvl} id="{id}">{}</h{lvl}>"# ,
201+ Escape ( s. as_slice( ) ) , lvl = level, id = id) ;
202+ text. with_c_str ( |p| unsafe { bufputs ( ob, p) } ) ;
203+ }
204+
158205 // This code is all lifted from examples/sundown.c in the sundown repo
159206 unsafe {
160207 let ob = bufnew ( OUTPUT_UNIT ) ;
@@ -175,9 +222,10 @@ pub fn render(w: &mut io::Writer, s: &str) -> fmt::Result {
175222 sdhtml_renderer ( & callbacks, & options, 0 ) ;
176223 let opaque = my_opaque {
177224 opt : options,
178- dfltblk : callbacks. blockcode ,
225+ dfltblk : callbacks. blockcode . unwrap ( ) ,
179226 } ;
180- callbacks. blockcode = block;
227+ callbacks. blockcode = Some ( block) ;
228+ callbacks. header = Some ( header) ;
181229 let markdown = sd_markdown_new ( extensions, 16 , & callbacks,
182230 & opaque as * my_opaque as * libc:: c_void ) ;
183231
@@ -225,7 +273,10 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
225273 MKDEXT_FENCED_CODE | MKDEXT_AUTOLINK |
226274 MKDEXT_STRIKETHROUGH ;
227275 let callbacks = sd_callbacks {
228- blockcode : block,
276+ blockcode : Some ( block) ,
277+ blockquote : None ,
278+ blockhtml : None ,
279+ header : None ,
229280 other : mem:: init ( )
230281 } ;
231282
@@ -239,6 +290,18 @@ pub fn find_testable_code(doc: &str, tests: &mut ::test::Collector) {
239290 }
240291}
241292
293+ /// By default this markdown renderer generates anchors for each header in the
294+ /// rendered document. The anchor name is the contents of the header spearated
295+ /// by hyphens, and a task-local map is used to disambiguate among duplicate
296+ /// headers (numbers are appended).
297+ ///
298+ /// This method will reset the local table for these headers. This is typically
299+ /// used at the beginning of rendering an entire HTML page to reset from the
300+ /// previous state (if any).
301+ pub fn reset_headers ( ) {
302+ local_data:: set ( used_header_map, HashMap :: new ( ) )
303+ }
304+
242305impl < ' a > fmt:: Show for Markdown < ' a > {
243306 fn fmt ( & self , fmt : & mut fmt:: Formatter ) -> fmt:: Result {
244307 let Markdown ( md) = * self ;
0 commit comments