html.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. //
  2. // Blackfriday Markdown Processor
  3. // Available at http://github.com/russross/blackfriday
  4. //
  5. // Copyright © 2011 Russ Ross <russ@russross.com>.
  6. // Distributed under the Simplified BSD License.
  7. // See README.md for details.
  8. //
  9. //
  10. //
  11. // HTML rendering backend
  12. //
  13. //
  14. package blackfriday
  15. import (
  16. "bytes"
  17. "fmt"
  18. "io"
  19. "regexp"
  20. "strings"
  21. )
  22. // HTMLFlags control optional behavior of HTML renderer.
  23. type HTMLFlags int
  24. // HTML renderer configuration options.
  25. const (
  26. HTMLFlagsNone HTMLFlags = 0
  27. SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks
  28. SkipImages // Skip embedded images
  29. SkipLinks // Skip all links
  30. Safelink // Only link to trusted protocols
  31. NofollowLinks // Only link with rel="nofollow"
  32. NoreferrerLinks // Only link with rel="noreferrer"
  33. HrefTargetBlank // Add a blank target
  34. CompletePage // Generate a complete HTML page
  35. UseXHTML // Generate XHTML output instead of HTML
  36. FootnoteReturnLinks // Generate a link at the end of a footnote to return to the source
  37. Smartypants // Enable smart punctuation substitutions
  38. SmartypantsFractions // Enable smart fractions (with Smartypants)
  39. SmartypantsDashes // Enable smart dashes (with Smartypants)
  40. SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants)
  41. SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering
  42. SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants)
  43. TOC // Generate a table of contents
  44. )
  45. var (
  46. htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag)
  47. )
  48. const (
  49. htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" +
  50. processingInstruction + "|" + declaration + "|" + cdata + ")"
  51. closeTag = "</" + tagName + "\\s*[>]"
  52. openTag = "<" + tagName + attribute + "*" + "\\s*/?>"
  53. attribute = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)"
  54. attributeValue = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")"
  55. attributeValueSpec = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")"
  56. attributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*"
  57. cdata = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>"
  58. declaration = "<![A-Z]+" + "\\s+[^>]*>"
  59. doubleQuotedValue = "\"[^\"]*\""
  60. htmlComment = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->"
  61. processingInstruction = "[<][?].*?[?][>]"
  62. singleQuotedValue = "'[^']*'"
  63. tagName = "[A-Za-z][A-Za-z0-9-]*"
  64. unquotedValue = "[^\"'=<>`\\x00-\\x20]+"
  65. )
  66. // HTMLRendererParameters is a collection of supplementary parameters tweaking
  67. // the behavior of various parts of HTML renderer.
  68. type HTMLRendererParameters struct {
  69. // Prepend this text to each relative URL.
  70. AbsolutePrefix string
  71. // Add this text to each footnote anchor, to ensure uniqueness.
  72. FootnoteAnchorPrefix string
  73. // Show this text inside the <a> tag for a footnote return link, if the
  74. // HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string
  75. // <sup>[return]</sup> is used.
  76. FootnoteReturnLinkContents string
  77. // If set, add this text to the front of each Heading ID, to ensure
  78. // uniqueness.
  79. HeadingIDPrefix string
  80. // If set, add this text to the back of each Heading ID, to ensure uniqueness.
  81. HeadingIDSuffix string
  82. Title string // Document title (used if CompletePage is set)
  83. CSS string // Optional CSS file URL (used if CompletePage is set)
  84. Icon string // Optional icon file URL (used if CompletePage is set)
  85. Flags HTMLFlags // Flags allow customizing this renderer's behavior
  86. }
  87. // HTMLRenderer is a type that implements the Renderer interface for HTML output.
  88. //
  89. // Do not create this directly, instead use the NewHTMLRenderer function.
  90. type HTMLRenderer struct {
  91. HTMLRendererParameters
  92. closeTag string // how to end singleton tags: either " />" or ">"
  93. // Track heading IDs to prevent ID collision in a single generation.
  94. headingIDs map[string]int
  95. lastOutputLen int
  96. disableTags int
  97. sr *SPRenderer
  98. }
  99. const (
  100. xhtmlClose = " />"
  101. htmlClose = ">"
  102. )
  103. // NewHTMLRenderer creates and configures an HTMLRenderer object, which
  104. // satisfies the Renderer interface.
  105. func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer {
  106. // configure the rendering engine
  107. closeTag := htmlClose
  108. if params.Flags&UseXHTML != 0 {
  109. closeTag = xhtmlClose
  110. }
  111. if params.FootnoteReturnLinkContents == "" {
  112. params.FootnoteReturnLinkContents = `<sup>[return]</sup>`
  113. }
  114. return &HTMLRenderer{
  115. HTMLRendererParameters: params,
  116. closeTag: closeTag,
  117. headingIDs: make(map[string]int),
  118. sr: NewSmartypantsRenderer(params.Flags),
  119. }
  120. }
  121. func isHTMLTag(tag []byte, tagname string) bool {
  122. found, _ := findHTMLTagPos(tag, tagname)
  123. return found
  124. }
  125. // Look for a character, but ignore it when it's in any kind of quotes, it
  126. // might be JavaScript
  127. func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int {
  128. inSingleQuote := false
  129. inDoubleQuote := false
  130. inGraveQuote := false
  131. i := start
  132. for i < len(html) {
  133. switch {
  134. case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote:
  135. return i
  136. case html[i] == '\'':
  137. inSingleQuote = !inSingleQuote
  138. case html[i] == '"':
  139. inDoubleQuote = !inDoubleQuote
  140. case html[i] == '`':
  141. inGraveQuote = !inGraveQuote
  142. }
  143. i++
  144. }
  145. return start
  146. }
  147. func findHTMLTagPos(tag []byte, tagname string) (bool, int) {
  148. i := 0
  149. if i < len(tag) && tag[0] != '<' {
  150. return false, -1
  151. }
  152. i++
  153. i = skipSpace(tag, i)
  154. if i < len(tag) && tag[i] == '/' {
  155. i++
  156. }
  157. i = skipSpace(tag, i)
  158. j := 0
  159. for ; i < len(tag); i, j = i+1, j+1 {
  160. if j >= len(tagname) {
  161. break
  162. }
  163. if strings.ToLower(string(tag[i]))[0] != tagname[j] {
  164. return false, -1
  165. }
  166. }
  167. if i == len(tag) {
  168. return false, -1
  169. }
  170. rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>')
  171. if rightAngle >= i {
  172. return true, rightAngle
  173. }
  174. return false, -1
  175. }
  176. func skipSpace(tag []byte, i int) int {
  177. for i < len(tag) && isspace(tag[i]) {
  178. i++
  179. }
  180. return i
  181. }
  182. func isRelativeLink(link []byte) (yes bool) {
  183. // a tag begin with '#'
  184. if link[0] == '#' {
  185. return true
  186. }
  187. // link begin with '/' but not '//', the second maybe a protocol relative link
  188. if len(link) >= 2 && link[0] == '/' && link[1] != '/' {
  189. return true
  190. }
  191. // only the root '/'
  192. if len(link) == 1 && link[0] == '/' {
  193. return true
  194. }
  195. // current directory : begin with "./"
  196. if bytes.HasPrefix(link, []byte("./")) {
  197. return true
  198. }
  199. // parent directory : begin with "../"
  200. if bytes.HasPrefix(link, []byte("../")) {
  201. return true
  202. }
  203. return false
  204. }
  205. func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string {
  206. for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] {
  207. tmp := fmt.Sprintf("%s-%d", id, count+1)
  208. if _, tmpFound := r.headingIDs[tmp]; !tmpFound {
  209. r.headingIDs[id] = count + 1
  210. id = tmp
  211. } else {
  212. id = id + "-1"
  213. }
  214. }
  215. if _, found := r.headingIDs[id]; !found {
  216. r.headingIDs[id] = 0
  217. }
  218. return id
  219. }
  220. func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte {
  221. if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' {
  222. newDest := r.AbsolutePrefix
  223. if link[0] != '/' {
  224. newDest += "/"
  225. }
  226. newDest += string(link)
  227. return []byte(newDest)
  228. }
  229. return link
  230. }
  231. func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string {
  232. if isRelativeLink(link) {
  233. return attrs
  234. }
  235. val := []string{}
  236. if flags&NofollowLinks != 0 {
  237. val = append(val, "nofollow")
  238. }
  239. if flags&NoreferrerLinks != 0 {
  240. val = append(val, "noreferrer")
  241. }
  242. if flags&HrefTargetBlank != 0 {
  243. attrs = append(attrs, "target=\"_blank\"")
  244. }
  245. if len(val) == 0 {
  246. return attrs
  247. }
  248. attr := fmt.Sprintf("rel=%q", strings.Join(val, " "))
  249. return append(attrs, attr)
  250. }
  251. func isMailto(link []byte) bool {
  252. return bytes.HasPrefix(link, []byte("mailto:"))
  253. }
  254. func needSkipLink(flags HTMLFlags, dest []byte) bool {
  255. if flags&SkipLinks != 0 {
  256. return true
  257. }
  258. return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest)
  259. }
  260. func isSmartypantable(node *Node) bool {
  261. pt := node.Parent.Type
  262. return pt != Link && pt != CodeBlock && pt != Code
  263. }
  264. func appendLanguageAttr(attrs []string, info []byte) []string {
  265. if len(info) == 0 {
  266. return attrs
  267. }
  268. endOfLang := bytes.IndexAny(info, "\t ")
  269. if endOfLang < 0 {
  270. endOfLang = len(info)
  271. }
  272. return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang]))
  273. }
  274. func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) {
  275. w.Write(name)
  276. if len(attrs) > 0 {
  277. w.Write(spaceBytes)
  278. w.Write([]byte(strings.Join(attrs, " ")))
  279. }
  280. w.Write(gtBytes)
  281. r.lastOutputLen = 1
  282. }
  283. func footnoteRef(prefix string, node *Node) []byte {
  284. urlFrag := prefix + string(slugify(node.Destination))
  285. anchor := fmt.Sprintf(`<a rel="footnote" href="#fn:%s">%d</a>`, urlFrag, node.NoteID)
  286. return []byte(fmt.Sprintf(`<sup class="footnote-ref" id="fnref:%s">%s</sup>`, urlFrag, anchor))
  287. }
  288. func footnoteItem(prefix string, slug []byte) []byte {
  289. return []byte(fmt.Sprintf(`<li id="fn:%s%s">`, prefix, slug))
  290. }
  291. func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte {
  292. const format = ` <a class="footnote-return" href="#fnref:%s%s">%s</a>`
  293. return []byte(fmt.Sprintf(format, prefix, slug, returnLink))
  294. }
  295. func itemOpenCR(node *Node) bool {
  296. if node.Prev == nil {
  297. return false
  298. }
  299. ld := node.Parent.ListData
  300. return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0
  301. }
  302. func skipParagraphTags(node *Node) bool {
  303. grandparent := node.Parent.Parent
  304. if grandparent == nil || grandparent.Type != List {
  305. return false
  306. }
  307. tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0
  308. return grandparent.Type == List && tightOrTerm
  309. }
  310. func cellAlignment(align CellAlignFlags) string {
  311. switch align {
  312. case TableAlignmentLeft:
  313. return "left"
  314. case TableAlignmentRight:
  315. return "right"
  316. case TableAlignmentCenter:
  317. return "center"
  318. default:
  319. return ""
  320. }
  321. }
  322. func (r *HTMLRenderer) out(w io.Writer, text []byte) {
  323. if r.disableTags > 0 {
  324. w.Write(htmlTagRe.ReplaceAll(text, []byte{}))
  325. } else {
  326. w.Write(text)
  327. }
  328. r.lastOutputLen = len(text)
  329. }
  330. func (r *HTMLRenderer) cr(w io.Writer) {
  331. if r.lastOutputLen > 0 {
  332. r.out(w, nlBytes)
  333. }
  334. }
  335. var (
  336. nlBytes = []byte{'\n'}
  337. gtBytes = []byte{'>'}
  338. spaceBytes = []byte{' '}
  339. )
  340. var (
  341. brTag = []byte("<br>")
  342. brXHTMLTag = []byte("<br />")
  343. emTag = []byte("<em>")
  344. emCloseTag = []byte("</em>")
  345. strongTag = []byte("<strong>")
  346. strongCloseTag = []byte("</strong>")
  347. delTag = []byte("<del>")
  348. delCloseTag = []byte("</del>")
  349. ttTag = []byte("<tt>")
  350. ttCloseTag = []byte("</tt>")
  351. aTag = []byte("<a")
  352. aCloseTag = []byte("</a>")
  353. preTag = []byte("<pre>")
  354. preCloseTag = []byte("</pre>")
  355. codeTag = []byte("<code>")
  356. codeCloseTag = []byte("</code>")
  357. pTag = []byte("<p>")
  358. pCloseTag = []byte("</p>")
  359. blockquoteTag = []byte("<blockquote>")
  360. blockquoteCloseTag = []byte("</blockquote>")
  361. hrTag = []byte("<hr>")
  362. hrXHTMLTag = []byte("<hr />")
  363. ulTag = []byte("<ul>")
  364. ulCloseTag = []byte("</ul>")
  365. olTag = []byte("<ol>")
  366. olCloseTag = []byte("</ol>")
  367. dlTag = []byte("<dl>")
  368. dlCloseTag = []byte("</dl>")
  369. liTag = []byte("<li>")
  370. liCloseTag = []byte("</li>")
  371. ddTag = []byte("<dd>")
  372. ddCloseTag = []byte("</dd>")
  373. dtTag = []byte("<dt>")
  374. dtCloseTag = []byte("</dt>")
  375. tableTag = []byte("<table>")
  376. tableCloseTag = []byte("</table>")
  377. tdTag = []byte("<td")
  378. tdCloseTag = []byte("</td>")
  379. thTag = []byte("<th")
  380. thCloseTag = []byte("</th>")
  381. theadTag = []byte("<thead>")
  382. theadCloseTag = []byte("</thead>")
  383. tbodyTag = []byte("<tbody>")
  384. tbodyCloseTag = []byte("</tbody>")
  385. trTag = []byte("<tr>")
  386. trCloseTag = []byte("</tr>")
  387. h1Tag = []byte("<h1")
  388. h1CloseTag = []byte("</h1>")
  389. h2Tag = []byte("<h2")
  390. h2CloseTag = []byte("</h2>")
  391. h3Tag = []byte("<h3")
  392. h3CloseTag = []byte("</h3>")
  393. h4Tag = []byte("<h4")
  394. h4CloseTag = []byte("</h4>")
  395. h5Tag = []byte("<h5")
  396. h5CloseTag = []byte("</h5>")
  397. h6Tag = []byte("<h6")
  398. h6CloseTag = []byte("</h6>")
  399. footnotesDivBytes = []byte("\n<div class=\"footnotes\">\n\n")
  400. footnotesCloseDivBytes = []byte("\n</div>\n")
  401. )
  402. func headingTagsFromLevel(level int) ([]byte, []byte) {
  403. switch level {
  404. case 1:
  405. return h1Tag, h1CloseTag
  406. case 2:
  407. return h2Tag, h2CloseTag
  408. case 3:
  409. return h3Tag, h3CloseTag
  410. case 4:
  411. return h4Tag, h4CloseTag
  412. case 5:
  413. return h5Tag, h5CloseTag
  414. default:
  415. return h6Tag, h6CloseTag
  416. }
  417. }
  418. func (r *HTMLRenderer) outHRTag(w io.Writer) {
  419. if r.Flags&UseXHTML == 0 {
  420. r.out(w, hrTag)
  421. } else {
  422. r.out(w, hrXHTMLTag)
  423. }
  424. }
  425. // RenderNode is a default renderer of a single node of a syntax tree. For
  426. // block nodes it will be called twice: first time with entering=true, second
  427. // time with entering=false, so that it could know when it's working on an open
  428. // tag and when on close. It writes the result to w.
  429. //
  430. // The return value is a way to tell the calling walker to adjust its walk
  431. // pattern: e.g. it can terminate the traversal by returning Terminate. Or it
  432. // can ask the walker to skip a subtree of this node by returning SkipChildren.
  433. // The typical behavior is to return GoToNext, which asks for the usual
  434. // traversal to the next node.
  435. func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus {
  436. attrs := []string{}
  437. switch node.Type {
  438. case Text:
  439. if r.Flags&Smartypants != 0 {
  440. var tmp bytes.Buffer
  441. escapeHTML(&tmp, node.Literal)
  442. r.sr.Process(w, tmp.Bytes())
  443. } else {
  444. if node.Parent.Type == Link {
  445. escLink(w, node.Literal)
  446. } else {
  447. escapeHTML(w, node.Literal)
  448. }
  449. }
  450. case Softbreak:
  451. r.cr(w)
  452. // TODO: make it configurable via out(renderer.softbreak)
  453. case Hardbreak:
  454. if r.Flags&UseXHTML == 0 {
  455. r.out(w, brTag)
  456. } else {
  457. r.out(w, brXHTMLTag)
  458. }
  459. r.cr(w)
  460. case Emph:
  461. if entering {
  462. r.out(w, emTag)
  463. } else {
  464. r.out(w, emCloseTag)
  465. }
  466. case Strong:
  467. if entering {
  468. r.out(w, strongTag)
  469. } else {
  470. r.out(w, strongCloseTag)
  471. }
  472. case Del:
  473. if entering {
  474. r.out(w, delTag)
  475. } else {
  476. r.out(w, delCloseTag)
  477. }
  478. case HTMLSpan:
  479. if r.Flags&SkipHTML != 0 {
  480. break
  481. }
  482. r.out(w, node.Literal)
  483. case Link:
  484. // mark it but don't link it if it is not a safe link: no smartypants
  485. dest := node.LinkData.Destination
  486. if needSkipLink(r.Flags, dest) {
  487. if entering {
  488. r.out(w, ttTag)
  489. } else {
  490. r.out(w, ttCloseTag)
  491. }
  492. } else {
  493. if entering {
  494. dest = r.addAbsPrefix(dest)
  495. var hrefBuf bytes.Buffer
  496. hrefBuf.WriteString("href=\"")
  497. escLink(&hrefBuf, dest)
  498. hrefBuf.WriteByte('"')
  499. attrs = append(attrs, hrefBuf.String())
  500. if node.NoteID != 0 {
  501. r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node))
  502. break
  503. }
  504. attrs = appendLinkAttrs(attrs, r.Flags, dest)
  505. if len(node.LinkData.Title) > 0 {
  506. var titleBuff bytes.Buffer
  507. titleBuff.WriteString("title=\"")
  508. escapeHTML(&titleBuff, node.LinkData.Title)
  509. titleBuff.WriteByte('"')
  510. attrs = append(attrs, titleBuff.String())
  511. }
  512. r.tag(w, aTag, attrs)
  513. } else {
  514. if node.NoteID != 0 {
  515. break
  516. }
  517. r.out(w, aCloseTag)
  518. }
  519. }
  520. case Image:
  521. if r.Flags&SkipImages != 0 {
  522. return SkipChildren
  523. }
  524. if entering {
  525. dest := node.LinkData.Destination
  526. dest = r.addAbsPrefix(dest)
  527. if r.disableTags == 0 {
  528. //if options.safe && potentiallyUnsafe(dest) {
  529. //out(w, `<img src="" alt="`)
  530. //} else {
  531. r.out(w, []byte(`<img src="`))
  532. escLink(w, dest)
  533. r.out(w, []byte(`" alt="`))
  534. //}
  535. }
  536. r.disableTags++
  537. } else {
  538. r.disableTags--
  539. if r.disableTags == 0 {
  540. if node.LinkData.Title != nil {
  541. r.out(w, []byte(`" title="`))
  542. escapeHTML(w, node.LinkData.Title)
  543. }
  544. r.out(w, []byte(`" />`))
  545. }
  546. }
  547. case Code:
  548. r.out(w, codeTag)
  549. escapeHTML(w, node.Literal)
  550. r.out(w, codeCloseTag)
  551. case Document:
  552. break
  553. case Paragraph:
  554. if skipParagraphTags(node) {
  555. break
  556. }
  557. if entering {
  558. // TODO: untangle this clusterfuck about when the newlines need
  559. // to be added and when not.
  560. if node.Prev != nil {
  561. switch node.Prev.Type {
  562. case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule:
  563. r.cr(w)
  564. }
  565. }
  566. if node.Parent.Type == BlockQuote && node.Prev == nil {
  567. r.cr(w)
  568. }
  569. r.out(w, pTag)
  570. } else {
  571. r.out(w, pCloseTag)
  572. if !(node.Parent.Type == Item && node.Next == nil) {
  573. r.cr(w)
  574. }
  575. }
  576. case BlockQuote:
  577. if entering {
  578. r.cr(w)
  579. r.out(w, blockquoteTag)
  580. } else {
  581. r.out(w, blockquoteCloseTag)
  582. r.cr(w)
  583. }
  584. case HTMLBlock:
  585. if r.Flags&SkipHTML != 0 {
  586. break
  587. }
  588. r.cr(w)
  589. r.out(w, node.Literal)
  590. r.cr(w)
  591. case Heading:
  592. openTag, closeTag := headingTagsFromLevel(node.Level)
  593. if entering {
  594. if node.IsTitleblock {
  595. attrs = append(attrs, `class="title"`)
  596. }
  597. if node.HeadingID != "" {
  598. id := r.ensureUniqueHeadingID(node.HeadingID)
  599. if r.HeadingIDPrefix != "" {
  600. id = r.HeadingIDPrefix + id
  601. }
  602. if r.HeadingIDSuffix != "" {
  603. id = id + r.HeadingIDSuffix
  604. }
  605. attrs = append(attrs, fmt.Sprintf(`id="%s"`, id))
  606. }
  607. r.cr(w)
  608. r.tag(w, openTag, attrs)
  609. } else {
  610. r.out(w, closeTag)
  611. if !(node.Parent.Type == Item && node.Next == nil) {
  612. r.cr(w)
  613. }
  614. }
  615. case HorizontalRule:
  616. r.cr(w)
  617. r.outHRTag(w)
  618. r.cr(w)
  619. case List:
  620. openTag := ulTag
  621. closeTag := ulCloseTag
  622. if node.ListFlags&ListTypeOrdered != 0 {
  623. openTag = olTag
  624. closeTag = olCloseTag
  625. }
  626. if node.ListFlags&ListTypeDefinition != 0 {
  627. openTag = dlTag
  628. closeTag = dlCloseTag
  629. }
  630. if entering {
  631. if node.IsFootnotesList {
  632. r.out(w, footnotesDivBytes)
  633. r.outHRTag(w)
  634. r.cr(w)
  635. }
  636. r.cr(w)
  637. if node.Parent.Type == Item && node.Parent.Parent.Tight {
  638. r.cr(w)
  639. }
  640. r.tag(w, openTag[:len(openTag)-1], attrs)
  641. r.cr(w)
  642. } else {
  643. r.out(w, closeTag)
  644. //cr(w)
  645. //if node.parent.Type != Item {
  646. // cr(w)
  647. //}
  648. if node.Parent.Type == Item && node.Next != nil {
  649. r.cr(w)
  650. }
  651. if node.Parent.Type == Document || node.Parent.Type == BlockQuote {
  652. r.cr(w)
  653. }
  654. if node.IsFootnotesList {
  655. r.out(w, footnotesCloseDivBytes)
  656. }
  657. }
  658. case Item:
  659. openTag := liTag
  660. closeTag := liCloseTag
  661. if node.ListFlags&ListTypeDefinition != 0 {
  662. openTag = ddTag
  663. closeTag = ddCloseTag
  664. }
  665. if node.ListFlags&ListTypeTerm != 0 {
  666. openTag = dtTag
  667. closeTag = dtCloseTag
  668. }
  669. if entering {
  670. if itemOpenCR(node) {
  671. r.cr(w)
  672. }
  673. if node.ListData.RefLink != nil {
  674. slug := slugify(node.ListData.RefLink)
  675. r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug))
  676. break
  677. }
  678. r.out(w, openTag)
  679. } else {
  680. if node.ListData.RefLink != nil {
  681. slug := slugify(node.ListData.RefLink)
  682. if r.Flags&FootnoteReturnLinks != 0 {
  683. r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug))
  684. }
  685. }
  686. r.out(w, closeTag)
  687. r.cr(w)
  688. }
  689. case CodeBlock:
  690. attrs = appendLanguageAttr(attrs, node.Info)
  691. r.cr(w)
  692. r.out(w, preTag)
  693. r.tag(w, codeTag[:len(codeTag)-1], attrs)
  694. escapeHTML(w, node.Literal)
  695. r.out(w, codeCloseTag)
  696. r.out(w, preCloseTag)
  697. if node.Parent.Type != Item {
  698. r.cr(w)
  699. }
  700. case Table:
  701. if entering {
  702. r.cr(w)
  703. r.out(w, tableTag)
  704. } else {
  705. r.out(w, tableCloseTag)
  706. r.cr(w)
  707. }
  708. case TableCell:
  709. openTag := tdTag
  710. closeTag := tdCloseTag
  711. if node.IsHeader {
  712. openTag = thTag
  713. closeTag = thCloseTag
  714. }
  715. if entering {
  716. align := cellAlignment(node.Align)
  717. if align != "" {
  718. attrs = append(attrs, fmt.Sprintf(`align="%s"`, align))
  719. }
  720. if node.Prev == nil {
  721. r.cr(w)
  722. }
  723. r.tag(w, openTag, attrs)
  724. } else {
  725. r.out(w, closeTag)
  726. r.cr(w)
  727. }
  728. case TableHead:
  729. if entering {
  730. r.cr(w)
  731. r.out(w, theadTag)
  732. } else {
  733. r.out(w, theadCloseTag)
  734. r.cr(w)
  735. }
  736. case TableBody:
  737. if entering {
  738. r.cr(w)
  739. r.out(w, tbodyTag)
  740. // XXX: this is to adhere to a rather silly test. Should fix test.
  741. if node.FirstChild == nil {
  742. r.cr(w)
  743. }
  744. } else {
  745. r.out(w, tbodyCloseTag)
  746. r.cr(w)
  747. }
  748. case TableRow:
  749. if entering {
  750. r.cr(w)
  751. r.out(w, trTag)
  752. } else {
  753. r.out(w, trCloseTag)
  754. r.cr(w)
  755. }
  756. default:
  757. panic("Unknown node type " + node.Type.String())
  758. }
  759. return GoToNext
  760. }
  761. // RenderHeader writes HTML document preamble and TOC if requested.
  762. func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) {
  763. r.writeDocumentHeader(w)
  764. if r.Flags&TOC != 0 {
  765. r.writeTOC(w, ast)
  766. }
  767. }
  768. // RenderFooter writes HTML document footer.
  769. func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) {
  770. if r.Flags&CompletePage == 0 {
  771. return
  772. }
  773. io.WriteString(w, "\n</body>\n</html>\n")
  774. }
  775. func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) {
  776. if r.Flags&CompletePage == 0 {
  777. return
  778. }
  779. ending := ""
  780. if r.Flags&UseXHTML != 0 {
  781. io.WriteString(w, "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" ")
  782. io.WriteString(w, "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n")
  783. io.WriteString(w, "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n")
  784. ending = " /"
  785. } else {
  786. io.WriteString(w, "<!DOCTYPE html>\n")
  787. io.WriteString(w, "<html>\n")
  788. }
  789. io.WriteString(w, "<head>\n")
  790. io.WriteString(w, " <title>")
  791. if r.Flags&Smartypants != 0 {
  792. r.sr.Process(w, []byte(r.Title))
  793. } else {
  794. escapeHTML(w, []byte(r.Title))
  795. }
  796. io.WriteString(w, "</title>\n")
  797. io.WriteString(w, " <meta name=\"GENERATOR\" content=\"Blackfriday Markdown Processor v")
  798. io.WriteString(w, Version)
  799. io.WriteString(w, "\"")
  800. io.WriteString(w, ending)
  801. io.WriteString(w, ">\n")
  802. io.WriteString(w, " <meta charset=\"utf-8\"")
  803. io.WriteString(w, ending)
  804. io.WriteString(w, ">\n")
  805. if r.CSS != "" {
  806. io.WriteString(w, " <link rel=\"stylesheet\" type=\"text/css\" href=\"")
  807. escapeHTML(w, []byte(r.CSS))
  808. io.WriteString(w, "\"")
  809. io.WriteString(w, ending)
  810. io.WriteString(w, ">\n")
  811. }
  812. if r.Icon != "" {
  813. io.WriteString(w, " <link rel=\"icon\" type=\"image/x-icon\" href=\"")
  814. escapeHTML(w, []byte(r.Icon))
  815. io.WriteString(w, "\"")
  816. io.WriteString(w, ending)
  817. io.WriteString(w, ">\n")
  818. }
  819. io.WriteString(w, "</head>\n")
  820. io.WriteString(w, "<body>\n\n")
  821. }
  822. func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) {
  823. buf := bytes.Buffer{}
  824. inHeading := false
  825. tocLevel := 0
  826. headingCount := 0
  827. ast.Walk(func(node *Node, entering bool) WalkStatus {
  828. if node.Type == Heading && !node.HeadingData.IsTitleblock {
  829. inHeading = entering
  830. if entering {
  831. node.HeadingID = fmt.Sprintf("toc_%d", headingCount)
  832. if node.Level == tocLevel {
  833. buf.WriteString("</li>\n\n<li>")
  834. } else if node.Level < tocLevel {
  835. for node.Level < tocLevel {
  836. tocLevel--
  837. buf.WriteString("</li>\n</ul>")
  838. }
  839. buf.WriteString("</li>\n\n<li>")
  840. } else {
  841. for node.Level > tocLevel {
  842. tocLevel++
  843. buf.WriteString("\n<ul>\n<li>")
  844. }
  845. }
  846. fmt.Fprintf(&buf, `<a href="#toc_%d">`, headingCount)
  847. headingCount++
  848. } else {
  849. buf.WriteString("</a>")
  850. }
  851. return GoToNext
  852. }
  853. if inHeading {
  854. return r.RenderNode(&buf, node, entering)
  855. }
  856. return GoToNext
  857. })
  858. for ; tocLevel > 0; tocLevel-- {
  859. buf.WriteString("</li>\n</ul>")
  860. }
  861. if buf.Len() > 0 {
  862. io.WriteString(w, "<nav>\n")
  863. w.Write(buf.Bytes())
  864. io.WriteString(w, "\n\n</nav>\n")
  865. }
  866. r.lastOutputLen = buf.Len()
  867. }