Skip to content

Commit

Permalink
Merge pull request #38 from tarent/optional-includes
Browse files Browse the repository at this point in the history
Optional includes
  • Loading branch information
denge90 committed Dec 12, 2016
2 parents 43aade2 + 6d96fa8 commit bf9413c
Show file tree
Hide file tree
Showing 8 changed files with 189 additions and 93 deletions.
39 changes: 30 additions & 9 deletions composition/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,9 @@ There are some predefined variables, constructed out of the request.
}
```

#### Preloaded Includes
On an unspecified include, the UI-Service has to load replace the include by a previously loaded fragment.
#### Includes
On an unspecified include, the UI-Service has to replace the include by a previously loaded fragment.
If the required fragment is missing, the composition will fail.

Example: Will be replaced by the Default Body Fragment of *example.com/foo*.

Expand All @@ -201,19 +202,39 @@ Example: Will be replaced by the *content* fragment of any random choosen page.
§[> #content]§
```

#### Loaded Includes
On a specified include, the UI-Service has to load the referenced page and has to replace the include with the referenced fragment.
Within the src attribute, there are also variable replacements possible.
#### Optional Includes
There is a syntax for optional includes with an alternative text.

Example: Will be replaced by the Default Body Fragment of *http://example.com/foo*.
Example: Will be replaced by the contents of *foo* or by the alternative content,
if no such element foo exists or an error occurs while replacing with foo.

```
<uic-include src="example.com/foo"/>
§[#> foo]§ alternative content §[/foo
```

Example: Will be replaced by the *content* fragment of *http://example.com/foo*. If it times out after 42 seconds, no error is returned.
#### Include HTML Syntax
There is also an html syntax for includes, as following:
```
<uic-include src="example.com/foo" required="true"/>
```
The default is `required=false`, if not specified.
The alternative content for optional html includes is currently not implemented.


#### Fetch directive
It is possible to specifiy an url for additional content to load,
while the composition takes place.

Example:

```
<uic-include src="example.com/foo#content" timeout="42000" required="false"/>
<uic-fetch src="example.com/foo" timeout="42000" required="false" name="foo"/>
```

The URL, referenced with the src Attribute will be fetched. It can than be references by the spcified name.
E.g. like so:
```
<uic-include src="foo#content"/>
```


