presentation: initial work for presentations

This commit is contained in:
Todd 2017-10-03 09:55:27 -05:00
parent 629cfb008c
commit f5a8df0deb
22 changed files with 1026 additions and 77 deletions

View File

@ -7,7 +7,8 @@ import (
func main() {
ppt := presentation.New()
ppt.AddSlide()
slide := ppt.AddSlide()
_ = slide
ppt.SaveToFile("simple.pptx")
}

View File

@ -0,0 +1,74 @@
package main
import (
"fmt"
"log"
"baliance.com/gooxml/color"
"baliance.com/gooxml/schema/soo/pml"
"baliance.com/gooxml/presentation"
)
func main() {
ppt, err := presentation.OpenTemplate("template.pptx")
for i, layout := range ppt.SlideLayouts() {
fmt.Println(i, " LL ", layout.Name(), "/", layout.Type())
}
// remove any existing slides
for _, s := range ppt.Slides() {
ppt.RemoveSlide(s)
}
l, err := ppt.GetLayoutByName("Title and Caption")
if err != nil {
log.Fatalf("error retrieving layout: %s", err)
}
sld, err := ppt.AddDefaultSlideWithLayout(l)
if err != nil {
log.Fatalf("error adding slide: %s", err)
}
ph, _ := sld.GetPlaceholder(pml.ST_PlaceholderTypeTitle)
ph.SetText("Using gooxml")
ph, _ = sld.GetPlaceholder(pml.ST_PlaceholderTypeBody)
ph.SetText("Created with baliance.com/gooxml/")
tac, _ := ppt.GetLayoutByName("Title and Content")
sld, err = ppt.AddDefaultSlideWithLayout(tac)
if err != nil {
log.Fatalf("error adding slide: %s", err)
}
ph, _ = sld.GetPlaceholder(pml.ST_PlaceholderTypeTitle)
ph.SetText("Placeholders")
ph, _ = sld.GetPlaceholderByIndex(1)
ph.ClearAll()
para := ph.AddParagraph()
run := para.AddRun()
run.SetText("Adding paragraphs can create bullets depending on the placeholder")
para.AddBreak()
run = para.AddRun()
run.SetText("Line breaks work as expected within a paragraph")
for i := 1; i < 5; i++ {
para = ph.AddParagraph()
para.Properties().SetLevel(int32(i))
run = para.AddRun()
run.SetText("Level controls indentation")
}
para = ph.AddParagraph()
run = para.AddRun()
run.SetText("One Last Paragraph in a different font")
run.Properties().SetSize(20)
run.Properties().SetFont("Courier")
run.Properties().SetSolidFill(color.Red)
if err != nil {
log.Fatalf("error opening template: %s", err)
}
ppt.SaveToFile("mod.pptx")
}

Binary file not shown.

View File

@ -79,3 +79,16 @@ func (c ContentTypes) EnsureOverride(path, contentType string) {
// Didn't find a matching override for the target path, so add one
c.AddOverride(path, contentType)
}
// RemoveOverride removes an override given a path.
func (c ContentTypes) RemoveOverride(path string) {
if !strings.HasPrefix(path, "/") {
path = "/" + path
}
for i, ovr := range c.x.Override {
if ovr.PartNameAttr == path {
copy(c.x.Override[i:], c.x.Override[i+1:])
c.x.Override = c.x.Override[0 : len(c.x.Override)-1]
}
}
}

View File

@ -158,7 +158,6 @@ func (d *Document) Save(w io.Writer) error {
if err := zippkg.MarshalXMLByType(z, dt, gooxml.CorePropertiesType, d.CoreProperties.X()); err != nil {
return err
}
if d.Thumbnail != nil {
tn, err := z.Create("docProps/thumbnail.jpeg")
if err != nil {

49
drawing/paragraph.go Normal file
View File

@ -0,0 +1,49 @@
// 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 drawing
import (
"baliance.com/gooxml/schema/soo/dml"
)
// MakeParagraph constructs a new paragraph wrapper.
func MakeParagraph(x *dml.CT_TextParagraph) Paragraph {
return Paragraph{x}
}
// Paragraph is a paragraph within a document.
type Paragraph struct {
x *dml.CT_TextParagraph
}
// X returns the inner wrapped XML type.
func (p Paragraph) X() *dml.CT_TextParagraph {
return p.x
}
// AddRun adds a new run to a paragraph.
func (p Paragraph) AddRun() Run {
r := MakeRun(dml.NewEG_TextRun())
p.x.EG_TextRun = append(p.x.EG_TextRun, r.X())
return r
}
// AddBreak adds a new line break to a paragraph.
func (p Paragraph) AddBreak() {
r := dml.NewEG_TextRun()
r.Br = dml.NewCT_TextLineBreak()
p.x.EG_TextRun = append(p.x.EG_TextRun, r)
}
// Properties returns the paragraph properties.
func (p Paragraph) Properties() ParagraphProperties {
if p.x.PPr == nil {
p.x.PPr = dml.NewCT_TextParagraphProperties()
}
return MakeParagraphProperties(p.x.PPr)
}

View File

@ -7,12 +7,32 @@
package drawing
import "baliance.com/gooxml/schema/soo/dml"
import (
"baliance.com/gooxml"
"baliance.com/gooxml/schema/soo/dml"
)
// ParagraphProperties allows controlling paragraph properties.
type ParagraphProperties struct {
x *dml.CT_TextParagraphProperties
}
// MakeParagraphProperties constructs a new ParagraphProperties wrapper.
func MakeParagraphProperties(x *dml.CT_TextParagraphProperties) ParagraphProperties {
return ParagraphProperties{x}
}
// SetLevel sets the level of indentation of a paragraph.
func (p ParagraphProperties) SetLevel(idx int32) {
p.x.LvlAttr = gooxml.Int32(idx)
}
// SetNumbered controls if bullets are numbered or not.
func (p ParagraphProperties) SetNumbered(scheme dml.ST_TextAutonumberScheme) {
if scheme == dml.ST_TextAutonumberSchemeUnset {
p.x.BuAutoNum = nil
} else {
p.x.BuAutoNum = dml.NewCT_TextAutonumberBullet()
p.x.BuAutoNum.TypeAttr = scheme
}
}

44
drawing/run.go Normal file
View File

@ -0,0 +1,44 @@
// 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 drawing
import "baliance.com/gooxml/schema/soo/dml"
// MakeRun constructs a new Run wrapper.
func MakeRun(x *dml.EG_TextRun) Run {
return Run{x}
}
// Run is a run within a paragraph.
type Run struct {
x *dml.EG_TextRun
}
// X returns the inner wrapped XML type.
func (r Run) X() *dml.EG_TextRun {
return r.x
}
// SetText sets the run's text contents.
func (r Run) SetText(s string) {
r.x.Br = nil
r.x.Fld = nil
r.x.R = dml.NewCT_RegularTextRun()
r.x.R.T = s
}
// Properties returns the run's properties.
func (r Run) Properties() RunProperties {
if r.x.R == nil {
r.x.R = dml.NewCT_RegularTextRun()
}
if r.x.R.RPr == nil {
r.x.R.RPr = dml.NewCT_TextCharacterProperties()
}
return RunProperties{r.x.R.RPr}
}

View File

@ -14,20 +14,27 @@ import (
"baliance.com/gooxml/schema/soo/dml"
)
// RunProperties controls the run properties.
type RunProperties struct {
x *dml.CT_TextCharacterProperties
}
// MakeRunProperties constructs a new RunProperties wrapper.
func MakeRunProperties(x *dml.CT_TextCharacterProperties) RunProperties {
return RunProperties{x}
}
// SetSize sets the font size of the run text
func (r RunProperties) SetSize(sz measurement.Distance) {
r.x.SzAttr = gooxml.Int32(int32(sz / measurement.HundredthPoint))
}
// SetBold controls the bolding of a run.
func (r RunProperties) SetBold(b bool) {
r.x.BAttr = gooxml.Bool(b)
}
// SetSolidFill controls the text color of a run.
func (r RunProperties) SetSolidFill(c color.Color) {
r.x.NoFill = nil
r.x.BlipFill = nil
@ -38,6 +45,8 @@ func (r RunProperties) SetSolidFill(c color.Color) {
r.x.SolidFill.SrgbClr = dml.NewCT_SRgbColor()
r.x.SolidFill.SrgbClr.ValAttr = *c.AsRGBString()
}
// SetFont controls the font of a run.
func (r RunProperties) SetFont(s string) {
r.x.Latin = dml.NewCT_TextFont()
r.x.Latin.TypefaceAttr = s

View File

@ -81,6 +81,8 @@ func AbsoluteFilename(dt DocType, typ string, index int) string {
return "xl/workbook.xml"
case DocTypeDocument:
return "word/document.xml"
case DocTypePresentation:
return "ppt/presentation.xml"
default:
Log("unsupported type %s pair and %v", typ, dt)
}
@ -91,6 +93,8 @@ func AbsoluteFilename(dt DocType, typ string, index int) string {
return fmt.Sprintf("xl/theme/theme%d.xml", index)
case DocTypeDocument:
return fmt.Sprintf("word/theme/theme%d.xml", index)
case DocTypePresentation:
return fmt.Sprintf("ppt/theme/theme%d.xml", index)
default:
Log("unsupported type %s pair and %v", typ, dt)
}
@ -101,6 +105,8 @@ func AbsoluteFilename(dt DocType, typ string, index int) string {
return "xl/styles.xml"
case DocTypeDocument:
return "word/styles.xml"
case DocTypePresentation:
return "ppt/styles.xml"
default:
Log("unsupported type %s pair and %v", typ, dt)
}
@ -145,6 +151,8 @@ func AbsoluteFilename(dt DocType, typ string, index int) string {
return fmt.Sprintf("word/media/image%d.png", index)
case DocTypeSpreadsheet:
return fmt.Sprintf("xl/media/image%d.png", index)
case DocTypePresentation:
return fmt.Sprintf("ppt/media/image%d.png", index)
default:
Log("unsupported type %s pair and %v", typ, dt)
}
@ -154,7 +162,7 @@ func AbsoluteFilename(dt DocType, typ string, index int) string {
case SharedStingsType, SharedStringsContentType:
return "xl/sharedStrings.xml"
// WML
// WML
case FontTableType:
return "word/fontTable.xml"
case EndNotesType:
@ -172,6 +180,14 @@ func AbsoluteFilename(dt DocType, typ string, index int) string {
case FooterType:
return fmt.Sprintf("word/footer%d.xml", index)
// PML
case SlideType:
return fmt.Sprintf("ppt/slides/slide%d.xml", index)
case SlideLayoutType:
return fmt.Sprintf("ppt/slideLayouts/slideLayout%d.xml", index)
case SlideMasterType:
return fmt.Sprintf("ppt/slideMasters/slideMaster%d.xml", index)
default:
Log("unsupported type %s", typ)
}

View File

@ -69,3 +69,28 @@ func TestSMLFilenames(t *testing.T) {
}
}
}
func TestPMLFilenames(t *testing.T) {
td := []struct {
Idx int
Type string
ExpAbs string
}{
{0, gooxml.CorePropertiesType, "docProps/core.xml"},
{0, gooxml.ExtendedPropertiesType, "docProps/app.xml"},
{0, gooxml.ThumbnailType, "docProps/thumbnail.jpeg"},
{0, gooxml.StylesType, "ppt/styles.xml"},
{0, gooxml.OfficeDocumentType, "ppt/presentation.xml"},
{4, gooxml.SlideType, "ppt/slides/slide4.xml"},
{5, gooxml.SlideLayoutType, "ppt/slideLayouts/slideLayout5.xml"},
{6, gooxml.SlideMasterType, "ppt/slideMasters/slideMaster6.xml"},
{7, gooxml.ThemeType, "ppt/theme/theme7.xml"},
}
for _, tc := range td {
abs := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, tc.Type, tc.Idx)
if abs != tc.ExpAbs {
t.Errorf("expected absolute filename of %s for document %s/%d, got %s", tc.ExpAbs, tc.Type, tc.Idx, abs)
}
}
}

6
log.go
View File

@ -7,12 +7,14 @@
package gooxml
import "log"
import (
"log"
)
// Log is used to log content from within the library. The intent is to use
// logging sparingly, preferring to return an error. At the very least this
// allows redirecting logs to somewhere more appropriate than stdout.
var Log func(string, ...interface{}) = log.Printf
var Log = log.Printf
// DisableLogging sets the Log function to a no-op so that any log messages are
// silently discarded.

36
presentation/open.go Normal file
View File

@ -0,0 +1,36 @@
// 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 presentation
import (
"fmt"
"os"
)
func OpenTemplate(fn string) (*Presentation, error) {
p, err := Open(fn)
if err != nil {
return nil, err
}
return p, nil
}
// Open opens and reads a document from a file (.docx).
func Open(filename string) (*Presentation, error) {
f, err := os.Open(filename)
if err != nil {
return nil, fmt.Errorf("error opening %s: %s", filename, err)
}
defer f.Close()
fi, err := os.Stat(filename)
if err != nil {
return nil, fmt.Errorf("error opening %s: %s", filename, err)
}
_ = fi
return Read(f, fi.Size())
}

104
presentation/placeholder.go Normal file
View File

@ -0,0 +1,104 @@
// 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 presentation
import (
"errors"
"baliance.com/gooxml"
"baliance.com/gooxml/drawing"
"baliance.com/gooxml/schema/soo/dml"
"baliance.com/gooxml/schema/soo/pml"
)
// PlaceHolder is a place holder from a slide.
type PlaceHolder struct {
x *pml.CT_Shape
sld *pml.Sld
}
// X returns the inner wrapped XML type.
func (s PlaceHolder) X() *pml.CT_Shape {
return s.x
}
// Type returns the placeholder type
func (s PlaceHolder) Type() pml.ST_PlaceholderType {
return s.x.NvSpPr.NvPr.Ph.TypeAttr
}
// Index returns the placeholder index
func (s PlaceHolder) Index() uint32 {
if s.x.NvSpPr.NvPr.Ph.IdxAttr == nil {
return 0
}
return *s.x.NvSpPr.NvPr.Ph.IdxAttr
}
// ClearAll completely clears a placeholder. To be useable, at least one
// paragraph must be added after ClearAll via AddParagraph.
func (s PlaceHolder) ClearAll() {
s.x.SpPr = dml.NewCT_ShapeProperties()
s.x.TxBody = dml.NewCT_TextBody()
s.x.TxBody.LstStyle = dml.NewCT_TextListStyle()
}
// Clear clears the placeholder contents and adds a single empty paragraph. The
// empty paragrah is required by PowerPoint or it will report the file as being
// invalid.
func (s PlaceHolder) Clear() {
s.ClearAll()
para := dml.NewCT_TextParagraph()
s.x.TxBody.P = []*dml.CT_TextParagraph{para}
para.EndParaRPr = dml.NewCT_TextCharacterProperties()
para.EndParaRPr.LangAttr = gooxml.String("en-US")
}
func (s PlaceHolder) Remove() error {
for i, spChc := range s.sld.CSld.SpTree.Choice {
for _, sp := range spChc.Sp {
if sp == s.x {
copy(s.sld.CSld.SpTree.Choice[i:], s.sld.CSld.SpTree.Choice[i+1:])
s.sld.CSld.SpTree.Choice = s.sld.CSld.SpTree.Choice[0 : len(s.sld.CSld.SpTree.Choice)-1]
return nil
}
}
}
return errors.New("placeholder not found in slide")
}
// SetText sets the text of a placeholder for the initial paragraph. This is a
// shortcut method that is useful for things like titles which only contain a
// single paragraph.
func (s PlaceHolder) SetText(text string) {
s.Clear()
tr := dml.NewEG_TextRun()
tr.R = dml.NewCT_RegularTextRun()
tr.R.T = text
if len(s.x.TxBody.P) == 0 {
s.x.TxBody.P = append(s.x.TxBody.P, dml.NewCT_TextParagraph())
}
s.x.TxBody.P[0].EG_TextRun = nil
s.x.TxBody.P[0].EG_TextRun = append(s.x.TxBody.P[0].EG_TextRun, tr)
}
// Paragraphs returns the paragraphs defined in the placeholder.
func (s PlaceHolder) Paragraphs() []drawing.Paragraph {
ret := []drawing.Paragraph{}
for _, p := range s.x.TxBody.P {
ret = append(ret, drawing.MakeParagraph(p))
}
return ret
}
// AddParagraph adds a new paragraph to a placeholder.
func (s PlaceHolder) AddParagraph() drawing.Paragraph {
p := drawing.MakeParagraph(dml.NewCT_TextParagraph())
s.x.TxBody.P = append(s.x.TxBody.P, p.X())
return p
}

View File

@ -9,15 +9,23 @@ package presentation
import (
"archive/zip"
"bytes"
"encoding/xml"
"errors"
"fmt"
"image"
"image/jpeg"
"io"
"log"
"os"
"path/filepath"
"baliance.com/gooxml"
"baliance.com/gooxml/common"
"baliance.com/gooxml/measurement"
"baliance.com/gooxml/schema/soo/dml"
"baliance.com/gooxml/schema/soo/ofc/sharedTypes"
"baliance.com/gooxml/schema/soo/pkg/relationships"
"baliance.com/gooxml/schema/soo/pml"
"baliance.com/gooxml/zippkg"
)
@ -34,6 +42,7 @@ type Presentation struct {
layouts []*pml.SldLayout
layoutRels []common.Relationships
themes []*dml.Theme
themeRels []common.Relationships
}
// New initializes and reurns a new presentation
@ -73,8 +82,11 @@ func New() *Presentation {
p.masters = append(p.masters, m)
p.ContentTypes.AddOverride("/ppt/slideMasters/slideMaster1.xml", gooxml.SlideMasterContentType)
mrelID := p.prels.AddRelationship("slideMasters/slideMaster1.xml", gooxml.SlideMasterRelationshipType)
smFn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideMasterType, 1)
p.ContentTypes.AddOverride(smFn, gooxml.SlideMasterContentType)
mrelID := p.prels.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.OfficeDocumentType,
1, gooxml.SlideMasterType)
smid := pml.NewCT_SlideMasterIdListEntry()
smid.IdAttr = gooxml.Uint32(2147483648)
smid.RIdAttr = mrelID.ID()
@ -83,9 +95,10 @@ func New() *Presentation {
p.masterRels = append(p.masterRels, mrel)
ls := pml.NewSldLayout()
lrid := mrel.AddRelationship("../slideLayouts/slideLayout1.xml", gooxml.SlideLayoutType)
p.ContentTypes.AddOverride("/ppt/slideLayouts/slideLayout1.xml", gooxml.SlideLayoutContentType)
mrel.AddRelationship("../theme/theme1.xml", gooxml.ThemeType)
lrid := mrel.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.SlideMasterType, 1, gooxml.SlideLayoutType)
slfn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideLayoutType, 1)
p.ContentTypes.AddOverride(slfn, gooxml.SlideLayoutContentType)
mrel.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.SlideMasterType, 1, gooxml.ThemeType)
p.layouts = append(p.layouts, ls)
m.SldLayoutIdLst = pml.NewCT_SlideLayoutIdList()
@ -96,7 +109,7 @@ func New() *Presentation {
lrel := common.NewRelationships()
p.layoutRels = append(p.layoutRels, lrel)
lrel.AddRelationship("../slideMasters/slideMaster1.xml", gooxml.SlideMasterRelationshipType)
lrel.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.SlideType, 1, gooxml.SlideMasterType)
p.x.NotesSz.CxAttr = 6858000
p.x.NotesSz.CyAttr = 9144000
@ -204,8 +217,13 @@ func New() *Presentation {
fp)
p.themes = append(p.themes, thm)
p.ContentTypes.AddOverride("/ppt/theme/theme1.xml", gooxml.ThemeContentType)
p.prels.AddRelationship("theme/theme1.xml", gooxml.ThemeType)
themeFn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.ThemeType, 1)
p.ContentTypes.AddOverride(themeFn, gooxml.ThemeContentType)
p.prels.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.OfficeDocumentType, 1, gooxml.ThemeType)
thmRel := common.NewRelationships()
p.themeRels = append(p.themeRels, thmRel)
return p
}
@ -214,6 +232,7 @@ func (p *Presentation) X() *pml.Presentation {
return p.x
}
// AddSlide adds a new slide to the presentation.
func (p *Presentation) AddSlide() Slide {
sd := pml.NewCT_SlideIdListEntry()
sd.IdAttr = 256
@ -231,82 +250,217 @@ func (p *Presentation) AddSlide() Slide {
slide.CSld.SpTree.GrpSpPr.Xfrm.ChOff = slide.CSld.SpTree.GrpSpPr.Xfrm.Off
slide.CSld.SpTree.GrpSpPr.Xfrm.ChExt = slide.CSld.SpTree.GrpSpPr.Xfrm.Ext
c := pml.NewCT_GroupShapeChoice()
slide.CSld.SpTree.Choice = append(slide.CSld.SpTree.Choice, c)
sp := pml.NewCT_Shape()
c.Sp = append(c.Sp, sp)
/*
c := pml.NewCT_GroupShapeChoice()
slide.CSld.SpTree.Choice = append(slide.CSld.SpTree.Choice, c)
sp := pml.NewCT_Shape()
c.Sp = append(c.Sp, sp)
sp.NvSpPr.NvPr.Ph = pml.NewCT_Placeholder()
sp.NvSpPr.NvPr.Ph.TypeAttr = pml.ST_PlaceholderTypeCtrTitle
sp.NvSpPr.NvPr.Ph = pml.NewCT_Placeholder()
sp.NvSpPr.NvPr.Ph.TypeAttr = pml.ST_PlaceholderTypeCtrTitle
sp.TxBody = dml.NewCT_TextBody()
para := dml.NewCT_TextParagraph()
sp.TxBody.P = append(sp.TxBody.P, para)
run := dml.NewEG_TextRun()
para.EG_TextRun = append(para.EG_TextRun, run)
run.R = dml.NewCT_RegularTextRun()
run.R.T = "testing 123"
sp.TxBody = dml.NewCT_TextBody()
para := dml.NewCT_TextParagraph()
sp.TxBody.P = append(sp.TxBody.P, para)
run := dml.NewEG_TextRun()
para.EG_TextRun = append(para.EG_TextRun, run)
run.R = dml.NewCT_RegularTextRun()
run.R.T = "testing 123"
*/
p.slides = append(p.slides, slide)
fn := fmt.Sprintf("slides/slide%d.xml", len(p.slides))
srelID := p.prels.AddRelationship(fn, gooxml.SlideType)
srelID := p.prels.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.OfficeDocumentType,
len(p.slides), gooxml.SlideType)
sd.RIdAttr = srelID.ID()
p.ContentTypes.AddOverride(fmt.Sprintf("/ppt/slides/slide%d.xml", len(p.slides)), gooxml.SlideContentType)
slidefn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideType, len(p.slides))
p.ContentTypes.AddOverride(slidefn, gooxml.SlideContentType)
srel := common.NewRelationships()
p.slideRels = append(p.slideRels, srel)
srel.AddRelationship("../slideLayouts/slideLayout1.xml", gooxml.SlideLayoutType)
// TODO: make the slide layout configurable
srel.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.SlideType,
len(p.layouts), gooxml.SlideLayoutType)
return Slide{sd, slide}
}
// AddSlideWithLayout adds a new slide with content copied from a layout. Normally you should
// use AddDefaultSlideWithLayout as it will do some post processing similar to PowerPoint to
// clear place holder text, etc.
func (p *Presentation) AddSlideWithLayout(l SlideLayout) (Slide, error) {
sd := pml.NewCT_SlideIdListEntry()
sd.IdAttr = 256
for _, id := range p.x.SldIdLst.SldId {
if id.IdAttr >= sd.IdAttr {
sd.IdAttr = id.IdAttr + 1
}
}
p.x.SldIdLst.SldId = append(p.x.SldIdLst.SldId, sd)
slide := pml.NewSld()
buf := bytes.Buffer{}
enc := xml.NewEncoder(&buf)
start := xml.StartElement{Name: xml.Name{Local: "slide"}}
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns"}, Value: "http://schemas.openxmlformats.org/presentationml/2006/main"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:a"}, Value: "http://schemas.openxmlformats.org/drawingml/2006/main"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:p"}, Value: "http://schemas.openxmlformats.org/presentationml/2006/main"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:r"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/relationships"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:sh"}, Value: "http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"})
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "xmlns:xml"}, Value: "http://www.w3.org/XML/1998/namespace"})
if err := l.x.CSld.MarshalXML(enc, start); err != nil {
return Slide{}, err
}
enc.Flush()
dec := xml.NewDecoder(&buf)
slide.CSld = pml.NewCT_CommonSlideData()
if err := dec.Decode(slide.CSld); err != nil {
return Slide{}, err
}
// clear layout name on the slide
slide.CSld.NameAttr = nil
//, chc := range slide.CSld.SpTree.Choice {
for i := 0; i < len(slide.CSld.SpTree.Choice); i++ {
chc := slide.CSld.SpTree.Choice[i]
for len(chc.Pic) > 0 {
copy(slide.CSld.SpTree.Choice[i:], slide.CSld.SpTree.Choice[i+1:])
slide.CSld.SpTree.Choice = slide.CSld.SpTree.Choice[0 : len(slide.CSld.SpTree.Choice)-1]
chc = slide.CSld.SpTree.Choice[i]
}
}
p.slides = append(p.slides, slide)
srelID := p.prels.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.OfficeDocumentType,
len(p.slides), gooxml.SlideType)
sd.RIdAttr = srelID.ID()
slidefn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideType, len(p.slides))
p.ContentTypes.AddOverride(slidefn, gooxml.SlideContentType)
srel := common.NewRelationships()
p.slideRels = append(p.slideRels, srel)
for i, lout := range p.layouts {
if lout == l.X() {
srel.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.SlideType,
i+1, gooxml.SlideLayoutType)
}
}
csld := Slide{sd, slide}
return csld, nil
}
// AddDefaultSlideWithLayout tries to replicate what PowerPoint does when
// inserting a slide with a new style by clearing placeholder content and removing
// some placeholders. Use AddSlideWithLayout if you need more control.
func (p *Presentation) AddDefaultSlideWithLayout(l SlideLayout) (Slide, error) {
sld, err := p.AddSlideWithLayout(l)
for _, ph := range sld.PlaceHolders() {
// clear all placeholder content
ph.Clear()
// and drop some of the placeholders (footer, slide date/time, slide number)
switch ph.Type() {
case pml.ST_PlaceholderTypeFtr, pml.ST_PlaceholderTypeDt, pml.ST_PlaceholderTypeSldNum:
ph.Remove()
}
}
return sld, err
}
// Save writes the presentation out to a writer in the Zip package format
func (p *Presentation) Save(w io.Writer) error {
if err := p.x.Validate(); err != nil {
log.Printf("validation error in document: %s", err)
}
dt := gooxml.DocTypePresentation
z := zip.NewWriter(w)
defer z.Close()
if err := zippkg.MarshalXML(z, gooxml.BaseRelsFilename, p.Rels.X()); err != nil {
return err
}
if err := zippkg.MarshalXMLByType(z, dt, gooxml.ExtendedPropertiesType, p.AppProperties.X()); err != nil {
return err
}
if err := zippkg.MarshalXMLByType(z, dt, gooxml.CorePropertiesType, p.CoreProperties.X()); err != nil {
return err
}
if p.Thumbnail != nil {
tn, err := z.Create("docProps/thumbnail.jpeg")
if err != nil {
return err
}
if err := jpeg.Encode(tn, p.Thumbnail, nil); err != nil {
return err
}
}
documentFn := gooxml.AbsoluteFilename(dt, gooxml.OfficeDocumentType, 0)
if err := zippkg.MarshalXML(z, documentFn, p.x); err != nil {
return err
}
if err := zippkg.MarshalXML(z, zippkg.RelationsPathFor(documentFn), p.prels.X()); err != nil {
return err
}
for i, slide := range p.slides {
spath := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideType, i+1)
zippkg.MarshalXML(z, spath, slide)
if !p.slideRels[i].IsEmpty() {
rpath := zippkg.RelationsPathFor(spath)
zippkg.MarshalXML(z, rpath, p.slideRels[i].X())
}
}
for i, m := range p.masters {
mpath := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideMasterType, i+1)
zippkg.MarshalXML(z, mpath, m)
if !p.masterRels[i].IsEmpty() {
rpath := zippkg.RelationsPathFor(mpath)
zippkg.MarshalXML(z, rpath, p.masterRels[i].X())
}
}
for i, l := range p.layouts {
mpath := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideLayoutType, i+1)
zippkg.MarshalXML(z, mpath, l)
if !p.layoutRels[i].IsEmpty() {
rpath := zippkg.RelationsPathFor(mpath)
zippkg.MarshalXML(z, rpath, p.layoutRels[i].X())
}
}
for i, l := range p.themes {
mpath := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.ThemeType, i+1)
zippkg.MarshalXML(z, mpath, l)
if !p.themeRels[i].IsEmpty() {
rpath := zippkg.RelationsPathFor(mpath)
zippkg.MarshalXML(z, rpath, p.themeRels[i].X())
}
}
for i, img := range p.Images {
fn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.ImageType, i+1)
if img.Path() != "" {
if err := zippkg.AddFileFromDisk(z, fn, img.Path()); err != nil {
return err
}
} else {
gooxml.Log("unsupported image source: %+v", img)
}
}
if err := zippkg.MarshalXML(z, gooxml.ContentTypesFilename, p.ContentTypes.X()); err != nil {
return err
}
if err := zippkg.MarshalXML(z, "_rels/.rels", p.Rels.X()); err != nil {
if err := p.WriteExtraFiles(z); err != nil {
return err
}
if err := zippkg.MarshalXML(z, "docProps/app.xml", p.AppProperties.X()); err != nil {
return err
}
if err := zippkg.MarshalXML(z, "docProps/core.xml", p.CoreProperties.X()); err != nil {
return err
}
if err := zippkg.MarshalXML(z, "ppt/presentation.xml", p.x); err != nil {
return err
}
if err := zippkg.MarshalXML(z, "ppt/_rels/presentation.xml.rels", p.prels.X()); err != nil {
return err
}
for i, slide := range p.slides {
spath := fmt.Sprintf("ppt/slides/slide%d.xml", i+1)
zippkg.MarshalXML(z, spath, slide)
rpath := zippkg.RelationsPathFor(spath)
zippkg.MarshalXML(z, rpath, p.slideRels[i].X())
}
for i, m := range p.masters {
mpath := fmt.Sprintf("ppt/slideMasters/slideMaster%d.xml", i+1)
zippkg.MarshalXML(z, mpath, m)
rpath := zippkg.RelationsPathFor(mpath)
zippkg.MarshalXML(z, rpath, p.masterRels[i].X())
}
for i, l := range p.layouts {
mpath := fmt.Sprintf("ppt/slideLayouts/slideLayout%d.xml", i+1)
zippkg.MarshalXML(z, mpath, l)
rpath := zippkg.RelationsPathFor(mpath)
zippkg.MarshalXML(z, rpath, p.layoutRels[i].X())
}
for i, l := range p.themes {
tpath := fmt.Sprintf("ppt/theme/theme%d.xml", i+1)
zippkg.MarshalXML(z, tpath, l)
}
p.WriteExtraFiles(z)
return nil
}
@ -341,3 +495,192 @@ func (p *Presentation) Validate() error {
}
return nil
}
// SlideMasters returns the slide masters defined in the presentation.
func (p *Presentation) SlideMasters() []SlideMaster {
ret := []SlideMaster{}
for i, m := range p.masters {
ret = append(ret, SlideMaster{p, p.masterRels[i], m})
}
return ret
}
// SlideLayouts returns the slide layouts defined in the presentation.
func (p *Presentation) SlideLayouts() []SlideLayout {
ret := []SlideLayout{}
for _, l := range p.layouts {
ret = append(ret, SlideLayout{l})
}
return ret
}
func (p *Presentation) onNewRelationship(decMap *zippkg.DecodeMap, target, typ string, files []*zip.File, rel *relationships.Relationship, src zippkg.Target) error {
dt := gooxml.DocTypePresentation
switch typ {
case gooxml.OfficeDocumentType:
p.x = pml.NewPresentation()
decMap.AddTarget(target, p.x, typ, 0)
decMap.AddTarget(zippkg.RelationsPathFor(target), p.prels.X(), typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.CorePropertiesType:
decMap.AddTarget(target, p.CoreProperties.X(), typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.ExtendedPropertiesType:
decMap.AddTarget(target, p.AppProperties.X(), typ, 0)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, 0)
case gooxml.SlideType:
sld := pml.NewSld()
p.slides = append(p.slides, sld)
decMap.AddTarget(target, sld, typ, uint32(len(p.slides)))
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(p.slides))
slRel := common.NewRelationships()
decMap.AddTarget(zippkg.RelationsPathFor(target), slRel.X(), typ, 0)
p.slideRels = append(p.slideRels, slRel)
case gooxml.SlideMasterType:
sm := pml.NewSldMaster()
if !decMap.AddTarget(target, sm, typ, uint32(len(p.masters)+1)) {
return nil
}
p.masters = append(p.masters, sm)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(p.masters))
// look for master rels
smRel := common.NewRelationships()
decMap.AddTarget(zippkg.RelationsPathFor(target), smRel.X(), typ, 0)
p.masterRels = append(p.masterRels, smRel)
case gooxml.SlideLayoutType:
sl := pml.NewSldLayout()
if !decMap.AddTarget(target, sl, typ, uint32(len(p.layouts)+1)) {
return nil
}
p.layouts = append(p.layouts, sl)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(p.layouts))
// look for layout rels
slRel := common.NewRelationships()
decMap.AddTarget(zippkg.RelationsPathFor(target), slRel.X(), typ, 0)
p.layoutRels = append(p.layoutRels, slRel)
case gooxml.ThumbnailType:
// read our thumbnail
for i, f := range files {
if f == nil {
continue
}
if f.Name == target {
rc, err := f.Open()
if err != nil {
return fmt.Errorf("error reading thumbnail: %s", err)
}
p.Thumbnail, _, err = image.Decode(rc)
rc.Close()
if err != nil {
return fmt.Errorf("error decoding thumbnail: %s", err)
}
files[i] = nil
}
}
case gooxml.ThemeType:
thm := dml.NewTheme()
if !decMap.AddTarget(target, thm, typ, uint32(len(p.themes)+1)) {
return nil
}
p.themes = append(p.themes, thm)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, len(p.themes))
// look for theme rels
thmRel := common.NewRelationships()
decMap.AddTarget(zippkg.RelationsPathFor(target), thmRel.X(), typ, 0)
p.themeRels = append(p.themeRels, thmRel)
case gooxml.ImageType:
target = filepath.Clean(target)
for i, f := range files {
if f == nil {
continue
}
if f.Name == target {
path, err := zippkg.ExtractToDiskTmp(f, p.TmpPath)
if err != nil {
return err
}
img, err := common.ImageFromFile(path)
if err != nil {
return err
}
iref := common.MakeImageRef(img, &p.DocBase, p.prels)
p.Images = append(p.Images, iref)
files[i] = nil
decMap.RecordIndex(target, len(p.Images))
break
}
}
idx := decMap.IndexFor(target)
rel.TargetAttr = gooxml.RelativeFilename(dt, src.Typ, typ, idx)
default:
gooxml.Log("unsupported relationship type: %s tgt: %s", typ, target)
}
return nil
}
// Slides returns the slides in the presentation.
func (p *Presentation) Slides() []Slide {
ret := []Slide{}
for i, v := range p.slides {
ret = append(ret, Slide{p.x.SldIdLst.SldId[i], v})
}
return ret
}
// RemoveSlide removes a slide from a presentation.
func (p *Presentation) RemoveSlide(s Slide) error {
removed := false
slideIdx := 0
for i, v := range p.slides {
if v == s.x {
if p.x.SldIdLst.SldId[i] != s.sid {
return errors.New("inconsistency in slides and ID list")
}
copy(p.slides[i:], p.slides[i+1:])
p.slides = p.slides[0 : len(p.slides)-1]
copy(p.slideRels[i:], p.slideRels[i+1:])
p.slideRels = p.slideRels[0 : len(p.slideRels)-1]
copy(p.x.SldIdLst.SldId[i:], p.x.SldIdLst.SldId[i+1:])
p.x.SldIdLst.SldId = p.x.SldIdLst.SldId[0 : len(p.x.SldIdLst.SldId)-1]
removed = true
slideIdx = i
}
}
if !removed {
return errors.New("unable to find slide")
}
// remove it from content types
fn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideType, slideIdx+1)
p.ContentTypes.RemoveOverride(fn)
return nil
}
// GetLayoutByName retrieves a slide layout given a layout name.
func (p *Presentation) GetLayoutByName(name string) (SlideLayout, error) {
for _, l := range p.layouts {
if l.CSld.NameAttr != nil && name == *l.CSld.NameAttr {
return SlideLayout{l}, nil
}
}
return SlideLayout{}, errors.New("unable to find layout with that name")
}

