diff options
Diffstat (limited to 'modules/markup/csv')
-rw-r--r-- | modules/markup/csv/csv.go | 157 | ||||
-rw-r--r-- | modules/markup/csv/csv_test.go | 33 |
2 files changed, 190 insertions, 0 deletions
diff --git a/modules/markup/csv/csv.go b/modules/markup/csv/csv.go new file mode 100644 index 0000000..092eec7 --- /dev/null +++ b/modules/markup/csv/csv.go @@ -0,0 +1,157 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markup + +import ( + "bufio" + "html" + "io" + "regexp" + "strconv" + + "code.gitea.io/gitea/modules/csv" + "code.gitea.io/gitea/modules/markup" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/translation" + "code.gitea.io/gitea/modules/util" +) + +func init() { + markup.RegisterRenderer(Renderer{}) +} + +// Renderer implements markup.Renderer for csv files +type Renderer struct{} + +// Name implements markup.Renderer +func (Renderer) Name() string { + return "csv" +} + +// Extensions implements markup.Renderer +func (Renderer) Extensions() []string { + return []string{".csv", ".tsv"} +} + +// SanitizerRules implements markup.Renderer +func (Renderer) SanitizerRules() []setting.MarkupSanitizerRule { + return []setting.MarkupSanitizerRule{ + {Element: "table", AllowAttr: "class", Regexp: regexp.MustCompile(`^data-table$`)}, + {Element: "th", AllowAttr: "class", Regexp: regexp.MustCompile(`^line-num$`)}, + {Element: "td", AllowAttr: "class", Regexp: regexp.MustCompile(`^line-num$`)}, + } +} + +func writeField(w io.Writer, element, class, field string) error { + if _, err := io.WriteString(w, "<"); err != nil { + return err + } + if _, err := io.WriteString(w, element); err != nil { + return err + } + if len(class) > 0 { + if _, err := io.WriteString(w, " class=\""); err != nil { + return err + } + if _, err := io.WriteString(w, class); err != nil { + return err + } + if _, err := io.WriteString(w, "\""); err != nil { + return err + } + } + if _, err := io.WriteString(w, ">"); err != nil { + return err + } + if _, err := io.WriteString(w, html.EscapeString(field)); err != nil { + return err + } + if _, err := io.WriteString(w, "</"); err != nil { + return err + } + if _, err := io.WriteString(w, element); err != nil { + return err + } + _, err := io.WriteString(w, ">") + return err +} + +// Render implements markup.Renderer +func (r Renderer) Render(ctx *markup.RenderContext, input io.Reader, output io.Writer) error { + tmpBlock := bufio.NewWriter(output) + maxSize := setting.UI.CSV.MaxFileSize + maxRows := setting.UI.CSV.MaxRows + + if maxSize != 0 { + input = io.LimitReader(input, maxSize+1) + } + + rd, err := csv.CreateReaderAndDetermineDelimiter(ctx, input) + if err != nil { + return err + } + if _, err := tmpBlock.WriteString(`<table class="data-table">`); err != nil { + return err + } + + row := 0 + for { + fields, err := rd.Read() + if err == io.EOF || (row >= maxRows && maxRows != 0) { + break + } + if err != nil { + continue + } + + if _, err := tmpBlock.WriteString("<tr>"); err != nil { + return err + } + element := "td" + if row == 0 { + element = "th" + } + if err := writeField(tmpBlock, element, "line-num", strconv.Itoa(row+1)); err != nil { + return err + } + for _, field := range fields { + if err := writeField(tmpBlock, element, "", field); err != nil { + return err + } + } + if _, err := tmpBlock.WriteString("</tr>"); err != nil { + return err + } + + row++ + } + + if _, err = tmpBlock.WriteString("</table>"); err != nil { + return err + } + + // Check if maxRows or maxSize is reached, and if true, warn. + if (row >= maxRows && maxRows != 0) || (rd.InputOffset() >= maxSize && maxSize != 0) { + warn := `<table class="data-table"><tr><td>` + rawLink := ` <a href="` + ctx.Links.RawLink() + `/` + util.PathEscapeSegments(ctx.RelativePath) + `">` + + // Try to get the user translation + if locale, ok := ctx.Ctx.Value(translation.ContextKey).(translation.Locale); ok { + warn += locale.TrString("repo.file_too_large") + rawLink += locale.TrString("repo.file_view_raw") + } else { + warn += "The file is too large to be shown." + rawLink += "View Raw" + } + + warn += rawLink + `</a></td></tr></table>` + + // Write the HTML string to the output + if _, err := tmpBlock.WriteString(warn); err != nil { + return err + } + } + + return tmpBlock.Flush() +} diff --git a/modules/markup/csv/csv_test.go b/modules/markup/csv/csv_test.go new file mode 100644 index 0000000..383f134 --- /dev/null +++ b/modules/markup/csv/csv_test.go @@ -0,0 +1,33 @@ +// Copyright 2018 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package markup + +import ( + "strings" + "testing" + + "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/markup" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestRenderCSV(t *testing.T) { + var render Renderer + kases := map[string]string{ + "a": "<table class=\"data-table\"><tr><th class=\"line-num\">1</th><th>a</th></tr></table>", + "1,2": "<table class=\"data-table\"><tr><th class=\"line-num\">1</th><th>1</th><th>2</th></tr></table>", + "1;2\n3;4": "<table class=\"data-table\"><tr><th class=\"line-num\">1</th><th>1</th><th>2</th></tr><tr><td class=\"line-num\">2</td><td>3</td><td>4</td></tr></table>", + "<br/>": "<table class=\"data-table\"><tr><th class=\"line-num\">1</th><th><br/></th></tr></table>", + } + + for k, v := range kases { + var buf strings.Builder + err := render.Render(&markup.RenderContext{Ctx: git.DefaultContext}, + strings.NewReader(k), &buf) + require.NoError(t, err) + assert.EqualValues(t, v, buf.String()) + } +} |