You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

goldmark.go 9.6 kB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365
  1. // Copyright 2019 The Gitea Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package markdown
  5. import (
  6. "bytes"
  7. "fmt"
  8. "regexp"
  9. "strings"
  10. "code.gitea.io/gitea/modules/markup"
  11. "code.gitea.io/gitea/modules/markup/common"
  12. "code.gitea.io/gitea/modules/setting"
  13. giteautil "code.gitea.io/gitea/modules/util"
  14. meta "github.com/yuin/goldmark-meta"
  15. "github.com/yuin/goldmark/ast"
  16. east "github.com/yuin/goldmark/extension/ast"
  17. "github.com/yuin/goldmark/parser"
  18. "github.com/yuin/goldmark/renderer"
  19. "github.com/yuin/goldmark/renderer/html"
  20. "github.com/yuin/goldmark/text"
  21. "github.com/yuin/goldmark/util"
  22. )
  23. var byteMailto = []byte("mailto:")
  24. // Header holds the data about a header.
  25. type Header struct {
  26. Level int
  27. Text string
  28. ID string
  29. }
  30. // ASTTransformer is a default transformer of the goldmark tree.
  31. type ASTTransformer struct{}
  32. // Transform transforms the given AST tree.
  33. func (g *ASTTransformer) Transform(node *ast.Document, reader text.Reader, pc parser.Context) {
  34. metaData := meta.GetItems(pc)
  35. firstChild := node.FirstChild()
  36. createTOC := false
  37. var toc = []Header{}
  38. rc := &RenderConfig{
  39. Meta: "table",
  40. Icon: "table",
  41. Lang: "",
  42. }
  43. if metaData != nil {
  44. rc.ToRenderConfig(metaData)
  45. metaNode := rc.toMetaNode(metaData)
  46. if metaNode != nil {
  47. node.InsertBefore(node, firstChild, metaNode)
  48. }
  49. createTOC = rc.TOC
  50. toc = make([]Header, 0, 100)
  51. }
  52. _ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
  53. if !entering {
  54. return ast.WalkContinue, nil
  55. }
  56. switch v := n.(type) {
  57. case *ast.Heading:
  58. if createTOC {
  59. text := n.Text(reader.Source())
  60. header := Header{
  61. Text: util.BytesToReadOnlyString(text),
  62. Level: v.Level,
  63. }
  64. if id, found := v.AttributeString("id"); found {
  65. header.ID = util.BytesToReadOnlyString(id.([]byte))
  66. }
  67. toc = append(toc, header)
  68. }
  69. case *ast.Image:
  70. // Images need two things:
  71. //
  72. // 1. Their src needs to munged to be a real value
  73. // 2. If they're not wrapped with a link they need a link wrapper
  74. // Check if the destination is a real link
  75. link := v.Destination
  76. if len(link) > 0 && !markup.IsLink(link) {
  77. prefix := pc.Get(urlPrefixKey).(string)
  78. if pc.Get(isWikiKey).(bool) {
  79. prefix = giteautil.URLJoin(prefix, "wiki", "raw")
  80. }
  81. prefix = strings.Replace(prefix, "/src/", "/media/", 1)
  82. lnk := string(link)
  83. lnk = giteautil.URLJoin(prefix, lnk)
  84. link = []byte(lnk)
  85. }
  86. v.Destination = link
  87. parent := n.Parent()
  88. // Create a link around image only if parent is not already a link
  89. if _, ok := parent.(*ast.Link); !ok && parent != nil {
  90. wrap := ast.NewLink()
  91. wrap.Destination = link
  92. wrap.Title = v.Title
  93. parent.ReplaceChild(parent, n, wrap)
  94. wrap.AppendChild(wrap, n)
  95. }
  96. case *ast.Link:
  97. // Links need their href to munged to be a real value
  98. link := v.Destination
  99. if len(link) > 0 && !markup.IsLink(link) &&
  100. link[0] != '#' && !bytes.HasPrefix(link, byteMailto) {
  101. // special case: this is not a link, a hash link or a mailto:, so it's a
  102. // relative URL
  103. lnk := string(link)
  104. if pc.Get(isWikiKey).(bool) {
  105. lnk = giteautil.URLJoin("wiki", lnk)
  106. }
  107. link = []byte(giteautil.URLJoin(pc.Get(urlPrefixKey).(string), lnk))
  108. }
  109. if len(link) > 0 && link[0] == '#' {
  110. link = []byte("#user-content-" + string(link)[1:])
  111. }
  112. v.Destination = link
  113. case *ast.List:
  114. if v.HasChildren() && v.FirstChild().HasChildren() && v.FirstChild().FirstChild().HasChildren() {
  115. if _, ok := v.FirstChild().FirstChild().FirstChild().(*east.TaskCheckBox); ok {
  116. v.SetAttributeString("class", []byte("task-list"))
  117. children := make([]ast.Node, 0, v.ChildCount())
  118. child := v.FirstChild()
  119. for child != nil {
  120. children = append(children, child)
  121. child = child.NextSibling()
  122. }
  123. v.RemoveChildren(v)
  124. for _, child := range children {
  125. listItem := child.(*ast.ListItem)
  126. newChild := NewTaskCheckBoxListItem(listItem)
  127. taskCheckBox := child.FirstChild().FirstChild().(*east.TaskCheckBox)
  128. newChild.IsChecked = taskCheckBox.IsChecked
  129. v.AppendChild(v, newChild)
  130. }
  131. }
  132. }
  133. }
  134. return ast.WalkContinue, nil
  135. })
  136. if createTOC && len(toc) > 0 {
  137. lang := rc.Lang
  138. if len(lang) == 0 {
  139. lang = setting.Langs[0]
  140. }
  141. tocNode := createTOCNode(toc, lang)
  142. if tocNode != nil {
  143. node.InsertBefore(node, firstChild, tocNode)
  144. }
  145. }
  146. if len(rc.Lang) > 0 {
  147. node.SetAttributeString("lang", []byte(rc.Lang))
  148. }
  149. }
  150. type prefixedIDs struct {
  151. values map[string]bool
  152. }
  153. // Generate generates a new element id.
  154. func (p *prefixedIDs) Generate(value []byte, kind ast.NodeKind) []byte {
  155. dft := []byte("id")
  156. if kind == ast.KindHeading {
  157. dft = []byte("heading")
  158. }
  159. return p.GenerateWithDefault(value, dft)
  160. }
  161. // Generate generates a new element id.
  162. func (p *prefixedIDs) GenerateWithDefault(value []byte, dft []byte) []byte {
  163. result := common.CleanValue(value)
  164. if len(result) == 0 {
  165. result = dft
  166. }
  167. if !bytes.HasPrefix(result, []byte("user-content-")) {
  168. result = append([]byte("user-content-"), result...)
  169. }
  170. if _, ok := p.values[util.BytesToReadOnlyString(result)]; !ok {
  171. p.values[util.BytesToReadOnlyString(result)] = true
  172. return result
  173. }
  174. for i := 1; ; i++ {
  175. newResult := fmt.Sprintf("%s-%d", result, i)
  176. if _, ok := p.values[newResult]; !ok {
  177. p.values[newResult] = true
  178. return []byte(newResult)
  179. }
  180. }
  181. }
  182. // Put puts a given element id to the used ids table.
  183. func (p *prefixedIDs) Put(value []byte) {
  184. p.values[util.BytesToReadOnlyString(value)] = true
  185. }
  186. func newPrefixedIDs() *prefixedIDs {
  187. return &prefixedIDs{
  188. values: map[string]bool{},
  189. }
  190. }
  191. // NewHTMLRenderer creates a HTMLRenderer to render
  192. // in the gitea form.
  193. func NewHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
  194. r := &HTMLRenderer{
  195. Config: html.NewConfig(),
  196. }
  197. for _, opt := range opts {
  198. opt.SetHTMLOption(&r.Config)
  199. }
  200. return r
  201. }
  202. // HTMLRenderer is a renderer.NodeRenderer implementation that
  203. // renders gitea specific features.
  204. type HTMLRenderer struct {
  205. html.Config
  206. }
  207. // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
  208. func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
  209. reg.Register(ast.KindDocument, r.renderDocument)
  210. reg.Register(KindDetails, r.renderDetails)
  211. reg.Register(KindSummary, r.renderSummary)
  212. reg.Register(KindIcon, r.renderIcon)
  213. reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
  214. reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
  215. }
  216. func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  217. n := node.(*ast.Document)
  218. if val, has := n.AttributeString("lang"); has {
  219. var err error
  220. if entering {
  221. _, err = w.WriteString("<div")
  222. if err == nil {
  223. _, err = w.WriteString(fmt.Sprintf(` lang=%q`, val))
  224. }
  225. if err == nil {
  226. _, err = w.WriteRune('>')
  227. }
  228. } else {
  229. _, err = w.WriteString("</div>")
  230. }
  231. if err != nil {
  232. return ast.WalkStop, err
  233. }
  234. }
  235. return ast.WalkContinue, nil
  236. }
  237. func (r *HTMLRenderer) renderDetails(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  238. var err error
  239. if entering {
  240. _, err = w.WriteString("<details>")
  241. } else {
  242. _, err = w.WriteString("</details>")
  243. }
  244. if err != nil {
  245. return ast.WalkStop, err
  246. }
  247. return ast.WalkContinue, nil
  248. }
  249. func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  250. var err error
  251. if entering {
  252. _, err = w.WriteString("<summary>")
  253. } else {
  254. _, err = w.WriteString("</summary>")
  255. }
  256. if err != nil {
  257. return ast.WalkStop, err
  258. }
  259. return ast.WalkContinue, nil
  260. }
  261. var validNameRE = regexp.MustCompile("^[a-z ]+$")
  262. func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  263. if !entering {
  264. return ast.WalkContinue, nil
  265. }
  266. n := node.(*Icon)
  267. name := strings.TrimSpace(strings.ToLower(string(n.Name)))
  268. if len(name) == 0 {
  269. // skip this
  270. return ast.WalkContinue, nil
  271. }
  272. if !validNameRE.MatchString(name) {
  273. // skip this
  274. return ast.WalkContinue, nil
  275. }
  276. var err error
  277. _, err = w.WriteString(fmt.Sprintf(`<i class="icon %s"></i>`, name))
  278. if err != nil {
  279. return ast.WalkStop, err
  280. }
  281. return ast.WalkContinue, nil
  282. }
  283. func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  284. n := node.(*TaskCheckBoxListItem)
  285. if entering {
  286. if n.Attributes() != nil {
  287. _, _ = w.WriteString("<li")
  288. html.RenderAttributes(w, n, html.ListItemAttributeFilter)
  289. _ = w.WriteByte('>')
  290. } else {
  291. _, _ = w.WriteString("<li>")
  292. }
  293. end := ">"
  294. if r.XHTML {
  295. end = " />"
  296. }
  297. var err error
  298. if n.IsChecked {
  299. _, err = w.WriteString(`<span class="ui checked checkbox"><input type="checkbox" checked="" readonly="readonly"` + end + `<label>`)
  300. } else {
  301. _, err = w.WriteString(`<span class="ui checkbox"><input type="checkbox" readonly="readonly"` + end + `<label>`)
  302. }
  303. if err != nil {
  304. return ast.WalkStop, err
  305. }
  306. fc := n.FirstChild()
  307. if fc != nil {
  308. if _, ok := fc.(*ast.TextBlock); !ok {
  309. _ = w.WriteByte('\n')
  310. }
  311. }
  312. } else {
  313. _, _ = w.WriteString("</label></span></li>\n")
  314. }
  315. return ast.WalkContinue, nil
  316. }
  317. func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  318. return ast.WalkContinue, nil
  319. }