Skip to content

Commit f472a81

Browse files
authored
Merge pull request #15 from qmuntal/fix-rel-ids
Generate rel IDs using rIdN pattern
2 parents b7acdd6 + 5b38ba0 commit f472a81

File tree

3 files changed

+41
-38
lines changed

3 files changed

+41
-38
lines changed

relationship.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import (
44
"encoding/xml"
55
"fmt"
66
"io"
7-
"math/rand"
87
"net/url"
8+
"sort"
99
"strings"
1010
)
1111

@@ -25,8 +25,8 @@ const externalMode = "External"
2525
const charBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789"
2626

2727
// Relationship is used to express a relationship between a source and a target part.
28-
// If the ID is not specified a random string with 8 characters will be generated.
29-
// If The TargetMode is not specified the default value is Internal.
28+
// If the ID is not specified a unique ID will be generated following the pattern rIdN.
29+
// If the TargetMode is not specified the default value is Internal.
3030
// Defined in ISO/IEC 29500-2 §9.3.
3131
type Relationship struct {
3232
ID string // The relationship identifier which shall conform the xsd:ID naming restrictions and unique within the part.
@@ -48,16 +48,22 @@ type relationshipXML struct {
4848
Mode string `xml:"TargetMode,attr,omitempty"`
4949
}
5050

51-
func (r *Relationship) ensureID(rnd *rand.Rand) {
52-
if r.ID != "" {
53-
return
54-
}
55-
56-
b := make([]byte, 8)
57-
for i := range b {
58-
b[i] = charBytes[rnd.Intn(len(charBytes))]
59-
}
60-
r.ID = string(b)
51+
func newRelationshipID(rels []*Relationship) string {
52+
ids := make([]string, len(rels))
53+
for i, rel := range rels {
54+
ids[i] = rel.ID
55+
}
56+
sort.Strings(ids)
57+
idFunc := func(i int) string { return fmt.Sprintf("rId%d", i) }
58+
var (
59+
i int
60+
id = idFunc(0)
61+
)
62+
for sort.SearchStrings(ids, id) < 0 {
63+
i++
64+
id = idFunc(i)
65+
}
66+
return id
6167
}
6268

6369
func (r *Relationship) validate(sourceURI string) error {

writer.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"compress/flate"
66
"fmt"
77
"io"
8-
"math/rand"
98
"path/filepath"
109
"strings"
1110
"time"
@@ -34,7 +33,6 @@ type Writer struct {
3433
p *pkg
3534
w *zip.Writer
3635
last *Part
37-
rnd *rand.Rand
3836
}
3937

4038
// NewWriter returns a new Writer writing an OPC file to w.
@@ -48,7 +46,7 @@ func NewWriter(w io.Writer) *Writer {
4846
},
4947
overrides: map[string]string{},
5048
},
51-
}, w: zip.NewWriter(w), rnd: rand.New(rand.NewSource(42))}
49+
}, w: zip.NewWriter(w)}
5250
}
5351

