Skip to content

Commit

Permalink
Merge 30f5a2c into 0630fbd
Browse files Browse the repository at this point in the history
  • Loading branch information
carlotacb committed Jan 9, 2019
2 parents 0630fbd + 30f5a2c commit a427d5c
Show file tree
Hide file tree
Showing 5 changed files with 124 additions and 51 deletions.
14 changes: 0 additions & 14 deletions part.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,20 +96,6 @@ func NormalizePartName(name string) string {
return p.EscapedPath()
}

func validateRelationships(rs []*Relationship) error {
ids := make(map[string]struct{}, 0)
for _, r := range rs {
if err := r.validate(); err != nil {
return err
}
// ISO/IEC 29500-2 M1.26
if _, ok := ids[r.ID]; ok {
return errors.New("OPC: reltionship ID shall be unique within the Relationships part")
}
}
return nil
}

func validateContentType(contentType string) error {
if len(contentType) == 0 {
return nil
Expand Down
35 changes: 30 additions & 5 deletions relationship.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"encoding/xml"
"errors"
"fmt"
"io"
"net/url"
"strings"
)
Expand Down Expand Up @@ -83,11 +84,6 @@ func (r *Relationship) toXML() *relationshipXML {
return x
}

// writeToXML encodes the relationship to the target.
func (r *Relationship) writeToXML(e *xml.Encoder) error {
return e.EncodeElement(r.toXML(), xml.StartElement{Name: xml.Name{Space: "", Local: relationshipName}})
}

// isRelationshipURI returns true if the uri points to a relationship part.
func isRelationshipURI(uri string) bool {
up := strings.ToUpper(uri)
Expand Down Expand Up @@ -138,8 +134,37 @@ func validateRelationshipTarget(sourceURI, targetURI string, targetMode TargetMo
return result
}

func validateRelationships(rs []*Relationship) error {
ids := make(map[string]struct{}, 0)
for _, r := range rs {
if err := r.validate(); err != nil {
return err
}
// ISO/IEC 29500-2 M1.26
if _, ok := ids[r.ID]; ok {
return errors.New("OPC: reltionship ID shall be unique within the Relationships part")
}
}
return nil
}

func uniqueRelationshipID() string {
b := make([]byte, 8)
rand.Read(b)
return fmt.Sprintf("%x", b)
}

type relationshipsXML struct {
XMLName xml.Name `xml:"Relationships"`
XML string `xml:"xmlns,attr"`
RelsXML []*relationshipXML `xml:"Relationship"`
}

func encodeRelationships(w io.Writer, rs []*Relationship) error {
w.Write(([]byte)(`<?xml version="1.0" encoding="UTF-8"?>`))
re := &relationshipsXML{XML: "http://schemas.openxmlformats.org/package/2006/relationships"}
for _, r := range rs {
re.RelsXML = append(re.RelsXML, r.toXML())
}
return xml.NewEncoder(w).Encode(re)
}
65 changes: 35 additions & 30 deletions relationship_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,9 @@ package gopc

import (
"bytes"
"encoding/xml"
"reflect"
"testing"
)

func TestRelationship_writeToXML(t *testing.T) {
tests := []struct {
name string
r *Relationship
want string
wantErr bool
}{
{"xmlWriter", &Relationship{ID: "fakeId", RelType: "fakeType", sourceURI: "", TargetURI: "fakeTarget", TargetMode: ModeInternal}, `<Relationship Id="fakeId" Type="fakeType" Target="/fakeTarget"></Relationship>`, false},
{"emptyURI", &Relationship{ID: "fakeId", RelType: "fakeType", sourceURI: "", TargetURI: "", TargetMode: ModeInternal}, `<Relationship Id="fakeId" Type="fakeType" Target="/"></Relationship>`, false},
{"externalMode", &Relationship{ID: "fakeId", RelType: "fakeType", sourceURI: "", TargetURI: "fakeTarget", TargetMode: ModeExternal}, `<Relationship Id="fakeId" Type="fakeType" Target="fakeTarget" TargetMode="External"></Relationship>`, false},
{"base", &Relationship{ID: "fakeId", RelType: "fakeType", sourceURI: "", TargetURI: "/fakeTarget", TargetMode: ModeInternal}, `<Relationship Id="fakeId" Type="fakeType" Target="/fakeTarget"></Relationship>`, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buff := bytes.NewBufferString("")
encoder := xml.NewEncoder(buff)
if err := tt.r.writeToXML(encoder); (err != nil) != tt.wantErr {
t.Errorf("Relationship.writeToXML() error = %v, wantErr %v", err, tt.wantErr)
return
}
got := buff.String()
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("Relationship.writeToXML() = %v, want %v", got, tt.want)
}
})
}
}

func Test_isRelationshipURI(t *testing.T) {
type args struct {
uri string
Expand Down Expand Up @@ -88,3 +58,38 @@ func TestRelationship_validate(t *testing.T) {
})
}
}

