jordi fita mas d945f55096 Add “part” of the bookings’ management
“Part”, because it is not possible to add or actually manage any
booking yet, but it has the export feature that we need to validate the
project.
2024-01-18 21:05:30 +01:00

171 lines
6.4 KiB
Go

package booking
import (
"archive/zip"
"bytes"
"encoding/xml"
"fmt"
"net/http"
"strings"
"time"
"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("2006-01-02"), 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)
}
}