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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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() {
  115. children := make([]ast.Node, 0, v.ChildCount())
  116. child := v.FirstChild()
  117. for child != nil {
  118. children = append(children, child)
  119. child = child.NextSibling()
  120. }
  121. v.RemoveChildren(v)
  122. for _, child := range children {
  123. listItem := child.(*ast.ListItem)
  124. if !child.HasChildren() || !child.FirstChild().HasChildren() {
  125. v.AppendChild(v, child)
  126. continue
  127. }
  128. taskCheckBox, ok := child.FirstChild().FirstChild().(*east.TaskCheckBox)
  129. if !ok {
  130. v.AppendChild(v, child)
  131. continue
  132. }
  133. newChild := NewTaskCheckBoxListItem(listItem)
  134. newChild.IsChecked = taskCheckBox.IsChecked
  135. newChild.SetAttributeString("class", []byte("task-list-item"))
  136. v.AppendChild(v, newChild)
  137. }
  138. }
  139. }
  140. return ast.WalkContinue, nil
  141. })
  142. if createTOC && len(toc) > 0 {
  143. lang := rc.Lang
  144. if len(lang) == 0 {
  145. lang = setting.Langs[0]
  146. }
  147. tocNode := createTOCNode(toc, lang)
  148. if tocNode != nil {
  149. node.InsertBefore(node, firstChild, tocNode)
  150. }
  151. }
  152. if len(rc.Lang) > 0 {
  153. node.SetAttributeString("lang", []byte(rc.Lang))
  154. }
  155. }
  156. type prefixedIDs struct {
  157. values map[string]bool
  158. }
  159. // Generate generates a new element id.
  160. func (p *prefixedIDs) Generate(value []byte, kind ast.NodeKind) []byte {
  161. dft := []byte("id")
  162. if kind == ast.KindHeading {
  163. dft = []byte("heading")
  164. }
  165. return p.GenerateWithDefault(value, dft)
  166. }
  167. // Generate generates a new element id.
  168. func (p *prefixedIDs) GenerateWithDefault(value []byte, dft []byte) []byte {
  169. result := common.CleanValue(value)
  170. if len(result) == 0 {
  171. result = dft
  172. }
  173. if !bytes.HasPrefix(result, []byte("user-content-")) {
  174. result = append([]byte("user-content-"), result...)
  175. }
  176. if _, ok := p.values[util.BytesToReadOnlyString(result)]; !ok {
  177. p.values[util.BytesToReadOnlyString(result)] = true
  178. return result
  179. }
  180. for i := 1; ; i++ {
  181. newResult := fmt.Sprintf("%s-%d", result, i)
  182. if _, ok := p.values[newResult]; !ok {
  183. p.values[newResult] = true
  184. return []byte(newResult)
  185. }
  186. }
  187. }
  188. // Put puts a given element id to the used ids table.
  189. func (p *prefixedIDs) Put(value []byte) {
  190. p.values[util.BytesToReadOnlyString(value)] = true
  191. }
  192. func newPrefixedIDs() *prefixedIDs {
  193. return &prefixedIDs{
  194. values: map[string]bool{},
  195. }
  196. }
  197. // NewHTMLRenderer creates a HTMLRenderer to render
  198. // in the gitea form.
  199. func NewHTMLRenderer(opts ...html.Option) renderer.NodeRenderer {
  200. r := &HTMLRenderer{
  201. Config: html.NewConfig(),
  202. }
  203. for _, opt := range opts {
  204. opt.SetHTMLOption(&r.Config)
  205. }
  206. return r
  207. }
  208. // HTMLRenderer is a renderer.NodeRenderer implementation that
  209. // renders gitea specific features.
  210. type HTMLRenderer struct {
  211. html.Config
  212. }
  213. // RegisterFuncs implements renderer.NodeRenderer.RegisterFuncs.
  214. func (r *HTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
  215. reg.Register(ast.KindDocument, r.renderDocument)
  216. reg.Register(KindDetails, r.renderDetails)
  217. reg.Register(KindSummary, r.renderSummary)
  218. reg.Register(KindIcon, r.renderIcon)
  219. reg.Register(KindTaskCheckBoxListItem, r.renderTaskCheckBoxListItem)
  220. reg.Register(east.KindTaskCheckBox, r.renderTaskCheckBox)
  221. }
  222. func (r *HTMLRenderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  223. n := node.(*ast.Document)
  224. if val, has := n.AttributeString("lang"); has {
  225. var err error
  226. if entering {
  227. _, err = w.WriteString("<div")
  228. if err == nil {
  229. _, err = w.WriteString(fmt.Sprintf(` lang=%q`, val))
  230. }
  231. if err == nil {
  232. _, err = w.WriteRune('>')
  233. }
  234. } else {
  235. _, err = w.WriteString("</div>")
  236. }
  237. if err != nil {
  238. return ast.WalkStop, err
  239. }
  240. }
  241. return ast.WalkContinue, nil
  242. }
  243. func (r *HTMLRenderer) renderDetails(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  244. var err error
  245. if entering {
  246. _, err = w.WriteString("<details>")
  247. } else {
  248. _, err = w.WriteString("</details>")
  249. }
  250. if err != nil {
  251. return ast.WalkStop, err
  252. }
  253. return ast.WalkContinue, nil
  254. }
  255. func (r *HTMLRenderer) renderSummary(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  256. var err error
  257. if entering {
  258. _, err = w.WriteString("<summary>")
  259. } else {
  260. _, err = w.WriteString("</summary>")
  261. }
  262. if err != nil {
  263. return ast.WalkStop, err
  264. }
  265. return ast.WalkContinue, nil
  266. }
  267. var validNameRE = regexp.MustCompile("^[a-z ]+$")
  268. func (r *HTMLRenderer) renderIcon(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  269. if !entering {
  270. return ast.WalkContinue, nil
  271. }
  272. n := node.(*Icon)
  273. name := strings.TrimSpace(strings.ToLower(string(n.Name)))
  274. if len(name) == 0 {
  275. // skip this
  276. return ast.WalkContinue, nil
  277. }
  278. if !validNameRE.MatchString(name) {
  279. // skip this
  280. return ast.WalkContinue, nil
  281. }
  282. var err error
  283. _, err = w.WriteString(fmt.Sprintf(`<i class="icon %s"></i>`, name))
  284. if err != nil {
  285. return ast.WalkStop, err
  286. }
  287. return ast.WalkContinue, nil
  288. }
  289. func (r *HTMLRenderer) renderTaskCheckBoxListItem(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  290. n := node.(*TaskCheckBoxListItem)
  291. if entering {
  292. if n.Attributes() != nil {
  293. _, _ = w.WriteString("<li")
  294. html.RenderAttributes(w, n, html.ListItemAttributeFilter)
  295. _ = w.WriteByte('>')
  296. } else {
  297. _, _ = w.WriteString("<li>")
  298. }
  299. end := ">"
  300. if r.XHTML {
  301. end = " />"
  302. }
  303. var err error
  304. if n.IsChecked {
  305. _, err = w.WriteString(`<span class="ui checked checkbox"><input type="checkbox" checked="" readonly="readonly"` + end + `<label>`)
  306. } else {
  307. _, err = w.WriteString(`<span class="ui checkbox"><input type="checkbox" readonly="readonly"` + end + `<label>`)
  308. }
  309. if err != nil {
  310. return ast.WalkStop, err
  311. }
  312. fc := n.FirstChild()
  313. if fc != nil {
  314. if _, ok := fc.(*ast.TextBlock); !ok {
  315. _ = w.WriteByte('\n')
  316. }
  317. }
  318. } else {
  319. _, _ = w.WriteString("</label></span></li>\n")
  320. }
  321. return ast.WalkContinue, nil
  322. }
  323. func (r *HTMLRenderer) renderTaskCheckBox(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
  324. return ast.WalkContinue, nil
  325. }