Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions attributes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package blackfriday

import "strings"

// attr - Abstraction for html attribute
type attr []string

// Add - adds one more attribute value
func (a attr) add(value string) attr {
for _, item := range a {
if item == value {
return a
}
}
return append(a, value)
}

// Remove - removes given value from attribute
func (a attr) remove(value string) attr {
for i := range a {
if a[i] == value {
return append(a[:i], a[i+1:]...)
}
}
return a
}

func (a attr) String() string {
return strings.Join(a, " ")
}

// Attributes - store for many attributes
type Attributes struct {
attrsMap map[string]attr
keys []string
}

// NewAttributes - creates new Attributes instance
func NewAttributes() *Attributes {
return &Attributes{
attrsMap: make(map[string]attr),
}
}

// Add - adds attribute if not exists and sets value for it
func (a *Attributes) Add(name, value string) *Attributes {
if _, ok := a.attrsMap[name]; !ok {
a.attrsMap[name] = make(attr, 0)
a.keys = append(a.keys, name)
}

a.attrsMap[name] = a.attrsMap[name].add(value)
return a
}

// Remove - removes attribute by name
func (a *Attributes) Remove(name string) *Attributes {
for i := range a.keys {
if a.keys[i] == name {
a.keys = append(a.keys[:i], a.keys[i+1:]...)
}
}

delete(a.attrsMap, name)
return a
}

// RemoveValue - removes given value from attribute by name
// If given attribues become empty it alose removes entire attribute
func (a *Attributes) RemoveValue(name, value string) *Attributes {
if attr, ok := a.attrsMap[name]; ok {
a.attrsMap[name] = attr.remove(value)
if len(a.attrsMap[name]) == 0 {
a.Remove(name)
}
}
return a
}

// Empty - checks if attributes is empty
func (a *Attributes) Empty() bool {
return len(a.keys) == 0
}

func (a *Attributes) String() string {
r := []string{}
for _, attrName := range a.keys {
r = append(r, attrName+"=\""+a.attrsMap[attrName].String()+"\"")
}

return strings.Join(r, " ")
}
132 changes: 132 additions & 0 deletions attributes_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package blackfriday

import (
"bytes"
"testing"
)

func TestEmtyAttributes(t *testing.T) {
a := NewAttributes()
r := a.String()
e := ""
if r != e {
t.Errorf("Expected: %s\nActual: %s\n", e, r)
}
}

func TestAddOneAttribute(t *testing.T) {
a := NewAttributes()
a.Add("class", "wrapper")
r := a.String()
e := "class=\"wrapper\""
if r != e {
t.Errorf("Expected: %s\nActual: %s\n", e, r)
}
}

func TestAddFewValuesToOneAttribute(t *testing.T) {
a := NewAttributes()
a.Add("class", "wrapper").Add("class", "-with-image")
r := a.String()
e := "class=\"wrapper -with-image\""
if r != e {
t.Errorf("Expected: %s\nActual: %s\n", e, r)
}
}

func TestAddSameValueToAttribute(t *testing.T) {
a := NewAttributes()
a.Add("class", "wrapper").Add("class", "wrapper")
r := a.String()
e := "class=\"wrapper\""
if r != e {
t.Errorf("Expected: %s\nActual: %s\n", e, r)
}
}

func TestRemoveValueFromOneAttribute(t *testing.T) {
a := NewAttributes()
a.Add("class", "wrapper").Add("class", "-with-image")
a.RemoveValue("class", "wrapper")
r := a.String()
e := "class=\"-with-image\""
if r != e {
t.Errorf("Expected: %s\nActual: %s\n", e, r)
}
}

func TestRemoveWholeAttribute(t *testing.T) {
a := NewAttributes()
a.Add("class", "wrapper")
a.Remove("class")
r := a.String()
e := ""
if r != e {
t.Errorf("Expected: %s\nActual: %s\n", e, r)
}
}

func TestRemoveWholeAttributeByValue(t *testing.T) {
a := NewAttributes()
a.Add("class", "wrapper")
a.RemoveValue("class", "wrapper")
r := a.String()
e := ""
if r != e {
t.Errorf("Expected: %s\nActual: %s\n", e, r)
}
}

func TestAddFewAttributes(t *testing.T) {
a := NewAttributes()
a.Add("class", "wrapper").Add("id", "main-block")
r := a.String()
e := "class=\"wrapper\" id=\"main-block\""
if r != e {
t.Errorf("Expected: %s\nActual: %s\n", e, r)
}
}

func TestAddComplexAttributes(t *testing.T) {
a := NewAttributes()
a.
Add("style", "background: #fff;").
Add("style", "font-size: 14px;").
Add("data-test-id", "block")
r := a.String()
e := "style=\"background: #fff; font-size: 14px;\" data-test-id=\"block\""
if r != e {
t.Errorf("Expected: %s\nActual: %s\n", e, r)
}
}

func TestASTModification(t *testing.T) {
input := "\nPicture signature\n![alt text](/p.jpg)\n"
expected := "<p class=\"img\">Picture signature\n<img src=\"/p.jpg\" alt=\"alt text\" /></p>\n"

r := NewHTMLRenderer(HTMLRendererParameters{
Flags: CommonHTMLFlags,
})
var buf bytes.Buffer
optList := []Option{
WithRenderer(r),
WithExtensions(CommonExtensions)}
parser := New(optList...)
ast := parser.Parse([]byte(input))
r.RenderHeader(&buf, ast)
ast.Walk(func(node *Node, entering bool) WalkStatus {
if node.Type == Image && entering && node.Parent.Type == Paragraph {
node.Parent.Attributes.Add("class", "img")
}
return GoToNext
})
ast.Walk(func(node *Node, entering bool) WalkStatus {
return r.RenderNode(&buf, node, entering)
})
r.RenderFooter(&buf, ast)
actual := buf.String()

if actual != expected {
t.Errorf("Expected: %s\nActual: %s\n", expected, actual)
}
}
Loading