“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.
171 lines
6.4 KiB
Go
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)
|
|
}
|
|
}
|