meta: move filename information out of packages

This seems like the better choice, a lot of the logic is shared
between the document types, and it allows generating filenames in
a single place.

The only downside is that you must pass in the document type as
some content types have different typical names depending on the
document type (e.g. an 'office document' is the main document.xml,
workbook.xml and presentation.xml
This commit is contained in:
Todd 2017-09-03 12:01:55 -05:00
parent f07c57cae7
commit 9090e4f93c
6 changed files with 177 additions and 19 deletions

View File

@ -8,7 +8,6 @@
package common
import (
"log"
"strings"
"baliance.com/gooxml/schema/schemas.openxmlformats.org/package/2006/content_types"
@ -52,7 +51,7 @@ func (c ContentTypes) AddDefault(fileExtension string, contentType string) {
// AddOverride adds an override content type for a given path name.
func (c ContentTypes) AddOverride(path, contentType string) {
if !strings.HasPrefix(path, "/") {
log.Printf("override path %s should start with a '/'", path)
path = "/" + path
}
or := content_types.NewOverride()
or.PartNameAttr = path

View File

@ -12,6 +12,7 @@ import (
"log"
"strings"
"baliance.com/gooxml"
"baliance.com/gooxml/schema/schemas.openxmlformats.org/package/2006/relationships"
)
@ -48,6 +49,13 @@ func (r Relationships) FindRIDForN(i int, t string) string {
return ""
}
// AddAutoRelationship adds a relationship with an automatically generated
// filename based off of the type. It should be preferred over AddRelationship
// to ensure consistent filenames are maintained.
func (r Relationships) AddAutoRelationship(dt gooxml.DocType, idx int, ctype string) Relationship {
return r.AddRelationship(gooxml.RelativeFilename(dt, ctype, idx), ctype)
}
// AddRelationship adds a relationship.
func (r Relationships) AddRelationship(target, ctype string) Relationship {
if !strings.HasPrefix(ctype, "http://") {

124
filenames.go Normal file
View File

@ -0,0 +1,124 @@
// Copyright 2017 Baliance. All rights reserved.
//
// Use of this source code is governed by the terms of the Affero GNU General
// Public License version 3.0 as published by the Free Software Foundation and
// appearing in the file LICENSE included in the packaging of this file. A
// commercial license can be purchased by contacting sales@baliance.com.
package gooxml
import (
"fmt"
"log"
)
// Common filenames used in zip packages.
const (
ContentTypesFilename = "[Content_Types].xml"
BaseRelsFilename = "_rels/.rels"
)
type DocType byte
const (
Unknown DocType = iota
DocTypeSpreadsheet
DocTypeDocument
DocTypePresentation
)
func RelativeFilename(dt DocType, typ string, index int) string {
switch typ {
case CorePropertiesType:
return "docProps/core.xml"
case ExtendedPropertiesType:
return "docProps/app.xml"
case ThumbnailType:
return "docProps/thumbnail.jpeg"
case StylesType:
return "styles.xml"
case ChartType:
return fmt.Sprintf("charts/chart%d.xml", index)
case DrawingType:
return fmt.Sprintf("drawings/drawing%d.xml", index)
case ThemeType, ThemeContentType:
return fmt.Sprintf("theme/theme%d.xml", index)
case OfficeDocumentType:
switch dt {
case DocTypeSpreadsheet:
return "xl/workbook.xml"
default:
log.Printf("unsupported type %s pair and %v", typ, dt)
}
// SML
case WorksheetType, WorksheetContentType:
return fmt.Sprintf("worksheets/sheet%d.xml", index)
case SharedStingsType, SharedStringsContentType:
return "sharedStrings.xml"
default:
log.Printf("unsupported type %s", typ)
}
return ""
}
func AbsoluteFilename(dt DocType, typ string, index int) string {
switch typ {
case CorePropertiesType:
return "docProps/core.xml"
case ExtendedPropertiesType:
return "docProps/app.xml"
case ThumbnailType:
return "docProps/thumbnail.jpeg"
case OfficeDocumentType:
switch dt {
case DocTypeSpreadsheet:
return "xl/workbook.xml"
default:
log.Printf("unsupported type %s pair and %v", typ, dt)
}
case ThemeType, ThemeContentType:
switch dt {
case DocTypeSpreadsheet:
return fmt.Sprintf("xl/theme/theme%d.xml", index)
default:
log.Printf("unsupported type %s pair and %v", typ, dt)
}
case StylesType:
switch dt {
case DocTypeSpreadsheet:
return "xl/styles.xml"
}
case ChartType:
switch dt {
case DocTypeSpreadsheet:
return fmt.Sprintf("xl/charts/chart%d.xml", index)
default:
log.Printf("unsupported type %s pair and %v", typ, dt)
}
case DrawingType:
switch dt {
case DocTypeSpreadsheet:
return fmt.Sprintf("xl/drawings/drawing%d.xml", index)
default:
log.Fatalf("unsupported type %s pair and %v", typ, dt)
}
// SML
case WorksheetType, WorksheetContentType:
return fmt.Sprintf("xl/worksheets/sheet%d.xml", index)
case SharedStingsType, SharedStringsContentType:
return "xl/sharedStrings.xml"
default:
log.Fatalf("unsupported type %s", typ)
}
return ""
}

View File

@ -26,8 +26,10 @@ const (
// SML
WorksheetType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"
SharedStringsContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
WorksheetContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"
SharedStingsType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings"
SharedStringsContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"
SMLStyleSheetContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
// WML
HeaderType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"

View File

@ -72,14 +72,15 @@ func (wb *Workbook) AddSheet() Sheet {
wb.xwsRels = append(wb.xwsRels, common.NewRelationships())
ws.SheetData = sml.NewCT_SheetData()
dt := gooxml.DocTypeSpreadsheet
// update the references
rid := wb.wbRels.AddRelationship(fmt.Sprintf("worksheets/sheet%d.xml", len(wb.x.Sheets.Sheet)),
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet")
rid := wb.wbRels.AddAutoRelationship(dt, len(wb.x.Sheets.Sheet), gooxml.WorksheetType)
rs.IdAttr = rid.ID()
// add the content type
wb.ContentTypes.AddOverride(fmt.Sprintf("/xl/worksheets/sheet%d.xml", len(wb.x.Sheets.Sheet)),
"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml")
wb.ContentTypes.AddOverride(gooxml.AbsoluteFilename(dt, gooxml.WorksheetContentType, len(wb.x.Sheets.Sheet)),
gooxml.WorksheetContentType)
return Sheet{wb, rs, ws}
}
@ -98,42 +99,49 @@ func (wb *Workbook) SaveToFile(path string) error {
func (wb *Workbook) Save(w io.Writer) error {
z := zip.NewWriter(w)
defer z.Close()
dt := gooxml.DocTypeSpreadsheet
if err := zippkg.MarshalXML(z, zippkg.ContentTypesFilename, wb.ContentTypes.X()); err != nil {
return err
}
if err := zippkg.MarshalXML(z, zippkg.BaseRelsFilename, wb.Rels.X()); err != nil {
return err
}
if err := zippkg.MarshalXML(z, zippkg.AppPropsFilename, wb.AppProperties.X()); err != nil {
if err := zippkg.MarshalXMLByType(z, dt, gooxml.ExtendedPropertiesType, wb.AppProperties.X()); err != nil {
return err
}
if err := zippkg.MarshalXML(z, zippkg.CorePropsFilename, wb.CoreProperties.X()); err != nil {
if err := zippkg.MarshalXMLByType(z, dt, gooxml.CorePropertiesType, wb.CoreProperties.X()); err != nil {
return err
}
if err := zippkg.MarshalXML(z, "xl/workbook.xml", wb.x); err != nil {
workbookFn := gooxml.AbsoluteFilename(dt, gooxml.OfficeDocumentType, 0)
if err := zippkg.MarshalXML(z, workbookFn, wb.x); err != nil {
return err
}
if err := zippkg.MarshalXML(z, "xl/styles.xml", wb.StyleSheet.X()); err != nil {
if err := zippkg.MarshalXML(z, zippkg.RelationsPathFor(workbookFn), wb.wbRels.X()); err != nil {
return err
}
if err := zippkg.MarshalXML(z, "xl/sharedStrings.xml", wb.SharedStrings.X()); err != nil {
if err := zippkg.MarshalXMLByType(z, dt, gooxml.StylesType, wb.StyleSheet.X()); err != nil {
return err
}
if err := zippkg.MarshalXML(z, "xl/_rels/workbook.xml.rels", wb.wbRels.X()); err != nil {
if err := zippkg.MarshalXMLByType(z, dt, gooxml.SharedStingsType, wb.SharedStrings.X()); err != nil {
return err
}
for i, thm := range wb.themes {
if err := zippkg.MarshalXML(z, fmt.Sprintf("xl/theme/theme%d.xml", i+1), thm); err != nil {
if err := zippkg.MarshalXMLByTypeIndex(z, dt, gooxml.ThemeType, i+1, thm); err != nil {
return err
}
}
for i, sheet := range wb.xws {
fn := fmt.Sprintf("xl/worksheets/sheet%d.xml", i+1)
fn := gooxml.AbsoluteFilename(dt, gooxml.WorksheetType, i+1)
zippkg.MarshalXML(z, fn, sheet)
zippkg.MarshalXML(z, zippkg.RelationsPathFor(fn), wb.xwsRels[i].X())
}
if wb.Thumbnail != nil {
tn, err := z.Create("docProps/thumbnail.jpeg")
fn := gooxml.AbsoluteFilename(dt, gooxml.ThumbnailType, 0)
tn, err := z.Create(fn)
if err != nil {
return err
}
@ -142,11 +150,11 @@ func (wb *Workbook) Save(w io.Writer) error {
}
}
for i, chart := range wb.charts {
fn := fmt.Sprintf("xl/charts/chart%d.xml", i+1)
fn := gooxml.AbsoluteFilename(dt, gooxml.ChartType, i+1)
zippkg.MarshalXML(z, fn, chart)
}
for i, drawing := range wb.drawings {
fn := fmt.Sprintf("xl/drawings/drawing%d.xml", i+1)
fn := gooxml.AbsoluteFilename(dt, gooxml.DrawingType, i+1)
zippkg.MarshalXML(z, fn, drawing)
zippkg.MarshalXML(z, zippkg.RelationsPathFor(fn), wb.drawingRels[i].X())
}
@ -204,6 +212,9 @@ func (wb Workbook) SheetCount() int {
}
func (wb *Workbook) onNewRelationship(decMap *zippkg.DecodeMap, target, typ string, files []*zip.File, rel *relationships.Relationship) error {
dt := gooxml.DocTypeSpreadsheet
rel.TargetAttr = gooxml.RelativeFilename(dt, typ, 0)
switch typ {
case gooxml.OfficeDocumentType:
wb.x = sml.NewWorkbook()
@ -228,7 +239,7 @@ func (wb *Workbook) onNewRelationship(decMap *zippkg.DecodeMap, target, typ stri
wb.xwsRels = append(wb.xwsRels, wksRel)
// fix the relationship target so it points to where we'll save
// the worksheet
rel.TargetAttr = fmt.Sprintf("worksheets/sheet%d.xml", len(wb.xws))
rel.TargetAttr = gooxml.RelativeFilename(dt, typ, len(wb.xws))
case gooxml.StylesType:
wb.StyleSheet = NewStyleSheet()
@ -271,11 +282,13 @@ func (wb *Workbook) onNewRelationship(decMap *zippkg.DecodeMap, target, typ stri
drel := common.NewRelationships()
decMap.AddTarget(zippkg.RelationsPathFor(target), drel.X())
wb.drawingRels = append(wb.drawingRels, drel)
rel.TargetAttr = gooxml.RelativeFilename(dt, typ, len(wb.drawings))
case gooxml.ChartType:
chart := crt.NewChartSpace()
decMap.AddTarget(target, chart)
wb.charts = append(wb.charts, chart)
rel.TargetAttr = gooxml.RelativeFilename(dt, typ, len(wb.charts))
default:
fmt.Println("unsupported relationship", target, typ)

View File

@ -12,6 +12,8 @@ import (
"encoding/xml"
"fmt"
"time"
"baliance.com/gooxml"
)
// XMLHeader is a header that MarshalXML uses to prefix the XML files it creates.
@ -19,6 +21,16 @@ const XMLHeader = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>` + "\
var nl = []byte{'\r', '\n'}
func MarshalXMLByTypeIndex(z *zip.Writer, dt gooxml.DocType, typ string, idx int, v interface{}) error {
fn := gooxml.AbsoluteFilename(dt, typ, idx)
return MarshalXML(z, fn, v)
}
func MarshalXMLByType(z *zip.Writer, dt gooxml.DocType, typ string, v interface{}) error {
fn := gooxml.AbsoluteFilename(dt, typ, 0)
return MarshalXML(z, fn, v)
}
// MarshalXML creates a file inside of a zip and marshals an object as xml, prefixing it
// with a standard XML header.
func MarshalXML(z *zip.Writer, filename string, v interface{}) error {