github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/markup/csv/csv.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package markup 7 8 import ( 9 "bufio" 10 "bytes" 11 "html" 12 "io" 13 "regexp" 14 "strconv" 15 16 "github.com/gitbundle/modules/csv" 17 "github.com/gitbundle/modules/markup" 18 "github.com/gitbundle/modules/setting" 19 ) 20 21 func init() { 22 markup.RegisterRenderer(Renderer{}) 23 } 24 25 // Renderer implements markup.Renderer for csv files 26 type Renderer struct{} 27 28 // Name implements markup.Renderer 29 func (Renderer) Name() string { 30 return "csv" 31 } 32 33 // Extensions implements markup.Renderer 34 func (Renderer) Extensions() []string { 35 return []string{".csv", ".tsv"} 36 } 37 38 // SanitizerRules implements markup.Renderer 39 func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { 40 return []setting.MarkupSanitizerRule{ 41 {Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`data-table`)}, 42 {Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)}, 43 {Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`line-num`)}, 44 } 45 } 46 47 func writeField(w io.Writer, element, class, field string) error { 48 if _, err := io.WriteString(w, "<"); err != nil { 49 return err 50 } 51 if _, err := io.WriteString(w, element); err != nil { 52 return err 53 } 54 if len(class) > 0 { 55 if _, err := io.WriteString(w, " class=\""); err != nil { 56 return err 57 } 58 if _, err := io.WriteString(w, class); err != nil { 59 return err 60 } 61 if _, err := io.WriteString(w, "\""); err != nil { 62 return err 63 } 64 } 65 if _, err := io.WriteString(w, ">"); err != nil { 66 return err 67 } 68 if _, err := io.WriteString(w, html.EscapeString(field)); err != nil { 69 return err 70 } 71 if _, err := io.WriteString(w, "</"); err != nil { 72 return err 73 } 74 if _, err := io.WriteString(w, element); err != nil { 75 return err 76 } 77 _, err := io.WriteString(w, ">") 78 return err 79 } 80 81 // Render implements markup.Renderer 82 func (Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { 83 tmpBlock := bufio.NewWriter(output) 84 85 // FIXME: don't read all to memory 86 rawBytes, err := io.ReadAll(input) 87 if err != nil { 88 return err 89 } 90 91 if setting.UI.CSV.MaxFileSize != 0 && setting.UI.CSV.MaxFileSize < int64(len(rawBytes)) { 92 if _, err := tmpBlock.WriteString("<pre>"); err != nil { 93 return err 94 } 95 if _, err := tmpBlock.WriteString(html.EscapeString(string(rawBytes))); err != nil { 96 return err 97 } 98 _, err = tmpBlock.WriteString("</pre>") 99 return err 100 } 101 102 rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, bytes.NewReader(rawBytes)) 103 if err != nil { 104 return err 105 } 106 107 if _, err := tmpBlock.WriteString(`<table class="data-table">`); err != nil { 108 return err 109 } 110 row := 1 111 for { 112 fields, err := rd.Read() 113 if err == io.EOF { 114 break 115 } 116 if err != nil { 117 continue 118 } 119 if _, err := tmpBlock.WriteString("<tr>"); err != nil { 120 return err 121 } 122 element := "td" 123 if row == 1 { 124 element = "th" 125 } 126 if err := writeField(tmpBlock, element, "line-num", strconv.Itoa(row)); err != nil { 127 return err 128 } 129 for _, field := range fields { 130 if err := writeField(tmpBlock, element, "", field); err != nil { 131 return err 132 } 133 } 134 if _, err := tmpBlock.WriteString("</tr>"); err != nil { 135 return err 136 } 137 138 row++ 139 } 140 if _, err = tmpBlock.WriteString("</table>"); err != nil { 141 return err 142 } 143 return tmpBlock.Flush() 144 }