ConvertContentH1 rewrites content only when it is HTML; a markdown source, its code samples included, is kept byte for byte and only its rendered content_html is converted, on API saves, imports, journal:fix-headings and translations. ConvertH1 and SetImageAlt keep the bytes of an unfinished tag at the end of the input.
97 lines
2.4 KiB
Go
97 lines
2.4 KiB
Go
package classes
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
|
|
xhtml "golang.org/x/net/html"
|
|
)
|
|
|
|
// SetImageAlt sets the alt attribute of every img in html whose src
|
|
// attribute (unescaped) equals src, adding it when missing. It returns the
|
|
// new HTML, the number of matching images and the alt the first match had
|
|
// (nil when it had none). It works on the x/net/html tokenizer: a matching
|
|
// img tag is written again from its token, so every attribute value,
|
|
// alt included, is escaped by the tokenizer and never concatenated; every
|
|
// other byte of html is copied as it is, the bytes of an unfinished tag at
|
|
// the end included. An empty alt marks a decorative image (alt="").
|
|
func SetImageAlt(html, src, alt string) (string, int, *string) {
|
|
if src == "" || !containsFold(html, "<img") {
|
|
return html, 0, nil
|
|
}
|
|
z := xhtml.NewTokenizer(strings.NewReader(html))
|
|
var b bytes.Buffer
|
|
b.Grow(len(html) + len(alt))
|
|
matches := 0
|
|
var old *string
|
|
consumed := 0
|
|
for {
|
|
tt := z.Next()
|
|
if tt == xhtml.ErrorToken {
|
|
// The bytes of an unfinished tag at the end are kept.
|
|
b.WriteString(html[min(consumed, len(html)):])
|
|
break
|
|
}
|
|
raw := z.Raw()
|
|
consumed += len(raw)
|
|
if tt == xhtml.StartTagToken || tt == xhtml.SelfClosingTagToken {
|
|
// Raw is copied before Token, which reads the tag; Token is
|
|
// the only reader of the tag (TagName would consume it).
|
|
kept := append([]byte(nil), raw...)
|
|
tok := z.Token()
|
|
if tok.Data == "img" && imgSrc(tok) == src {
|
|
prev := setAlt(&tok, alt)
|
|
if matches == 0 {
|
|
old = prev
|
|
}
|
|
matches++
|
|
b.WriteString(tok.String())
|
|
continue
|
|
}
|
|
b.Write(kept)
|
|
continue
|
|
}
|
|
b.Write(raw)
|
|
}
|
|
if matches == 0 {
|
|
return html, 0, nil
|
|
}
|
|
return b.String(), matches, old
|
|
}
|
|
|
|
func imgSrc(tok xhtml.Token) string {
|
|
for _, a := range tok.Attr {
|
|
if a.Namespace == "" && a.Key == "src" {
|
|
return a.Val
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// setAlt sets tok's alt to alt and returns the previous value (nil when
|
|
// there was none). Repeated alt attributes collapse to one.
|
|
func setAlt(tok *xhtml.Token, alt string) *string {
|
|
var prev *string
|
|
attrs := tok.Attr[:0]
|
|
set := false
|
|
for _, a := range tok.Attr {
|
|
if a.Namespace == "" && a.Key == "alt" {
|
|
if prev == nil {
|
|
v := a.Val
|
|
prev = &v
|
|
}
|
|
if set {
|
|
continue
|
|
}
|
|
a.Val = alt
|
|
set = true
|
|
}
|
|
attrs = append(attrs, a)
|
|
}
|
|
if !set {
|
|
attrs = append(attrs, xhtml.Attribute{Key: "alt", Val: alt})
|
|
}
|
|
tok.Attr = attrs
|
|
return prev
|
|
}
|