mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00
presentation: initial work for presentations
This commit is contained in:
parent
629cfb008c
commit
f5a8df0deb
@ -7,7 +7,8 @@ import (
|
||||
|
||||
func main() {
|
||||
ppt := presentation.New()
|
||||
ppt.AddSlide()
|
||||
slide := ppt.AddSlide()
|
||||
_ = slide
|
||||
|
||||
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
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Thumbnail != nil {
|
||||
tn, err := z.Create("docProps/thumbnail.jpeg")
|
||||
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
|
||||
|
||||
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
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"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
18
filenames.go
18
filenames.go
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
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
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 (
|
||||
"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
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
|
||||
|
||||
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")
|
||||
}
|
||||
|
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
|
||||
}
|
15
schemas.go
15
schemas.go
@ -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"
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user