2025-09-11 20:50:00 +02:00
|
|
|
package html
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
2025-10-05 14:27:48 +02:00
|
|
|
"strings"
|
2025-09-11 20:50:00 +02:00
|
|
|
)
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
const (
|
|
|
|
|
defaultPWidth = 112
|
|
|
|
|
unicodeNBSP = 0xa0
|
|
|
|
|
)
|
2025-09-11 20:50:00 +02:00
|
|
|
|
|
|
|
|
type renderGuide struct {
|
2025-10-05 14:27:48 +02:00
|
|
|
inline bool
|
|
|
|
|
inlineChildren bool
|
|
|
|
|
void bool
|
|
|
|
|
script bool
|
|
|
|
|
verbatim bool
|
2025-09-11 20:50:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type renderer struct {
|
|
|
|
|
out io.Writer
|
2025-10-05 14:27:48 +02:00
|
|
|
originalOut io.Writer
|
|
|
|
|
indent Indentation
|
2025-09-11 20:50:00 +02:00
|
|
|
pwidth int
|
|
|
|
|
currentIndent string
|
|
|
|
|
err error
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func mergeRenderingGuides(rgs []renderGuide) renderGuide {
|
|
|
|
|
var rg renderGuide
|
|
|
|
|
for _, rgi := range rgs {
|
|
|
|
|
rg.inline = rg.inline || rgi.inline
|
2025-10-05 14:27:48 +02:00
|
|
|
rg.inlineChildren = rg.inlineChildren || rgi.inlineChildren
|
2025-09-11 20:50:00 +02:00
|
|
|
rg.void = rg.void || rgi.void
|
|
|
|
|
rg.script = rg.script || rgi.script
|
|
|
|
|
rg.verbatim = rg.verbatim || rgi.verbatim
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return rg
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func attributeEscape(value string) string {
|
|
|
|
|
var rr []rune
|
|
|
|
|
r := []rune(value)
|
|
|
|
|
for i := range r {
|
|
|
|
|
switch r[i] {
|
|
|
|
|
case '"':
|
|
|
|
|
rr = append(rr, []rune(""")...)
|
|
|
|
|
case '&':
|
|
|
|
|
rr = append(rr, []rune("&")...)
|
|
|
|
|
default:
|
|
|
|
|
rr = append(rr, r[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string(rr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func htmlEscape(s string) string {
|
|
|
|
|
var (
|
|
|
|
|
rr []rune
|
|
|
|
|
lastWS, wsStart bool
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
r := []rune(s)
|
|
|
|
|
for i := range r {
|
|
|
|
|
switch r[i] {
|
|
|
|
|
case '<':
|
|
|
|
|
rr = append(rr, []rune("<")...)
|
|
|
|
|
case '>':
|
|
|
|
|
rr = append(rr, []rune(">")...)
|
|
|
|
|
case '&':
|
|
|
|
|
rr = append(rr, []rune("&")...)
|
2025-10-05 14:27:48 +02:00
|
|
|
case unicodeNBSP:
|
|
|
|
|
rr = append(rr, []rune(" ")...)
|
|
|
|
|
case ' ':
|
2025-09-11 20:50:00 +02:00
|
|
|
if wsStart && lastWS {
|
|
|
|
|
rr = append(rr[:len(rr)-1], []rune(" ")...)
|
|
|
|
|
} else if lastWS {
|
|
|
|
|
rr = append(rr, []rune(" ")...)
|
|
|
|
|
} else {
|
|
|
|
|
rr = append(rr, r[i])
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
rr = append(rr, r[i])
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
ws := r[i] == ' '
|
2025-09-11 20:50:00 +02:00
|
|
|
wsStart = ws && !lastWS
|
|
|
|
|
lastWS = ws
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string(rr)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
func indentLines(indent string, s string) string {
|
|
|
|
|
l := strings.Split(s, "\n")
|
|
|
|
|
for i := range l {
|
|
|
|
|
l[i] = fmt.Sprintf("%s%s", indent, l[i])
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return strings.Join(l, "\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *renderer) getPrintf(tagName string) func(f string, a ...any) {
|
|
|
|
|
return func(f string, a ...any) {
|
|
|
|
|
if r.err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, r.err = fmt.Fprintf(r.out, f, a...)
|
|
|
|
|
if r.err != nil {
|
|
|
|
|
r.err = fmt.Errorf("tag %s: %w", tagName, r.err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *renderer) renderAttributes(tagName string, a []Attributes) {
|
|
|
|
|
printf := r.getPrintf(tagName)
|
|
|
|
|
for _, ai := range a {
|
|
|
|
|
for name, value := range ai {
|
|
|
|
|
printf(" %s=\"%s\"", name, attributeEscape(value))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *renderer) renderUnindented(name string, rg renderGuide, a []Attributes, children []any) {
|
|
|
|
|
printf := r.getPrintf(name)
|
|
|
|
|
printf("<%s", name)
|
|
|
|
|
r.renderAttributes(name, a)
|
|
|
|
|
printf(">")
|
|
|
|
|
if rg.void {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, c := range children {
|
|
|
|
|
if ct, ok := c.(Tag); ok {
|
|
|
|
|
ct(r)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := fmt.Sprint(c)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !rg.verbatim && !rg.script {
|
|
|
|
|
s = htmlEscape(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("</%s>", name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *renderer) ensureWrapper() bool {
|
|
|
|
|
if _, ok := r.out.(*wrapper); ok {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.originalOut = r.out
|
|
|
|
|
r.out = newWrapper(r.originalOut, r.pwidth, r.currentIndent)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *renderer) clearWrapper() {
|
|
|
|
|
w, ok := r.out.(*wrapper)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := w.Flush(); err != nil {
|
|
|
|
|
r.err = err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.out = r.originalOut
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *renderer) renderInline(name string, rg renderGuide, a []Attributes, children []any) {
|
|
|
|
|
newWrapper := r.ensureWrapper()
|
|
|
|
|
printf := r.getPrintf(name)
|
|
|
|
|
printf("<%s", name)
|
|
|
|
|
r.renderAttributes(name, a)
|
|
|
|
|
printf(">")
|
|
|
|
|
if rg.void {
|
|
|
|
|
if newWrapper {
|
|
|
|
|
r.clearWrapper()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var lastBlock bool
|
|
|
|
|
for _, c := range children {
|
|
|
|
|
ct, isTag := c.(Tag)
|
|
|
|
|
if !isTag && rg.verbatim {
|
|
|
|
|
s := fmt.Sprint(c)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.clearWrapper()
|
|
|
|
|
s = indentLines(r.currentIndent+r.indent.Indent, s)
|
|
|
|
|
printf("\n%s", s)
|
|
|
|
|
lastBlock = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !isTag && rg.script {
|
|
|
|
|
s := fmt.Sprint(c)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.clearWrapper()
|
|
|
|
|
printf("\n%s", s)
|
|
|
|
|
lastBlock = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !isTag {
|
|
|
|
|
s := fmt.Sprint(c)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if lastBlock {
|
|
|
|
|
printf("\n%s", r.currentIndent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.ensureWrapper() {
|
|
|
|
|
newWrapper = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s = htmlEscape(s)
|
|
|
|
|
printf(s)
|
|
|
|
|
lastBlock = false
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var rgq renderGuidesQuery
|
|
|
|
|
ct(&rgq)
|
|
|
|
|
crg := mergeRenderingGuides(rgq.value)
|
|
|
|
|
if crg.inline {
|
|
|
|
|
if lastBlock {
|
|
|
|
|
printf("\n%s", r.currentIndent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.ensureWrapper() {
|
|
|
|
|
newWrapper = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ct(r)
|
|
|
|
|
lastBlock = false
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.clearWrapper()
|
|
|
|
|
cr := new(renderer)
|
|
|
|
|
*cr = *r
|
|
|
|
|
cr.currentIndent += cr.indent.Indent
|
|
|
|
|
cr.pwidth -= len([]rune(cr.indent.Indent))
|
|
|
|
|
if cr.pwidth < cr.indent.MinPWidth {
|
|
|
|
|
cr.pwidth = cr.indent.MinPWidth
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("\n%s", cr.currentIndent)
|
|
|
|
|
ct(cr)
|
|
|
|
|
if cr.err != nil {
|
|
|
|
|
r.err = cr.err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastBlock = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if lastBlock {
|
|
|
|
|
printf("\n%s", r.currentIndent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("</%s>", name)
|
|
|
|
|
if newWrapper {
|
|
|
|
|
r.clearWrapper()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *renderer) renderBlock(name string, rg renderGuide, a []Attributes, children []any) {
|
|
|
|
|
printf := r.getPrintf(name)
|
|
|
|
|
printf("<%s", name)
|
|
|
|
|
r.renderAttributes(name, a)
|
|
|
|
|
printf(">")
|
|
|
|
|
if rg.void {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if len(children) == 0 {
|
|
|
|
|
printf("</%s>", name)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastBlock := true
|
|
|
|
|
originalIndent, originalWidth := r.currentIndent, r.pwidth
|
|
|
|
|
r.currentIndent += r.indent.Indent
|
|
|
|
|
r.pwidth -= len([]rune(r.indent.Indent))
|
|
|
|
|
if r.pwidth < r.indent.MinPWidth {
|
|
|
|
|
r.pwidth = r.indent.MinPWidth
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, c := range children {
|
|
|
|
|
ct, isTag := c.(Tag)
|
|
|
|
|
if !isTag && rg.verbatim {
|
|
|
|
|
s := fmt.Sprint(c)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.clearWrapper()
|
|
|
|
|
s = indentLines(r.currentIndent, s)
|
|
|
|
|
printf("\n%s", s)
|
|
|
|
|
lastBlock = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !isTag && rg.script {
|
|
|
|
|
s := fmt.Sprint(c)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.clearWrapper()
|
|
|
|
|
printf("\n%s", s)
|
|
|
|
|
lastBlock = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !isTag {
|
|
|
|
|
s := fmt.Sprint(c)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if lastBlock {
|
|
|
|
|
printf("\n%s", r.currentIndent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.ensureWrapper()
|
|
|
|
|
s = htmlEscape(s)
|
|
|
|
|
printf(s)
|
|
|
|
|
lastBlock = false
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var rgq renderGuidesQuery
|
|
|
|
|
ct(&rgq)
|
|
|
|
|
crg := mergeRenderingGuides(rgq.value)
|
|
|
|
|
if crg.inline {
|
|
|
|
|
if lastBlock {
|
|
|
|
|
printf("\n%s", r.currentIndent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.ensureWrapper()
|
|
|
|
|
ct(r)
|
|
|
|
|
lastBlock = false
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.clearWrapper()
|
|
|
|
|
cr := new(renderer)
|
|
|
|
|
*cr = *r
|
|
|
|
|
printf("\n%s", cr.currentIndent)
|
|
|
|
|
ct(cr)
|
|
|
|
|
if cr.err != nil {
|
|
|
|
|
r.err = cr.err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastBlock = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.clearWrapper()
|
|
|
|
|
r.currentIndent, r.pwidth = originalIndent, originalWidth
|
|
|
|
|
printf("\n%s</%s>", r.currentIndent, name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (r *renderer) render(name string, children []any) {
|
2025-09-11 20:50:00 +02:00
|
|
|
if r.err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
a, c, rgs := groupChildren(children)
|
|
|
|
|
rg := mergeRenderingGuides(rgs)
|
|
|
|
|
if r.indent.Indent == "" && r.indent.PWidth <= 0 {
|
|
|
|
|
r.renderUnindented(name, rg, a, c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if rg.inline || rg.inlineChildren {
|
|
|
|
|
r.renderInline(name, rg, a, c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
r.renderBlock(name, rg, a, c)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
func getPrintf(out io.Writer) func(f string, a ...any) {
|
|
|
|
|
return func(f string, a ...any) {
|
2025-09-11 20:50:00 +02:00
|
|
|
if r.err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, r.err = fmt.Fprintf(r.out, f, a...)
|
|
|
|
|
if r.err != nil {
|
|
|
|
|
r.err = fmt.Errorf("tag %s: %w", name, r.err)
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-05 14:27:48 +02:00
|
|
|
}
|
2025-09-11 20:50:00 +02:00
|
|
|
|
2025-10-05 14:27:48 +02:00
|
|
|
func renderAttributes(out io.Writer, a []Attributes) {
|
|
|
|
|
printf := getPrintf(out)
|
2025-09-11 20:50:00 +02:00
|
|
|
for _, ai := range a {
|
|
|
|
|
for name, value := range ai {
|
|
|
|
|
printf(" %s=\"%s\"", name, attributeEscape(value))
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-05 14:27:48 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func renderUnindented(r *renderer, name string, rg renderGuide, a []Attributes, children []any) {
|
|
|
|
|
printf := getPrintf(r.out)
|
|
|
|
|
printf("<%s", name)
|
|
|
|
|
renderAttributes(r.out, a)
|
|
|
|
|
printf(">")
|
|
|
|
|
if rg.void {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, c := range children {
|
|
|
|
|
if ct, ok := c.(Tag); ok {
|
|
|
|
|
ct(r)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := fmt.Sprint(c)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !rg.verbatim && !rg.script {
|
|
|
|
|
s = htmlEscape(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("</%s>", name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func renderInline(r *renderer, name string, rg renderGuide, a []Attributes, children []any) {
|
|
|
|
|
printf := getPrintf(r.out)
|
|
|
|
|
printf("<%s", name)
|
|
|
|
|
renderAttributes(r.out, a)
|
|
|
|
|
printf(">")
|
|
|
|
|
if rg.void {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, c := range children {
|
|
|
|
|
if ct, ok := c.(Tag); ok {
|
|
|
|
|
var rgq renderGuidesQuery
|
|
|
|
|
ct(&rgq)
|
|
|
|
|
crg := mergeRenderingGuides(rgq.value)
|
|
|
|
|
if crg.inline {
|
|
|
|
|
ct(r)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("\n")
|
|
|
|
|
cr := new(renderer)
|
|
|
|
|
*cr = *r
|
|
|
|
|
cr.currentIndent += cr.indent
|
|
|
|
|
ct(cr)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := fmt.Sprint(c)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !rg.verbatim && !rg.script {
|
|
|
|
|
s = htmlEscape(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("</%s>", name)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func renderBlock(r *renderer, name string, rg renderGuide, a []Attributes, children []any) {
|
|
|
|
|
if r.direct == nil {
|
|
|
|
|
r.direct = r.out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf := getPrintf(r.direct)
|
|
|
|
|
printf(r.currentIndent)
|
|
|
|
|
printf("<%s", name)
|
|
|
|
|
renderAttributes(r.direct, a)
|
|
|
|
|
printf(">")
|
|
|
|
|
if len(c) == 0 {
|
|
|
|
|
printf("</%s>", name)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.indent != "" {
|
|
|
|
|
printf("\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
inlineBuffer bytes.Buffer
|
|
|
|
|
cr *renderer
|
|
|
|
|
lastInline bool
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
for i, c := range children {
|
|
|
|
|
if ct, ok := c.(Tag); ok {
|
|
|
|
|
var rgq renderGuidesQuery
|
|
|
|
|
ct(&rgq)
|
|
|
|
|
crg := mergeRenderingGuides(rgq.value)
|
|
|
|
|
if crg.inline {
|
|
|
|
|
if cr == nil {
|
|
|
|
|
cr = new(renderer)
|
|
|
|
|
*cr = *r
|
|
|
|
|
cr.currentIndent += cr.indent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cr.out = &inlineBuffer
|
|
|
|
|
if !lastInline {
|
|
|
|
|
printf(r.currentIndent + r.indent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
ct(cr)
|
|
|
|
|
lastInline = true
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline := inlineBuffer.String()
|
|
|
|
|
if inline != "" {
|
|
|
|
|
// flush
|
|
|
|
|
// newline
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
lastInline = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inline := inlineBuffer.String()
|
|
|
|
|
if inline != "" {
|
|
|
|
|
// flush inline
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.indent != "" {
|
|
|
|
|
printf("\n")
|
|
|
|
|
printf(r.currentIndent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("</%s>", name)
|
|
|
|
|
if r.indent != "" {
|
|
|
|
|
printf("\n")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func render(r *renderer, name string, children []any) {
|
|
|
|
|
if r.err != nil {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
a, c, rgs := groupChildren(children)
|
|
|
|
|
rg := mergeRenderingGuides(rgs)
|
|
|
|
|
if r.indent == "" {
|
|
|
|
|
renderUnindented(r, name, rg, a, c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if rg.inline {
|
|
|
|
|
// TODO:
|
|
|
|
|
// - may need to wrap it here
|
|
|
|
|
// - could use a wrapping buffer
|
|
|
|
|
renderInline(r, name, rg, a, c)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderBlock(r, name, rg, a, c)
|
|
|
|
|
|
|
|
|
|
// --
|
|
|
|
|
|
|
|
|
|
printf("<%s", name)
|
2025-09-11 20:50:00 +02:00
|
|
|
|
|
|
|
|
printf(">")
|
|
|
|
|
if r.indent != "" && !rg.inline && len(c) > 0 {
|
|
|
|
|
printf("\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if rg.void {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var inlineBuffer *bytes.Buffer
|
|
|
|
|
|
|
|
|
|
// TODO:
|
|
|
|
|
// - avoid rendering an inline buffer into another inline buffer
|
|
|
|
|
// - why?
|
|
|
|
|
// - or, if inline, just use the inline buffer without indentation
|
|
|
|
|
// - check the wrapping again, if it preserves or eliminates the spaces the right way
|
|
|
|
|
for i, ci := range c {
|
2025-10-05 14:27:48 +02:00
|
|
|
// tag && rg.inline && crg.inline
|
|
|
|
|
// tag && rg.inline && !crg.inline
|
|
|
|
|
// tag && !rg.inline && crg.inline
|
|
|
|
|
// tag && !rg.inline && !crg.inline
|
|
|
|
|
// !tag && rg.inline && crg.inline
|
|
|
|
|
// !tag && rg.inline && !crg.inline
|
|
|
|
|
// !tag && !rg.inline && crg.inline
|
|
|
|
|
// !tag && !rg.inline && !crg.inline
|
|
|
|
|
|
2025-09-11 20:50:00 +02:00
|
|
|
if tag, ok := ci.(Tag); ok {
|
|
|
|
|
if rg.inline {
|
2025-10-05 14:27:48 +02:00
|
|
|
if inlineBuffer == nil {
|
|
|
|
|
inlineBuffer = bytes.NewBuffer(nil)
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 20:50:00 +02:00
|
|
|
var rgq renderGuidesQuery
|
|
|
|
|
tag(&rgq)
|
|
|
|
|
crg := mergeRenderingGuides(rgq.value)
|
|
|
|
|
if r.indent != "" && !crg.inline && inlineBuffer.Len() > 0 {
|
|
|
|
|
w := r.pwidth
|
|
|
|
|
if w == 0 {
|
|
|
|
|
w = defaultPWidth
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inlineBuffer = wrap(inlineBuffer, w, "")
|
|
|
|
|
if _, err := io.Copy(r.out, inlineBuffer); err != nil {
|
|
|
|
|
r.err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inlineBuffer = bytes.NewBuffer(nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if i > 0 && r.indent != "" && !crg.inline {
|
|
|
|
|
printf("\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rr := new(renderer)
|
|
|
|
|
*rr = *r
|
|
|
|
|
rr.indent = ""
|
|
|
|
|
rr.currentIndent = ""
|
|
|
|
|
tag(rr)
|
|
|
|
|
} else {
|
|
|
|
|
var rgq renderGuidesQuery
|
|
|
|
|
tag(&rgq)
|
|
|
|
|
crg := mergeRenderingGuides(rgq.value)
|
|
|
|
|
if r.indent != "" && !crg.inline && inlineBuffer.Len() > 0 {
|
|
|
|
|
w := r.pwidth
|
|
|
|
|
if w == 0 {
|
|
|
|
|
w = defaultPWidth
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inlineBuffer = wrap(inlineBuffer, w, r.currentIndent+r.indent)
|
|
|
|
|
if _, err := io.Copy(r.out, inlineBuffer); err != nil {
|
|
|
|
|
r.err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inlineBuffer = bytes.NewBuffer(nil)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if i > 0 && r.indent != "" && !crg.inline {
|
|
|
|
|
printf("\n")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
rr := new(renderer)
|
|
|
|
|
*rr = *r
|
|
|
|
|
rr.currentIndent += r.indent
|
|
|
|
|
if r.indent != "" && crg.inline {
|
|
|
|
|
rr.out = inlineBuffer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tag(rr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s := fmt.Sprint(ci)
|
|
|
|
|
if s == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !rg.verbatim && !rg.script {
|
|
|
|
|
s = htmlEscape(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.indent == "" {
|
|
|
|
|
printf(s)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inlineBuffer.WriteString(s)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if r.indent != "" && inlineBuffer.Len() > 0 {
|
|
|
|
|
w := r.pwidth
|
|
|
|
|
if w == 0 {
|
|
|
|
|
w = defaultPWidth
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var indent string
|
|
|
|
|
if !rg.inline && !rg.script {
|
|
|
|
|
indent = r.currentIndent + r.indent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
inlineBuffer = wrap(inlineBuffer, w, indent)
|
|
|
|
|
if _, err := io.Copy(r.out, inlineBuffer); err != nil {
|
|
|
|
|
r.err = err
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !rg.inline {
|
|
|
|
|
printf("\n")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !rg.inline {
|
|
|
|
|
printf(r.currentIndent)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
printf("</%s>", name)
|
|
|
|
|
if r.indent != "" && !rg.inline {
|
|
|
|
|
printf("\n")
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-05 14:27:48 +02:00
|
|
|
*/
|