5452
// Flush flushes any buffered data to the underlying writer.
@@ -148,7 +146,9 @@ func (w *Writer) createOwnRelationships() error {
148146
return nil
149147
}
150148
for _, r := range w.Relationships {
151-
r.ensureID(w.rnd)
149+
if r.ID == "" {
150+
r.ID = newRelationshipID(w.Relationships)
151+
}
152152
}
153153
if err := validateRelationships("/", w.Relationships); err != nil {
154154
return err
@@ -165,7 +165,9 @@ func (w *Writer) createLastPartRelationships() error {
165165
return nil
166166
}
167167
for _, r := range w.last.Relationships {
168-
r.ensureID(w.rnd)
168+
if r.ID == "" {
169+
r.ID = newRelationshipID(w.Relationships)
170+
}
169171
}
170172
if err := validateRelationships(w.last.Name, w.last.Relationships); err != nil {
171173
return err

writer_test.go

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package opc
33
import (
44
"archive/zip"
55
"bytes"
6-
"math/rand"
76
"testing"
87
)
98

@@ -24,10 +23,6 @@ func TestWriter_Flush(t *testing.T) {
2423
}
2524
}
2625

27-
func fakeRand() *rand.Rand {
28-
return rand.New(rand.NewSource(42))
29-
}
30-
3126
func TestWriter_Close(t *testing.T) {
3227
p := newPackage()
3328
p.contentTypes.add("/a.xml", "a/b")
@@ -44,17 +39,17 @@ func TestWriter_Close(t *testing.T) {
4439
wantErr bool
4540
}{
4641
{"base", NewWriter(&bytes.Buffer{}), false},
47-
{"invalidContentType", &Writer{p: pC, w: zip.NewWriter(&bytes.Buffer{}), rnd: fakeRand()}, true},
48-
{"withCt", &Writer{p: p, w: zip.NewWriter(&bytes.Buffer{}), rnd: fakeRand()}, false},
49-
{"invalidPartRel", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), last: &Part{Name: "/b.xml", Relationships: []*Relationship{{}}}, rnd: fakeRand()}, true},
50-
{"invalidOwnRel", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Relationships: []*Relationship{{}}, rnd: fakeRand()}, true},
51-
{"withDuplicatedCoreProps", &Writer{p: pCore, w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}, rnd: fakeRand()}, true},
52-
{"withDuplicatedRels", &Writer{p: pRel, w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}, rnd: fakeRand()}, true},
53-
{"withCoreProps", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}, rnd: fakeRand()}, false},
42+
{"invalidContentType", &Writer{p: pC, w: zip.NewWriter(&bytes.Buffer{})}, true},
43+
{"withCt", &Writer{p: p, w: zip.NewWriter(&bytes.Buffer{})}, false},
44+
{"invalidPartRel", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), last: &Part{Name: "/b.xml", Relationships: []*Relationship{{}}}}, true},
45+
{"invalidOwnRel", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Relationships: []*Relationship{{}}}, true},
46+
{"withDuplicatedCoreProps", &Writer{p: pCore, w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}}, true},
47+
{"withDuplicatedRels", &Writer{p: pRel, w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}}, true},
48+
{"withCoreProps", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song"}}, false},
5449
{"withCorePropsWithName", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Relationships: []*Relationship{
5550
{TargetURI: "props.xml", Type: corePropsRel},
56-
}, Properties: CoreProperties{Title: "Song", PartName: "props.xml"}, rnd: fakeRand()}, false},
57-
{"withCorePropsWithNameAndId", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song", PartName: "/docProps/props.xml", RelationshipID: "rId1"}, rnd: fakeRand()}, false},
51+
}, Properties: CoreProperties{Title: "Song", PartName: "props.xml"}}, false},
52+
{"withCorePropsWithNameAndId", &Writer{p: newPackage(), w: zip.NewWriter(&bytes.Buffer{}), Properties: CoreProperties{Title: "Song", PartName: "/docProps/props.xml", RelationshipID: "rId1"}}, false},
5853
}
5954
for _, tt := range tests {
6055
t.Run(tt.name, func(t *testing.T) {
@@ -154,8 +149,8 @@ func TestWriter_CreatePart(t *testing.T) {
154149
}{
155150
{"fhErr", NewWriter(&bytes.Buffer{}), args{&Part{"/a.xml", "a/b", nil}, -3}, true},
156151
{"nameErr", NewWriter(&bytes.Buffer{}), args{&Part{"a.xml", "a/b", nil}, CompressionNone}, true},
157-
{"failRel", &Writer{w: zip.NewWriter(nil), last: &Part{Name: "/b.xml", Relationships: []*Relationship{{}}}, rnd: fakeRand()}, args{&Part{"/a.xml", "a/b", nil}, CompressionNone}, true},
158-
{"failRel2", &Writer{p: pRel, w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{rel}}, rnd: fakeRand()}, args{&Part{"/b.xml", "a/b", nil}, CompressionNone}, true},
152+
{"failRel", &Writer{w: zip.NewWriter(nil), last: &Part{Name: "/b.xml", Relationships: []*Relationship{{}}}}, args{&Part{"/a.xml", "a/b", nil}, CompressionNone}, true},
153+
{"failRel2", &Writer{p: pRel, w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{rel}}}, args{&Part{"/b.xml", "a/b", nil}, CompressionNone}, true},
159154
{"base", w, args{&Part{"/a.xml", "a/b", nil}, CompressionNone}, false},
160155
{"multipleDiffName", w, args{&Part{"/b.xml", "a/b", nil}, CompressionNone}, false},
161156
{"multipleDiffContentType", w, args{&Part{"/c.xml", "c/d", nil}, CompressionNone}, false},
@@ -184,11 +179,11 @@ func TestWriter_createLastPartRelationships(t *testing.T) {
184179
w *Writer
185180
wantErr bool
186181
}{
187-
{"base", &Writer{p: newPackage(), w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{rel}}, rnd: fakeRand()}, false},
188-
{"base2", &Writer{p: newPackage(), w: zip.NewWriter(nil), last: &Part{Name: "/b/a.xml", Relationships: []*Relationship{rel}}, rnd: fakeRand()}, false},
182+
{"base", &Writer{p: newPackage(), w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{rel}}}, false},
183+
{"base2", &Writer{p: newPackage(), w: zip.NewWriter(nil), last: &Part{Name: "/b/a.xml", Relationships: []*Relationship{rel}}}, false},
189184
{"hasSome", w, false},
190-
{"duplicated", &Writer{w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{rel, rel}}, rnd: fakeRand()}, true},
191-
{"invalidRelation", &Writer{w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{{}}}, rnd: fakeRand()}, true},
185+
{"duplicated", &Writer{w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{rel, rel}}}, true},
186+
{"invalidRelation", &Writer{w: zip.NewWriter(nil), last: &Part{Name: "/a.xml", Relationships: []*Relationship{{}}}}, true},
192187
{"empty", NewWriter(&bytes.Buffer{}), false},
193188
}
194189
for _, tt := range tests {

0 commit comments

Comments
 (0)