ViewVC Help
View File | Revision Log | Show Annotations | Revision Graph | Root Listing
root/aya/vendor/github.com/russross/blackfriday/v2/html.go
Revision: 1.1
Committed: Mon Sep 30 00:42:06 2024 UTC (6 weeks, 4 days ago) by yakumo_izuru
Branch: MAIN
CVS Tags: HEAD
Log Message:
Mirrored from https://git.chaotic.ninja/git/yakumo_izuru/aya

File Contents

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