/
subpptx.go
132 lines (108 loc) · 3.07 KB
/
subpptx.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package subpptx
import (
"io/ioutil"
"os"
"path/filepath"
"strings"
"github.com/beevik/etree"
"github.com/mholt/archiver"
)
// Finds and removes elements
func removeElements(elem *etree.Element, path string) {
for _, e := range elem.FindElements(path) {
e.Parent().RemoveChild(e)
}
}
// Injects a subtitle for one slide file.
func injectSubtitle(slidePath string, done chan bool) {
defer func() {
done <- true
}()
relsPath := filepath.Join(filepath.Dir(slidePath), "_rels", filepath.Base(slidePath)+".rels")
rels := etree.NewDocument()
rels.ReadFromFile(relsPath)
notesRel := rels.FindElement("//Relationship[@Type='http://schemas.openxmlformats.org/officeDocument/2006/relationships/notesSlide']")
if notesRel == nil {
// This slide does not have notes
return
}
notesPath := notesRel.SelectAttrValue("Target", "")
notesPath = filepath.Join(filepath.Dir(slidePath), notesPath)
notes := etree.NewDocument()
notes.ReadFromFile(notesPath)
// Find the subtitle body in the notes
e := notes.FindElement("//p:ph[@type='body']")
e = e.Parent().Parent().Parent()
txBody := e.FindElement("./p:txBody")
// Eliminate harmful tags
removeElements(txBody, ".//a:pPr")
removeElements(txBody, ".//a:rPr")
// Create a new footer
tmpDoc := etree.NewDocument()
tmpDoc.ReadFromString(`
<p:sp>
<p:nvSpPr>
<p:cNvPr id="999999" name="Subtitle" />
<p:cNvSpPr><a:spLocks noGrp="1" /></p:cNvSpPr>
<p:nvPr><p:ph type="ftr" sz="quarter" idx="11" /></p:nvPr>
</p:nvSpPr>
<p:spPr />
</p:sp>
`)
sp := tmpDoc.Root()
sp.AddChild(txBody)
// Inject the footer
slide := etree.NewDocument()
slide.ReadFromFile(slidePath)
spTree := slide.FindElement("//p:spTree")
spTree.AddChild(sp)
slide.WriteToFile(slidePath)
}
func InjectSubtitles(pptxPath string, outputPath string, monitor chan<- int) {
// Create temp dir
pptxBase := filepath.Base(pptxPath)
tempDir, err := ioutil.TempDir("", pptxBase)
if err != nil {
panic("failed to create temp dir")
}
// Unzip PPTX into temp dir
archiver.Zip.Open(pptxPath, tempDir)
// Collect files in ppt/slides
slidesDir := filepath.Join(tempDir, "ppt", "slides")
slidesFiles, err := ioutil.ReadDir(slidesDir)
if err != nil {
panic("PPTX directory structure corruptted")
}
// Inject subtitles over slide*.xml
count := 0
done := make(chan bool)
for _, file := range slidesFiles {
if strings.HasPrefix(file.Name(), "slide") && strings.HasSuffix(file.Name(), ".xml") {
slidePath := filepath.Join(slidesDir, file.Name())
go injectSubtitle(slidePath, done)
count += 1
}
}
// Let the caller know the number of slides
monitor <- count
// Join the goroutines
for i := 0; i < count; i++ {
<-done
monitor <- i
}
// Collect filenames to be archived
rootFiles, err := ioutil.ReadDir(tempDir)
if err != nil {
panic("failed to read temp dir")
}
filenames := make([]string, len(rootFiles))
for i, file := range rootFiles {
filenames[i] = filepath.Join(tempDir, file.Name())
}
// Save as a new PPTX
archiver.Zip.Make(outputPath, filenames)
monitor <- 0
// Remove the temp dir
os.RemoveAll(tempDir)
monitor <- 0
}