From 37c46b7435310bd51e9e194bd9dee755c5a7e724 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BAlio=20C=C3=A9sar=20Batista?= Date: Thu, 6 Apr 2023 15:16:32 -0300 Subject: [PATCH] add CLI test --- e2etests-cli/main_test.go | 27 +++++++++++++ lib/pptx/pptx.go | 17 ++------ lib/pptx/presentation.go | 24 ++++++------ lib/pptx/validate.go | 82 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 24 deletions(-) create mode 100644 lib/pptx/validate.go diff --git a/e2etests-cli/main_test.go b/e2etests-cli/main_test.go index dc75a4f22..0601d591e 100644 --- a/e2etests-cli/main_test.go +++ b/e2etests-cli/main_test.go @@ -9,6 +9,7 @@ import ( "time" "oss.terrastruct.com/d2/d2cli" + "oss.terrastruct.com/d2/lib/pptx" "oss.terrastruct.com/util-go/assert" "oss.terrastruct.com/util-go/diff" "oss.terrastruct.com/util-go/xmain" @@ -234,6 +235,32 @@ layers: { testdataIgnoreDiff(t, ".pdf", pdf) }, }, + { + name: "how_to_solve_problems_pptx", + run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) { + writeFile(t, dir, "in.d2", `how to solve a hard problem? +steps: { + 1: { + w: write down the problem + } + 2: { + w -> t + t: think really hard about it + } + 3: { + t -> w2 + w2: write down the solution + } +} +`) + err := runTestMain(t, ctx, dir, env, "in.d2", "how_to_solve_problems.pptx") + assert.Success(t, err) + + file := readFile(t, dir, "how_to_solve_problems.pptx") + err = pptx.Validate(file, 4) + assert.Success(t, err) + }, + }, { name: "stdin", run: func(t *testing.T, ctx context.Context, dir string, env *xos.Env) { diff --git a/lib/pptx/pptx.go b/lib/pptx/pptx.go index b37503113..9deea80b6 100644 --- a/lib/pptx/pptx.go +++ b/lib/pptx/pptx.go @@ -5,7 +5,6 @@ import ( "bytes" _ "embed" "fmt" - "io" "strings" "time" ) @@ -19,21 +18,12 @@ func copyPptxTemplateTo(w *zip.Writer) error { reader := bytes.NewReader(pptx_template) zipReader, err := zip.NewReader(reader, reader.Size()) if err != nil { - fmt.Printf("%v", err) + fmt.Printf("error creating zip reader: %v", err) } for _, f := range zipReader.File { - fw, err := w.Create(f.Name) - if err != nil { - return err - } - fr, err := f.Open() - if err != nil { - return err - } - _, err = io.Copy(fw, fr) - if err != nil { - return err + if err := w.Copy(f); err != nil { + return fmt.Errorf("error copying %s: %v", f.Name, err) } } return nil @@ -116,6 +106,7 @@ func getPresentationXml(slideFileNames []string) string { builder.WriteString("") for i, name := range slideFileNames { + // in the exported presentation, the first slide ID was 256, so keeping it here for compatibility builder.WriteString(fmt.Sprintf(``, 256+i, name)) } builder.WriteString("") diff --git a/lib/pptx/presentation.go b/lib/pptx/presentation.go index 1ec4286ab..ac16ad52a 100644 --- a/lib/pptx/presentation.go +++ b/lib/pptx/presentation.go @@ -88,10 +88,12 @@ func (p *Presentation) SaveTo(filePath string) error { return err } defer f.Close() - zipFile := zip.NewWriter(f) - defer zipFile.Close() + zipWriter := zip.NewWriter(f) + defer zipWriter.Close() - copyPptxTemplateTo(zipFile) + if err = copyPptxTemplateTo(zipWriter); err != nil { + return err + } var slideFileNames []string for i, slide := range p.Slides { @@ -99,7 +101,7 @@ func (p *Presentation) SaveTo(filePath string) error { slideFileName := fmt.Sprintf("slide%d", i+1) slideFileNames = append(slideFileNames, slideFileName) - imageWriter, err := zipFile.Create(fmt.Sprintf("ppt/media/%s.png", imageId)) + imageWriter, err := zipWriter.Create(fmt.Sprintf("ppt/media/%s.png", imageId)) if err != nil { return err } @@ -108,13 +110,13 @@ func (p *Presentation) SaveTo(filePath string) error { return err } - err = addFile(zipFile, fmt.Sprintf("ppt/slides/_rels/%s.xml.rels", slideFileName), getRelsSlideXml(imageId)) + err = addFile(zipWriter, fmt.Sprintf("ppt/slides/_rels/%s.xml.rels", slideFileName), getRelsSlideXml(imageId)) if err != nil { return err } err = addFile( - zipFile, + zipWriter, fmt.Sprintf("ppt/slides/%s.xml", slideFileName), getSlideXml(slide.BoardPath, imageId, slide.ImageTop, slide.ImageLeft, slide.ImageWidth, slide.ImageHeight), ) @@ -123,27 +125,27 @@ func (p *Presentation) SaveTo(filePath string) error { } } - err = addFile(zipFile, "[Content_Types].xml", getContentTypesXml(slideFileNames)) + err = addFile(zipWriter, "[Content_Types].xml", getContentTypesXml(slideFileNames)) if err != nil { return err } - err = addFile(zipFile, "ppt/_rels/presentation.xml.rels", getPresentationXmlRels(slideFileNames)) + err = addFile(zipWriter, "ppt/_rels/presentation.xml.rels", getPresentationXmlRels(slideFileNames)) if err != nil { return err } - err = addFile(zipFile, "ppt/presentation.xml", getPresentationXml(slideFileNames)) + err = addFile(zipWriter, "ppt/presentation.xml", getPresentationXml(slideFileNames)) if err != nil { return err } - err = addFile(zipFile, "docProps/core.xml", getCoreXml(p.Title, p.Subject, p.Description, p.Creator)) + err = addFile(zipWriter, "docProps/core.xml", getCoreXml(p.Title, p.Subject, p.Description, p.Creator)) if err != nil { return err } - err = addFile(zipFile, "docProps/app.xml", getAppXml(p.Slides, p.D2Version)) + err = addFile(zipWriter, "docProps/app.xml", getAppXml(p.Slides, p.D2Version)) if err != nil { return err } diff --git a/lib/pptx/validate.go b/lib/pptx/validate.go new file mode 100644 index 000000000..72a84859a --- /dev/null +++ b/lib/pptx/validate.go @@ -0,0 +1,82 @@ +package pptx + +import ( + "archive/zip" + "bytes" + "encoding/xml" + "fmt" + "io" + "strings" +) + +func Validate(pptxContent []byte, nSlides int) error { + reader := bytes.NewReader(pptxContent) + zipReader, err := zip.NewReader(reader, reader.Size()) + if err != nil { + fmt.Printf("error reading pptx content: %v", err) + } + + expectedCount := getExpectedPptxFileCount(nSlides) + if len(zipReader.File) != expectedCount { + return fmt.Errorf("expected %d files, got %d", expectedCount, len(zipReader.File)) + } + + for i := 0; i < nSlides; i++ { + if err := checkFile(zipReader, fmt.Sprintf("ppt/slides/slide%d.xml", i+1)); err != nil { + return err + } + if err := checkFile(zipReader, fmt.Sprintf("ppt/slides/_rels/slide%d.xml.rels", i+1)); err != nil { + return err + } + if err := checkFile(zipReader, fmt.Sprintf("ppt/media/slide%dImage.png", i+1)); err != nil { + return err + } + } + + for _, file := range zipReader.File { + if !strings.Contains(file.Name, ".xml") { + continue + } + // checks if the XML content is valid + f, err := file.Open() + if err != nil { + return fmt.Errorf("error opening %s: %v", file.Name, err) + } + decoder := xml.NewDecoder(f) + for { + if err := decoder.Decode(new(interface{})); err != nil { + if err == io.EOF { + break + } + return fmt.Errorf("error parsing xml content in %s: %v", file.Name, err) + } + } + defer f.Close() + } + + return nil +} + +func checkFile(reader *zip.Reader, fname string) error { + f, err := reader.Open(fname) + if err != nil { + return fmt.Errorf("error opening file %s: %v", fname, err) + } + defer f.Close() + if _, err = f.Stat(); err != nil { + return fmt.Errorf("error getting file info %s: %v", fname, err) + } + return nil +} + +func getExpectedPptxFileCount(nSlides int) int { + reader := bytes.NewReader(pptx_template) + zipReader, err := zip.NewReader(reader, reader.Size()) + if err != nil { + return -1 + } + baseFiles := len(zipReader.File) + presentationFiles := 5 // presentation, rels, app, core, content types + slideFiles := 3 * nSlides // slides, rels, images + return baseFiles + presentationFiles + slideFiles +}