Skip to content

Commit

Permalink
Merge 5996df6 into f05f8e2
Browse files Browse the repository at this point in the history
  • Loading branch information
cborgolte committed Aug 14, 2017
2 parents f05f8e2 + 5996df6 commit cb5335c
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 54 deletions.
48 changes: 35 additions & 13 deletions composition/content_merge.go
Expand Up @@ -21,7 +21,7 @@ const (
type ContentMerge struct {
MetaJSON map[string]interface{}
Head []Fragment
BodyAttrs []Fragment
BodyAttrs [][]html.Attribute

// Aggregator for the Body Fragments of the results.
// Each fragment is insertes twice with full name and local name,
Expand Down Expand Up @@ -49,7 +49,6 @@ func NewContentMerge(metaJSON map[string]interface{}) *ContentMerge {
cntx := &ContentMerge{
MetaJSON: metaJSON,
Head: make([]Fragment, 0, 0),
BodyAttrs: make([]Fragment, 0, 0),
Body: make(map[string]Fragment),
Tail: make([]Fragment, 0, 0),
Buffered: true,
Expand Down Expand Up @@ -97,6 +96,35 @@ func generateExecutionFunction(cntx *ContentMerge, w io.Writer) (executeFragment
return executeFragment
}

func collectBodyAttrs(bodyAttrs [][]html.Attribute) string {
var result map[string]string = make(map[string]string)
for i := range bodyAttrs {
for j := range bodyAttrs[i] {
attr := &bodyAttrs[i][j]
val, exists := result[attr.Key]
if strings.ToLower(attr.Key) == "class" {
// aggregate all class attributes
var newVal string
if exists {
newVal = val + " "
}
newVal = newVal + attr.Val
result[attr.Key] = newVal
} else {
// but overwrite others
result[attr.Key] = attr.Val
}
}
}

var sResult string
for k, v := range result {
sResult = sResult + fmt.Sprintf(` %s="%s"`, k, v)
}

return sResult
}

func (cntx *ContentMerge) GetHtml() ([]byte, error) {

if len(cntx.priorities) > 0 {
Expand All @@ -118,13 +146,7 @@ func (cntx *ContentMerge) GetHtml() ([]byte, error) {
// open body tag
body := bytes.NewBuffer(make([]byte, 0, DefaultBufferSize))
io.WriteString(body, "\n <body")
for _, f := range cntx.BodyAttrs {
io.WriteString(body, " ")
executeFragment := generateExecutionFunction(cntx, body)
if err := f.Execute(body, cntx.MetaJSON, executeFragment); err != nil {
return nil, err
}
}
io.WriteString(body, collectBodyAttrs(cntx.BodyAttrs))

io.WriteString(body, ">\n ")

Expand Down Expand Up @@ -178,7 +200,7 @@ func (cntx *ContentMerge) GetBodyFragmentByName(name string) (Fragment, bool) {

func (cntx *ContentMerge) AddContent(c Content, priority int) {
cntx.addHead(c.Head())
cntx.addBodyAttributes(c.BodyAttributes())
cntx.addBodyAttributes(c.BodyAttributesArray())
cntx.addBody(c)
cntx.addTail(c.Tail())
if priority > 0 {
Expand All @@ -192,9 +214,9 @@ func (cntx *ContentMerge) addHead(f Fragment) {
}
}

func (cntx *ContentMerge) addBodyAttributes(f Fragment) {
if f != nil {
cntx.BodyAttrs = append(cntx.BodyAttrs, f)
func (cntx *ContentMerge) addBodyAttributes(a []html.Attribute) {
if a != nil {
cntx.BodyAttrs = append(cntx.BodyAttrs, a)
}
}

Expand Down
69 changes: 67 additions & 2 deletions composition/content_merge_test.go
Expand Up @@ -2,12 +2,69 @@ package composition

import (
"errors"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"golang.org/x/net/html"
)

func Test_ContentMerge_MergeBodyAttributes(t *testing.T) {
a := assert.New(t)

layout := NewStringFragment(
`<page1-body-main>
§[> page2-a]§
§[> example.com#page2-b]§
§[> page3]§
</page1-body-main>
`)

cm := NewContentMerge(nil)

cm.AddContent(&MemoryContent{
name: LayoutFragmentName,
head: NewStringFragment("<page1-head/>\n"),
bodyAttributes: htmlAttributes(map[string]string{"a": "b", "foo": "bar0", "class": "class1 class2"}),
tail: NewStringFragment(" <page1-tail/>\n"),
body: map[string]Fragment{"": layout},
}, 0)

cm.AddContent(&MemoryContent{
name: "example.com",
head: NewStringFragment(" <page2-head/>\n"),
bodyAttributes: htmlAttributes(map[string]string{"foo": "bar1", "class": "class3"}),
tail: NewStringFragment(" <page2-tail/>"),
body: map[string]Fragment{
"page2-a": NewStringFragment("<page2-body-a/>"),
"page2-b": NewStringFragment("<page2-body-b/>"),
}}, 0)

cm.AddContent(&MemoryContent{
name: "page3",
head: NewStringFragment(" <page3-head/>"),
bodyAttributes: htmlAttributes(map[string]string{"foo": "bar2", "class": "class4"}),
body: map[string]Fragment{
"": NewStringFragment("<page3-body-a/>"),
}}, MAX_PRIORITY) // just to trigger the priority-parsing and see that it doesn't crash..

html, err := cm.GetHtml()
a.Contains(string(html), `<body`)
bodyElement := strings.SplitN(string(html), "<body", 2)[1]
bodyElement = strings.SplitN(bodyElement, ">", 2)[0]
a.NoError(err)
// expect class attributes to be aggregated, all others to be overwritten (here: foo)
a.Contains(bodyElement, `a="b"`)
a.Contains(bodyElement, `foo="bar2"`)
a.NotContains(bodyElement, `foo="bar0"`)
a.NotContains(bodyElement, `foo="bar1"`)
a.Contains(bodyElement, `class="class1 class2 class3 class4"`)
// assure, there are no additional class attributes
a.NotContains(bodyElement, `class="class1 class2"`)
a.NotContains(bodyElement, `class="class3 class4"`)
a.NotContains(bodyElement, `class="class4"`)
}

func Test_ContentMerge_PositiveCase(t *testing.T) {
a := assert.New(t)

Expand Down Expand Up @@ -51,15 +108,15 @@ func Test_ContentMerge_PositiveCase(t *testing.T) {
cm.AddContent(&MemoryContent{
name: LayoutFragmentName,
head: NewStringFragment("<page1-head/>\n"),
bodyAttributes: NewStringFragment(`a="b"`),
bodyAttributes: htmlAttributes(map[string]string{"a": "b"}),
tail: NewStringFragment(" <page1-tail/>\n"),
body: map[string]Fragment{"": body},
}, 0)

cm.AddContent(&MemoryContent{
name: "example.com",
head: NewStringFragment(" <page2-head/>\n"),
bodyAttributes: NewStringFragment(`foo="bar"`),
bodyAttributes: htmlAttributes(map[string]string{"foo": "bar"}),
tail: NewStringFragment(" <page2-tail/>"),
body: map[string]Fragment{
"page2-a": NewStringFragment("<page2-body-a/>"),
Expand Down Expand Up @@ -252,3 +309,11 @@ func (buff closedWriterMock) Write(b []byte) (int, error) {
func asFetchResult(c Content) *FetchResult {
return &FetchResult{Content: c, Def: &FetchDefinition{URL: c.Name()}}
}

func htmlAttributes(m map[string]string) []html.Attribute {
var result []html.Attribute
for k, v := range m {
result = append(result, html.Attribute{Key: k, Val: v})
}
return result
}
52 changes: 27 additions & 25 deletions composition/html_content_parser.go
Expand Up @@ -117,7 +117,9 @@ func (parser *HtmlContentParser) parseBody(z *html.Tokenizer, c *MemoryContent)

attrs = readAttributes(z, attrs)
if len(attrs) > 0 {
c.bodyAttributes = NewStringFragment(joinAttrs(attrs))
for _, a := range attrs {
c.bodyAttributes = append(c.bodyAttributes, a)
}
}

forloop:
Expand Down Expand Up @@ -356,20 +358,20 @@ forloop:

switch {
case string(tag) == "meta":
if (processMetaTag(string(tag), attrs, headPropertyMap)) {
if processMetaTag(string(tag), attrs, headPropertyMap) {
headBuff.Write(raw)
}
continue forloop
case string(tag) == "link":
if (processLinkTag(attrs, headPropertyMap)) {
if processLinkTag(attrs, headPropertyMap) {
headBuff.Write(raw)
}
continue forloop
case string(tag) == "title":
if (headPropertyMap["title"] == "") {
if headPropertyMap["title"] == "" {
headPropertyMap["title"] = "title"
headBuff.Write(raw)
} else if (tt != html.SelfClosingTagToken) {
} else if tt != html.SelfClosingTagToken {
skipCompleteTag(z, "title")
}
continue forloop
Expand Down Expand Up @@ -442,33 +444,33 @@ func processMetaTag(tagName string, attrs []html.Attribute, metaMap map[string]s
/**
Returns true if a link tag can be processed.
Checks if a <link> tag contains a canonical relation and avoids multiple canonical definitions.
*/
*/
func processLinkTag(attrs []html.Attribute, metaMap map[string]string) bool {
if (len(attrs) == 0) {
if len(attrs) == 0 {
return true
}

const canonical = "canonical"
const canonical = "canonical"
var key string
var value string

// e.g.: <link rel="canonical" href="/baumarkt/suche"> => key = canonical; val = /baumarkt/suche
// e.g.: <link rel="canonical" href="/baumarkt/suche"> => key = canonical; val = /baumarkt/suche
for _, attr := range attrs {
if (attr.Key == "rel" && attr.Val == canonical) {
key = canonical
}
if (attr.Key == "href") {
value = attr.Val
}
}
if (key == canonical && metaMap[canonical] != "") {
// if canonical is already in map then don't process this link tag
return false
}

if (key != "" && value != "") {
metaMap[key] = value
}
if attr.Key == "rel" && attr.Val == canonical {
key = canonical
}
if attr.Key == "href" {
value = attr.Val
}
}
if key == canonical && metaMap[canonical] != "" {
// if canonical is already in map then don't process this link tag
return false
}

if key != "" && value != "" {
metaMap[key] = value
}
return true
}

Expand Down Expand Up @@ -608,4 +610,4 @@ var voidElements = map[string]bool{
"source": true,
"track": true,
"wbr": true,
}
}
18 changes: 10 additions & 8 deletions composition/html_content_parser_test.go
Expand Up @@ -299,7 +299,7 @@ func Test_HtmlContentParser_parseHead_withMultipleMetaTags_and_Titles_and_Canoni
err := parser.parseHead(z, c)
a.NoError(err)

containsFragment(t, "<title>navigationservice</title>", c.Head())
containsFragment(t, "<title>navigationservice</title>", c.Head())
}

func Test_HtmlContentParser_parseHead(t *testing.T) {
Expand Down Expand Up @@ -449,6 +449,9 @@ func Test_HtmlContentParser_parseBody(t *testing.T) {
`§[> local]§`, c.Body()["content"])
eqFragment(t, "<!-- tail -->§[> example.com/tail]§", c.Tail())

a.Equal(`some="attribute"`, joinAttrs(c.BodyAttributesArray()))

// check deprecated BodyAttributes() method
eqFragment(t, `some="attribute"`, c.BodyAttributes())

a.Equal(5, len(c.Dependencies()))
Expand Down Expand Up @@ -746,7 +749,6 @@ func containsFragment(t *testing.T, contained string, f Fragment) {
}
}


func Test_ParseHeadFragment_Filter_Title(t *testing.T) {
a := assert.New(t)

Expand Down Expand Up @@ -942,7 +944,7 @@ func Test_ParseHeadFragment_Filter_Meta_Tag(t *testing.T) {
func Test_ParseHeadFragment_Filter_Link_Canonical_Tag(t *testing.T) {
a := assert.New(t)

// GIVEN
// GIVEN
originalHeadString := `<meta charset="utf-8">
<link rel="canonical" href="/navigationservice">
Expand Down Expand Up @@ -1011,18 +1013,18 @@ func Test_ParseHeadFragment_Filter_Link_Canonical_Tag(t *testing.T) {
headMetaPropertyMap["canonical"] = "/baumarkt/suche"

headFragment := NewStringFragment(originalHeadString)
// WHEN
// WHEN
ParseHeadFragment(headFragment, headMetaPropertyMap)

// THEN
// THEN
expectedParsedHead = removeTabsAndNewLines(expectedParsedHead)
resultString := removeTabsAndNewLines(headFragment.Content())

a.Equal(expectedParsedHead, resultString)
}

func Test_ParseHeadFragment_Filter_Link_Canonical_Tag_without_existing_Map(t *testing.T) {
// GIVEN
// GIVEN
a := assert.New(t)

originalHeadString := `
Expand All @@ -1049,10 +1051,10 @@ func Test_ParseHeadFragment_Filter_Link_Canonical_Tag_without_existing_Map(t *te
headMetaPropertyMap := make(map[string]string)

headFragment := NewStringFragment(originalHeadString)
// WHEN
// WHEN
ParseHeadFragment(headFragment, headMetaPropertyMap)

// THEN
// THEN
expectedParsedHead = removeTabsAndNewLines(expectedParsedHead)
resultString := removeTabsAndNewLines(headFragment.Content())

Expand Down

0 comments on commit cb5335c

Please sign in to comment.