package reports import ( "encoding/csv" "io" ) // WriteCSV schreibt einen Bericht als CSV. // // CSV ist ein flaches Format, ein Bericht ist es nicht. Statt die Abschnitte // wegzulassen, werden sie mit einer eigenen Zeile eingeleitet — so bleibt die // Datei in jeder Tabellenkalkulation lesbar und verliert nichts. // // Der entscheidende Punkt steht in writeMetric: Eine ungemessene Kennzahl // erzeugt **keine Null**. Eine Null in einer Tabellenkalkulation wird summiert, // gemittelt und in ein Diagramm gezeichnet; sie ueberlebt jede muendliche // Erlaeuterung, mit der man sie einordnen wollte. func WriteCSV(outputWriter io.Writer, report *Report) error { // Byte Order Mark: Ohne ihn zeigt Excel unter Windows aus einer // UTF-8-Datei „Sicherungslÿufe" an. Der Bericht traegt deutsche // Beschriftungen, die Frage stellt sich also bei jeder Datei. if _, writeError := outputWriter.Write([]byte{0xEF, 0xBB, 0xBF}); writeError != nil { return writeError } csvWriter := csv.NewWriter(outputWriter) headerRows := [][]string{ {"Bericht", report.Title}, {"Art", string(report.Type)}, {"Zeitraum von", FormatTimestamp(report.PeriodFrom)}, {"Zeitraum bis", FormatTimestamp(report.PeriodTo)}, {"Erzeugt am", FormatTimestamp(report.GeneratedAt)}, } if report.GeneratedBy != "" { headerRows = append(headerRows, []string{"Erzeugt von", report.GeneratedBy}) } for _, headerRow := range headerRows { if writeError := csvWriter.Write(headerRow); writeError != nil { return writeError } } for _, section := range report.Sections { if writeError := writeCSVSection(csvWriter, section); writeError != nil { return writeError } } if len(report.Notes) > 0 { if writeError := writeCSVBlank(csvWriter); writeError != nil { return writeError } if writeError := csvWriter.Write([]string{"Hinweise"}); writeError != nil { return writeError } for _, note := range report.Notes { if writeError := csvWriter.Write([]string{"", note}); writeError != nil { return writeError } } } csvWriter.Flush() return csvWriter.Error() } // writeCSVSection schreibt einen Abschnitt. func writeCSVSection(csvWriter *csv.Writer, section Section) error { if writeError := writeCSVBlank(csvWriter); writeError != nil { return writeError } if writeError := csvWriter.Write([]string{section.Title}); writeError != nil { return writeError } if len(section.Metrics) > 0 { if writeError := csvWriter.Write([]string{"Kennzahl", "Wert", "Einheit", "Anmerkung"}); writeError != nil { return writeError } for _, metric := range section.Metrics { if writeError := csvWriter.Write(metricCSVRow(metric)); writeError != nil { return writeError } } } for _, table := range section.Tables { if writeError := writeCSVTable(csvWriter, table); writeError != nil { return writeError } } return nil } // metricCSVRow bildet die Zeile einer Kennzahl. // // Bei einer ungemessenen Kennzahl bleibt die Wertspalte leer und die Anmerkung // traegt den Grund. Eine leere Zelle laesst sich nicht versehentlich summieren. func metricCSVRow(metric Metric) []string { if !metric.IsKnown { return []string{metric.Label, "", string(metric.Unit), "nicht gemessen: " + metric.UnknownReason} } if metric.Text != "" { return []string{metric.Label, metric.Text, "", ""} } // Der Rohwert, nicht die aufbereitete Form: Eine Tabellenkalkulation soll // rechnen koennen. „1,4 GiB" ist Text, 1503238553 ist eine Zahl. return []string{metric.Label, trimNumber(metric.Value), string(metric.Unit), ""} } // writeCSVTable schreibt eine Tabelle. func writeCSVTable(csvWriter *csv.Writer, table Table) error { if writeError := writeCSVBlank(csvWriter); writeError != nil { return writeError } if writeError := csvWriter.Write([]string{table.Title}); writeError != nil { return writeError } if len(table.Rows) == 0 { return csvWriter.Write([]string{"", emptyNoticeOf(table)}) } if writeError := csvWriter.Write(table.Columns); writeError != nil { return writeError } for _, tableRow := range table.Rows { if writeError := csvWriter.Write(tableRow); writeError != nil { return writeError } } return nil } // writeCSVBlank schreibt eine Trennzeile. func writeCSVBlank(csvWriter *csv.Writer) error { return csvWriter.Write([]string{""}) } // emptyNoticeOf liefert den Hinweis einer leeren Tabelle. func emptyNoticeOf(table Table) string { if table.EmptyNotice != "" { return table.EmptyNotice } return "Keine Einträge im gewählten Zeitraum." }