package server import ( "encoding/csv" "fmt" "io" "net/http" "strings" ) // exportFormats maps ?format= values to file extension and content type. var exportFormats = map[string]struct{ ext, contentType string }{ "csv": {"csv", "text/csv; charset=utf-8"}, "txt": {"txt", "text/plain; charset=utf-8"}, } // handleExport downloads a list's titles, one per line, with nothing else: // no ranks, header row or metadata. Line N is place #N, so an empty place // is a blank line, and importing the file puts everything back where it was. func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) { format := r.URL.Query().Get("format") if format == "" { format = "txt" } f, ok := exportFormats[format] if !ok { http.Error(w, `format must be "csv" or "txt"`, http.StatusBadRequest) return } l, ok := s.visibleList(w, r) if !ok { return } items, err := s.store.Items(r.Context(), l.ID, 0, 0) if err != nil { s.serverError(w, r, err) return } var titles []string for _, it := range items { for len(titles) < it.Rank-1 { titles = append(titles, "") } titles = append(titles, it.Title) } w.Header().Set("Content-Type", f.contentType) w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.%s"`, l.Slug, f.ext)) w.Header().Set("Cache-Control", "no-store") if err := writeTitles(w, format, titles); err != nil { s.log.Warn("export failed", "list", l.Slug, "err", err) } } // flatten keeps one title per line even if a title contains a newline. var flatten = strings.NewReplacer("\r\n", " ", "\n", " ", "\r", " ") func writeTitles(w io.Writer, format string, titles []string) error { if format == "csv" { // The BOM makes Excel read accented titles (Les Misérables) as UTF-8. if _, err := io.WriteString(w, "\xef\xbb\xbf"); err != nil { return err } cw := csv.NewWriter(w) cw.UseCRLF = true for _, t := range titles { if err := cw.Write([]string{flatten.Replace(t)}); err != nil { return err } } cw.Flush() return cw.Error() } for _, t := range titles { if _, err := io.WriteString(w, flatten.Replace(t)+"\n"); err != nil { return err } } return nil }