56
presentation/read.go Normal file
View File

@ -0,0 +1,56 @@
// 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 presentation
import (
"archive/zip"
"fmt"
"io"
"io/ioutil"
"baliance.com/gooxml"
"baliance.com/gooxml/zippkg"
)
// Read reads a document from an io.Reader.
func Read(r io.ReaderAt, size int64) (*Presentation, error) {
doc := New()
td, err := ioutil.TempDir("", "gooxml-pptx")
if err != nil {
return nil, err
}
doc.TmpPath = td
zr, err := zip.NewReader(r, size)
if err != nil {
return nil, fmt.Errorf("parsing zip: %s", err)
}
files := []*zip.File{}
files = append(files, zr.File...)
decMap := zippkg.DecodeMap{}
decMap.SetOnNewRelationshipFunc(doc.onNewRelationship)
// we should discover all contents by starting with these two files
decMap.AddTarget(gooxml.ContentTypesFilename, doc.ContentTypes.X(), "", 0)
decMap.AddTarget(gooxml.BaseRelsFilename, doc.Rels.X(), "", 0)
if err := decMap.Decode(files); err != nil {
return nil, err
}
for _, f := range files {
if f == nil {
continue
}
if err := doc.AddExtraFileFromZip(f); err != nil {
return nil, err
}
}
return doc, nil
}

