Skip to content

Commit

Permalink
Merge pull request #51 from cborgolte/bugfix/stylesheet-collection
Browse files Browse the repository at this point in the history
Bugfix/stylesheet collection
  • Loading branch information
cborgolte committed May 31, 2017
2 parents c825d99 + 1f48119 commit b0d2519
Show file tree
Hide file tree
Showing 13 changed files with 315 additions and 54 deletions.
65 changes: 50 additions & 15 deletions composition/content_merge.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ package composition
import (
"bytes"
"errors"
"fmt"
"io"
"strings"

"golang.org/x/net/html"
)

const (
Expand Down Expand Up @@ -33,6 +36,9 @@ type ContentMerge struct {
// merge priorities for the content objects
// no entry means priority == 0
priorities map[Content]int

// all stylesheets contained in used fragments
stylesheets [][]html.Attribute
}

// NewContentMerge creates a new buffered ContentMerge
Expand All @@ -49,59 +55,88 @@ func NewContentMerge(metaJSON map[string]interface{}) *ContentMerge {
return cntx
}

func (cntx *ContentMerge) GetHtml() ([]byte, error) {
if len(cntx.priorities) > 0 {
cntx.processMetaPriorityParsing()
func (cntx *ContentMerge) collectStylesheets(f Fragment) {
cntx.stylesheets = append(cntx.stylesheets, f.Stylesheets()...)
}

func (cntx *ContentMerge) writeStylesheets(w io.Writer) {
for _, attrs := range cntx.stylesheets {
joinedAttr := joinAttrs(attrs)
stylesheet := fmt.Sprintf("\n <link %s>", joinedAttr)
io.WriteString(w, stylesheet)
}
w := bytes.NewBuffer(make([]byte, 0, DefaultBufferSize))
}

var executeFragment func(fragmentName string) error
func generateExecutionFunction(cntx *ContentMerge, w io.Writer) (executeFragment func(fragmentName string) error) {
executeFragment = func(fragmentName string) error {
f, exist := cntx.GetBodyFragmentByName(fragmentName)
if !exist {
missingFragmentString := generateMissingFragmentString(cntx.Body, fragmentName)
return errors.New(missingFragmentString)
}
cntx.collectStylesheets(f)
return f.Execute(w, cntx.MetaJSON, executeFragment)
}
return executeFragment
}

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

if len(cntx.priorities) > 0 {
cntx.processMetaPriorityParsing()
}

io.WriteString(w, "<!DOCTYPE html>\n<html>\n <head>\n ")
// start header, but don't close it. We will add stylsheets later on
header := bytes.NewBuffer(make([]byte, 0, DefaultBufferSize))
io.WriteString(header, "<!DOCTYPE html>\n<html>\n <head>\n ")

for _, f := range cntx.Head {
if err := f.Execute(w, cntx.MetaJSON, executeFragment); err != nil {
cntx.collectStylesheets(f)
executeFragment := generateExecutionFunction(cntx, header)
if err := f.Execute(header, cntx.MetaJSON, executeFragment); err != nil {
return nil, err
}
}
io.WriteString(w, "\n </head>\n <body")

// open body tag
body := bytes.NewBuffer(make([]byte, 0, DefaultBufferSize))
io.WriteString(body, "\n <body")
for _, f := range cntx.BodyAttrs {
io.WriteString(w, " ")

if err := f.Execute(w, cntx.MetaJSON, executeFragment); err != nil {
io.WriteString(body, " ")
executeFragment := generateExecutionFunction(cntx, body)
if err := f.Execute(body, cntx.MetaJSON, executeFragment); err != nil {
return nil, err
}
}

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

startFragmentName := ""
if _, exist := cntx.GetBodyFragmentByName(LayoutFragmentName); exist {
startFragmentName = LayoutFragmentName
}

// recursively process body fragments
executeFragment := generateExecutionFunction(cntx, body)
if err := executeFragment(startFragmentName); err != nil {
return nil, err
}

for _, f := range cntx.Tail {
if err := f.Execute(w, cntx.MetaJSON, executeFragment); err != nil {
cntx.collectStylesheets(f)
if err := f.Execute(body, cntx.MetaJSON, executeFragment); err != nil {
return nil, err
}
}
io.WriteString(body, "\n </body>\n</html>\n")

io.WriteString(w, "\n </body>\n</html>\n")
// write the collected stylesheets to the header and close it
cntx.writeStylesheets(header)
io.WriteString(header, "\n </head>")

return w.Bytes(), nil
// return concatenated header and body
html := append(header.Bytes(), body.Bytes()...)
return html, nil
}

// GetBodyFragmentByName returns a fragment by ists name.
Expand Down
84 changes: 73 additions & 11 deletions composition/content_merge_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ package composition

import (
"errors"
"github.com/stretchr/testify/assert"
"testing"

"github.com/stretchr/testify/assert"
"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)

Expand All @@ -15,6 +22,8 @@ func Test_ContentMerge_PositiveCase(t *testing.T) {
<page1-head/>
<page2-head/>
<page3-head/>
<link rel="stylesheet" type="text/css" href="/abc/def">
<link rel="stylesheet" type="text/css" href="/üst/das/möglich">
</head>
<body a="b" foo="bar">
<page1-body-main>
Expand All @@ -36,6 +45,12 @@ func Test_ContentMerge_PositiveCase(t *testing.T) {
</page1-body-main>
`)

sheets := [][]html.Attribute{
stylesheetAttrs("/abc/def"),
stylesheetAttrs("/üst/das/möglich"),
}

body.AddStylesheets(sheets)
cm := NewContentMerge(nil)

cm.AddContent(&MemoryContent{
Expand Down Expand Up @@ -75,6 +90,14 @@ func Test_ContentMerge_BodyCompositionWithExplicitNames(t *testing.T) {
<html>
<head>
<link rel="stylesheet" type="text/css" href="/body/first">
<link rel="stylesheet" type="text/css" href="/body/second">
<link rel="stylesheet" type="text/css" href="/page/2A/first">
<link rel="stylesheet" type="text/css" href="/page/2A/second">
<link rel="stylesheet" type="text/css" href="/page/2B/first">
<link rel="stylesheet" type="text/css" href="/page/2B/second">
<link rel="stylesheet" type="text/css" href="/page/3A/first">
<link rel="stylesheet" type="text/css" href="/page/3A/second">
</head>
<body>
<page1-body-main>
Expand All @@ -88,32 +111,71 @@ func Test_ContentMerge_BodyCompositionWithExplicitNames(t *testing.T) {

cm := NewContentMerge(nil)

cm.AddContent(&MemoryContent{
name: LayoutFragmentName,
body: map[string]Fragment{
"": NewStringFragment(
`<page1-body-main>
body := NewStringFragment(
`<page1-body-main>
§[> page2-a]§
§[> example1.com#page2-b]§
§[> page3-a]§
</page1-body-main>`)}}, 0)
</page1-body-main>`)

sheets := [][]html.Attribute{
stylesheetAttrs("/body/first"),
stylesheetAttrs("/body/second"),
}
body.AddStylesheets(sheets)

cm.AddContent(&MemoryContent{
name: LayoutFragmentName,
body: map[string]Fragment{
"": body}}, 0)

page2A := NewStringFragment("<page2-body-a/>")
sheets = [][]html.Attribute{
stylesheetAttrs("/page/2A/first"),
stylesheetAttrs("/page/2A/second"),
}
page2A.AddStylesheets(sheets)

page2B := NewStringFragment("<page2-body-b/>")
sheets = [][]html.Attribute{
stylesheetAttrs("/page/2B/first"),
stylesheetAttrs("/page/2B/second"),
}
page2B.AddStylesheets(sheets)

// this fragment is not rendered, so it's stylesheets should not appear in page header
pageUnreferenced := NewStringFragment("<unreferenced-body/>")
sheets = [][]html.Attribute{
stylesheetAttrs("/unreferenced/first"),
stylesheetAttrs("/unreferenced/second"),
}
pageUnreferenced.AddStylesheets(sheets)

cm.AddContent(&MemoryContent{
name: "example1.com",
body: map[string]Fragment{
"page2-a": NewStringFragment("<page2-body-a/>"),
"page2-b": NewStringFragment("<page2-body-b/>"),
"page2-a": page2A,
"page2-b": page2B,
"unreferenced": pageUnreferenced,
}}, 0)

page3A := NewStringFragment("<page3-body-a/>")
sheets = [][]html.Attribute{
stylesheetAttrs("/page/3A/first"),
stylesheetAttrs("/page/3A/second"),
}
page3A.AddStylesheets(sheets)
cm.AddContent(&MemoryContent{
name: "example2.com",
body: map[string]Fragment{
"page3-a": NewStringFragment("<page3-body-a/>"),
"page3-a": page3A,
}}, 0)

html, err := cm.GetHtml()
a.NoError(err)
a.Equal(expected, string(html))
expected = removeTabsAndNewLines(expected)
result := removeTabsAndNewLines(string(html))
a.Equal(expected, result)
}

func Test_ContentMerge_LookupByDifferentFragmentNames(t *testing.T) {
Expand Down
2 changes: 1 addition & 1 deletion composition/discovered_fetch_definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ func Test_FetchDefinition_DiscoveredByError(t *testing.T) {
a.Panics(func() {
testSubject.DiscoveredBy("a")
})
}
}
46 changes: 39 additions & 7 deletions composition/html_content_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import (
"bytes"
"encoding/json"
"fmt"
"golang.org/x/net/html"
"io"
"strconv"
"strings"
"time"

"golang.org/x/net/html"
)

const (
Expand Down Expand Up @@ -51,6 +52,7 @@ func (parser *HtmlContentParser) Parse(c *MemoryContent, in io.Reader) error {
}

func (parser *HtmlContentParser) parseHead(z *html.Tokenizer, c *MemoryContent) error {
var stylesheets [][]html.Attribute
attrs := make([]html.Attribute, 0, 10)
headBuff := bytes.NewBuffer(nil)

Expand All @@ -77,6 +79,10 @@ forloop:
}
continue
}
if styleAttrs, isStylesheet := getStylesheet(tag, attrs); isStylesheet {
stylesheets = append(stylesheets, styleAttrs)
continue
}
case tt == html.EndTagToken:
if string(tag) == "head" {
break forloop
Expand All @@ -87,13 +93,25 @@ forloop:

s := headBuff.String()
st := strings.Trim(s, " \n")
if len(st) > 0 {
c.head = NewStringFragment(st)
if len(st) > 0 || len(stylesheets) > 0 {
frg := NewStringFragment(st)
frg.AddStylesheets(stylesheets)
c.head = frg
}
return nil
}

func getStylesheet(tag []byte, attrs []html.Attribute) (styleAttrs []html.Attribute, isStylesheet bool) {
styleAttrs = nil
if string(tag) == "link" && attrHasValue(attrs, "rel", "stylesheet") {
styleAttrs = append(styleAttrs, attrs...)
return styleAttrs, true
}
return styleAttrs, false
}

func (parser *HtmlContentParser) parseBody(z *html.Tokenizer, c *MemoryContent) error {
var stylesheets [][]html.Attribute
attrs := make([]html.Attribute, 0, 10)
bodyBuff := bytes.NewBuffer(nil)

Expand Down Expand Up @@ -161,6 +179,10 @@ forloop:
continue
}
}
if styleAttrs, isStylesheet := getStylesheet(tag, attrs); isStylesheet {
stylesheets = append(stylesheets, styleAttrs)
continue
}

case tt == html.EndTagToken:
if string(tag) == "body" {
Expand All @@ -172,15 +194,18 @@ forloop:

s := bodyBuff.String()
if _, defaultFragmentExists := c.body[""]; !defaultFragmentExists {
if st := strings.Trim(s, " \n"); len(st) > 0 {
c.body[""] = NewStringFragment(st)
if st := strings.Trim(s, " \n"); len(st) > 0 || len(stylesheets) > 0 {
frg := NewStringFragment(st)
frg.AddStylesheets(stylesheets)
c.body[""] = frg
}
}

return nil
}

func parseFragment(z *html.Tokenizer) (f Fragment, dependencies map[string]Params, err error) {
var stylesheets [][]html.Attribute
attrs := make([]html.Attribute, 0, 10)
dependencies = make(map[string]Params)

Expand Down Expand Up @@ -216,6 +241,11 @@ forloop:
continue
}

if styleAttrs, isStylesheet := getStylesheet(tag, attrs); isStylesheet {
stylesheets = append(stylesheets, styleAttrs)
continue
}

case tt == html.EndTagToken:
if string(tag) == UicFragment || string(tag) == UicTail {
break forloop
Expand All @@ -224,7 +254,9 @@ forloop:
buff.Write(raw)
}

return NewStringFragment(buff.String()), dependencies, nil
frg := NewStringFragment(buff.String())
frg.AddStylesheets(stylesheets)
return frg, dependencies, nil
}

func getInclude(z *html.Tokenizer, attrs []html.Attribute) (startMarker, endMarker, dependencyName string, dependencyParams Params, error error) {
Expand Down Expand Up @@ -347,7 +379,7 @@ forloop:
s := headBuff.String()

if len(s) > 0 {
*fragment = *NewStringFragment(s)
fragment.SetContent(s)
}
return nil
}
Expand Down
Loading

0 comments on commit b0d2519

Please sign in to comment.