From c84448d85e0629874ad93d6c4fbd570fe9a688a5 Mon Sep 17 00:00:00 2001 From: Artem Pavlov Date: Sun, 2 Sep 2018 15:26:37 +0300 Subject: [PATCH 1/5] added ability to manipulate html-attributes on ast node --- attributes.go | 87 ++++++++++++++++++++++++++++++++++++ attributes_test.go | 86 +++++++++++++++++++++++++++++++++++ html.go | 109 +++++++++++++++++++++------------------------ node.go | 7 ++- 4 files changed, 229 insertions(+), 60 deletions(-) create mode 100644 attributes.go create mode 100644 attributes_test.go diff --git a/attributes.go b/attributes.go new file mode 100644 index 00000000..7d400f02 --- /dev/null +++ b/attributes.go @@ -0,0 +1,87 @@ +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 { + 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, " ") +} diff --git a/attributes_test.go b/attributes_test.go new file mode 100644 index 00000000..7f2977fe --- /dev/null +++ b/attributes_test.go @@ -0,0 +1,86 @@ +package blackfriday + +import "testing" + +func TestEmtyAttributes(t *testing.T) { + a := NewAttributes() + r := a.String() + e := "" + if r != e { + t.Errorf("Emmpty attributes must return empty string\nExpected: %s\nActual: %s", e, r) + } +} + +func TestAddOneAttribute(t *testing.T) { + a := NewAttributes() + a.Add("class", "wrapper") + if s := a.String(); s != "class=\"wrapper\"" { + t.Errorf("Unexprected output: %s", s) + } +} + +func TestAddFewValuesToOneAttribute(t *testing.T) { + a := NewAttributes() + a.Add("class", "wrapper").Add("class", "-with-image") + if s := a.String(); s != "class=\"wrapper -with-image\"" { + t.Errorf("Unexpected output: %s", s) + } +} + +func TestRemoveValueFromOneAttribute(t *testing.T) { + a := NewAttributes() + a.Add("class", "wrapper").Add("class", "-with-image") + if s := a.String(); s != "class=\"wrapper -with-image\"" { + t.Errorf("Unexpected output: %s", s) + } + a.RemoveValue("class", "wrapper") + if s := a.String(); s != "class=\"-with-image\"" { + t.Errorf("Unexpected output: %s", s) + } +} + +func TestRemoveWholeAttribute(t *testing.T) { + a := NewAttributes() + a.Add("class", "wrapper") + if s := a.String(); s != "class=\"wrapper\"" { + t.Errorf("Unexprected output: %s", s) + } + a.Remove("class") + if a.String() != "" { + t.Errorf("Emmpty attributes must return empty string") + } +} + +func TestRemoveWholeAttributeByValue(t *testing.T) { + a := NewAttributes() + a.Add("class", "wrapper") + if s := a.String(); s != "class=\"wrapper\"" { + t.Errorf("Unexprected output: %s", s) + } + a.RemoveValue("class", "wrapper") + r := a.String() + e := "" + if r != e { + t.Errorf("Emmpty attributes must return empty string\nExpected: %s\nActual: %s", e, r) + } +} + +func TestAddFewAttributes(t *testing.T) { + a := NewAttributes() + a.Add("class", "wrapper").Add("id", "main-block") + if s := a.String(); s != "class=\"wrapper\" id=\"main-block\"" { + t.Errorf("Unexprected output: %s", s) + } +} + +func TestAddComplexAttributes(t *testing.T) { + a := NewAttributes() + a. + Add("style", "background: #fff;"). + Add("style", "font-size: 14px;"). + Add("data-test-id", "block") + e := "style=\"background: #fff; font-size: 14px;\" data-test-id=\"block\"" + if s := a.String(); s != e { + t.Errorf("Unexpected output: %s", s) + } +} diff --git a/html.go b/html.go index 284c8718..c97382f2 100644 --- a/html.go +++ b/html.go @@ -276,28 +276,22 @@ func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte { return link } -func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string { +func appendLinkAttrs(attrs *Attributes, flags HTMLFlags, link []byte) { if isRelativeLink(link) { - return attrs + return + } + if flags&HrefTargetBlank != 0 { + attrs.Add("target", "_blank") } - val := []string{} if flags&NofollowLinks != 0 { - val = append(val, "nofollow") + attrs.Add("rel", "nofollow") } if flags&NoreferrerLinks != 0 { - val = append(val, "noreferrer") + attrs.Add("rel", "noreferrer") } if flags&NoopenerLinks != 0 { - val = append(val, "noopener") - } - if flags&HrefTargetBlank != 0 { - attrs = append(attrs, "target=\"_blank\"") - } - if len(val) == 0 { - return attrs + attrs.Add("rel", "noopener") } - attr := fmt.Sprintf("rel=%q", strings.Join(val, " ")) - return append(attrs, attr) } func isMailto(link []byte) bool { @@ -316,23 +310,26 @@ func isSmartypantable(node *Node) bool { return pt != Link && pt != CodeBlock && pt != Code } -func appendLanguageAttr(attrs []string, info []byte) []string { +func appendLanguageAttr(attrs *Attributes, info []byte) { if len(info) == 0 { - return attrs + return } endOfLang := bytes.IndexAny(info, "\t ") if endOfLang < 0 { endOfLang = len(info) } - return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang])) + attrs.Add("class", fmt.Sprintf("language-%s", info[:endOfLang])) } -func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) { - w.Write(name) - if len(attrs) > 0 { - w.Write(spaceBytes) - w.Write([]byte(strings.Join(attrs, " "))) +func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs *Attributes) { + if attrs.Empty() { + r.out(w, name) + return } + + w.Write(name[:len(name)-1]) + w.Write(spaceBytes) + w.Write([]byte(attrs.String())) w.Write(gtBytes) r.lastOutputLen = 1 } @@ -414,7 +411,7 @@ var ( delCloseTag = []byte("") ttTag = []byte("") ttCloseTag = []byte("") - aTag = []byte("") aCloseTag = []byte("") preTag = []byte("
")
 	preCloseTag        = []byte("
") @@ -440,9 +437,9 @@ var ( dtCloseTag = []byte("") tableTag = []byte("") tableCloseTag = []byte("
") - tdTag = []byte("") tdCloseTag = []byte("") - thTag = []byte("") thCloseTag = []byte("") theadTag = []byte("") theadCloseTag = []byte("") @@ -450,17 +447,17 @@ var ( tbodyCloseTag = []byte("") trTag = []byte("") trCloseTag = []byte("") - h1Tag = []byte("") h1CloseTag = []byte("") - h2Tag = []byte("") h2CloseTag = []byte("") - h3Tag = []byte("") h3CloseTag = []byte("") - h4Tag = []byte("") h4CloseTag = []byte("") - h5Tag = []byte("") h5CloseTag = []byte("") - h6Tag = []byte("") h6CloseTag = []byte("") footnotesDivBytes = []byte("\n
\n\n") @@ -503,7 +500,7 @@ func (r *HTMLRenderer) outHRTag(w io.Writer) { // The typical behavior is to return GoToNext, which asks for the usual // traversal to the next node. func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus { - attrs := []string{} + attrs := node.Attributes switch node.Type { case Text: if r.Flags&Smartypants != 0 { @@ -522,26 +519,26 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt // TODO: make it configurable via out(renderer.softbreak) case Hardbreak: if r.Flags&UseXHTML == 0 { - r.out(w, brTag) + r.tag(w, brTag, attrs) } else { r.out(w, brXHTMLTag) } r.cr(w) case Emph: if entering { - r.out(w, emTag) + r.tag(w, emTag, attrs) } else { r.out(w, emCloseTag) } case Strong: if entering { - r.out(w, strongTag) + r.tag(w, strongTag, attrs) } else { r.out(w, strongCloseTag) } case Del: if entering { - r.out(w, delTag) + r.tag(w, delTag, attrs) } else { r.out(w, delCloseTag) } @@ -555,7 +552,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt dest := node.LinkData.Destination if needSkipLink(r.Flags, dest) { if entering { - r.out(w, ttTag) + r.tag(w, ttTag, attrs) } else { r.out(w, ttCloseTag) } @@ -563,21 +560,17 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt if entering { dest = r.addAbsPrefix(dest) var hrefBuf bytes.Buffer - hrefBuf.WriteString("href=\"") escLink(&hrefBuf, dest) - hrefBuf.WriteByte('"') - attrs = append(attrs, hrefBuf.String()) + attrs.Add("href", hrefBuf.String()) if node.NoteID != 0 { r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node)) break } - attrs = appendLinkAttrs(attrs, r.Flags, dest) + appendLinkAttrs(attrs, r.Flags, dest) if len(node.LinkData.Title) > 0 { var titleBuff bytes.Buffer - titleBuff.WriteString("title=\"") escapeHTML(&titleBuff, node.LinkData.Title) - titleBuff.WriteByte('"') - attrs = append(attrs, titleBuff.String()) + attrs.Add("title", titleBuff.String()) } r.tag(w, aTag, attrs) } else { @@ -615,7 +608,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt } } case Code: - r.out(w, codeTag) + r.tag(w, codeTag, attrs) escapeHTML(w, node.Literal) r.out(w, codeCloseTag) case Document: @@ -636,7 +629,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt if node.Parent.Type == BlockQuote && node.Prev == nil { r.cr(w) } - r.out(w, pTag) + r.tag(w, pTag, attrs) } else { r.out(w, pCloseTag) if !(node.Parent.Type == Item && node.Next == nil) { @@ -646,7 +639,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt case BlockQuote: if entering { r.cr(w) - r.out(w, blockquoteTag) + r.tag(w, blockquoteTag, attrs) } else { r.out(w, blockquoteCloseTag) r.cr(w) @@ -663,7 +656,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt openTag, closeTag := headingTagsFromLevel(headingLevel) if entering { if node.IsTitleblock { - attrs = append(attrs, `class="title"`) + attrs.Add("class", "title") } if node.HeadingID != "" { id := r.ensureUniqueHeadingID(node.HeadingID) @@ -673,7 +666,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt if r.HeadingIDSuffix != "" { id = id + r.HeadingIDSuffix } - attrs = append(attrs, fmt.Sprintf(`id="%s"`, id)) + attrs.Add("id", id) } r.cr(w) r.tag(w, openTag, attrs) @@ -708,7 +701,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt if node.Parent.Type == Item && node.Parent.Parent.Tight { r.cr(w) } - r.tag(w, openTag[:len(openTag)-1], attrs) + r.tag(w, openTag, attrs) r.cr(w) } else { r.out(w, closeTag) @@ -746,7 +739,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug)) break } - r.out(w, openTag) + r.tag(w, openTag, attrs) } else { if node.ListData.RefLink != nil { slug := slugify(node.ListData.RefLink) @@ -758,10 +751,10 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt r.cr(w) } case CodeBlock: - attrs = appendLanguageAttr(attrs, node.Info) + appendLanguageAttr(attrs, node.Info) r.cr(w) r.out(w, preTag) - r.tag(w, codeTag[:len(codeTag)-1], attrs) + r.tag(w, codeTag, attrs) escapeHTML(w, node.Literal) r.out(w, codeCloseTag) r.out(w, preCloseTag) @@ -771,7 +764,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt case Table: if entering { r.cr(w) - r.out(w, tableTag) + r.tag(w, tableTag, attrs) } else { r.out(w, tableCloseTag) r.cr(w) @@ -786,7 +779,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt if entering { align := cellAlignment(node.Align) if align != "" { - attrs = append(attrs, fmt.Sprintf(`align="%s"`, align)) + attrs.Add("align", align) } if node.Prev == nil { r.cr(w) @@ -799,7 +792,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt case TableHead: if entering { r.cr(w) - r.out(w, theadTag) + r.tag(w, theadTag, attrs) } else { r.out(w, theadCloseTag) r.cr(w) @@ -807,7 +800,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt case TableBody: if entering { r.cr(w) - r.out(w, tbodyTag) + r.tag(w, tbodyTag, attrs) // XXX: this is to adhere to a rather silly test. Should fix test. if node.FirstChild == nil { r.cr(w) @@ -819,7 +812,7 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt case TableRow: if entering { r.cr(w) - r.out(w, trTag) + r.tag(w, trTag, attrs) } else { r.out(w, trCloseTag) r.cr(w) diff --git a/node.go b/node.go index 51b9e8c1..af6a1b86 100644 --- a/node.go +++ b/node.go @@ -128,6 +128,8 @@ type Node struct { LinkData // Populated if Type is Link TableCellData // Populated if Type is TableCell + Attributes *Attributes // Contains HTML-attributes for current node + content []byte // Markdown content of the block nodes open bool // Specifies an open block node that has not been finished to process yet } @@ -135,8 +137,9 @@ type Node struct { // NewNode allocates a node of a specified type. func NewNode(typ NodeType) *Node { return &Node{ - Type: typ, - open: true, + Type: typ, + open: true, + Attributes: NewAttributes(), } } From 648e128ec3132797c05da093b04ec254cd212684 Mon Sep 17 00:00:00 2001 From: Artem Pavlov Date: Sun, 2 Sep 2018 16:24:33 +0300 Subject: [PATCH 2/5] make attr type private; improved tests --- attributes.go | 20 +++++++++---------- attributes_test.go | 48 ++++++++++++++++++++++++---------------------- 2 files changed, 35 insertions(+), 33 deletions(-) diff --git a/attributes.go b/attributes.go index 7d400f02..48d9364a 100644 --- a/attributes.go +++ b/attributes.go @@ -2,16 +2,16 @@ package blackfriday import "strings" -// Attr - Abstraction for html attribute -type Attr []string +// attr - Abstraction for html attribute +type attr []string // Add - adds one more attribute value -func (a Attr) Add(value string) Attr { +func (a attr) add(value string) attr { return append(a, value) } // Remove - removes given value from attribute -func (a Attr) Remove(value string) Attr { +func (a attr) remove(value string) attr { for i := range a { if a[i] == value { return append(a[:i], a[i+1:]...) @@ -20,31 +20,31 @@ func (a Attr) Remove(value string) Attr { return a } -func (a Attr) String() string { +func (a attr) String() string { return strings.Join(a, " ") } // Attributes - store for many attributes type Attributes struct { - attrsMap map[string]Attr + attrsMap map[string]attr keys []string } // NewAttributes - creates new Attributes instance func NewAttributes() *Attributes { return &Attributes{ - attrsMap: make(map[string]Attr), + 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.attrsMap[name] = make(attr, 0) a.keys = append(a.keys, name) } - a.attrsMap[name] = a.attrsMap[name].Add(value) + a.attrsMap[name] = a.attrsMap[name].add(value) return a } @@ -64,7 +64,7 @@ func (a *Attributes) Remove(name string) *Attributes { // 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) + a.attrsMap[name] = attr.remove(value) if len(a.attrsMap[name]) == 0 { a.Remove(name) } diff --git a/attributes_test.go b/attributes_test.go index 7f2977fe..b5651bb4 100644 --- a/attributes_test.go +++ b/attributes_test.go @@ -7,69 +7,70 @@ func TestEmtyAttributes(t *testing.T) { r := a.String() e := "" if r != e { - t.Errorf("Emmpty attributes must return empty string\nExpected: %s\nActual: %s", e, r) + t.Errorf("Expected: %s\nActual: %s\n", e, r) } } func TestAddOneAttribute(t *testing.T) { a := NewAttributes() a.Add("class", "wrapper") - if s := a.String(); s != "class=\"wrapper\"" { - t.Errorf("Unexprected output: %s", s) + 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") - if s := a.String(); s != "class=\"wrapper -with-image\"" { - t.Errorf("Unexpected output: %s", s) + r := a.String() + e := "class=\"wrapper -with-image\"" + 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") - if s := a.String(); s != "class=\"wrapper -with-image\"" { - t.Errorf("Unexpected output: %s", s) - } a.RemoveValue("class", "wrapper") - if s := a.String(); s != "class=\"-with-image\"" { - t.Errorf("Unexpected output: %s", s) + 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") - if s := a.String(); s != "class=\"wrapper\"" { - t.Errorf("Unexprected output: %s", s) - } a.Remove("class") - if a.String() != "" { - t.Errorf("Emmpty attributes must return empty string") + 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") - if s := a.String(); s != "class=\"wrapper\"" { - t.Errorf("Unexprected output: %s", s) - } a.RemoveValue("class", "wrapper") r := a.String() e := "" if r != e { - t.Errorf("Emmpty attributes must return empty string\nExpected: %s\nActual: %s", e, r) + t.Errorf("Expected: %s\nActual: %s\n", e, r) } } func TestAddFewAttributes(t *testing.T) { a := NewAttributes() a.Add("class", "wrapper").Add("id", "main-block") - if s := a.String(); s != "class=\"wrapper\" id=\"main-block\"" { - t.Errorf("Unexprected output: %s", s) + r := a.String() + e := "class=\"wrapper\" id=\"main-block\"" + if r != e { + t.Errorf("Expected: %s\nActual: %s\n", e, r) } } @@ -79,8 +80,9 @@ func TestAddComplexAttributes(t *testing.T) { 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 s := a.String(); s != e { - t.Errorf("Unexpected output: %s", s) + if r != e { + t.Errorf("Expected: %s\nActual: %s\n", e, r) } } From a3c461d46c3a333be237ab4ac6eb6eab37eb144b Mon Sep 17 00:00:00 2001 From: Artem Pavlov Date: Sun, 2 Sep 2018 16:44:16 +0300 Subject: [PATCH 3/5] added complete ast modification test --- attributes_test.go | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/attributes_test.go b/attributes_test.go index b5651bb4..3a515e68 100644 --- a/attributes_test.go +++ b/attributes_test.go @@ -1,6 +1,9 @@ package blackfriday -import "testing" +import ( + "bytes" + "testing" +) func TestEmtyAttributes(t *testing.T) { a := NewAttributes() @@ -86,3 +89,34 @@ func TestAddComplexAttributes(t *testing.T) { t.Errorf("Expected: %s\nActual: %s\n", e, r) } } + +func TestASTModification(t *testing.T) { + input := "\nPicture signature\n![alt text](/p.jpg)\n" + expected := "

Picture signature\n\"alt

\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) + } +} From 2dd91c7adda15206e567b98f04367a1df42c82df Mon Sep 17 00:00:00 2001 From: Artem Pavlov Date: Sun, 2 Sep 2018 16:54:25 +0300 Subject: [PATCH 4/5] prevent from adding same attribute values --- attributes.go | 5 +++++ attributes_test.go | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/attributes.go b/attributes.go index 48d9364a..ffdfeff7 100644 --- a/attributes.go +++ b/attributes.go @@ -7,6 +7,11 @@ 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) } diff --git a/attributes_test.go b/attributes_test.go index 3a515e68..f6cf7fe4 100644 --- a/attributes_test.go +++ b/attributes_test.go @@ -34,6 +34,16 @@ func TestAddFewValuesToOneAttribute(t *testing.T) { } } +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") From 9aaf5e3dd6ec69f3ba2526fcac441284be485273 Mon Sep 17 00:00:00 2001 From: Artem Pavlov Date: Mon, 3 Sep 2018 23:21:28 +0300 Subject: [PATCH 5/5] print extra attributes for image tag --- html.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/html.go b/html.go index c97382f2..93d1a47c 100644 --- a/html.go +++ b/html.go @@ -604,7 +604,9 @@ func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkSt r.out(w, []byte(`" title="`)) escapeHTML(w, node.LinkData.Title) } - r.out(w, []byte(`" />`)) + r.out(w, []byte(`" `)) + r.out(w, []byte(attrs.String())) + r.out(w, []byte(`/>`)) } } case Code: