gitea源码

html_codepreview.go 2.9KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package markup
  4. import (
  5. "html/template"
  6. "net/url"
  7. "strconv"
  8. "strings"
  9. "code.gitea.io/gitea/modules/httplib"
  10. "code.gitea.io/gitea/modules/log"
  11. "golang.org/x/net/html"
  12. )
  13. type RenderCodePreviewOptions struct {
  14. FullURL string
  15. OwnerName string
  16. RepoName string
  17. CommitID string
  18. FilePath string
  19. LineStart, LineStop int
  20. }
  21. func renderCodeBlock(ctx *RenderContext, node *html.Node) (urlPosStart, urlPosStop int, htm template.HTML, err error) {
  22. m := globalVars().codePreviewPattern.FindStringSubmatchIndex(node.Data)
  23. if m == nil {
  24. return 0, 0, "", nil
  25. }
  26. opts := RenderCodePreviewOptions{
  27. FullURL: node.Data[m[0]:m[1]],
  28. OwnerName: node.Data[m[2]:m[3]],
  29. RepoName: node.Data[m[4]:m[5]],
  30. CommitID: node.Data[m[6]:m[7]],
  31. FilePath: node.Data[m[8]:m[9]],
  32. }
  33. if !httplib.IsCurrentGiteaSiteURL(ctx, opts.FullURL) {
  34. return 0, 0, "", nil
  35. }
  36. u, err := url.Parse(opts.FilePath)
  37. if err != nil {
  38. return 0, 0, "", err
  39. }
  40. opts.FilePath = strings.TrimPrefix(u.Path, "/")
  41. lineStartStr, lineStopStr, _ := strings.Cut(node.Data[m[10]:m[11]], "-")
  42. lineStart, _ := strconv.Atoi(strings.TrimPrefix(lineStartStr, "L"))
  43. lineStop, _ := strconv.Atoi(strings.TrimPrefix(lineStopStr, "L"))
  44. opts.LineStart, opts.LineStop = lineStart, lineStop
  45. h, err := DefaultRenderHelperFuncs.RenderRepoFileCodePreview(ctx, opts)
  46. return m[0], m[1], h, err
  47. }
  48. func codePreviewPatternProcessor(ctx *RenderContext, node *html.Node) {
  49. nodeStop := node.NextSibling
  50. for node != nodeStop {
  51. if node.Type != html.TextNode {
  52. node = node.NextSibling
  53. continue
  54. }
  55. urlPosStart, urlPosEnd, renderedCodeBlock, err := renderCodeBlock(ctx, node)
  56. if err != nil || renderedCodeBlock == "" {
  57. if err != nil {
  58. log.Error("Unable to render code preview: %v", err)
  59. }
  60. node = node.NextSibling
  61. continue
  62. }
  63. next := node.NextSibling
  64. textBefore := node.Data[:urlPosStart]
  65. textAfter := node.Data[urlPosEnd:]
  66. // "textBefore" could be empty if there is only a URL in the text node, then an empty node (p, or li) will be left here.
  67. // However, the empty node can't be simply removed, because:
  68. // 1. the following processors will still try to access it (need to double-check undefined behaviors)
  69. // 2. the new node is inserted as "<p>{TextBefore}<div NewNode/>{TextAfter}</p>" (the parent could also be "li")
  70. // then it is resolved as: "<p>{TextBefore}</p><div NewNode/><p>{TextAfter}</p>",
  71. // so unless it could correctly replace the parent "p/li" node, it is very difficult to eliminate the "TextBefore" empty node.
  72. node.Data = textBefore
  73. renderedCodeNode := &html.Node{Type: html.RawNode, Data: string(ctx.RenderInternal.ProtectSafeAttrs(renderedCodeBlock))}
  74. node.Parent.InsertBefore(renderedCodeNode, next)
  75. if textAfter != "" {
  76. node.Parent.InsertBefore(&html.Node{Type: html.TextNode, Data: textAfter}, next)
  77. }
  78. node = next
  79. }
  80. }