node.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614
  1. package mark
  2. import (
  3. "fmt"
  4. "regexp"
  5. "strconv"
  6. "strings"
  7. )
  8. // A Node is an element in the parse tree.
  9. type Node interface {
  10. Type() NodeType
  11. Render() string
  12. }
  13. // NodeType identifies the type of a parse tree node.
  14. type NodeType int
  15. // Type returns itself and provides an easy default implementation
  16. // for embedding in a Node. Embedded in all non-trivial Nodes.
  17. func (t NodeType) Type() NodeType {
  18. return t
  19. }
  20. // Render function, used for overriding default rendering.
  21. type RenderFn func(Node) string
  22. const (
  23. NodeText NodeType = iota // A plain text
  24. NodeParagraph // A Paragraph
  25. NodeEmphasis // An emphasis(strong, em, ...)
  26. NodeHeading // A heading (h1, h2, ...)
  27. NodeBr // A link break
  28. NodeHr // A horizontal rule
  29. NodeImage // An image
  30. NodeRefImage // A image reference
  31. NodeList // A list of ListItems
  32. NodeListItem // A list item node
  33. NodeLink // A link(href)
  34. NodeRefLink // A link reference
  35. NodeDefLink // A link definition
  36. NodeTable // A table of NodeRows
  37. NodeRow // A row of NodeCells
  38. NodeCell // A table-cell(td)
  39. NodeCode // A code block(wrapped with pre)
  40. NodeBlockQuote // A blockquote
  41. NodeHTML // An inline HTML
  42. NodeCheckbox // A checkbox
  43. )
  44. // ParagraphNode hold simple paragraph node contains text
  45. // that may be emphasis.
  46. type ParagraphNode struct {
  47. NodeType
  48. Pos
  49. Nodes []Node
  50. }
  51. // Render returns the html representation of ParagraphNode
  52. func (n *ParagraphNode) Render() (s string) {
  53. for _, node := range n.Nodes {
  54. s += node.Render()
  55. }
  56. return wrap("p", s)
  57. }
  58. func (p *parse) newParagraph(pos Pos) *ParagraphNode {
  59. return &ParagraphNode{NodeType: NodeParagraph, Pos: pos}
  60. }
  61. // TextNode holds plain text.
  62. type TextNode struct {
  63. NodeType
  64. Pos
  65. Text string
  66. }
  67. // Render returns the string representation of TexNode
  68. func (n *TextNode) Render() string {
  69. return n.Text
  70. }
  71. func (p *parse) newText(pos Pos, text string) *TextNode {
  72. return &TextNode{NodeType: NodeText, Pos: pos, Text: p.text(text)}
  73. }
  74. // HTMLNode holds the raw html source.
  75. type HTMLNode struct {
  76. NodeType
  77. Pos
  78. Src string
  79. }
  80. // Render returns the src of the HTMLNode
  81. func (n *HTMLNode) Render() string {
  82. return n.Src
  83. }
  84. func (p *parse) newHTML(pos Pos, src string) *HTMLNode {
  85. return &HTMLNode{NodeType: NodeHTML, Pos: pos, Src: src}
  86. }
  87. // HrNode represents horizontal rule
  88. type HrNode struct {
  89. NodeType
  90. Pos
  91. }
  92. // Render returns the html representation of hr.
  93. func (n *HrNode) Render() string {
  94. return "<hr>"
  95. }
  96. func (p *parse) newHr(pos Pos) *HrNode {
  97. return &HrNode{NodeType: NodeHr, Pos: pos}
  98. }
  99. // BrNode represents a link-break element.
  100. type BrNode struct {
  101. NodeType
  102. Pos
  103. }
  104. // Render returns the html representation of line-break.
  105. func (n *BrNode) Render() string {
  106. return "<br>"
  107. }
  108. func (p *parse) newBr(pos Pos) *BrNode {
  109. return &BrNode{NodeType: NodeBr, Pos: pos}
  110. }
  111. // EmphasisNode holds plain-text wrapped with style.
  112. // (strong, em, del, code)
  113. type EmphasisNode struct {
  114. NodeType
  115. Pos
  116. Style itemType
  117. Nodes []Node
  118. }
  119. // Tag return the tagName based on the Style field.
  120. func (n *EmphasisNode) Tag() (s string) {
  121. switch n.Style {
  122. case itemStrong:
  123. s = "strong"
  124. case itemItalic:
  125. s = "em"
  126. case itemStrike:
  127. s = "del"
  128. case itemCode:
  129. s = "code"
  130. }
  131. return
  132. }
  133. // Return the html representation of emphasis text.
  134. func (n *EmphasisNode) Render() string {
  135. var s string
  136. for _, node := range n.Nodes {
  137. s += node.Render()
  138. }
  139. return wrap(n.Tag(), s)
  140. }
  141. func (p *parse) newEmphasis(pos Pos, style itemType) *EmphasisNode {
  142. return &EmphasisNode{NodeType: NodeEmphasis, Pos: pos, Style: style}
  143. }
  144. // HeadingNode holds heaing element with specific level(1-6).
  145. type HeadingNode struct {
  146. NodeType
  147. Pos
  148. Level int
  149. Text string
  150. Nodes []Node
  151. }
  152. // Render returns the html representation based on heading level.
  153. func (n *HeadingNode) Render() (s string) {
  154. for _, node := range n.Nodes {
  155. s += node.Render()
  156. }
  157. re := regexp.MustCompile(`[^\w]+`)
  158. id := re.ReplaceAllString(n.Text, "-")
  159. // ToLowerCase
  160. id = strings.ToLower(id)
  161. return fmt.Sprintf("<%[1]s id=\"%s\">%s</%[1]s>", "h"+strconv.Itoa(n.Level), id, s)
  162. }
  163. func (p *parse) newHeading(pos Pos, level int, text string) *HeadingNode {
  164. return &HeadingNode{NodeType: NodeHeading, Pos: pos, Level: level, Text: p.text(text)}
  165. }
  166. // Code holds CodeBlock node with specific lang field.
  167. type CodeNode struct {
  168. NodeType
  169. Pos
  170. Lang, Text string
  171. }
  172. // Return the html representation of codeBlock
  173. func (n *CodeNode) Render() string {
  174. var attr string
  175. if n.Lang != "" {
  176. attr = fmt.Sprintf(" class=\"lang-%s\"", n.Lang)
  177. }
  178. code := fmt.Sprintf("<%[1]s%s>%s</%[1]s>", "code", attr, n.Text)
  179. return wrap("pre", code)
  180. }
  181. func (p *parse) newCode(pos Pos, lang, text string) *CodeNode {
  182. // DRY: see `escape()` below
  183. text = strings.NewReplacer("<", "&lt;", ">", "&gt;", "\"", "&quot;", "&", "&amp;").Replace(text)
  184. return &CodeNode{NodeType: NodeCode, Pos: pos, Lang: lang, Text: text}
  185. }
  186. // Link holds a tag with optional title
  187. type LinkNode struct {
  188. NodeType
  189. Pos
  190. Title, Href string
  191. Nodes []Node
  192. }
  193. // Return the html representation of link node
  194. func (n *LinkNode) Render() (s string) {
  195. for _, node := range n.Nodes {
  196. s += node.Render()
  197. }
  198. attrs := fmt.Sprintf("href=\"%s\"", n.Href)
  199. if n.Title != "" {
  200. attrs += fmt.Sprintf(" title=\"%s\"", n.Title)
  201. }
  202. return fmt.Sprintf("<a %s>%s</a>", attrs, s)
  203. }
  204. func (p *parse) newLink(pos Pos, title, href string, nodes ...Node) *LinkNode {
  205. return &LinkNode{NodeType: NodeLink, Pos: pos, Title: p.text(title), Href: p.text(href), Nodes: nodes}
  206. }
  207. // RefLink holds link with refrence to link definition
  208. type RefNode struct {
  209. NodeType
  210. Pos
  211. tr *parse
  212. Text, Ref, Raw string
  213. Nodes []Node
  214. }
  215. // rendering based type
  216. func (n *RefNode) Render() string {
  217. var node Node
  218. ref := strings.ToLower(n.Ref)
  219. if l, ok := n.tr.links[ref]; ok {
  220. if n.Type() == NodeRefLink {
  221. node = n.tr.newLink(n.Pos, l.Title, l.Href, n.Nodes...)
  222. } else {
  223. node = n.tr.newImage(n.Pos, l.Title, l.Href, n.Text)
  224. }
  225. } else {
  226. node = n.tr.newText(n.Pos, n.Raw)
  227. }
  228. return node.Render()
  229. }
  230. // newRefLink create new RefLink that suitable for link
  231. func (p *parse) newRefLink(typ itemType, pos Pos, raw, ref string, text []Node) *RefNode {
  232. return &RefNode{NodeType: NodeRefLink, Pos: pos, tr: p.root(), Raw: raw, Ref: ref, Nodes: text}
  233. }
  234. // newRefImage create new RefLink that suitable for image
  235. func (p *parse) newRefImage(typ itemType, pos Pos, raw, ref, text string) *RefNode {
  236. return &RefNode{NodeType: NodeRefImage, Pos: pos, tr: p.root(), Raw: raw, Ref: ref, Text: text}
  237. }
  238. // DefLinkNode refresent single reference to link-definition
  239. type DefLinkNode struct {
  240. NodeType
  241. Pos
  242. Name, Href, Title string
  243. }
  244. // Deflink have no representation(Transparent node)
  245. func (n *DefLinkNode) Render() string {
  246. return ""
  247. }
  248. func (p *parse) newDefLink(pos Pos, name, href, title string) *DefLinkNode {
  249. return &DefLinkNode{NodeType: NodeLink, Pos: pos, Name: name, Href: href, Title: title}
  250. }
  251. // ImageNode represents an image element with optional alt and title attributes.
  252. type ImageNode struct {
  253. NodeType
  254. Pos
  255. Title, Src, Alt string
  256. }
  257. // Render returns the html representation on image node
  258. func (n *ImageNode) Render() string {
  259. attrs := fmt.Sprintf("src=\"%s\" alt=\"%s\"", n.Src, n.Alt)
  260. if n.Title != "" {
  261. attrs += fmt.Sprintf(" title=\"%s\"", n.Title)
  262. }
  263. return fmt.Sprintf("<img %s>", attrs)
  264. }
  265. func (p *parse) newImage(pos Pos, title, src, alt string) *ImageNode {
  266. return &ImageNode{NodeType: NodeImage, Pos: pos, Title: p.text(title), Src: p.text(src), Alt: p.text(alt)}
  267. }
  268. // ListNode holds list items nodes in ordered or unordered states.
  269. type ListNode struct {
  270. NodeType
  271. Pos
  272. Ordered bool
  273. Items []*ListItemNode
  274. }
  275. func (n *ListNode) append(item *ListItemNode) {
  276. n.Items = append(n.Items, item)
  277. }
  278. // Render returns the html representation of orderd(ol) or unordered(ul) list.
  279. func (n *ListNode) Render() (s string) {
  280. tag := "ul"
  281. if n.Ordered {
  282. tag = "ol"
  283. }
  284. for _, item := range n.Items {
  285. s += "\n" + item.Render()
  286. }
  287. s += "\n"
  288. return wrap(tag, s)
  289. }
  290. func (p *parse) newList(pos Pos, ordered bool) *ListNode {
  291. return &ListNode{NodeType: NodeList, Pos: pos, Ordered: ordered}
  292. }
  293. // ListItem represents single item in ListNode that may contains nested nodes.
  294. type ListItemNode struct {
  295. NodeType
  296. Pos
  297. Nodes []Node
  298. }
  299. func (l *ListItemNode) append(n Node) {
  300. l.Nodes = append(l.Nodes, n)
  301. }
  302. // Render returns the html representation of list-item
  303. func (l *ListItemNode) Render() (s string) {
  304. for _, node := range l.Nodes {
  305. s += node.Render()
  306. }
  307. return wrap("li", s)
  308. }
  309. func (p *parse) newListItem(pos Pos) *ListItemNode {
  310. return &ListItemNode{NodeType: NodeListItem, Pos: pos}
  311. }
  312. // TableNode represents table element contains head and body
  313. type TableNode struct {
  314. NodeType
  315. Pos
  316. Rows []*RowNode
  317. }
  318. func (n *TableNode) append(row *RowNode) {
  319. n.Rows = append(n.Rows, row)
  320. }
  321. // Render returns the html representation of a table
  322. func (n *TableNode) Render() string {
  323. var s string
  324. for i, row := range n.Rows {
  325. s += "\n"
  326. switch i {
  327. case 0:
  328. s += wrap("thead", "\n"+row.Render()+"\n")
  329. case 1:
  330. s += "<tbody>\n"
  331. fallthrough
  332. default:
  333. s += row.Render()
  334. }
  335. }
  336. s += "\n</tbody>\n"
  337. return wrap("table", s)
  338. }
  339. func (p *parse) newTable(pos Pos) *TableNode {
  340. return &TableNode{NodeType: NodeTable, Pos: pos}
  341. }
  342. // RowNode represnt tr that holds list of cell-nodes
  343. type RowNode struct {
  344. NodeType
  345. Pos
  346. Cells []*CellNode
  347. }
  348. func (r *RowNode) append(cell *CellNode) {
  349. r.Cells = append(r.Cells, cell)
  350. }
  351. // Render returns the html representation of table-row
  352. func (r *RowNode) Render() string {
  353. var s string
  354. for _, cell := range r.Cells {
  355. s += "\n" + cell.Render()
  356. }
  357. s += "\n"
  358. return wrap("tr", s)
  359. }
  360. func (p *parse) newRow(pos Pos) *RowNode {
  361. return &RowNode{NodeType: NodeRow, Pos: pos}
  362. }
  363. // AlignType identifies the aligment-type of specfic cell.
  364. type AlignType int
  365. // Align returns itself and provides an easy default implementation
  366. // for embedding in a Node.
  367. func (t AlignType) Align() AlignType {
  368. return t
  369. }
  370. // Alignment
  371. const (
  372. None AlignType = iota
  373. Right
  374. Left
  375. Center
  376. )
  377. // Cell types
  378. const (
  379. Header = iota
  380. Data
  381. )
  382. // CellNode represents table-data/cell that holds simple text(may be emphasis)
  383. // Note: the text in <th> elements are bold and centered by default.
  384. type CellNode struct {
  385. NodeType
  386. Pos
  387. AlignType
  388. Kind int
  389. Nodes []Node
  390. }
  391. // Render returns the html reprenestation of table-cell
  392. func (c *CellNode) Render() string {
  393. var s string
  394. tag := "td"
  395. if c.Kind == Header {
  396. tag = "th"
  397. }
  398. for _, node := range c.Nodes {
  399. s += node.Render()
  400. }
  401. return fmt.Sprintf("<%[1]s%s>%s</%[1]s>", tag, c.Style(), s)
  402. }
  403. // Style return the cell-style based on alignment field
  404. func (c *CellNode) Style() string {
  405. s := " style=\"text-align:"
  406. switch c.Align() {
  407. case Right:
  408. s += "right\""
  409. case Left:
  410. s += "left\""
  411. case Center:
  412. s += "center\""
  413. default:
  414. s = ""
  415. }
  416. return s
  417. }
  418. func (p *parse) newCell(pos Pos, kind int, align AlignType) *CellNode {
  419. return &CellNode{NodeType: NodeCell, Pos: pos, Kind: kind, AlignType: align}
  420. }
  421. // BlockQuote represents block-quote tag.
  422. type BlockQuoteNode struct {
  423. NodeType
  424. Pos
  425. Nodes []Node
  426. }
  427. // Render returns the html representation of BlockQuote
  428. func (n *BlockQuoteNode) Render() string {
  429. var s string
  430. for _, node := range n.Nodes {
  431. s += node.Render()
  432. }
  433. return wrap("blockquote", s)
  434. }
  435. func (p *parse) newBlockQuote(pos Pos) *BlockQuoteNode {
  436. return &BlockQuoteNode{NodeType: NodeBlockQuote, Pos: pos}
  437. }
  438. // CheckboxNode represents checked and unchecked checkbox tag.
  439. // Used in task lists.
  440. type CheckboxNode struct {
  441. NodeType
  442. Pos
  443. Checked bool
  444. }
  445. // Render returns the html representation of checked and unchecked CheckBox.
  446. func (n *CheckboxNode) Render() string {
  447. s := "<input type=\"checkbox\""
  448. if n.Checked {
  449. s += " checked"
  450. }
  451. return s + ">"
  452. }
  453. func (p *parse) newCheckbox(pos Pos, checked bool) *CheckboxNode {
  454. return &CheckboxNode{NodeType: NodeCheckbox, Pos: pos, Checked: checked}
  455. }
  456. // Wrap text with specific tag.
  457. func wrap(tag, body string) string {
  458. return fmt.Sprintf("<%[1]s>%s</%[1]s>", tag, body)
  459. }
  460. // Group all text configuration in one place(escaping, smartypants, etc..)
  461. func (p *parse) text(input string) string {
  462. opts := p.root().options
  463. if opts.Smartypants {
  464. input = smartypants(input)
  465. }
  466. if opts.Fractions {
  467. input = smartyfractions(input)
  468. }
  469. return escape(input)
  470. }
  471. // Helper escaper
  472. func escape(str string) (cpy string) {
  473. emp := regexp.MustCompile(`&\w+;`)
  474. for i := 0; i < len(str); i++ {
  475. switch s := str[i]; s {
  476. case '>':
  477. cpy += "&gt;"
  478. case '"':
  479. cpy += "&quot;"
  480. case '\'':
  481. cpy += "&#39;"
  482. case '<':
  483. if res := reHTML.tag.FindString(str[i:]); res != "" {
  484. cpy += res
  485. i += len(res) - 1
  486. } else {
  487. cpy += "&lt;"
  488. }
  489. case '&':
  490. if res := emp.FindString(str[i:]); res != "" {
  491. cpy += res
  492. i += len(res) - 1
  493. } else {
  494. cpy += "&amp;"
  495. }
  496. default:
  497. cpy += str[i : i+1]
  498. }
  499. }
  500. return
  501. }
  502. // Smartypants transformation helper, translate from marked.js
  503. func smartypants(text string) string {
  504. // em-dashes, en-dashes, ellipses
  505. re := strings.NewReplacer("---", "\u2014", "--", "\u2013", "...", "\u2026")
  506. text = re.Replace(text)
  507. // opening singles
  508. text = regexp.MustCompile("(^|[-\u2014/(\\[{\"\\s])'").ReplaceAllString(text, "$1\u2018")
  509. // closing singles & apostrophes
  510. text = strings.Replace(text, "'", "\u2019", -1)
  511. // opening doubles
  512. text = regexp.MustCompile("(^|[-\u2014/(\\[{\u2018\\s])\"").ReplaceAllString(text, "$1\u201c")
  513. // closing doubles
  514. text = strings.Replace(text, "\"", "\u201d", -1)
  515. return text
  516. }
  517. // Smartyfractions transformation helper.
  518. func smartyfractions(text string) string {
  519. re := regexp.MustCompile(`(\d+)(/\d+)(/\d+|)`)
  520. return re.ReplaceAllStringFunc(text, func(str string) string {
  521. var match []string
  522. // If it's date like
  523. if match = re.FindStringSubmatch(str); match[3] != "" {
  524. return str
  525. }
  526. switch n := match[1] + match[2]; n {
  527. case "1/2", "1/3", "2/3", "1/4", "3/4", "1/5", "2/5", "3/5", "4/5",
  528. "1/6", "5/6", "1/7", "1/8", "3/8", "5/8", "7/8":
  529. return fmt.Sprintf("&frac%s;", strings.Replace(n, "/", "", 1))
  530. default:
  531. return fmt.Sprintf("<sup>%s</sup>&frasl;<sub>%s</sub>",
  532. match[1], strings.Replace(match[2], "/", "", 1))
  533. }
  534. })
  535. }