diff --git a/composition/content_merge.go b/composition/content_merge.go index 8cc8ee1..8d2bb8c 100644 --- a/composition/content_merge.go +++ b/composition/content_merge.go @@ -60,7 +60,8 @@ func (cntx *ContentMerge) collectStylesheets(f Fragment) { } func (cntx *ContentMerge) writeStylesheets(w io.Writer) { - for _, attrs := range cntx.stylesheets { + stylesheets := stylesheetDeduplicationStrategy.Deduplicate(cntx.stylesheets) + for _, attrs := range stylesheets { joinedAttr := joinAttrs(attrs) stylesheet := fmt.Sprintf("\n ", joinedAttr) io.WriteString(w, stylesheet) diff --git a/composition/content_merge_test.go b/composition/content_merge_test.go index ed2226f..87c6e00 100644 --- a/composition/content_merge_test.go +++ b/composition/content_merge_test.go @@ -8,11 +8,6 @@ import ( "golang.org/x/net/html" ) -func stylesheetAttrs(href string) []html.Attribute { - commonAttr := []html.Attribute{{Key: "rel", Val: "stylesheet"}, {Key: "type", Val: "text/css"}} - return append(commonAttr, html.Attribute{Key: "href", Val: href}) -} - func Test_ContentMerge_PositiveCase(t *testing.T) { a := assert.New(t) diff --git a/composition/interfaces.go b/composition/interfaces.go index d51043e..2e25577 100644 --- a/composition/interfaces.go +++ b/composition/interfaces.go @@ -124,3 +124,7 @@ type Cache interface { Invalidate() PurgeEntries(keys []string) } + +type StylesheetDeduplicationStrategy interface { + Deduplicate(stylesheetAttrs [][]html.Attribute) [][]html.Attribute +} diff --git a/composition/stylesheet_deduplication.go b/composition/stylesheet_deduplication.go new file mode 100644 index 0000000..39601cb --- /dev/null +++ b/composition/stylesheet_deduplication.go @@ -0,0 +1,53 @@ +package composition + +import ( + "strings" + + "golang.org/x/net/html" +) + +// Initialization: Sets the default deduplication strategy to be used. +func init() { + SetStrategy(new(IdentityDeduplicationStrategy)) +} + +// Set another deduplication strategy. +func SetStrategy(strategy StylesheetDeduplicationStrategy) { + stylesheetDeduplicationStrategy = strategy +} + +// NOOP strategy. +// This stragegy will insert all found stylesheets w/o any filtering. +type IdentityDeduplicationStrategy struct { +} + +func (strategy *IdentityDeduplicationStrategy) Deduplicate(stylesheets [][]html.Attribute) [][]html.Attribute { + return stylesheets +} + +// Simple strategy +// Implements a very simple deduplication stragegy. That is, it filters out +// stylesheets with duplicate href value. +type SimpleDeduplicationStrategy struct { +} + +// Remove duplicate entries from hrefs. +func (strategy *SimpleDeduplicationStrategy) Deduplicate(stylesheets [][]html.Attribute) (result [][]html.Attribute) { + var knownHrefs string + const delimiter = "-|-" + for _, stylesheetAttrs := range stylesheets { + hrefAttr, attrExists := getAttr(stylesheetAttrs, "href") + if !attrExists { + continue + } + href := hrefAttr.Val + if !strings.Contains(knownHrefs, href) { + result = append(result, stylesheetAttrs) + knownHrefs += delimiter + href + } + } + return result +} + +// Variable to hold the active strategy +var stylesheetDeduplicationStrategy StylesheetDeduplicationStrategy diff --git a/composition/stylesheet_deduplication_test.go b/composition/stylesheet_deduplication_test.go new file mode 100644 index 0000000..1c1322c --- /dev/null +++ b/composition/stylesheet_deduplication_test.go @@ -0,0 +1,75 @@ +package composition + +import ( + "testing" + + "golang.org/x/net/html" + + "github.com/stretchr/testify/assert" +) + +func stylesheetAttrs(href string) []html.Attribute { + commonAttr := []html.Attribute{{Key: "rel", Val: "stylesheet"}, {Key: "type", Val: "text/css"}} + return append(commonAttr, html.Attribute{Key: "href", Val: href}) +} + +func Test_DefaultDeduplicationStrategy(t *testing.T) { + a := assert.New(t) + stylesheets := [][]html.Attribute{stylesheetAttrs("/a"), stylesheetAttrs("/b")} + result := stylesheetDeduplicationStrategy.Deduplicate(stylesheets) + a.EqualValues(stylesheets, result) +} + +func Test_SimpleDeduplicationStrategy(t *testing.T) { + a := assert.New(t) + stylesheets := [][]html.Attribute{ + stylesheetAttrs("/a"), + stylesheetAttrs("/b"), + stylesheetAttrs("/a"), + stylesheetAttrs("/b"), + stylesheetAttrs("/c"), + stylesheetAttrs("/a"), + } + expected := [][]html.Attribute{ + stylesheetAttrs("/a"), + stylesheetAttrs("/b"), + stylesheetAttrs("/c"), + } + deduper := new(SimpleDeduplicationStrategy) + result := deduper.Deduplicate(stylesheets) + a.EqualValues(expected, result) +} + +// Tests for setting an own deduplication strategy +type Strategy struct { +} + +func (strategy *Strategy) Deduplicate(stylesheets [][]html.Attribute) (result [][]html.Attribute) { + for i, stylesheetAttrs := range stylesheets { + if i%2 == 0 { + result = append(result, stylesheetAttrs) + } + } + return result +} + +func Test_OwnDeduplicationStrategy(t *testing.T) { + strategy := new(Strategy) + SetStrategy(strategy) + + a := assert.New(t) + stylesheets := [][]html.Attribute{ + stylesheetAttrs("/a"), + stylesheetAttrs("/b"), + stylesheetAttrs("/c"), + stylesheetAttrs("/d"), + stylesheetAttrs("/e"), + } + expected := [][]html.Attribute{ + stylesheetAttrs("/a"), + stylesheetAttrs("/c"), + stylesheetAttrs("/e"), + } + result := stylesheetDeduplicationStrategy.Deduplicate(stylesheets) + a.EqualValues(expected, result) +} diff --git a/composition_example/example_ui_service_test.go b/composition_example/example_ui_service_test.go index 0bcbb6d..d58a736 100644 --- a/composition_example/example_ui_service_test.go +++ b/composition_example/example_ui_service_test.go @@ -28,10 +28,6 @@ func Test_integration_test(t *testing.T) { expected, err := ioutil.ReadFile("./expected_test_result.html") expectedS := strings.Replace(string(expected), "http://127.0.0.1:8080", s.URL, -1) - // debug - don't commit! - ioutil.WriteFile("/tmp/expected", []byte(expectedS), 0644) - ioutil.WriteFile("/tmp/result", body, 0644) - a.NoError(err) htmlEqual(t, expectedS, string(body)) }