View File

@ -7,9 +7,64 @@
package presentation
import "baliance.com/gooxml/schema/soo/pml"
import (
"errors"
"baliance.com/gooxml/schema/soo/pml"
)
type Slide struct {
sid *pml.CT_SlideIdListEntry
x *pml.Sld
}
// X returns the inner wrapped XML type.
func (s Slide) X() *pml.Sld {
return s.x
}
// PlaceHolders returns all of the content place holders within a given slide.
func (s Slide) PlaceHolders() []PlaceHolder {
ret := []PlaceHolder{}
for _, spChc := range s.x.CSld.SpTree.Choice {
for _, sp := range spChc.Sp {
if sp.NvSpPr != nil && sp.NvSpPr.NvPr != nil && sp.NvSpPr.NvPr.Ph != nil {
ret = append(ret, PlaceHolder{sp, s.x})
}
}
}
return ret
}
// GetPlaceholder returns a placeholder given its type. If there are multiplace
// placeholders of the same type, this method returns the first one. You must use the
// PlaceHolders() method to access the others.
func (s Slide) GetPlaceholder(t pml.ST_PlaceholderType) (PlaceHolder, error) {
for _, spChc := range s.x.CSld.SpTree.Choice {
for _, sp := range spChc.Sp {
if sp.NvSpPr != nil && sp.NvSpPr.NvPr != nil && sp.NvSpPr.NvPr.Ph != nil {
if sp.NvSpPr.NvPr.Ph.TypeAttr == t {
return PlaceHolder{sp, s.x}, nil
}
}
}
}
return PlaceHolder{}, errors.New("unable to find placeholder")
}
// GetPlaceholderByIndex returns a placeholder given its index. If there are multiplace
// placeholders of the same index, this method returns the first one. You must use the
// PlaceHolders() method to access the others.
func (s Slide) GetPlaceholderByIndex(idx uint32) (PlaceHolder, error) {
for _, spChc := range s.x.CSld.SpTree.Choice {
for _, sp := range spChc.Sp {
if sp.NvSpPr != nil && sp.NvSpPr.NvPr != nil && sp.NvSpPr.NvPr.Ph != nil {
if (idx == 0 && sp.NvSpPr.NvPr.Ph.IdxAttr == nil) ||
(sp.NvSpPr.NvPr.Ph.IdxAttr != nil && *sp.NvSpPr.NvPr.Ph.IdxAttr == idx) {
return PlaceHolder{sp, s.x}, nil
}
}
}
}
return PlaceHolder{}, errors.New("unable to find placeholder")
}

