Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unescape Value-Type: TEXT in Golang Model #91

Merged
merged 9 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
/.idea/
/testdata/serialization/actual
36 changes: 18 additions & 18 deletions calendar.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ const (
)

func (ps ObjectStatus) KeyValue(s ...interface{}) (string, []string) {
return string(PropertyStatus), []string{ToText(string(ps))}
return string(PropertyStatus), []string{string(ps)}
}

type RelationshipType string
Expand Down Expand Up @@ -317,72 +317,72 @@ func (calendar *Calendar) SerializeTo(w io.Writer) error {
}

func (calendar *Calendar) SetMethod(method Method, props ...PropertyParameter) {
calendar.setProperty(PropertyMethod, ToText(string(method)), props...)
calendar.setProperty(PropertyMethod, string(method), props...)
}

func (calendar *Calendar) SetXPublishedTTL(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyXPublishedTTL, string(s), props...)
calendar.setProperty(PropertyXPublishedTTL, s, props...)
}

func (calendar *Calendar) SetVersion(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyVersion, ToText(s), props...)
calendar.setProperty(PropertyVersion, s, props...)
}

func (calendar *Calendar) SetProductId(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyProductId, ToText(s), props...)
calendar.setProperty(PropertyProductId, s, props...)
}

func (calendar *Calendar) SetName(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyName, string(s), props...)
calendar.setProperty(PropertyXWRCalName, string(s), props...)
calendar.setProperty(PropertyName, s, props...)
calendar.setProperty(PropertyXWRCalName, s, props...)
}

func (calendar *Calendar) SetColor(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyColor, string(s), props...)
calendar.setProperty(PropertyColor, s, props...)
}

func (calendar *Calendar) SetXWRCalName(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyXWRCalName, string(s), props...)
calendar.setProperty(PropertyXWRCalName, s, props...)
}

func (calendar *Calendar) SetXWRCalDesc(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyXWRCalDesc, string(s), props...)
calendar.setProperty(PropertyXWRCalDesc, s, props...)
}

func (calendar *Calendar) SetXWRTimezone(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyXWRTimezone, string(s), props...)
calendar.setProperty(PropertyXWRTimezone, s, props...)
}

func (calendar *Calendar) SetXWRCalID(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyXWRCalID, string(s), props...)
calendar.setProperty(PropertyXWRCalID, s, props...)
}

func (calendar *Calendar) SetDescription(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyDescription, ToText(s), props...)
calendar.setProperty(PropertyDescription, s, props...)
}

func (calendar *Calendar) SetLastModified(t time.Time, props ...PropertyParameter) {
calendar.setProperty(PropertyLastModified, t.UTC().Format(icalTimestampFormatUtc), props...)
}

func (calendar *Calendar) SetRefreshInterval(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyRefreshInterval, string(s), props...)
calendar.setProperty(PropertyRefreshInterval, s, props...)
}

func (calendar *Calendar) SetCalscale(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyCalscale, string(s), props...)
calendar.setProperty(PropertyCalscale, s, props...)
}

func (calendar *Calendar) SetUrl(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyUrl, string(s), props...)
calendar.setProperty(PropertyUrl, s, props...)
}

func (calendar *Calendar) SetTzid(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyTzid, string(s), props...)
calendar.setProperty(PropertyTzid, s, props...)
}

func (calendar *Calendar) SetTimezoneId(s string, props ...PropertyParameter) {
calendar.setProperty(PropertyTimezoneId, string(s), props...)
calendar.setProperty(PropertyTimezoneId, s, props...)
}

