improved package api

This commit is contained in:
Erik Winter 2022-06-11 09:30:22 +02:00
parent f744089b62
commit 3c3ea3c767
19 changed files with 184 additions and 121 deletions

33
adoc.go
View File

@ -1,22 +1,29 @@
package adoc package adoc
import ( import (
"time" "io"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/formatter"
"ewintr.nl/adoc/parser"
) )
type ADoc struct { func NewDocument() *document.Document {
Title string return document.New()
Attributes map[string]string
Author string
Date time.Time
Content []element.Element
} }
func New() *ADoc { func NewParser(reader io.Reader) *parser.Parser {
return &ADoc{ return parser.New(reader)
Attributes: map[string]string{}, }
Content: []element.Element{},
} func NewTextFormatter() *formatter.Text {
return formatter.NewText()
}
func NewAsciiDocFormatter() *formatter.AsciiDoc {
return formatter.NewAsciiDoc()
}
func NewHTMLFormatter() *formatter.HTML {
return formatter.NewHTML()
} }

22
document/document.go Normal file
View File

@ -0,0 +1,22 @@
package document
import (
"time"
"ewintr.nl/adoc/element"
)
type Document struct {
Title string
Attributes map[string]string
Author string
Date time.Time
Content []element.Element
}
func New() *Document {
return &Document{
Attributes: map[string]string{},
Content: []element.Element{},
}
}

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
@ -14,13 +14,13 @@ func TestCodeBlock(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
input string input string
exp *adoc.ADoc exp *document.Document
}{ }{
{ {
name: "empty", name: "empty",
input: `---- input: `----
----`, ----`,
exp: &adoc.ADoc{ exp: &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: []element.Element{element.CodeBlock{}}, Content: []element.Element{element.CodeBlock{}},
}, },
@ -32,7 +32,7 @@ code
more more
----`, ----`,
exp: &adoc.ADoc{ exp: &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: []element.Element{element.CodeBlock{ Content: []element.Element{element.CodeBlock{
element.Word("code"), element.Word("code"),
@ -48,7 +48,7 @@ more
code code
---- ----
`, `,
exp: &adoc.ADoc{ exp: &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: []element.Element{element.CodeBlock{ Content: []element.Element{element.CodeBlock{
element.Word("code"), element.Word("code"),
@ -63,7 +63,7 @@ code
more more
`, `,
exp: &adoc.ADoc{ exp: &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: []element.Element{ Content: []element.Element{
element.Paragraph{[]element.Element{ element.Paragraph{[]element.Element{

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
"time" "time"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
@ -15,12 +15,12 @@ func TestHeader(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
input string input string
exp *adoc.ADoc exp *document.Document
}{ }{
{ {
name: "just title", name: "just title",
input: "= Title", input: "= Title",
exp: &adoc.ADoc{ exp: &document.Document{
Title: "Title", Title: "Title",
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: []element.Element{}, Content: []element.Element{},
@ -29,7 +29,7 @@ func TestHeader(t *testing.T) {
{ {
name: "empty title", name: "empty title",
input: "= ", input: "= ",
exp: adoc.New(), exp: document.New(),
}, },
{ {
name: "full header", name: "full header",
@ -40,7 +40,7 @@ Author Name
:key2: value2 :key2: value2
First paragraph`, First paragraph`,
exp: &adoc.ADoc{ exp: &document.Document{
Title: "Title with words", Title: "Title with words",
Date: time.Date(2022, time.Month(3), 4, 0, 0, 0, 0, time.UTC), Date: time.Date(2022, time.Month(3), 4, 0, 0, 0, 0, time.UTC),
Author: "Author Name", Author: "Author Name",

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
@ -49,7 +49,7 @@ func TestLink(t *testing.T) {
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
par := parser.New(strings.NewReader(tc.input)) par := parser.New(strings.NewReader(tc.input))
exp := &adoc.ADoc{ exp := &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: tc.exp, Content: tc.exp,
} }

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
@ -69,7 +69,7 @@ and some text`,
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
par := parser.New(strings.NewReader(tc.input)) par := parser.New(strings.NewReader(tc.input))
exp := &adoc.ADoc{ exp := &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: tc.exp, Content: tc.exp,
} }

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
@ -14,12 +14,12 @@ func TestParagraph(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
input string input string
exp *adoc.ADoc exp *document.Document
}{ }{
{ {
name: "single paragraph", name: "single paragraph",
input: "some text", input: "some text",
exp: &adoc.ADoc{ exp: &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: []element.Element{ Content: []element.Element{
element.Paragraph{Elements: []element.Element{ element.Paragraph{Elements: []element.Element{
@ -36,7 +36,7 @@ func TestParagraph(t *testing.T) {
paragraph one paragraph one
paragraph two`, paragraph two`,
exp: &adoc.ADoc{ exp: &document.Document{
Title: "Title", Title: "Title",
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: []element.Element{ Content: []element.Element{
@ -61,7 +61,7 @@ two
three three
`, `,
exp: &adoc.ADoc{ exp: &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: []element.Element{ Content: []element.Element{
element.Paragraph{Elements: []element.Element{element.Word("one")}}, element.Paragraph{Elements: []element.Element{element.Word("one")}},

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
@ -93,7 +93,7 @@ func TestStyles(t *testing.T) {
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
par := parser.New(strings.NewReader(tc.input)) par := parser.New(strings.NewReader(tc.input))
exp := &adoc.ADoc{ exp := &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: tc.exp, Content: tc.exp,
} }

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
@ -38,7 +38,7 @@ func TestSubTitle(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
exp := &adoc.ADoc{ exp := &document.Document{
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: tc.exp, Content: tc.exp,
} }

View File

@ -1,16 +0,0 @@
package format
import (
"fmt"
"ewintr.nl/adoc"
)
func Text(doc *adoc.ADoc) string {
txt := fmt.Sprintf("%s\n\n", doc.Title)
for _, el := range doc.Content {
txt += fmt.Sprintf("%s\n\n", el.Text())
}
return txt
}

View File

@ -1,17 +1,23 @@
package format package formatter
import ( import (
"fmt" "fmt"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
) )
func AsciiDoc(doc *adoc.ADoc) string { type AsciiDoc struct{}
return fmt.Sprintf("%s\n%s", AsciiDocHeader(doc), AsciiDocFragment(doc.Content...))
func NewAsciiDoc() *AsciiDoc {
return &AsciiDoc{}
} }
func AsciiDocHeader(doc *adoc.ADoc) string { func (ad *AsciiDoc) Format(doc *document.Document) string {
return fmt.Sprintf("%s\n%s", asciiDocHeader(doc), ad.FormatFragments(doc.Content...))
}
func asciiDocHeader(doc *document.Document) string {
header := fmt.Sprintf("= %s\n", doc.Title) header := fmt.Sprintf("= %s\n", doc.Title)
if doc.Author != "" { if doc.Author != "" {
header += fmt.Sprintf("%s\n", doc.Author) header += fmt.Sprintf("%s\n", doc.Author)
@ -26,16 +32,16 @@ func AsciiDocHeader(doc *adoc.ADoc) string {
return header return header
} }
func AsciiDocFragment(els ...element.Element) string { func (ad *AsciiDoc) FormatFragments(els ...element.Element) string {
var asciiDoc string var asciiDoc string
for _, el := range els { for _, el := range els {
asciiDoc += asciiDocElement(el) asciiDoc += ad.asciiDocElement(el)
} }
return asciiDoc return asciiDoc
} }
func asciiDocElement(el element.Element) string { func (ad *AsciiDoc) asciiDocElement(el element.Element) string {
switch v := el.(type) { switch v := el.(type) {
case element.SubTitle: case element.SubTitle:
return fmt.Sprintf("== %s\n\n", v.Text()) return fmt.Sprintf("== %s\n\n", v.Text())
@ -46,19 +52,19 @@ func asciiDocElement(el element.Element) string {
for _, i := range v { for _, i := range v {
items = append(items, i) items = append(items, i)
} }
return fmt.Sprintf("%s\n", AsciiDocFragment(items...)) return fmt.Sprintf("%s\n", ad.FormatFragments(items...))
case element.ListItem: case element.ListItem:
return fmt.Sprintf("* %s\n", AsciiDocFragment(v...)) return fmt.Sprintf("* %s\n", ad.FormatFragments(v...))
case element.CodeBlock: case element.CodeBlock:
return fmt.Sprintf("----\n%s\n----\n\n", v.Text()) return fmt.Sprintf("----\n%s\n----\n\n", v.Text())
case element.Paragraph: case element.Paragraph:
return fmt.Sprintf("%s\n\n", AsciiDocFragment(v.Elements...)) return fmt.Sprintf("%s\n\n", ad.FormatFragments(v.Elements...))
case element.Strong: case element.Strong:
return fmt.Sprintf("*%s*", AsciiDocFragment(v...)) return fmt.Sprintf("*%s*", ad.FormatFragments(v...))
case element.Emphasis: case element.Emphasis:
return fmt.Sprintf("_%s_", AsciiDocFragment(v...)) return fmt.Sprintf("_%s_", ad.FormatFragments(v...))
case element.Code: case element.Code:
return fmt.Sprintf("`%s`", AsciiDocFragment(v...)) return fmt.Sprintf("`%s`", ad.FormatFragments(v...))
case element.Link: case element.Link:
return fmt.Sprintf("%s[%s]", v.URL, v.Title) return fmt.Sprintf("%s[%s]", v.URL, v.Title)
case element.Word: case element.Word:

View File

@ -1,48 +1,46 @@
package format_test package formatter_test
import ( import (
"strings"
"testing" "testing"
"time" "time"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/format" "ewintr.nl/adoc/formatter"
"ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
) )
func TestAsciiDoc(t *testing.T) { func TestAsciiDoc(t *testing.T) {
input := `= A Title input := &document.Document{
Title: "A Title",
Some document Author: "Author",
Date: time.Date(2022, time.Month(6), 11, 0, 0, 0, 0, time.UTC),
With some text.
`
doc := parser.New(strings.NewReader(input)).Parse()
test.Equals(t, input, format.AsciiDoc(doc))
}
func TestAsciiDocHeader(t *testing.T) {
input := &adoc.ADoc{
Title: "title",
Author: "author",
Date: time.Date(2022, time.Month(6), 11, 12, 0, 0, 0, time.UTC),
Attributes: map[string]string{ Attributes: map[string]string{
"key1": "value 1", "key1": "value 1",
"key2": "value 2", "key2": "value 2",
}, },
Content: []element.Element{
element.Paragraph{
Elements: []element.Element{
element.Word("some"),
element.WhiteSpace(" "),
element.Word("text"),
},
},
},
} }
exp := `= title
author exp := `= A Title
Author
2022-06-11 2022-06-11
:key1: value 1 :key1: value 1
:key2: value 2 :key2: value 2
some text
` `
test.Equals(t, exp, format.AsciiDocHeader(input)) test.Equals(t, exp, formatter.NewAsciiDoc().Format(input))
} }
func TestAsciiDocFragment(t *testing.T) { func TestAsciiDocFragment(t *testing.T) {
@ -172,7 +170,7 @@ some text
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
test.Equals(t, tc.exp, format.AsciiDocFragment(tc.input)) test.Equals(t, tc.exp, formatter.NewAsciiDoc().FormatFragments(tc.input))
}) })
} }
} }

11
formatter/formatter.go Normal file
View File

@ -0,0 +1,11 @@
package formatter
import (
"ewintr.nl/adoc/document"
"ewintr.nl/adoc/element"
)
type Formatter interface {
Format(doc *document.Document) string
FormatFragments(els ...element.Element) string
}

View File

@ -1,10 +1,10 @@
package format package formatter
import ( import (
"fmt" "fmt"
"html" "html"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/go-kit/slugify" "ewintr.nl/go-kit/slugify"
) )
@ -19,20 +19,26 @@ const htmlPageTemplate = `<!DOCTYPE html>
</html> </html>
` `
func HTML(doc *adoc.ADoc) string { type HTML struct{}
return fmt.Sprintf(htmlPageTemplate, html.EscapeString(doc.Title), HTMLFragment(doc.Content...))
func NewHTML() *HTML {
return &HTML{}
} }
func HTMLFragment(els ...element.Element) string { func (h *HTML) Format(doc *document.Document) string {
return fmt.Sprintf(htmlPageTemplate, html.EscapeString(doc.Title), h.FormatFragments(doc.Content...))
}
func (h *HTML) FormatFragments(els ...element.Element) string {
var html string var html string
for _, el := range els { for _, el := range els {
html += htmlElement(el) html += h.htmlElement(el)
} }
return html return html
} }
func htmlElement(el element.Element) string { func (h *HTML) htmlElement(el element.Element) string {
switch v := el.(type) { switch v := el.(type) {
case element.SubTitle: case element.SubTitle:
return fmt.Sprintf("<h2 id=%q>%s</h2>\n", slugify.Slugify(v.Text()), html.EscapeString(v.Text())) return fmt.Sprintf("<h2 id=%q>%s</h2>\n", slugify.Slugify(v.Text()), html.EscapeString(v.Text()))
@ -43,19 +49,19 @@ func htmlElement(el element.Element) string {
for _, i := range v { for _, i := range v {
items = append(items, i) items = append(items, i)
} }
return fmt.Sprintf("<ul>\n%s</ul>\n", HTMLFragment(items...)) return fmt.Sprintf("<ul>\n%s</ul>\n", h.FormatFragments(items...))
case element.ListItem: case element.ListItem:
return fmt.Sprintf("<li>%s</li>\n", HTMLFragment(v...)) return fmt.Sprintf("<li>%s</li>\n", h.FormatFragments(v...))
case element.CodeBlock: case element.CodeBlock:
return fmt.Sprintf("<pre><code>%s</code></pre>", html.EscapeString(v.Text())) return fmt.Sprintf("<pre><code>%s</code></pre>", html.EscapeString(v.Text()))
case element.Paragraph: case element.Paragraph:
return fmt.Sprintf("<p>%s</p>\n", HTMLFragment(v.Elements...)) return fmt.Sprintf("<p>%s</p>\n", h.FormatFragments(v.Elements...))
case element.Strong: case element.Strong:
return fmt.Sprintf("<strong>%s</strong>", HTMLFragment(v...)) return fmt.Sprintf("<strong>%s</strong>", h.FormatFragments(v...))
case element.Emphasis: case element.Emphasis:
return fmt.Sprintf("<em>%s</em>", HTMLFragment(v...)) return fmt.Sprintf("<em>%s</em>", h.FormatFragments(v...))
case element.Code: case element.Code:
return fmt.Sprintf("<code>%s</code>", HTMLFragment(v...)) return fmt.Sprintf("<code>%s</code>", h.FormatFragments(v...))
case element.Link: case element.Link:
return fmt.Sprintf("<a href=%q>%s</a>", v.URL, html.EscapeString(v.Title)) return fmt.Sprintf("<a href=%q>%s</a>", v.URL, html.EscapeString(v.Title))
case element.Word: case element.Word:

View File

@ -1,11 +1,11 @@
package format_test package formatter_test
import ( import (
"strings" "strings"
"testing" "testing"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/format" "ewintr.nl/adoc/formatter"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
) )
@ -29,7 +29,7 @@ With some text.`
</html> </html>
` `
doc := parser.New(strings.NewReader(input)).Parse() doc := parser.New(strings.NewReader(input)).Parse()
test.Equals(t, exp, format.HTML(doc)) test.Equals(t, exp, formatter.NewHTML().Format(doc))
} }
func TestHTMLFragment(t *testing.T) { func TestHTMLFragment(t *testing.T) {
@ -160,7 +160,7 @@ func TestHTMLFragment(t *testing.T) {
}, },
} { } {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
test.Equals(t, tc.exp, format.HTMLFragment(tc.input)) test.Equals(t, tc.exp, formatter.NewHTML().FormatFragments(tc.input))
}) })
} }
} }

29
formatter/text.go Normal file
View File

@ -0,0 +1,29 @@
package formatter
import (
"fmt"
"ewintr.nl/adoc/document"
"ewintr.nl/adoc/element"
)
type Text struct{}
func NewText() *Text {
return &Text{}
}
func (t *Text) Format(doc *document.Document) string {
txt := fmt.Sprintf("%s\n\n", doc.Title)
txt += t.FormatFragments(doc.Content...)
return txt
}
func (t *Text) FormatFragments(els ...element.Element) string {
var text string
for _, el := range els {
text += fmt.Sprintf("%s\n\n", el.Text())
}
return text
}

View File

@ -1,10 +1,10 @@
package format_test package formatter_test
import ( import (
"strings" "strings"
"testing" "testing"
"ewintr.nl/adoc/format" "ewintr.nl/adoc/formatter"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
) )
@ -25,5 +25,5 @@ With some text.
` `
doc := parser.New(strings.NewReader(input)).Parse() doc := parser.New(strings.NewReader(input)).Parse()
test.Equals(t, exp, format.Text(doc)) test.Equals(t, exp, formatter.NewText().Format(doc))
} }

View File

@ -3,13 +3,13 @@ package parser
import ( import (
"io" "io"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/token" "ewintr.nl/adoc/token"
) )
type Parser struct { type Parser struct {
doc *adoc.ADoc doc *document.Document
tr *token.TokenReader tr *token.TokenReader
els []element.Element els []element.Element
} }
@ -21,13 +21,13 @@ func New(reader io.Reader) *Parser {
func NewParserFromChannel(toks chan token.Token) *Parser { func NewParserFromChannel(toks chan token.Token) *Parser {
return &Parser{ return &Parser{
doc: adoc.New(), doc: document.New(),
tr: token.NewTokenReader(toks), tr: token.NewTokenReader(toks),
els: []element.Element{}, els: []element.Element{},
} }
} }
func (p *Parser) Parse() *adoc.ADoc { func (p *Parser) Parse() *document.Document {
result, ok := element.NewHeaderFromTokens(p.tr) result, ok := element.NewHeaderFromTokens(p.tr)
if ok { if ok {
if h, ok := result.Element.(element.Header); ok { if h, ok := result.Element.(element.Header); ok {

View File

@ -4,7 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"ewintr.nl/adoc" "ewintr.nl/adoc/document"
"ewintr.nl/adoc/element" "ewintr.nl/adoc/element"
"ewintr.nl/adoc/parser" "ewintr.nl/adoc/parser"
"ewintr.nl/go-kit/test" "ewintr.nl/go-kit/test"
@ -14,11 +14,11 @@ func TestParser(t *testing.T) {
for _, tc := range []struct { for _, tc := range []struct {
name string name string
input string input string
exp *adoc.ADoc exp *document.Document
}{ }{
{ {
name: "empty", name: "empty",
exp: adoc.New(), exp: document.New(),
}, },
{ {
name: "codeblock paragraph edge", name: "codeblock paragraph edge",
@ -29,7 +29,7 @@ a code block
---- ----
And then some text`, And then some text`,
exp: &adoc.ADoc{ exp: &document.Document{
Title: "some title", Title: "some title",
Attributes: map[string]string{}, Attributes: map[string]string{},
Content: []element.Element{ Content: []element.Element{