View File

@ -0,0 +1,35 @@
// 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 presentation
import (
"baliance.com/gooxml/schema/soo/pml"
)
// SlideLayout
type SlideLayout struct {
x *pml.SldLayout
}
// X returns the inner wrapped XML type.
func (s SlideLayout) X() *pml.SldLayout {
return s.x
}
// Type returns the type of the slide layout.
func (s SlideLayout) Type() pml.ST_SlideLayoutType {
return s.x.TypeAttr
}
// Name returns the name of the slide layout.
func (s SlideLayout) Name() string {
if s.x.CSld != nil && s.x.CSld.NameAttr != nil {
return *s.x.CSld.NameAttr
}
return ""
}

View File

@ -0,0 +1,48 @@
// 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 presentation
import (
"strconv"
"strings"
"baliance.com/gooxml/common"
"baliance.com/gooxml/schema/soo/pml"
)
// SlideMaster is the slide master for a presentation.
type SlideMaster struct {
p *Presentation
rels common.Relationships
x *pml.SldMaster
}
// X returns the inner wrapped XML type.
func (s SlideMaster) X() *pml.SldMaster {
return s.x
}
func (s SlideMaster) SlideLayouts() []SlideLayout {
nameToLayoutIdx := map[string]int{}
layouts := []SlideLayout{}
for _, r := range s.rels.Relationships() {
idxTxt := strings.Replace(r.Target(), "../slideLayouts/slideLayout", "", -1)
idxTxt = strings.Replace(idxTxt, ".xml", "", -1)
if idx, err := strconv.ParseInt(idxTxt, 10, 32); err == nil {
nameToLayoutIdx[r.ID()] = int(idx)
}
}
for _, lid := range s.x.SldLayoutIdLst.SldLayoutId {
if idx, ok := nameToLayoutIdx[lid.RIdAttr]; ok {
lout := s.p.layouts[idx-1]
layouts = append(layouts, SlideLayout{lout})
}
}
return layouts
}