74 changes: 55 additions & 19 deletions composition/html_content_parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
const (
UicRemove = "uic-remove"
UicInclude = "uic-include"
UicFetch = "uic-fetch"
UicFragment = "uic-fragment"
UicTail = "uic-tail"
ScriptTypeMeta = "text/uic-meta"
Expand Down Expand Up @@ -139,12 +140,22 @@ forloop:
}
continue
}
if string(tag) == UicInclude {
if fd, replaceText, err := getInclude(z, attrs); err != nil {
if string(tag) == UicFetch {
if fd, err := getFetch(z, attrs); err != nil {
return err
} else {
c.requiredContent[fd.URL] = fd
bodyBuff.WriteString(replaceText)
continue
}
}
if string(tag) == UicInclude {
if replaceTextStart, replaceTextEnd, err := getInclude(z, attrs); err != nil {
return err
} else {
bodyBuff.WriteString(replaceTextStart)
// Enhancement: WriteOut sub tree, to allow alternative content
// for optional includes.
bodyBuff.WriteString(replaceTextEnd)
continue
}
}
Expand Down Expand Up @@ -187,11 +198,13 @@ forloop:
break forloop
case tt == html.StartTagToken || tt == html.SelfClosingTagToken:
if string(tag) == UicInclude {
if fd, replaceText, err := getInclude(z, attrs); err != nil {
if replaceTextStart, replaceTextEnd, err := getInclude(z, attrs); err != nil {
return nil, nil, err
} else {
dependencies = append(dependencies, fd)
fmt.Fprintf(buff, replaceText)
fmt.Fprintf(buff, replaceTextStart)
// Enhancement: WriteOut sub tree, to allow alternative content
// for optional includes.
fmt.Fprintf(buff, replaceTextEnd)
continue
}
}
Expand All @@ -211,47 +224,70 @@ forloop:
return StringFragment(buff.String()), dependencies, nil
}

func getInclude(z *html.Tokenizer, attrs []html.Attribute) (*FetchDefinition, string, error) {
fd := &FetchDefinition{}

func getInclude(z *html.Tokenizer, attrs []html.Attribute) (startMarker, endMarker string, error error) {
var srcString string
if url, hasUrl := getAttr(attrs, "src"); !hasUrl {
return nil, "", fmt.Errorf("include definition without src %s", z.Raw())
return "", "", fmt.Errorf("include definition without src %s", z.Raw())
} else {
srcString = strings.TrimSpace(url.Val)
if strings.HasPrefix(srcString, "#") {
srcString = srcString[1:]
}
}

if hashPosition := strings.Index(srcString, "#"); hashPosition > -1 {
fd.URL = srcString[:hashPosition]
required := false
if r, hasRequired := getAttr(attrs, "required"); hasRequired {
if requiredBool, err := strconv.ParseBool(r.Val); err != nil {
return "", "", fmt.Errorf("error parsing bool in %s: %s", z.Raw(), err.Error())
} else {
required = requiredBool
}
}

if required {
return fmt.Sprintf("§[> %s]§", srcString), "", nil
} else {
fd.URL = srcString
return fmt.Sprintf("§[#> %s]§", srcString), fmt.Sprintf("§[/%s]§", srcString), nil
}
}

func getFetch(z *html.Tokenizer, attrs []html.Attribute) (*FetchDefinition, error) {
fd := &FetchDefinition{}

url, hasUrl := getAttr(attrs, "src")
if !hasUrl {
return nil, fmt.Errorf("include definition without src %s", z.Raw())
}
fd.URL = strings.TrimSpace(url.Val)

if name, hasName := getAttr(attrs, "name"); hasName {
fd.Name = name.Val
} else {
fd.Name = urlToName(fd.URL)
}
fd.Name = urlToName(fd.URL)

if timeout, hasTimeout := getAttr(attrs, "timeout"); hasTimeout {
if timeoutInt, err := strconv.Atoi(timeout.Val); err != nil {
return nil, "", fmt.Errorf("error parsing timeout in %s: %s", z.Raw(), err.Error())
return nil, fmt.Errorf("error parsing timeout in %s: %s", z.Raw(), err.Error())
} else {
fd.Timeout = time.Millisecond * time.Duration(timeoutInt)
}
}

if required, hasRequired := getAttr(attrs, "required"); hasRequired {
if requiredBool, err := strconv.ParseBool(required.Val); err != nil {
return nil, "", fmt.Errorf("error parsing bool in %s: %s", z.Raw(), err.Error())
return nil, fmt.Errorf("error parsing bool in %s: %s", z.Raw(), err.Error())
} else {
fd.Required = requiredBool
}
}

placeholder := urlToName(srcString)

attr, found := getAttr(attrs, "discoveredBy")
if found {
fd.DiscoveredBy(attr.Val)
}

return fd, fmt.Sprintf("§[> %s]§", placeholder), nil
return fd, nil
}

func ParseHeadFragment(fragment *StringFragment, headPropertyMap map[string]string) error {
Expand Down
85 changes: 40 additions & 45 deletions composition/html_content_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -231,8 +231,11 @@ var integratedTestHtml = `<html>
</uic-fragment>
<uic-fragment name="content">
Bli Bla blub
<uic-include src="example.com/foo#content" timeout="42000" required="true"/>
<uic-include src="example.com/optional#content" timeout="100" required="false"/>
<uic-fetch src="example.com/foo" timeout="100" required="true"/>
<uic-include src="example.com/foo#content" required="true"/>
<uic-include src="example.com/optional#content">
<p>some alternative text</p>
</uic-include>
<div uic-remove>
Some element for testing
</div>
Expand Down Expand Up @@ -264,7 +267,9 @@ var integratedTestHtmlExpectedHeadline = `<h1>This is a headline</h1>`
var integratedTestHtmlExpectedContent = `
Bli Bla blub
§[> example.com/foo#content]§
§[> example.com/optional#content]§
§[#> example.com/optional#content]§
<p>some alternative text</p>
§[/example.com/optional#content]§
<hr/>
Bli Bla blub`

Expand Down Expand Up @@ -361,16 +366,16 @@ func Test_HtmlContentParser_parseBody(t *testing.T) {
</ul>
<uic-fragment name="headline">
<h1>Headline</h1>
<uic-include src="example.com/optional#content" timeout="100" required="false"/>
<uic-include src="example.com/optional#content"/>
</uic-fragment>
<uic-fragment name="content">
some content
<uic-include src="example.com/foo#content" timeout="42000" required="true"/>
<uic-include src="example.com/optional#content" timeout="100" required="false"/>
<uic-include src="example.com/foo#content" required="true"/>
<uic-include src="example.com/optional#content" required="false"/>
</uic-fragment>
<uic-tail>
<!-- tail -->
<uic-include src="example.com/tail" timeout="100" required="false"/>
<uic-include src="example.com/tail" timeout="100" required="true"/>
</uic-tail>
</body>`))

Expand All @@ -381,16 +386,35 @@ func Test_HtmlContentParser_parseBody(t *testing.T) {

a.Equal(3, len(c.Body()))
eqFragment(t, "<h1>Default Fragment Content</h1><br>", c.Body()[""])
eqFragment(t, `<h1>Headline</h1> §[> example.com/optional#content]§`, c.Body()["headline"])
eqFragment(t, `some content §[> example.com/foo#content]§ §[> example.com/optional#content]§`, c.Body()["content"])
eqFragment(t, `<h1>Headline</h1> §[#> example.com/optional#content]§§[/example.com/optional#content]§`, c.Body()["headline"])
eqFragment(t, `some content §[> example.com/foo#content]§ §[#> example.com/optional#content]§§[/example.com/optional#content]§`, c.Body()["content"])
eqFragment(t, "<!-- tail -->§[> example.com/tail]§", c.Tail())

eqFragment(t, `some="attribute"`, c.BodyAttributes())

a.Equal(3, len(c.RequiredContent()))
}

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

parser := &HtmlContentParser{}
z := html.NewTokenizer(bytes.NewBufferString(`<body>
foo
<uic-fetch src="example.com/foo" timeout="42000" required="true" name="foo"/>
<uic-fetch src="example.com/optional" timeout="100" required="false"/>
</body>`))

z.Next() // At <body ..
c := NewMemoryContent()
err := parser.parseBody(z, c)
a.NoError(err)

eqFragment(t, "foo", c.Body()[""])

a.Equal(2, len(c.RequiredContent()))
a.Equal(&FetchDefinition{
URL: "example.com/foo",
Name: "example.com/foo",
Name: "foo",
Timeout: time.Millisecond * 42000,
Required: true,
}, c.requiredContent["example.com/foo"])
Expand All @@ -401,12 +425,6 @@ func Test_HtmlContentParser_parseBody(t *testing.T) {
Timeout: time.Millisecond * 100,
Required: false,
}, c.requiredContent["example.com/optional"])
a.Equal(&FetchDefinition{
URL: "example.com/tail",
Name: "example.com/tail",
Timeout: time.Millisecond * 100,
Required: false,
}, c.requiredContent["example.com/tail"])
}

func Test_HtmlContentParser_parseBody_OnlyDefaultFragment(t *testing.T) {
Expand All @@ -415,7 +433,7 @@ func Test_HtmlContentParser_parseBody_OnlyDefaultFragment(t *testing.T) {
parser := &HtmlContentParser{}
z := html.NewTokenizer(bytes.NewBufferString(`<body>
<h1>Default Fragment Content</h1><br>
<uic-include src="example.com/foo#content" timeout="42000" required="true"/>
<uic-include src="example.com/foo#content" required="true"/>
</body>`))

z.Next() // At <body ..
Expand All @@ -425,14 +443,6 @@ func Test_HtmlContentParser_parseBody_OnlyDefaultFragment(t *testing.T) {

a.Equal(1, len(c.Body()))
eqFragment(t, "<h1>Default Fragment Content</h1><br> §[> example.com/foo#content]§", c.Body()[""])

a.Equal(1, len(c.RequiredContent()))
a.Equal(&FetchDefinition{
URL: "example.com/foo",
Name: "example.com/foo",
Timeout: time.Millisecond * 42000,
Required: true,
}, c.requiredContent["example.com/foo"])
}

func Test_HtmlContentParser_parseBody_DefaultFragmentOverwritten(t *testing.T) {
Expand Down Expand Up @@ -478,8 +488,8 @@ func Test_HtmlContentParser_parseFragment(t *testing.T) {
z := html.NewTokenizer(bytes.NewBufferString(`<uic-fragment name="content">
Bli Bla blub
<br>
<uic-include src="example.com/foo#content" timeout="42000" required="true"/>
<uic-include src="example.com/optional#content" timeout="100" required="false"/>
<uic-include src="example.com/foo#content" required="true"/>
<uic-include src="example.com/optional#content" required="false"/>
<div uic-remove>
<br>
Some element for testing
Expand All @@ -489,29 +499,14 @@ func Test_HtmlContentParser_parseFragment(t *testing.T) {
</uic-fragment><testend>`))

z.Next() // At <uic-fragment name ..
f, deps, err := parseFragment(z)
f, _, err := parseFragment(z)
a.NoError(err)

a.Equal(2, len(deps))
a.Equal(&FetchDefinition{
URL: "example.com/foo",
Name: "example.com/foo",
Timeout: time.Millisecond * 42000,
Required: true,
}, deps[0])

a.Equal(&FetchDefinition{
URL: "example.com/optional",
Name: "example.com/optional",
Timeout: time.Millisecond * 100,
Required: false,
}, deps[1])

sFragment := f.(StringFragment)
expected := `Bli Bla blub
<br>
§[> example.com/foo#content]§
§[> example.com/optional#content]§
§[#> example.com/optional#content]§§[/example.com/optional#content]§
<hr/>
Bli Bla §[ aVariable ]§ blub`
eqFragment(t, expected, sFragment)
Expand Down
Loading

0 comments on commit bf9413c

Please sign in to comment.