package booking import ( "archive/zip" "bytes" "encoding/xml" "fmt" "net/http" "strings" "time" "dev.tandem.ws/tandem/camper/pkg/database" "dev.tandem.ws/tandem/camper/pkg/locale" ) const ( mimetype = "application/vnd.oasis.opendocument.spreadsheet" metaDashInfManifestXml = `<?xml version="1.0" encoding="UTF-8"?> <manifest:manifest xmlns:manifest="urn:oasis:names:tc:opendocument:xmlns:manifest:1.0" manifest:version="1.3"> <manifest:file-entry manifest:full-path="/" manifest:version="1.3" manifest:media-type="application/vnd.oasis.opendocument.spreadsheet"/> <manifest:file-entry manifest:full-path="meta.xml" manifest:media-type="text/xml"/> <manifest:file-entry manifest:full-path="styles.xml" manifest:media-type="text/xml"/> <manifest:file-entry manifest:full-path="content.xml" manifest:media-type="text/xml"/> </manifest:manifest> ` metaXml = `<?xml version="1.0" encoding="UTF-8"?> <office:document-meta xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.3"> <office:meta> <meta:creation-date></meta:creation-date> <meta:generator>Camper</meta:generator> </office:meta> </office:document-meta> ` stylesXml = `<?xml version="1.0" encoding="UTF-8"?> <office:document-styles xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" office:version="1.3"> </office:document-styles> ` ) func writeTableOds[K interface{}](rows []*K, columns []string, locale *locale.Locale, writeRow func(*strings.Builder, *K) error) ([]byte, error) { var sb strings.Builder sb.WriteString(`<?xml version="1.0" encoding="UTF-8"?> <office:document-content xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:calcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" office:version="1.3"> <office:scripts/> <office:font-face-decls> <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> </office:font-face-decls> <office:automatic-styles> <style:style style:name="co1" style:family="table-column"> <style:table-column-properties fo:break-before="auto" style:column-width="0.889in"/> </style:style> <style:style style:name="ro1" style:family="table-row"> <style:table-row-properties style:row-height="0.178in" fo:break-before="auto" style:use-optimal-row-height="true"/> </style:style> <style:style style:name="ta1" style:family="table" style:master-page-name="Default"> <style:table-properties table:display="true" style:writing-mode="lr-tb"/> </style:style> <number:date-style style:name="N37" number:automatic-order="true"> <number:day number:style="long"/> <number:text>/</number:text> <number:month number:style="long"/> <number:text>/</number:text> <number:year/> </number:date-style> <style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N37"/> </office:automatic-styles> <office:body> <office:spreadsheet> <table:calculation-settings table:automatic-find-labels="false" table:use-regular-expressions="false" table:use-wildcards="true"/> <table:table table:name="Sheet1" table:style-name="ta1"> `) sb.WriteString(fmt.Sprintf(" <table:table-column table:style-name=\"co1\" table:number-columns-repeated=\"%d\" table:default-cell-style-name=\"Default\"/>\n", len(columns))) sb.WriteString(` <table:table-row table:style-name="ro1"> `) for _, t := range columns { if err := writeCellString(&sb, locale.GetC(t, "header")); err != nil { return nil, err } } sb.WriteString(" </table:table-row>\n") for _, row := range rows { sb.WriteString(" <table:table-row table:style-name=\"ro1\">\n") if err := writeRow(&sb, row); err != nil { return nil, err } sb.WriteString(" </table:table-row>\n") } sb.WriteString(` </table:table> <table:named-expressions/> </office:spreadsheet> </office:body> </office:document-content> `) return writeOds(sb.String()) } func writeOds(content string) ([]byte, error) { buf := new(bytes.Buffer) ods := zip.NewWriter(buf) if err := writeOdsFile(ods, "mimetype", mimetype, zip.Store); err != nil { return nil, err } if err := writeOdsFile(ods, "META-INF/manifest.xml", metaDashInfManifestXml, zip.Deflate); err != nil { return nil, err } if err := writeOdsFile(ods, "meta.xml", metaXml, zip.Deflate); err != nil { return nil, err } if err := writeOdsFile(ods, "styles.xml", stylesXml, zip.Deflate); err != nil { return nil, err } if err := writeOdsFile(ods, "content.xml", content, zip.Deflate); err != nil { return nil, err } if err := ods.Close(); err != nil { return nil, err } return buf.Bytes(), nil } func writeOdsFile(ods *zip.Writer, name string, content string, method uint16) error { f, err := ods.CreateHeader(&zip.FileHeader{ Name: name, Method: method, Modified: time.Now(), }) if err != nil { return err } _, err = f.Write([]byte(content)) return err } func writeCellString(sb *strings.Builder, s string) error { sb.WriteString(` <table:table-cell office:value-type="string" calcext:value-type="string"><text:p>`) if err := xml.EscapeText(sb, []byte(s)); err != nil { return err } sb.WriteString("</text:p></table:table-cell>\n") return nil } func writeCellDate(sb *strings.Builder, t time.Time) { sb.WriteString(fmt.Sprintf(" <table:table-cell table:style-name=\"ce1\" office:value-type=\"date\" office:date-value=\"%s\" calcext:value-type=\"date\"><text:p>%s</text:p></table:table-cell>\n", t.Format(database.ISODateFormat), t.Format("02/01/06"))) } func mustWriteOdsResponse(w http.ResponseWriter, ods []byte, filename string) { w.Header().Set("Content-Type", "application/vnd.oasis.opendocument.spreadsheet") w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) w.WriteHeader(http.StatusOK) if _, err := w.Write(ods); err != nil { panic(err) } }