func (sw *SheetWriter) Close() error {
	if sw.closed {
		panic("SheetWriter already closed")
	}
	cellEndX, cellEndY := CellIndex(sw.maxNCols-1, sw.currentIndex-1)
	sheetEnd := fmt.Sprintf(`<dimension ref="A1:%s%d"/>`, cellEndX, cellEndY)
	sheetEnd += `</sheetData></worksheet>`
	_, err := io.WriteString(sw.f, sheetEnd)
	sw.closed = true
	return err
}
Closes the SheetWriter
package xlsx
import (
	"archive/zip"
	"bufio"
	"bytes"
	"fmt"
	"html"
	"io"
	"os"
	"strconv"
	"time"
)
type CellType uint
const (
	CellTypeNumber CellType = iota
	CellTypeString
	CellTypeDatetime
	CellTypeInlineString
)
type Cell struct {
	Type  CellType
	Value string
}
type Row struct {
	Cells []Cell
}
type Column struct {
	Name  string
	Width uint64
}
type DocumentInfo struct {
	CreatedBy  string
	ModifiedBy string
	CreatedAt  time.Time
	ModifiedAt time.Time
}
type Sheet struct {
	Title           string
	columns         []Column
	rows            []Row
	sharedStringMap map[string]int
	sharedStrings   []string
	DocumentInfo    DocumentInfo
}
func NewSheet() Sheet {
	c := make([]Column, 0)
	r := make([]Row, 0)
	ssm := make(map[string]int)
	sst := make([]string, 0)
	s := Sheet{
		Title:           "Data",
		columns:         c,
		rows:            r,
		sharedStringMap: ssm,
		sharedStrings:   sst,
	}
	return s
}
func NewSheetWithColumns(c []Column) Sheet {
	r := make([]Row, 0)
	ssm := make(map[string]int)
	sst := make([]string, 0)
	s := Sheet{
		Title:           "Data",
		columns:         c,
		rows:            r,
		sharedStringMap: ssm,
		sharedStrings:   sst,
	}
	s.DocumentInfo.CreatedBy = "xlsx.go"
	s.DocumentInfo.CreatedAt = time.Now()
	s.DocumentInfo.ModifiedBy = s.DocumentInfo.CreatedBy
	s.DocumentInfo.ModifiedAt = s.DocumentInfo.CreatedAt
	return s
}
func (s *Sheet) NewRow() Row {
	c := make([]Cell, len(s.columns))
	r := Row{
		Cells: c,
	}
	return r
}
func (s *Sheet) AppendRow(r Row) error {
	if len(r.Cells) != len(s.columns) {
		return fmt.Errorf("the given row has %d cells and %d were expected", len(r.Cells), len(s.columns))
	}
	cells := make([]Cell, len(s.columns))
	for n, c := range r.Cells {
		cells[n].Type = c.Type
		cells[n].Value = c.Value
		if cells[n].Type == CellTypeString {
			cells[n].Value = html.EscapeString(cells[n].Value)
			i, exists := s.sharedStringMap[cells[n].Value]
			if !exists {
				i = len(s.sharedStrings)
				s.sharedStringMap[cells[n].Value] = i
				s.sharedStrings = append(s.sharedStrings, cells[n].Value)
			}
			cells[n].Value = strconv.Itoa(i)
		}
	}
	row := s.NewRow()
	row.Cells = cells
	s.rows = append(s.rows, row)
	return nil
}
func (s *Sheet) SharedStrings() []string {
	return s.sharedStrings
}
func CellIndex(x, y uint64) (string, uint64) {
	return colName(x), (y + 1)
}
func colName(n uint64) string {
	var s string
	n += 1
	for n > 0 {
		n -= 1
		s = string(65+(n%26)) + s
		n /= 26
	}
	return s
}
func OADate(d time.Time) string {
	epoch := time.Date(1899, 12, 30, 0, 0, 0, 0, time.UTC)
	nsPerDay := 24 * time.Hour
	v := -1 * float64(epoch.Sub(d)) / float64(nsPerDay)
	if d.Hour() == 0 && d.Minute() == 0 && d.Second() == 0 {
		return fmt.Sprintf("%d", int64(v))
	} else {
		return fmt.Sprintf("%f", v)
	}
}
func (s *Sheet) SaveToFile(filename string) error {
	outputfile, err := os.Create(filename)
	if err != nil {
		return err
	}
	w := bufio.NewWriter(outputfile)
	err = s.SaveToWriter(w)
	defer w.Flush()
	return err
}
func (s *Sheet) SaveToWriter(w io.Writer) error {
	ww := NewWorkbookWriter(w)
	sw, err := ww.NewSheetWriter(s)
	if err != nil {
		return err
	}
	err = sw.WriteRows(s.rows)
	if err != nil {
		return err
	}
	err = ww.Close()
	return err
}
type WorkbookWriter struct {
	zipWriter     *zip.Writer
	sheetWriter   *SheetWriter
	headerWritten bool
	closed        bool
}
func NewWorkbookWriter(w io.Writer) *WorkbookWriter {
	return &WorkbookWriter{zip.NewWriter(w), nil, false, false}
}
func (ww *WorkbookWriter) WriteHeader(s *Sheet) error {
	if ww.closed {
		panic("Can not write to closed WorkbookWriter")
	}
	if ww.headerWritten {
		panic("Workbook header already written")
	}
	z := ww.zipWriter
	f, err := z.Create("[Content_Types].xml")
	err = TemplateContentTypes.Execute(f, nil)
	if err != nil {
		return err
	}
	f, err = z.Create("docProps/app.xml")
	err = TemplateApp.Execute(f, s)
	if err != nil {
		return err
	}
	f, err = z.Create("docProps/core.xml")
	err = TemplateCore.Execute(f, s.DocumentInfo)
	if err != nil {
		return err
	}
	f, err = z.Create("_rels/.rels")
	err = TemplateRelationships.Execute(f, nil)
	if err != nil {
		return err
	}
	f, err = z.Create("xl/workbook.xml")
	err = TemplateWorkbook.Execute(f, s)
	if err != nil {
		return err
	}
	f, err = z.Create("xl/_rels/workbook.xml.rels")
	err = TemplateWorkbookRelationships.Execute(f, nil)
	if err != nil {
		return err
	}
	f, err = z.Create("xl/styles.xml")
	err = TemplateStyles.Execute(f, nil)
	if err != nil {
		return err
	}
	f, err = z.Create("xl/sharedStrings.xml")
	err = TemplateStringLookups.Execute(f, s.SharedStrings())
	if err != nil {
		return err
	}
	return nil
}
func (ww *WorkbookWriter) Close() error {
	if ww.closed {
		panic("WorkbookWriter already closed")
	}
	if ww.sheetWriter != nil {
		err := ww.sheetWriter.Close()
		if err != nil {
			return err
		}
	}
	ww.closed = true
	return ww.zipWriter.Close()
}
func (ww *WorkbookWriter) NewSheetWriter(s *Sheet) (*SheetWriter, error) {
	if ww.closed {
		panic("Can not write to closed WorkbookWriter")
	}
	if !ww.headerWritten {
		err := ww.WriteHeader(s)
		if err != nil {
			return nil, err
		}
	}
	f, err := ww.zipWriter.Create("xl/worksheets/" + "sheet1" + ".xml")
	sw := &SheetWriter{f, err, 0, 0, false}
	if ww.sheetWriter != nil {
		err = ww.sheetWriter.Close()
		if err != nil {
			return nil, err
		}
	}
	ww.sheetWriter = sw
	err = sw.WriteHeader(s)
	return sw, err
}
type SheetWriter struct {
	f            io.Writer
	err          error
	currentIndex uint64
	maxNCols     uint64
	closed       bool
}
func (sw *SheetWriter) WriteRows(rows []Row) error {
	if sw.closed {
		panic("Can not write to closed SheetWriter")
	}
	var err error
	for i, r := range rows {
		rb := &bytes.Buffer{}
		if sw.maxNCols < uint64(len(r.Cells)) {
			sw.maxNCols = uint64(len(r.Cells))
		}
		for j, c := range r.Cells {
			cellX, cellY := CellIndex(uint64(j), uint64(i)+sw.currentIndex)
			if c.Type == CellTypeDatetime {
				d, err := time.Parse(time.RFC3339, c.Value)
				if err == nil {
					c.Value = OADate(d)
				}
			} else if c.Type == CellTypeInlineString {
				c.Value = html.EscapeString(c.Value)
			}
			var cellString string
			switch c.Type {
			case CellTypeString:
				cellString = `<c r="%s%d" t="s" s="1"><v>%s</v></c>`
			case CellTypeInlineString:
				cellString = `<c r="%s%d" t="inlineStr"><is><t>%s</t></is></c>`
			case CellTypeNumber:
				cellString = `<c r="%s%d" t="n" s="1"><v>%s</v></c>`
			case CellTypeDatetime:
				cellString = `<c r="%s%d" s="2"><v>%s</v></c>`
			}
			io.WriteString(rb, fmt.Sprintf(cellString, cellX, cellY, c.Value))
			if err != nil {
				return err
			}
		}
		rowString := fmt.Sprintf(`<row r="%d">%s</row>`, uint64(i)+sw.currentIndex+1, rb.String())
		_, err = io.WriteString(sw.f, rowString)
		if err != nil {
			return err
		}
	}
	sw.currentIndex += uint64(len(rows))
	return nil
}