mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-29 13:49:10 +08:00
presentation: initial work for presentations
This commit is contained in:
parent
629cfb008c
commit
f5a8df0deb
@ -7,7 +7,8 @@ import (
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
ppt := presentation.New()
|
ppt := presentation.New()
|
||||||
ppt.AddSlide()
|
slide := ppt.AddSlide()
|
||||||
|
_ = slide
|
||||||
|
|
||||||
ppt.SaveToFile("simple.pptx")
|
ppt.SaveToFile("simple.pptx")
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
74
_examples/presentation/use-template/main.go
Normal file
74
_examples/presentation/use-template/main.go
Normal 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")
|
||||||
|
}
|
BIN
_examples/presentation/use-template/template.pptx
Normal file
BIN
_examples/presentation/use-template/template.pptx
Normal file
Binary file not shown.
@ -79,3 +79,16 @@ func (c ContentTypes) EnsureOverride(path, contentType string) {
|
|||||||
// Didn't find a matching override for the target path, so add one
|
// Didn't find a matching override for the target path, so add one
|
||||||
c.AddOverride(path, contentType)
|
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]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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 {
|
if err := zippkg.MarshalXMLByType(z, dt, gooxml.CorePropertiesType, d.CoreProperties.X()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if d.Thumbnail != nil {
|
if d.Thumbnail != nil {
|
||||||
tn, err := z.Create("docProps/thumbnail.jpeg")
|
tn, err := z.Create("docProps/thumbnail.jpeg")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
49
drawing/paragraph.go
Normal file
49
drawing/paragraph.go
Normal 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)
|
||||||
|
}
|
@ -7,12 +7,32 @@
|
|||||||
|
|
||||||
package drawing
|
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 {
|
type ParagraphProperties struct {
|
||||||
x *dml.CT_TextParagraphProperties
|
x *dml.CT_TextParagraphProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeParagraphProperties constructs a new ParagraphProperties wrapper.
|
||||||
func MakeParagraphProperties(x *dml.CT_TextParagraphProperties) ParagraphProperties {
|
func MakeParagraphProperties(x *dml.CT_TextParagraphProperties) ParagraphProperties {
|
||||||
return ParagraphProperties{x}
|
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
44
drawing/run.go
Normal 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}
|
||||||
|
}
|
@ -14,20 +14,27 @@ import (
|
|||||||
"baliance.com/gooxml/schema/soo/dml"
|
"baliance.com/gooxml/schema/soo/dml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RunProperties controls the run properties.
|
||||||
type RunProperties struct {
|
type RunProperties struct {
|
||||||
x *dml.CT_TextCharacterProperties
|
x *dml.CT_TextCharacterProperties
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MakeRunProperties constructs a new RunProperties wrapper.
|
||||||
func MakeRunProperties(x *dml.CT_TextCharacterProperties) RunProperties {
|
func MakeRunProperties(x *dml.CT_TextCharacterProperties) RunProperties {
|
||||||
return RunProperties{x}
|
return RunProperties{x}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSize sets the font size of the run text
|
||||||
func (r RunProperties) SetSize(sz measurement.Distance) {
|
func (r RunProperties) SetSize(sz measurement.Distance) {
|
||||||
r.x.SzAttr = gooxml.Int32(int32(sz / measurement.HundredthPoint))
|
r.x.SzAttr = gooxml.Int32(int32(sz / measurement.HundredthPoint))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetBold controls the bolding of a run.
|
||||||
func (r RunProperties) SetBold(b bool) {
|
func (r RunProperties) SetBold(b bool) {
|
||||||
r.x.BAttr = gooxml.Bool(b)
|
r.x.BAttr = gooxml.Bool(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSolidFill controls the text color of a run.
|
||||||
func (r RunProperties) SetSolidFill(c color.Color) {
|
func (r RunProperties) SetSolidFill(c color.Color) {
|
||||||
r.x.NoFill = nil
|
r.x.NoFill = nil
|
||||||
r.x.BlipFill = 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 = dml.NewCT_SRgbColor()
|
||||||
r.x.SolidFill.SrgbClr.ValAttr = *c.AsRGBString()
|
r.x.SolidFill.SrgbClr.ValAttr = *c.AsRGBString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetFont controls the font of a run.
|
||||||
func (r RunProperties) SetFont(s string) {
|
func (r RunProperties) SetFont(s string) {
|
||||||
r.x.Latin = dml.NewCT_TextFont()
|
r.x.Latin = dml.NewCT_TextFont()
|
||||||
r.x.Latin.TypefaceAttr = s
|
r.x.Latin.TypefaceAttr = s
|
||||||
|
16
filenames.go
16
filenames.go
@ -81,6 +81,8 @@ func AbsoluteFilename(dt DocType, typ string, index int) string {
|
|||||||
return "xl/workbook.xml"
|
return "xl/workbook.xml"
|
||||||
case DocTypeDocument:
|
case DocTypeDocument:
|
||||||
return "word/document.xml"
|
return "word/document.xml"
|
||||||
|
case DocTypePresentation:
|
||||||
|
return "ppt/presentation.xml"
|
||||||
default:
|
default:
|
||||||
Log("unsupported type %s pair and %v", typ, dt)
|
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)
|
return fmt.Sprintf("xl/theme/theme%d.xml", index)
|
||||||
case DocTypeDocument:
|
case DocTypeDocument:
|
||||||
return fmt.Sprintf("word/theme/theme%d.xml", index)
|
return fmt.Sprintf("word/theme/theme%d.xml", index)
|
||||||
|
case DocTypePresentation:
|
||||||
|
return fmt.Sprintf("ppt/theme/theme%d.xml", index)
|
||||||
default:
|
default:
|
||||||
Log("unsupported type %s pair and %v", typ, dt)
|
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"
|
return "xl/styles.xml"
|
||||||
case DocTypeDocument:
|
case DocTypeDocument:
|
||||||
return "word/styles.xml"
|
return "word/styles.xml"
|
||||||
|
case DocTypePresentation:
|
||||||
|
return "ppt/styles.xml"
|
||||||
default:
|
default:
|
||||||
Log("unsupported type %s pair and %v", typ, dt)
|
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)
|
return fmt.Sprintf("word/media/image%d.png", index)
|
||||||
case DocTypeSpreadsheet:
|
case DocTypeSpreadsheet:
|
||||||
return fmt.Sprintf("xl/media/image%d.png", index)
|
return fmt.Sprintf("xl/media/image%d.png", index)
|
||||||
|
case DocTypePresentation:
|
||||||
|
return fmt.Sprintf("ppt/media/image%d.png", index)
|
||||||
default:
|
default:
|
||||||
Log("unsupported type %s pair and %v", typ, dt)
|
Log("unsupported type %s pair and %v", typ, dt)
|
||||||
}
|
}
|
||||||
@ -172,6 +180,14 @@ func AbsoluteFilename(dt DocType, typ string, index int) string {
|
|||||||
case FooterType:
|
case FooterType:
|
||||||
return fmt.Sprintf("word/footer%d.xml", index)
|
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:
|
default:
|
||||||
Log("unsupported type %s", typ)
|
Log("unsupported type %s", typ)
|
||||||
}
|
}
|
||||||
|
@ -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
6
log.go
@ -7,12 +7,14 @@
|
|||||||
|
|
||||||
package gooxml
|
package gooxml
|
||||||
|
|
||||||
import "log"
|
import (
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
// Log is used to log content from within the library. The intent is to use
|
// 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
|
// logging sparingly, preferring to return an error. At the very least this
|
||||||
// allows redirecting logs to somewhere more appropriate than stdout.
|
// 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
|
// DisableLogging sets the Log function to a no-op so that any log messages are
|
||||||
// silently discarded.
|
// silently discarded.
|
||||||
|
36
presentation/open.go
Normal file
36
presentation/open.go
Normal 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
104
presentation/placeholder.go
Normal 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
|
||||||
|
}
|
@ -9,15 +9,23 @@ package presentation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/zip"
|
"archive/zip"
|
||||||
|
"bytes"
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"image"
|
||||||
|
"image/jpeg"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
"baliance.com/gooxml"
|
"baliance.com/gooxml"
|
||||||
"baliance.com/gooxml/common"
|
"baliance.com/gooxml/common"
|
||||||
"baliance.com/gooxml/measurement"
|
"baliance.com/gooxml/measurement"
|
||||||
"baliance.com/gooxml/schema/soo/dml"
|
"baliance.com/gooxml/schema/soo/dml"
|
||||||
"baliance.com/gooxml/schema/soo/ofc/sharedTypes"
|
"baliance.com/gooxml/schema/soo/ofc/sharedTypes"
|
||||||
|
"baliance.com/gooxml/schema/soo/pkg/relationships"
|
||||||
"baliance.com/gooxml/schema/soo/pml"
|
"baliance.com/gooxml/schema/soo/pml"
|
||||||
"baliance.com/gooxml/zippkg"
|
"baliance.com/gooxml/zippkg"
|
||||||
)
|
)
|
||||||
@ -34,6 +42,7 @@ type Presentation struct {
|
|||||||
layouts []*pml.SldLayout
|
layouts []*pml.SldLayout
|
||||||
layoutRels []common.Relationships
|
layoutRels []common.Relationships
|
||||||
themes []*dml.Theme
|
themes []*dml.Theme
|
||||||
|
themeRels []common.Relationships
|
||||||
}
|
}
|
||||||
|
|
||||||
// New initializes and reurns a new presentation
|
// New initializes and reurns a new presentation
|
||||||
@ -73,8 +82,11 @@ func New() *Presentation {
|
|||||||
|
|
||||||
p.masters = append(p.masters, m)
|
p.masters = append(p.masters, m)
|
||||||
|
|
||||||
p.ContentTypes.AddOverride("/ppt/slideMasters/slideMaster1.xml", gooxml.SlideMasterContentType)
|
smFn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideMasterType, 1)
|
||||||
mrelID := p.prels.AddRelationship("slideMasters/slideMaster1.xml", gooxml.SlideMasterRelationshipType)
|
p.ContentTypes.AddOverride(smFn, gooxml.SlideMasterContentType)
|
||||||
|
|
||||||
|
mrelID := p.prels.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.OfficeDocumentType,
|
||||||
|
1, gooxml.SlideMasterType)
|
||||||
smid := pml.NewCT_SlideMasterIdListEntry()
|
smid := pml.NewCT_SlideMasterIdListEntry()
|
||||||
smid.IdAttr = gooxml.Uint32(2147483648)
|
smid.IdAttr = gooxml.Uint32(2147483648)
|
||||||
smid.RIdAttr = mrelID.ID()
|
smid.RIdAttr = mrelID.ID()
|
||||||
@ -83,9 +95,10 @@ func New() *Presentation {
|
|||||||
p.masterRels = append(p.masterRels, mrel)
|
p.masterRels = append(p.masterRels, mrel)
|
||||||
|
|
||||||
ls := pml.NewSldLayout()
|
ls := pml.NewSldLayout()
|
||||||
lrid := mrel.AddRelationship("../slideLayouts/slideLayout1.xml", gooxml.SlideLayoutType)
|
lrid := mrel.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.SlideMasterType, 1, gooxml.SlideLayoutType)
|
||||||
p.ContentTypes.AddOverride("/ppt/slideLayouts/slideLayout1.xml", gooxml.SlideLayoutContentType)
|
slfn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideLayoutType, 1)
|
||||||
mrel.AddRelationship("../theme/theme1.xml", gooxml.ThemeType)
|
p.ContentTypes.AddOverride(slfn, gooxml.SlideLayoutContentType)
|
||||||
|
mrel.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.SlideMasterType, 1, gooxml.ThemeType)
|
||||||
p.layouts = append(p.layouts, ls)
|
p.layouts = append(p.layouts, ls)
|
||||||
|
|
||||||
m.SldLayoutIdLst = pml.NewCT_SlideLayoutIdList()
|
m.SldLayoutIdLst = pml.NewCT_SlideLayoutIdList()
|
||||||
@ -96,7 +109,7 @@ func New() *Presentation {
|
|||||||
|
|
||||||
lrel := common.NewRelationships()
|
lrel := common.NewRelationships()
|
||||||
p.layoutRels = append(p.layoutRels, lrel)
|
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.CxAttr = 6858000
|
||||||
p.x.NotesSz.CyAttr = 9144000
|
p.x.NotesSz.CyAttr = 9144000
|
||||||
|
|
||||||
@ -204,8 +217,13 @@ func New() *Presentation {
|
|||||||
fp)
|
fp)
|
||||||
|
|
||||||
p.themes = append(p.themes, thm)
|
p.themes = append(p.themes, thm)
|
||||||
p.ContentTypes.AddOverride("/ppt/theme/theme1.xml", gooxml.ThemeContentType)
|
themeFn := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.ThemeType, 1)
|
||||||
p.prels.AddRelationship("theme/theme1.xml", gooxml.ThemeType)
|
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
|
return p
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -214,6 +232,7 @@ func (p *Presentation) X() *pml.Presentation {
|
|||||||
return p.x
|
return p.x
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddSlide adds a new slide to the presentation.
|
||||||
func (p *Presentation) AddSlide() Slide {
|
func (p *Presentation) AddSlide() Slide {
|
||||||
sd := pml.NewCT_SlideIdListEntry()
|
sd := pml.NewCT_SlideIdListEntry()
|
||||||
sd.IdAttr = 256
|
sd.IdAttr = 256
|
||||||
@ -231,6 +250,7 @@ func (p *Presentation) AddSlide() Slide {
|
|||||||
slide.CSld.SpTree.GrpSpPr.Xfrm.ChOff = slide.CSld.SpTree.GrpSpPr.Xfrm.Off
|
slide.CSld.SpTree.GrpSpPr.Xfrm.ChOff = slide.CSld.SpTree.GrpSpPr.Xfrm.Off
|
||||||
slide.CSld.SpTree.GrpSpPr.Xfrm.ChExt = slide.CSld.SpTree.GrpSpPr.Xfrm.Ext
|
slide.CSld.SpTree.GrpSpPr.Xfrm.ChExt = slide.CSld.SpTree.GrpSpPr.Xfrm.Ext
|
||||||
|
|
||||||
|
/*
|
||||||
c := pml.NewCT_GroupShapeChoice()
|
c := pml.NewCT_GroupShapeChoice()
|
||||||
slide.CSld.SpTree.Choice = append(slide.CSld.SpTree.Choice, c)
|
slide.CSld.SpTree.Choice = append(slide.CSld.SpTree.Choice, c)
|
||||||
sp := pml.NewCT_Shape()
|
sp := pml.NewCT_Shape()
|
||||||
@ -247,66 +267,200 @@ func (p *Presentation) AddSlide() Slide {
|
|||||||
para.EG_TextRun = append(para.EG_TextRun, run)
|
para.EG_TextRun = append(para.EG_TextRun, run)
|
||||||
run.R = dml.NewCT_RegularTextRun()
|
run.R = dml.NewCT_RegularTextRun()
|
||||||
run.R.T = "testing 123"
|
run.R.T = "testing 123"
|
||||||
|
*/
|
||||||
p.slides = append(p.slides, slide)
|
p.slides = append(p.slides, slide)
|
||||||
fn := fmt.Sprintf("slides/slide%d.xml", len(p.slides))
|
srelID := p.prels.AddAutoRelationship(gooxml.DocTypePresentation, gooxml.OfficeDocumentType,
|
||||||
srelID := p.prels.AddRelationship(fn, gooxml.SlideType)
|
len(p.slides), gooxml.SlideType)
|
||||||
sd.RIdAttr = srelID.ID()
|
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()
|
srel := common.NewRelationships()
|
||||||
p.slideRels = append(p.slideRels, srel)
|
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}
|
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
|
// Save writes the presentation out to a writer in the Zip package format
|
||||||
func (p *Presentation) Save(w io.Writer) error {
|
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)
|
z := zip.NewWriter(w)
|
||||||
defer z.Close()
|
defer z.Close()
|
||||||
if err := zippkg.MarshalXML(z, gooxml.ContentTypesFilename, p.ContentTypes.X()); err != nil {
|
if err := zippkg.MarshalXML(z, gooxml.BaseRelsFilename, p.Rels.X()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := zippkg.MarshalXML(z, "_rels/.rels", p.Rels.X()); err != nil {
|
if err := zippkg.MarshalXMLByType(z, dt, gooxml.ExtendedPropertiesType, p.AppProperties.X()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := zippkg.MarshalXML(z, "docProps/app.xml", p.AppProperties.X()); err != nil {
|
if err := zippkg.MarshalXMLByType(z, dt, gooxml.CorePropertiesType, p.CoreProperties.X()); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := zippkg.MarshalXML(z, "docProps/core.xml", p.CoreProperties.X()); err != nil {
|
if p.Thumbnail != nil {
|
||||||
|
tn, err := z.Create("docProps/thumbnail.jpeg")
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := zippkg.MarshalXML(z, "ppt/presentation.xml", p.x); err != nil {
|
if err := jpeg.Encode(tn, p.Thumbnail, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := zippkg.MarshalXML(z, "ppt/_rels/presentation.xml.rels", p.prels.X()); err != nil {
|
}
|
||||||
|
|
||||||
|
documentFn := gooxml.AbsoluteFilename(dt, gooxml.OfficeDocumentType, 0)
|
||||||
|
if err := zippkg.MarshalXML(z, documentFn, p.x); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := zippkg.MarshalXML(z, zippkg.RelationsPathFor(documentFn), p.prels.X()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
for i, slide := range p.slides {
|
for i, slide := range p.slides {
|
||||||
spath := fmt.Sprintf("ppt/slides/slide%d.xml", i+1)
|
spath := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideType, i+1)
|
||||||
zippkg.MarshalXML(z, spath, slide)
|
zippkg.MarshalXML(z, spath, slide)
|
||||||
|
if !p.slideRels[i].IsEmpty() {
|
||||||
rpath := zippkg.RelationsPathFor(spath)
|
rpath := zippkg.RelationsPathFor(spath)
|
||||||
zippkg.MarshalXML(z, rpath, p.slideRels[i].X())
|
zippkg.MarshalXML(z, rpath, p.slideRels[i].X())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for i, m := range p.masters {
|
for i, m := range p.masters {
|
||||||
mpath := fmt.Sprintf("ppt/slideMasters/slideMaster%d.xml", i+1)
|
mpath := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideMasterType, i+1)
|
||||||
zippkg.MarshalXML(z, mpath, m)
|
zippkg.MarshalXML(z, mpath, m)
|
||||||
|
if !p.masterRels[i].IsEmpty() {
|
||||||
rpath := zippkg.RelationsPathFor(mpath)
|
rpath := zippkg.RelationsPathFor(mpath)
|
||||||
zippkg.MarshalXML(z, rpath, p.masterRels[i].X())
|
zippkg.MarshalXML(z, rpath, p.masterRels[i].X())
|
||||||
}
|
}
|
||||||
|
}
|
||||||
for i, l := range p.layouts {
|
for i, l := range p.layouts {
|
||||||
mpath := fmt.Sprintf("ppt/slideLayouts/slideLayout%d.xml", i+1)
|
mpath := gooxml.AbsoluteFilename(gooxml.DocTypePresentation, gooxml.SlideLayoutType, i+1)
|
||||||
zippkg.MarshalXML(z, mpath, l)
|
zippkg.MarshalXML(z, mpath, l)
|
||||||
|
if !p.layoutRels[i].IsEmpty() {
|
||||||
rpath := zippkg.RelationsPathFor(mpath)
|
rpath := zippkg.RelationsPathFor(mpath)
|
||||||
zippkg.MarshalXML(z, rpath, p.layoutRels[i].X())
|
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)
|
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 := p.WriteExtraFiles(z); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,3 +495,192 @@ func (p *Presentation) Validate() error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
56
presentation/read.go
Normal 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
|
||||||
|
}
|
@ -7,9 +7,64 @@
|
|||||||
|
|
||||||
package presentation
|
package presentation
|
||||||
|
|
||||||
import "baliance.com/gooxml/schema/soo/pml"
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"baliance.com/gooxml/schema/soo/pml"
|
||||||
|
)
|
||||||
|
|
||||||
type Slide struct {
|
type Slide struct {
|
||||||
sid *pml.CT_SlideIdListEntry
|
sid *pml.CT_SlideIdListEntry
|
||||||
x *pml.Sld
|
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")
|
||||||
|
}
|
||||||
|
35
presentation/slidelayout.go
Normal file
35
presentation/slidelayout.go
Normal 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 ""
|
||||||
|
}
|
48
presentation/slidemaster.go
Normal file
48
presentation/slidemaster.go
Normal 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
|
||||||
|
}
|
@ -35,6 +35,8 @@ const (
|
|||||||
SMLStyleSheetContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
|
SMLStyleSheetContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"
|
||||||
TableType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
|
TableType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/table"
|
||||||
TableContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml"
|
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
|
// WML
|
||||||
HeaderType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"
|
HeaderType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/header"
|
||||||
@ -48,10 +50,11 @@ const (
|
|||||||
// PML
|
// PML
|
||||||
SlideType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
|
SlideType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slide"
|
||||||
SlideContentType = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"
|
SlideContentType = "application/vnd.openxmlformats-officedocument.presentationml.slide+xml"
|
||||||
SlideMasterRelationshipType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"
|
SlideMasterType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideMaster"
|
||||||
SlideMasterContentType = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"
|
SlideMasterContentType = "application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"
|
||||||
SlideLayoutType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"
|
SlideLayoutType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout"
|
||||||
SlideLayoutContentType = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"
|
SlideLayoutContentType = "application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"
|
||||||
|
PresentationPropertiesType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/presProps"
|
||||||
|
|
||||||
// VML
|
// VML
|
||||||
VMLDrawingType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
|
VMLDrawingType = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing"
|
||||||
|
@ -35,6 +35,8 @@ type DecodeMap struct {
|
|||||||
basePaths map[*relationships.Relationships]string
|
basePaths map[*relationships.Relationships]string
|
||||||
rels []Target
|
rels []Target
|
||||||
decodeFunc OnNewRelationshipFunc
|
decodeFunc OnNewRelationshipFunc
|
||||||
|
decoded map[string]struct{}
|
||||||
|
indices map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOnNewRelationshipFunc sets the function to be called when a new
|
// 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
|
// 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
|
// 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.
|
// 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 {
|
if d.pathsToIfcs == nil {
|
||||||
d.pathsToIfcs = make(map[string]Target)
|
d.pathsToIfcs = make(map[string]Target)
|
||||||
d.basePaths = make(map[*relationships.Relationships]string)
|
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
|
// Decode loops decoding targets registered with AddTarget and calling th
|
||||||
|
Loading…
x
Reference in New Issue
Block a user