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.
113 lines
3.4 KiB
Go
113 lines
3.4 KiB
Go
package classes
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
|
|
xhtml "golang.org/x/net/html"
|
|
)
|
|
|
|
// ConvertH1 turns every h1 element of html into an h2 and returns the
|
|
// result with the number of headings it converted. It works on the
|
|
// x/net/html tokenizer's raw tokens: only the tag name of an h1 start, end
|
|
// or self-closing tag changes, and every other byte (attributes, text,
|
|
// comments, the content of script or style) is copied as it is. Text that
|
|
// merely looks like a tag inside raw-text elements or comments is not a
|
|
// tag and is not touched. html without an h1 comes back unchanged with 0,
|
|
// and the bytes of an unfinished tag at the end of html are kept.
|
|
func ConvertH1(html string) (string, int) {
|
|
if !containsFold(html, "<h1") && !containsFold(html, "</h1") {
|
|
return html, 0
|
|
}
|
|
z := xhtml.NewTokenizer(strings.NewReader(html))
|
|
var b bytes.Buffer
|
|
b.Grow(len(html))
|
|
count := 0
|
|
consumed := 0
|
|
for {
|
|
tt := z.Next()
|
|
if tt == xhtml.ErrorToken {
|
|
// The input may end inside something the tokenizer reads as a
|
|
// tag ("a <b"): those bytes are kept as they are.
|
|
b.WriteString(html[min(consumed, len(html)):])
|
|
break
|
|
}
|
|
raw := z.Raw()
|
|
consumed += len(raw)
|
|
switch tt {
|
|
case xhtml.StartTagToken, xhtml.SelfClosingTagToken:
|
|
if name, _ := z.TagName(); string(name) == "h1" && len(raw) >= 3 {
|
|
b.WriteString("<h2")
|
|
b.Write(raw[3:])
|
|
count++
|
|
continue
|
|
}
|
|
case xhtml.EndTagToken:
|
|
if name, _ := z.TagName(); string(name) == "h1" && len(raw) >= 4 {
|
|
b.WriteString("</h2")
|
|
b.Write(raw[4:])
|
|
continue
|
|
}
|
|
}
|
|
b.Write(raw)
|
|
}
|
|
if count == 0 && b.String() == html {
|
|
return html, 0
|
|
}
|
|
return b.String(), count
|
|
}
|
|
|
|
// CountH1 is the number of h1 start tags in html, as ConvertH1 counts them.
|
|
func CountH1(html string) int {
|
|
_, n := ConvertH1(html)
|
|
return n
|
|
}
|
|
|
|
func containsFold(s, sub string) bool {
|
|
return strings.Contains(strings.ToLower(s), sub)
|
|
}
|
|
|
|
// ConvertContentH1 applies ConvertH1 to a post body and its rendered HTML
|
|
// (content and content_html) and returns both with the number of headings
|
|
// converted: content_html's count, or content's when content_html had none.
|
|
// content is rewritten only when it is HTML (LooksLikeHTML, the rich
|
|
// editor's body): a markdown body is never rewritten, so a code sample that
|
|
// shows an <h1> keeps it, and only its rendered content_html is converted
|
|
// (every render path derives content_html from the markdown again and
|
|
// converts it again).
|
|
func ConvertContentH1(content, contentHTML string) (string, string, int) {
|
|
contentHTML, nHTML := ConvertH1(contentHTML)
|
|
if !LooksLikeHTML(content) {
|
|
return content, contentHTML, nHTML
|
|
}
|
|
content, nContent := ConvertH1(content)
|
|
if nHTML > 0 {
|
|
return content, contentHTML, nHTML
|
|
}
|
|
return content, contentHTML, nContent
|
|
}
|
|
|
|
// ConvertTranslationH1 applies ConvertContentH1 to the string content and
|
|
// content_html of one locale's translated attributes, in place, and returns
|
|
// the number of headings converted. A markdown content is never rewritten
|
|
// (ConvertContentH1). Other attributes and values that are not strings are
|
|
// left alone.
|
|
func ConvertTranslationH1(attrs map[string]any) int {
|
|
if attrs == nil {
|
|
return 0
|
|
}
|
|
content, hasContent := attrs["content"].(string)
|
|
html, hasHTML := attrs["content_html"].(string)
|
|
if !hasContent && !hasHTML {
|
|
return 0
|
|
}
|
|
content, html, n := ConvertContentH1(content, html)
|
|
if hasContent {
|
|
attrs["content"] = content
|
|
}
|
|
if hasHTML {
|
|
attrs["content_html"] = html
|
|
}
|
|
return n
|
|
}
|