func Test_encodeRelationships(t *testing.T) {
type args struct {
rs []*Relationship
}
tests := []struct {
name string
args args
wantW string
wantErr bool
}{
{"base", args{[]*Relationship{&Relationship{ID: "fakeId", RelType: "asd", sourceURI: "", TargetURI: "fakeTarget", TargetMode: ModeInternal}}}, expectedsolution(), false},
{"base2", args{[]*Relationship{&Relationship{ID: "fakeId", RelType: "asd", sourceURI: "", TargetURI: "fakeTarget", TargetMode: ModeExternal}}}, expectedsolution2(), false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := &bytes.Buffer{}
if err := encodeRelationships(w, tt.args.rs); (err != nil) != tt.wantErr {
t.Errorf("encodeRelationships() error = %v, wantErr %v", err, tt.wantErr)
return
}
if gotW := w.String(); gotW != tt.wantW {
t.Errorf("encodeRelationships() = %v, want %v", gotW, tt.wantW)
}
})
}
}

func expectedsolution() string {
return `<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="fakeId" Type="asd" Target="/fakeTarget"></Relationship></Relationships>`
}

func expectedsolution2() string {
return `<?xml version="1.0" encoding="UTF-8"?><Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="fakeId" Type="asd" Target="fakeTarget" TargetMode="External"></Relationship></Relationships>`
}
36 changes: 34 additions & 2 deletions writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ package gopc
import (
"archive/zip"
"compress/flate"
"errors"
"fmt"
"io"
"path/filepath"
"time"
)

// Writer implements a OPC file writer.
type Writer struct {
p *Package
w *zip.Writer
p *Package
w *zip.Writer
last *Part
testRelationshipFail bool // Only true for testing
}

// NewWriter returns a new Writer writing an OPC file to w.
Expand All @@ -29,6 +34,10 @@ func (w *Writer) Flush() error {
// Close finishes writing the opc file.
// It does not close the underlying writer.
func (w *Writer) Close() error {
if err := w.createRelationships(); err != nil {
w.w.Close()
return err
}
return w.w.Close()
}

Expand All @@ -55,7 +64,29 @@ func (w *Writer) CreatePart(part *Part, compression CompressionOption) (io.Write
return w.add(part, compression)
}

func (w *Writer) createRelationships() error {
if w.last == nil || len(w.last.Relationships) == 0 {
return nil
}
if err := validateRelationships(w.last.Relationships); err != nil {
return err
}
filepath.Dir(w.last.Name)
relWriter, err := w.w.Create(fmt.Sprintf("%s/_rels/%s.rels", filepath.Dir(w.last.Name)[1:], filepath.Base(w.last.Name)))
if w.testRelationshipFail {
err = errors.New("")
}
if err != nil {
return err
}
return encodeRelationships(relWriter, w.last.Relationships)
}

func (w *Writer) add(part *Part, compression CompressionOption) (io.Writer, error) {
if err := w.createRelationships(); err != nil {
return nil, err
}

// Validate name and check for duplicated names ISO/IEC 29500-2 M3.3
if err := w.p.add(part); err != nil {
return nil, err
Expand All @@ -72,6 +103,7 @@ func (w *Writer) add(part *Part, compression CompressionOption) (io.Writer, erro
w.p.deletePart(part.Name)
return nil, err
}
w.last = part
return pw, nil
}

Expand Down
25 changes: 25 additions & 0 deletions writer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func TestWriter_Close(t *testing.T) {
wantErr bool
}{
{"base", NewWriter(&bytes.Buffer{}), false},
{"fail", &Writer{w: zip.NewWriter(&bytes.Buffer{}), last: &Part{Name: "/b.xml", Relationships: []*Relationship{&Relationship{}}}, testRelationshipFail: true}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand Down Expand Up @@ -148,6 +149,7 @@ func TestWriter_CreatePart(t *testing.T) {
{"fhErr", NewWriter(&bytes.Buffer{}), args{&Part{"/a.xml", "a/b", nil}, -3}, true},
{"nameErr", NewWriter(&bytes.Buffer{}), args{&Part{"a.xml", "a/b", nil}, CompressionNone}, true},
{"base", NewWriter(&bytes.Buffer{}), args{&Part{"/a.xml", "a/b", nil}, CompressionNone}, false},
{"failRel", &Writer{w: zip.NewWriter(nil), last: &Part{Name: "/b.xml", Relationships: []*Relationship{&Relationship{}}}, testRelationshipFail: true}, args{&Part{"/a.xml", "a/b", nil}, CompressionNone}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
Expand All @@ -162,3 +164,26 @@ func TestWriter_CreatePart(t *testing.T) {
})
}
}

func TestWriter_createRelationships(t *testing.T) {
w := NewWriter(&bytes.Buffer{})
w.testRelationshipFail = true
w.last = &Part{Name: "/a.xml", Relationships: []*Relationship{&Relationship{}}}
tests := []struct {
name string
w *Writer
wantErr bool
}{
{"base", &Writer{w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{&Relationship{ID: "fakeId", RelType: "asd", sourceURI: "/", TargetURI: "/fakeTarget", TargetMode: ModeInternal}}}}, false},
{"invalidRelation", &Writer{w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{&Relationship{}}}}, true},
{"empty", NewWriter(&bytes.Buffer{}), false},
{"hasSome", w, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := tt.w.createRelationships(); (err != nil) != tt.wantErr {
t.Errorf("Writer.createRelationships() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

0 comments on commit a427d5c

Please sign in to comment.