From 3482f80d1cd4664d22ecfaf4c419854e9b26e6fb Mon Sep 17 00:00:00 2001 From: colorcrow Date: Mon, 1 Jan 2024 16:54:04 +0800 Subject: [PATCH] =?UTF-8?q?WriteCsvFile=E8=87=AA=E5=AE=9A=E4=B9=89?= =?UTF-8?q?=E5=88=86=E5=89=B2=E7=AC=A6,=E5=A2=9E=E5=8A=A0map=E5=88=87?= =?UTF-8?q?=E7=89=87=E5=86=99=E5=85=A5csv=EF=BC=8C=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E8=BF=BD=E8=B8=AA=E5=87=BD=E6=95=B0=E8=BF=90=E8=A1=8C=E6=97=B6?= =?UTF-8?q?=E9=97=B4=E7=9A=84=E5=87=BD=E6=95=B0=20(#157)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * WriteCsvFile now support custom delimiter,Add write map slice to csv * add trace func time --- datetime/datetime.go | 9 +++++++ fileutil/file.go | 63 ++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 70 insertions(+), 2 deletions(-) diff --git a/datetime/datetime.go b/datetime/datetime.go index db80649..5432deb 100644 --- a/datetime/datetime.go +++ b/datetime/datetime.go @@ -381,3 +381,12 @@ func TimestampNano(timezone ...string) int64 { return t.UnixNano() } + +// TraceFuncTime: trace the func costed time,just call it at top of the func like `defer TraceFuncTime()()` +func TraceFuncTime() func() { + pre := time.Now() + return func() { + elapsed := time.Since(pre) + fmt.Println("Costs Time:\t", elapsed) + } +} diff --git a/fileutil/file.go b/fileutil/file.go index 18f2b68..3328d45 100644 --- a/fileutil/file.go +++ b/fileutil/file.go @@ -645,8 +645,10 @@ func ReadCsvFile(filepath string) ([][]string, error) { } // WriteCsvFile write content to target csv file. +// append: append to existing csv file +// delimiter: specifies csv delimiter // Play: https://go.dev/play/p/dAXm58Q5U1o -func WriteCsvFile(filepath string, records [][]string, append bool) error { +func WriteCsvFile(filepath string, records [][]string, append bool, delimiter ...rune) error { flag := os.O_RDWR | os.O_CREATE if append { @@ -661,7 +663,19 @@ func WriteCsvFile(filepath string, records [][]string, append bool) error { defer f.Close() writer := csv.NewWriter(f) - writer.Comma = ',' + // 设置默认分隔符为逗号,除非另外指定 + if len(delimiter) > 0 { + writer.Comma = delimiter[0] + } else { + writer.Comma = ',' + } + + // 遍历所有记录并处理包含分隔符或双引号的单元格 + for i := range records { + for j := range records[i] { + records[i][j] = escapeCSVField(records[i][j], writer.Comma) + } + } return writer.WriteAll(records) } @@ -720,3 +734,48 @@ func ReadFile(path string) (reader io.ReadCloser, closeFn func(), err error) { return nil, func() {}, errors.New("unknown file type") } } + +// escapeCSVField 处理单元格内容,如果包含分隔符,则用双引号包裹 +func escapeCSVField(field string, delimiter rune) string { + // 替换所有的双引号为两个双引号 + escapedField := strings.ReplaceAll(field, "\"", "\"\"") + + // 如果字段包含分隔符、双引号或换行符,用双引号包裹整个字段 + if strings.ContainsAny(escapedField, string(delimiter)+"\"\n") { + escapedField = fmt.Sprintf("\"%s\"", escapedField) + } + + return escapedField +} + +// map切片写入csv文件中 +func WriteMapsToCSV(filepath string, records []map[string]string, append_to_existing_file bool, delimiter ...rune) error { + var datas_to_write [][]string + // 标题(列名) + var headers []string + if len(records) > 0 { + for key := range records[0] { + headers = append(headers, key) + } + } + // 追加模式不重复写字段名 + if !append_to_existing_file { + datas_to_write = append(datas_to_write, headers) + } + // 写入数据行 + for _, record := range records { + var row []string + for _, header := range headers { + row = append(row, record[header]) + } + datas_to_write = append(datas_to_write, row) + } + // 提取自定义分隔符 + var sep rune + if len(delimiter) > 0 { + sep = delimiter[0] + } else { + sep = ',' + } + return WriteCsvFile(filepath, datas_to_write, append_to_existing_file, sep) +}