View File

@ -35,6 +35,8 @@ const (
SMLStyleSheetContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
TableType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
TableContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
ViewPropertiesType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/viewProps"
TableStylesType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/tableStyles"
// WML
HeaderType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"
@ -46,12 +48,13 @@ const (
EndNotesType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/endnotes"
// PML
SlideType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
SlideContentType = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"
SlideMasterRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"
SlideMasterContentType = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"
SlideLayoutType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"
SlideLayoutContentType = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"
SlideType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
SlideContentType = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"
SlideMasterType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"
SlideMasterContentType = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"
SlideLayoutType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"
SlideLayoutContentType = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"
PresentationPropertiesType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps"
// VML
VMLDrawingType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"

View File

@ -35,6 +35,8 @@ type DecodeMap struct {
basePaths map[*relationships.Relationships]string
rels []Target
decodeFunc OnNewRelationshipFunc
decoded map[string]struct{}
indices map[string]int
}
// SetOnNewRelationshipFunc sets the function to be called when a new
@ -55,12 +57,27 @@ type Target struct {
// unmarshaled to. filePath is the absolute path to the target, ifc is the
// object to decode into, sourceFileType is the type of file that the reference
// was discovered in, and index is the index of the source file type.
func (d *DecodeMap) AddTarget(filePath string, ifc interface{}, sourceFileType string, idx uint32) {
func (d *DecodeMap) AddTarget(filePath string, ifc interface{}, sourceFileType string, idx uint32) bool {
if d.pathsToIfcs == nil {
d.pathsToIfcs = make(map[string]Target)
d.basePaths = make(map[*relationships.Relationships]string)
d.decoded = make(map[string]struct{})
d.indices = make(map[string]int)
}
d.pathsToIfcs[filepath.Clean(filePath)] = Target{Path: filePath, Typ: sourceFileType, Ifc: ifc, Index: idx}
fn := filepath.Clean(filePath)
if _, ok := d.decoded[fn]; ok {
// already decoded this file
return false
}
d.decoded[fn] = struct{}{}
d.pathsToIfcs[fn] = Target{Path: filePath, Typ: sourceFileType, Ifc: ifc, Index: idx}
return true
}
func (d *DecodeMap) RecordIndex(path string, idx int) {
d.indices[path] = idx
}
func (d *DecodeMap) IndexFor(path string) int {
return d.indices[path]
}
// Decode loops decoding targets registered with AddTarget and calling th