Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added ability to manipulate HTML-attributes with AST #483

Open
wants to merge 5 commits into
base: v2
Choose a base branch
from
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