1 // Copyright 2013-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 //! Markdown formatting for rustdoc
13 //! This module implements markdown formatting through the hoedown C-library
14 //! (bundled into the rust runtime). This module self-contains the C bindings
15 //! and necessary legwork to render markdown, and exposes all of the
16 //! functionality through a unit-struct, `Markdown`, which has an implementation
17 //! of `fmt::Display`. Example usage:
20 //! use rustdoc::html::markdown::Markdown;
22 //! let s = "My *markdown* _text_";
23 //! let html = format!("{}", Markdown(s));
24 //! // ... something using html
27 #![allow(non_camel_case_types)]
30 use rustc
::session
::config
::get_unstable_features_setting
;
31 use std
::ascii
::AsciiExt
;
32 use std
::cell
::RefCell
;
33 use std
::default::Default
;
34 use std
::ffi
::CString
;
38 use syntax
::feature_gate
::UnstableFeatures
;
40 use html
::render
::derive_id
;
41 use html
::toc
::TocBuilder
;
43 use html
::escape
::Escape
;
46 /// A unit struct which has the `fmt::Display` trait implemented. When
47 /// formatted, this struct will emit the HTML corresponding to the rendered
48 /// version of the contained markdown string.
49 pub struct Markdown
<'a
>(pub &'a
str);
50 /// A unit struct like `Markdown`, that renders the markdown with a
51 /// table of contents.
52 pub struct MarkdownWithToc
<'a
>(pub &'a
str);
54 const DEF_OUNIT
: libc
::size_t
= 64;
55 const HOEDOWN_EXT_NO_INTRA_EMPHASIS
: libc
::c_uint
= 1 << 11;
56 const HOEDOWN_EXT_TABLES
: libc
::c_uint
= 1 << 0;
57 const HOEDOWN_EXT_FENCED_CODE
: libc
::c_uint
= 1 << 1;
58 const HOEDOWN_EXT_AUTOLINK
: libc
::c_uint
= 1 << 3;
59 const HOEDOWN_EXT_STRIKETHROUGH
: libc
::c_uint
= 1 << 4;
60 const HOEDOWN_EXT_SUPERSCRIPT
: libc
::c_uint
= 1 << 8;
61 const HOEDOWN_EXT_FOOTNOTES
: libc
::c_uint
= 1 << 2;
63 const HOEDOWN_EXTENSIONS
: libc
::c_uint
=
64 HOEDOWN_EXT_NO_INTRA_EMPHASIS
| HOEDOWN_EXT_TABLES
|
65 HOEDOWN_EXT_FENCED_CODE
| HOEDOWN_EXT_AUTOLINK
|
66 HOEDOWN_EXT_STRIKETHROUGH
| HOEDOWN_EXT_SUPERSCRIPT
|
67 HOEDOWN_EXT_FOOTNOTES
;
69 enum hoedown_document {}
71 type blockcodefn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
72 *const hoedown_buffer
, *const hoedown_renderer_data
);
74 type blockquotefn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
75 *const hoedown_renderer_data
);
77 type headerfn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
78 libc
::c_int
, *const hoedown_renderer_data
);
80 type blockhtmlfn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
81 *const hoedown_renderer_data
);
83 type codespanfn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
84 *const hoedown_renderer_data
) -> libc
::c_int
;
86 type linkfn
= extern "C" fn (*mut hoedown_buffer
, *const hoedown_buffer
,
87 *const hoedown_buffer
, *const hoedown_buffer
,
88 *const hoedown_renderer_data
) -> libc
::c_int
;
90 type entityfn
= extern "C" fn (*mut hoedown_buffer
, *const hoedown_buffer
,
91 *const hoedown_renderer_data
);
93 type normaltextfn
= extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
94 *const hoedown_renderer_data
);
97 struct hoedown_renderer_data
{
98 opaque
: *mut libc
::c_void
,
102 struct hoedown_renderer
{
103 opaque
: *mut libc
::c_void
,
105 blockcode
: Option
<blockcodefn
>,
106 blockquote
: Option
<blockquotefn
>,
107 header
: Option
<headerfn
>,
109 other_block_level_callbacks
: [libc
::size_t
; 11],
111 blockhtml
: Option
<blockhtmlfn
>,
113 /* span level callbacks - NULL or return 0 prints the span verbatim */
114 autolink
: libc
::size_t
, // unused
115 codespan
: Option
<codespanfn
>,
116 other_span_level_callbacks_1
: [libc
::size_t
; 7],
117 link
: Option
<linkfn
>,
118 other_span_level_callbacks_2
: [libc
::size_t
; 6],
120 /* low level callbacks - NULL copies input directly into the output */
121 entity
: Option
<entityfn
>,
122 normal_text
: Option
<normaltextfn
>,
124 /* header and footer */
125 other_callbacks
: [libc
::size_t
; 2],
129 struct hoedown_html_renderer_state
{
130 opaque
: *mut libc
::c_void
,
131 toc_data
: html_toc_data
,
133 link_attributes
: Option
<extern "C" fn(*mut hoedown_buffer
,
134 *const hoedown_buffer
,
135 *const hoedown_renderer_data
)>,
139 struct html_toc_data
{
140 header_count
: libc
::c_int
,
141 current_level
: libc
::c_int
,
142 level_offset
: libc
::c_int
,
143 nesting_level
: libc
::c_int
,
147 dfltblk
: extern "C" fn(*mut hoedown_buffer
, *const hoedown_buffer
,
148 *const hoedown_buffer
, *const hoedown_renderer_data
),
149 toc_builder
: Option
<TocBuilder
>,
153 struct hoedown_buffer
{
161 #[link(name = "hoedown", kind = "static")]
162 #[cfg(not(cargobuild))]
166 fn hoedown_html_renderer_new(render_flags
: libc
::c_uint
,
167 nesting_level
: libc
::c_int
)
168 -> *mut hoedown_renderer
;
169 fn hoedown_html_renderer_free(renderer
: *mut hoedown_renderer
);
171 fn hoedown_document_new(rndr
: *const hoedown_renderer
,
172 extensions
: libc
::c_uint
,
173 max_nesting
: libc
::size_t
) -> *mut hoedown_document
;
174 fn hoedown_document_render(doc
: *mut hoedown_document
,
175 ob
: *mut hoedown_buffer
,
177 doc_size
: libc
::size_t
);
178 fn hoedown_document_free(md
: *mut hoedown_document
);
180 fn hoedown_buffer_new(unit
: libc
::size_t
) -> *mut hoedown_buffer
;
181 fn hoedown_buffer_put(b
: *mut hoedown_buffer
, c
: *const libc
::c_char
,
183 fn hoedown_buffer_puts(b
: *mut hoedown_buffer
, c
: *const libc
::c_char
);
184 fn hoedown_buffer_free(b
: *mut hoedown_buffer
);
188 // hoedown_buffer helpers
189 impl hoedown_buffer
{
190 fn as_bytes(&self) -> &[u8] {
191 unsafe { slice::from_raw_parts(self.data, self.size as usize) }
195 /// Returns Some(code) if `s` is a line that should be stripped from
196 /// documentation but used in example code. `code` is the portion of
197 /// `s` that should be used in tests. (None for lines that should be
199 fn stripped_filtered_line
<'a
>(s
: &'a
str) -> Option
<&'a
str> {
200 let trimmed
= s
.trim();
203 } else if trimmed
.starts_with("# ") {
210 /// Returns a new string with all consecutive whitespace collapsed into
213 /// Any leading or trailing whitespace will be trimmed.
214 fn collapse_whitespace(s
: &str) -> String
{
215 s
.split_whitespace().collect
::<Vec
<_
>>().join(" ")
218 thread_local
!(pub static PLAYGROUND_KRATE
: RefCell
<Option
<Option
<String
>>> = {
222 pub fn render(w
: &mut fmt
::Formatter
, s
: &str, print_toc
: bool
) -> fmt
::Result
{
223 extern fn block(ob
: *mut hoedown_buffer
, orig_text
: *const hoedown_buffer
,
224 lang
: *const hoedown_buffer
, data
: *const hoedown_renderer_data
) {
226 if orig_text
.is_null() { return }
228 let opaque
= (*data
).opaque
as *mut hoedown_html_renderer_state
;
229 let my_opaque
: &MyOpaque
= &*((*opaque
).opaque
as *const MyOpaque
);
230 let text
= (*orig_text
).as_bytes();
231 let origtext
= str::from_utf8(text
).unwrap();
232 debug
!("docblock: ==============\n{:?}\n=======", text
);
233 let rendered
= if lang
.is_null() {
236 let rlang
= (*lang
).as_bytes();
237 let rlang
= str::from_utf8(rlang
).unwrap();
238 if !LangString
::parse(rlang
).rust
{
239 (my_opaque
.dfltblk
)(ob
, orig_text
, lang
,
240 opaque
as *const hoedown_renderer_data
);
247 let lines
= origtext
.lines().filter(|l
| {
248 stripped_filtered_line(*l
).is_none()
250 let text
= lines
.collect
::<Vec
<&str>>().join("\n");
251 if rendered { return }
252 PLAYGROUND_KRATE
.with(|krate
| {
253 // insert newline to clearly separate it from the
254 // previous block so we can shorten the html output
255 let mut s
= String
::from("\n");
256 krate
.borrow().as_ref().map(|krate
| {
257 let test
= origtext
.lines().map(|l
| {
258 stripped_filtered_line(l
).unwrap_or(l
)
259 }).collect
::<Vec
<&str>>().join("\n");
260 let krate
= krate
.as_ref().map(|s
| &**s
);
261 let test
= test
::maketest(&test
, krate
, false,
262 &Default
::default());
263 s
.push_str(&format
!("<span class='rusttest'>{}</span>", Escape(&test
)));
265 s
.push_str(&highlight
::render_with_highlighting(&text
,
266 Some("rust-example-rendered"),
268 let output
= CString
::new(s
).unwrap();
269 hoedown_buffer_puts(ob
, output
.as_ptr());
274 extern fn header(ob
: *mut hoedown_buffer
, text
: *const hoedown_buffer
,
275 level
: libc
::c_int
, data
: *const hoedown_renderer_data
) {
276 // hoedown does this, we may as well too
277 unsafe { hoedown_buffer_puts(ob, "\n\0".as_ptr() as *const _); }
279 // Extract the text provided
280 let s
= if text
.is_null() {
283 let s
= unsafe { (*text).as_bytes() }
;
284 str::from_utf8(&s
).unwrap().to_owned()
287 // Discard '<em>', '<code>' tags and some escaped characters,
288 // transform the contents of the header into a hyphenated string
289 // without non-alphanumeric characters other than '-' and '_'.
291 // This is a terrible hack working around how hoedown gives us rendered
292 // html for text rather than the raw text.
293 let mut id
= s
.clone();
294 let repl_sub
= vec
!["<em>", "</em>", "<code>", "</code>",
295 "<strong>", "</strong>",
296 "<", ">", "&", "'", """];
297 for sub
in repl_sub
{
298 id
= id
.replace(sub
, "");
300 let id
= id
.chars().filter_map(|c
| {
301 if c
.is_alphanumeric() || c
== '
-'
|| c
== '_'
{
303 Some(c
.to_ascii_lowercase())
307 } else if c
.is_whitespace() && c
.is_ascii() {
312 }).collect
::<String
>();
314 let opaque
= unsafe { (*data).opaque as *mut hoedown_html_renderer_state }
;
315 let opaque
= unsafe { &mut *((*opaque).opaque as *mut MyOpaque) }
;
317 let id
= derive_id(id
);
319 let sec
= opaque
.toc_builder
.as_mut().map_or("".to_owned(), |builder
| {
320 format
!("{} ", builder
.push(level
as u32, s
.clone(), id
.clone()))
324 let text
= format
!("<h{lvl} id='{id}' class='section-header'>\
325 <a href='#{id}'>{sec}{}</a></h{lvl}>",
326 s
, lvl
= level
, id
= id
, sec
= sec
);
328 let text
= CString
::new(text
).unwrap();
329 unsafe { hoedown_buffer_puts(ob, text.as_ptr()) }
333 ob
: *mut hoedown_buffer
,
334 text
: *const hoedown_buffer
,
335 _
: *const hoedown_renderer_data
,
337 let content
= if text
.is_null() {
340 let bytes
= unsafe { (*text).as_bytes() }
;
341 let s
= str::from_utf8(bytes
).unwrap();
342 collapse_whitespace(s
)
345 let content
= format
!("<code>{}</code>", Escape(&content
));
346 let element
= CString
::new(content
).unwrap();
347 unsafe { hoedown_buffer_puts(ob, element.as_ptr()); }
348 // Return anything except 0, which would mean "also print the code span verbatim".
353 let ob
= hoedown_buffer_new(DEF_OUNIT
);
354 let renderer
= hoedown_html_renderer_new(0, 0);
355 let mut opaque
= MyOpaque
{
356 dfltblk
: (*renderer
).blockcode
.unwrap(),
357 toc_builder
: if print_toc {Some(TocBuilder::new())}
else {None}
359 (*((*renderer
).opaque
as *mut hoedown_html_renderer_state
)).opaque
360 = &mut opaque
as *mut _
as *mut libc
::c_void
;
361 (*renderer
).blockcode
= Some(block
);
362 (*renderer
).header
= Some(header
);
363 (*renderer
).codespan
= Some(codespan
);
365 let document
= hoedown_document_new(renderer
, HOEDOWN_EXTENSIONS
, 16);
366 hoedown_document_render(document
, ob
, s
.as_ptr(),
367 s
.len() as libc
::size_t
);
368 hoedown_document_free(document
);
370 hoedown_html_renderer_free(renderer
);
372 let mut ret
= opaque
.toc_builder
.map_or(Ok(()), |builder
| {
373 write
!(w
, "<nav id=\"TOC\">{}</nav>", builder
.into_toc())
377 let buf
= (*ob
).as_bytes();
378 ret
= w
.write_str(str::from_utf8(buf
).unwrap());
380 hoedown_buffer_free(ob
);
385 pub fn find_testable_code(doc
: &str, tests
: &mut ::test
::Collector
) {
386 extern fn block(_ob
: *mut hoedown_buffer
,
387 text
: *const hoedown_buffer
,
388 lang
: *const hoedown_buffer
,
389 data
: *const hoedown_renderer_data
) {
391 if text
.is_null() { return }
392 let block_info
= if lang
.is_null() {
393 LangString
::all_false()
395 let lang
= (*lang
).as_bytes();
396 let s
= str::from_utf8(lang
).unwrap();
399 if !block_info
.rust { return }
400 let text
= (*text
).as_bytes();
401 let opaque
= (*data
).opaque
as *mut hoedown_html_renderer_state
;
402 let tests
= &mut *((*opaque
).opaque
as *mut ::test
::Collector
);
403 let text
= str::from_utf8(text
).unwrap();
404 let lines
= text
.lines().map(|l
| {
405 stripped_filtered_line(l
).unwrap_or(l
)
407 let text
= lines
.collect
::<Vec
<&str>>().join("\n");
408 tests
.add_test(text
.to_owned(),
409 block_info
.should_panic
, block_info
.no_run
,
410 block_info
.ignore
, block_info
.test_harness
,
411 block_info
.compile_fail
);
415 extern fn header(_ob
: *mut hoedown_buffer
,
416 text
: *const hoedown_buffer
,
417 level
: libc
::c_int
, data
: *const hoedown_renderer_data
) {
419 let opaque
= (*data
).opaque
as *mut hoedown_html_renderer_state
;
420 let tests
= &mut *((*opaque
).opaque
as *mut ::test
::Collector
);
422 tests
.register_header("", level
as u32);
424 let text
= (*text
).as_bytes();
425 let text
= str::from_utf8(text
).unwrap();
426 tests
.register_header(text
, level
as u32);
432 let ob
= hoedown_buffer_new(DEF_OUNIT
);
433 let renderer
= hoedown_html_renderer_new(0, 0);
434 (*renderer
).blockcode
= Some(block
);
435 (*renderer
).header
= Some(header
);
436 (*((*renderer
).opaque
as *mut hoedown_html_renderer_state
)).opaque
437 = tests
as *mut _
as *mut libc
::c_void
;
439 let document
= hoedown_document_new(renderer
, HOEDOWN_EXTENSIONS
, 16);
440 hoedown_document_render(document
, ob
, doc
.as_ptr(),
441 doc
.len() as libc
::size_t
);
442 hoedown_document_free(document
);
444 hoedown_html_renderer_free(renderer
);
445 hoedown_buffer_free(ob
);
449 #[derive(Eq, PartialEq, Clone, Debug)]
460 fn all_false() -> LangString
{
465 rust
: true, // NB This used to be `notrust = false`
471 fn parse(string
: &str) -> LangString
{
472 let mut seen_rust_tags
= false;
473 let mut seen_other_tags
= false;
474 let mut data
= LangString
::all_false();
475 let allow_compile_fail
= match get_unstable_features_setting() {
476 UnstableFeatures
::Allow
| UnstableFeatures
::Cheat
=> true,
480 let tokens
= string
.split(|c
: char|
481 !(c
== '_'
|| c
== '
-'
|| c
.is_alphanumeric())
484 for token
in tokens
{
487 "should_panic" => { data.should_panic = true; seen_rust_tags = true; }
,
488 "no_run" => { data.no_run = true; seen_rust_tags = true; }
,
489 "ignore" => { data.ignore = true; seen_rust_tags = true; }
,
490 "rust" => { data.rust = true; seen_rust_tags = true; }
,
491 "test_harness" => { data.test_harness = true; seen_rust_tags = true; }
,
492 "compile_fail" if allow_compile_fail
=> {
493 data
.compile_fail
= true;
494 seen_rust_tags
= true;
497 _
=> { seen_other_tags = true }
501 data
.rust
&= !seen_other_tags
|| seen_rust_tags
;
507 impl<'a
> fmt
::Display
for Markdown
<'a
> {
508 fn fmt(&self, fmt
: &mut fmt
::Formatter
) -> fmt
::Result
{
509 let Markdown(md
) = *self;
510 // This is actually common enough to special-case
511 if md
.is_empty() { return Ok(()) }
512 render(fmt
, md
, false)
516 impl<'a
> fmt
::Display
for MarkdownWithToc
<'a
> {
517 fn fmt(&self, fmt
: &mut fmt
::Formatter
) -> fmt
::Result
{
518 let MarkdownWithToc(md
) = *self;
519 render(fmt
, md
, true)
523 pub fn plain_summary_line(md
: &str) -> String
{
524 extern fn link(_ob
: *mut hoedown_buffer
,
525 _link
: *const hoedown_buffer
,
526 _title
: *const hoedown_buffer
,
527 content
: *const hoedown_buffer
,
528 data
: *const hoedown_renderer_data
) -> libc
::c_int
531 if !content
.is_null() && (*content
).size
> 0 {
532 let ob
= (*data
).opaque
as *mut hoedown_buffer
;
533 hoedown_buffer_put(ob
, (*content
).data
as *const libc
::c_char
,
540 extern fn normal_text(_ob
: *mut hoedown_buffer
,
541 text
: *const hoedown_buffer
,
542 data
: *const hoedown_renderer_data
)
545 let ob
= (*data
).opaque
as *mut hoedown_buffer
;
546 hoedown_buffer_put(ob
, (*text
).data
as *const libc
::c_char
,
552 let ob
= hoedown_buffer_new(DEF_OUNIT
);
553 let mut plain_renderer
: hoedown_renderer
= ::std
::mem
::zeroed();
554 let renderer
: *mut hoedown_renderer
= &mut plain_renderer
;
555 (*renderer
).opaque
= ob
as *mut libc
::c_void
;
556 (*renderer
).link
= Some(link
);
557 (*renderer
).normal_text
= Some(normal_text
);
559 let document
= hoedown_document_new(renderer
, HOEDOWN_EXTENSIONS
, 16);
560 hoedown_document_render(document
, ob
, md
.as_ptr(),
561 md
.len() as libc
::size_t
);
562 hoedown_document_free(document
);
563 let plain_slice
= (*ob
).as_bytes();
564 let plain
= str::from_utf8(plain_slice
).unwrap_or("").to_owned();
565 hoedown_buffer_free(ob
);
572 use super::{LangString, Markdown}
;
573 use super::plain_summary_line
;
574 use html
::render
::reset_ids
;
577 fn test_lang_string_parse() {
579 should_panic
: bool
, no_run
: bool
, ignore
: bool
, rust
: bool
, test_harness
: bool
,
580 compile_fail
: bool
) {
581 assert_eq
!(LangString
::parse(s
), LangString
{
582 should_panic
: should_panic
,
586 test_harness
: test_harness
,
587 compile_fail
: compile_fail
,
591 // marker | should_panic| no_run| ignore| rust | test_harness| compile_fail
592 t("", false, false, false, true, false, false);
593 t("rust", false, false, false, true, false, false);
594 t("sh", false, false, false, false, false, false);
595 t("ignore", false, false, true, true, false, false);
596 t("should_panic", true, false, false, true, false, false);
597 t("no_run", false, true, false, true, false, false);
598 t("test_harness", false, false, false, true, true, false);
599 t("compile_fail", false, true, false, true, false, true);
600 t("{.no_run .example}", false, true, false, true, false, false);
601 t("{.sh .should_panic}", true, false, false, true, false, false);
602 t("{.example .rust}", false, false, false, true, false, false);
603 t("{.test_harness .rust}", false, false, false, true, true, false);
608 let markdown
= "# title";
609 format
!("{}", Markdown(markdown
));
615 fn t(input
: &str, expect
: &str) {
616 let output
= format
!("{}", Markdown(input
));
617 assert_eq
!(output
, expect
);
621 t("# Foo bar", "\n<h1 id='foo-bar' class='section-header'>\
622 <a href='#foo-bar'>Foo bar</a></h1>");
623 t("## Foo-bar_baz qux", "\n<h2 id='foo-bar_baz-qux' class=\'section-\
624 header'><a href='#foo-bar_baz-qux'>Foo-bar_baz qux</a></h2>");
625 t("### **Foo** *bar* baz!?!& -_qux_-%",
626 "\n<h3 id='foo-bar-baz--_qux_-' class='section-header'>\
627 <a href='#foo-bar-baz--_qux_-'><strong>Foo</strong> \
628 <em>bar</em> baz!?!& -_qux_-%</a></h3>");
629 t("####**Foo?** & \\*bar?!* _`baz`_ ❤ #qux",
630 "\n<h4 id='foo--bar--baz--qux' class='section-header'>\
631 <a href='#foo--bar--baz--qux'><strong>Foo?</strong> & *bar?!* \
632 <em><code>baz</code></em> ❤ #qux</a></h4>");
636 fn test_header_ids_multiple_blocks() {
637 fn t(input
: &str, expect
: &str) {
638 let output
= format
!("{}", Markdown(input
));
639 assert_eq
!(output
, expect
);
643 t("# Example", "\n<h1 id='example' class='section-header'>\
644 <a href='#example'>Example</a></h1>");
645 t("# Panics", "\n<h1 id='panics' class='section-header'>\
646 <a href='#panics'>Panics</a></h1>");
647 t("# Example", "\n<h1 id='example-1' class='section-header'>\
648 <a href='#example-1'>Example</a></h1>");
649 t("# Main", "\n<h1 id='main-1' class='section-header'>\
650 <a href='#main-1'>Main</a></h1>");
651 t("# Example", "\n<h1 id='example-2' class='section-header'>\
652 <a href='#example-2'>Example</a></h1>");
653 t("# Panics", "\n<h1 id='panics-1' class='section-header'>\
654 <a href='#panics-1'>Panics</a></h1>");
662 fn test_plain_summary_line() {
663 fn t(input
: &str, expect
: &str) {
664 let output
= plain_summary_line(input
);
665 assert_eq
!(output
, expect
);
668 t("hello [Rust](https://www.rust-lang.org) :)", "hello Rust :)");
669 t("code `let x = i32;` ...", "code `let x = i32;` ...");
670 t("type `Type<'static>` ...", "type `Type<'static>` ...");
671 t("# top header", "top header");
672 t("## header", "header");