func (calendar *Calendar) setProperty(property Property, value string, props ...PropertyParameter) {
Expand Down
78 changes: 78 additions & 0 deletions calendar_serialization_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
//go:build go1.16
// +build go1.16

package ics

import (
"bytes"
"fmt"
"os"
"path/filepath"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/require"
)

func TestCalendar_ReSerialization(t *testing.T) {
testDir := "testdata/serialization"
expectedDir := filepath.Join(testDir, "expected")
actualDir := filepath.Join(testDir, "actual")

testFileNames := []string{
"input1.ics",
"input2.ics",
"input3.ics",
"input4.ics",
"input5.ics",
"input6.ics",
"input7.ics",
}

for _, filename := range testFileNames {
t.Run(fmt.Sprintf("compare serialized -> deserialized -> serialized: %s", filename), func(t *testing.T) {
//given
originalSeriailizedCal, err := os.ReadFile(filepath.Join(testDir, filename))
require.NoError(t, err)

//when
deserializedCal, err := ParseCalendar(bytes.NewReader(originalSeriailizedCal))
require.NoError(t, err)
serializedCal := deserializedCal.Serialize()

//then
expectedCal, err := os.ReadFile(filepath.Join(expectedDir, filename))
require.NoError(t, err)
if diff := cmp.Diff(string(expectedCal), serializedCal); diff != "" {
err = os.MkdirAll(actualDir, 0755)
if err != nil {
t.Logf("failed to create actual dir: %v", err)
}
err = os.WriteFile(filepath.Join(actualDir, filename), []byte(serializedCal), 0644)
if err != nil {
t.Logf("failed to write actual file: %v", err)
}
t.Error(diff)
}
})

t.Run(fmt.Sprintf("compare deserialized -> serialized -> deserialized: %s", filename), func(t *testing.T) {
//given
loadIcsContent, err := os.ReadFile(filepath.Join(testDir, filename))
require.NoError(t, err)
originalDeserializedCal, err := ParseCalendar(bytes.NewReader(loadIcsContent))
require.NoError(t, err)

//when
serializedCal := originalDeserializedCal.Serialize()
deserializedCal, err := ParseCalendar(strings.NewReader(serializedCal))
require.NoError(t, err)

//then
if diff := cmp.Diff(originalDeserializedCal, deserializedCal); diff != "" {
t.Error(diff)
}
})
}
}
16 changes: 8 additions & 8 deletions components.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (cb ComponentBase) serializeThis(writer io.Writer, componentType string) {
func NewComponent(uniqueId string) ComponentBase {
return ComponentBase{
Properties: []IANAProperty{
{BaseProperty{IANAToken: ToText(string(ComponentPropertyUniqueId)), Value: uniqueId}},
{BaseProperty{IANAToken: string(ComponentPropertyUniqueId), Value: uniqueId}},
},
}
}
Expand Down Expand Up @@ -214,19 +214,19 @@ func (cb *ComponentBase) GetDtStampTime() (time.Time, error) {
}

func (cb *ComponentBase) SetSummary(s string, props ...PropertyParameter) {
cb.SetProperty(ComponentPropertySummary, ToText(s), props...)
cb.SetProperty(ComponentPropertySummary, s, props...)
}

func (cb *ComponentBase) SetStatus(s ObjectStatus, props ...PropertyParameter) {
cb.SetProperty(ComponentPropertyStatus, ToText(string(s)), props...)
cb.SetProperty(ComponentPropertyStatus, string(s), props...)
}

func (cb *ComponentBase) SetDescription(s string, props ...PropertyParameter) {
cb.SetProperty(ComponentPropertyDescription, ToText(s), props...)
cb.SetProperty(ComponentPropertyDescription, s, props...)
}

func (cb *ComponentBase) SetLocation(s string, props ...PropertyParameter) {
cb.SetProperty(ComponentPropertyLocation, ToText(s), props...)
cb.SetProperty(ComponentPropertyLocation, s, props...)
}

func (cb *ComponentBase) setGeo(lat interface{}, lng interface{}, props ...PropertyParameter) {
Expand Down Expand Up @@ -675,12 +675,12 @@ type VBusy struct {

func (c *VBusy) Serialize() string {
b := &bytes.Buffer{}
c.ComponentBase.serializeThis(b, "VBUSY")
c.ComponentBase.serializeThis(b, "VFREEBUSY")
return b.String()
}

func (c *VBusy) serialize(w io.Writer) {
c.ComponentBase.serializeThis(w, "VBUSY")
c.ComponentBase.serializeThis(w, "VFREEBUSY")
}

func NewBusy(uniqueId string) *VBusy {
Expand Down Expand Up @@ -729,7 +729,7 @@ func NewTimezone(tzId string) *VTimezone {
e := &VTimezone{
ComponentBase{
Properties: []IANAProperty{
{BaseProperty{IANAToken: ToText(string(ComponentPropertyTzid)), Value: tzId}},
{BaseProperty{IANAToken: string(ComponentPropertyTzid), Value: tzId}},
},
},
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.13

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
github.com/stretchr/testify v1.7.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
Expand Down
68 changes: 65 additions & 3 deletions property.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"log"
"regexp"
"sort"
"strconv"
"strings"
"unicode/utf8"
Expand Down Expand Up @@ -87,10 +88,64 @@ func trimUT8StringUpTo(maxLength int, s string) string {
return s[:length]
}

func (p *BaseProperty) GetValueType() ValueDataType {
for k, v := range p.ICalParameters {
if Parameter(k) == ParameterValue && len(v) == 1 {
return ValueDataType(v[0])
}
}

// defaults from spec if unspecified
switch Property(p.IANAToken) {
default:
fallthrough
case PropertyCalscale, PropertyMethod, PropertyProductId, PropertyVersion, PropertyCategories, PropertyClass,
PropertyComment, PropertyDescription, PropertyLocation, PropertyResources, PropertyStatus, PropertySummary,
PropertyTransp, PropertyTzid, PropertyTzname, PropertyContact, PropertyRelatedTo, PropertyUid, PropertyAction,
PropertyRequestStatus:
return ValueDataTypeText

case PropertyAttach, PropertyTzurl, PropertyUrl:
return ValueDataTypeUri

case PropertyGeo:
return ValueDataTypeFloat

case PropertyPercentComplete, PropertyPriority, PropertyRepeat, PropertySequence:
return ValueDataTypeInteger

case PropertyCompleted, PropertyDtend, PropertyDue, PropertyDtstart, PropertyRecurrenceId, PropertyExdate,
PropertyRdate, PropertyCreated, PropertyDtstamp, PropertyLastModified:
return ValueDataTypeDateTime

case PropertyDuration, PropertyTrigger:
return ValueDataTypeDuration

case PropertyFreebusy:
return ValueDataTypePeriod

case PropertyTzoffsetfrom, PropertyTzoffsetto:
return ValueDataTypeUtcOffset

case PropertyAttendee, PropertyOrganizer:
return ValueDataTypeCalAddress

case PropertyRrule:
return ValueDataTypeRecur
}
}

func (property *BaseProperty) serialize(w io.Writer) {
b := bytes.NewBufferString("")
fmt.Fprint(b, property.IANAToken)
for k, vs := range property.ICalParameters {

var keys []string
for k := range property.ICalParameters {
keys = append(keys, k)
}
sort.Strings(keys)
arran4 marked this conversation as resolved.
Show resolved Hide resolved
for _, k := range keys {
vs := property.ICalParameters[k]
fmt.Fprint(b, ";")
fmt.Fprint(b, k)
fmt.Fprint(b, "=")
Expand All @@ -109,7 +164,11 @@ func (property *BaseProperty) serialize(w io.Writer) {
}
}
fmt.Fprint(b, ":")
fmt.Fprint(b, property.Value)
propertyValue := property.Value
if property.GetValueType() == ValueDataTypeText {
propertyValue = ToText(propertyValue)
}
fmt.Fprint(b, propertyValue)
r := b.String()
if len(r) > 75 {
l := trimUT8StringUpTo(75, r)
Expand Down Expand Up @@ -298,7 +357,10 @@ func parsePropertyValue(r *BaseProperty, contentLine string, p int) *BasePropert
if tokenPos == nil {
return nil
}
r.Value = string(contentLine[p : p+tokenPos[1]])
r.Value = contentLine[p : p+tokenPos[1]]
if r.GetValueType() == ValueDataTypeText {
r.Value = FromText(r.Value)
}
return r
}

Expand Down
16 changes: 16 additions & 0 deletions testdata/serialization/expected/input1.ics
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
BEGIN:VCALENDAR
PRODID:-//xyz Corp//NONSGML PDA Calendar Version 1.0//EN
VERSION:2.0
BEGIN:VEVENT
DTSTAMP:19960704T120000Z
UID:[email protected]
ORGANIZER:mailto:[email protected]
DTSTART:19960918T143000Z
DTEND:19960920T220000Z
STATUS:CONFIRMED
CATEGORIES:CONFERENCE
SUMMARY:Networld+Interop Conference
DESCRIPTION:Networld+Interop Conference and Exhibit\nAtlanta World Congress
Center\nAtlanta\, Georgia
END:VEVENT
END:VCALENDAR
Loading