claude: fold in old sow-tools
This commit is contained in:
@@ -0,0 +1,629 @@
|
||||
package topdata
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
type wikiTemplateHTML string
|
||||
|
||||
func extractWikiTemplateBlock(source string, offset int, blockName, path, pageID string) (string, int, error) {
|
||||
depth := 1
|
||||
bodyStart := offset
|
||||
for offset < len(source) {
|
||||
start := strings.Index(source[offset:], "{{")
|
||||
if start < 0 {
|
||||
break
|
||||
}
|
||||
start += offset
|
||||
end := strings.Index(source[start+2:], "}}")
|
||||
if end < 0 {
|
||||
return "", offset, fmt.Errorf("render wiki template %s for %s: unclosed placeholder", path, pageID)
|
||||
}
|
||||
end += start + 2
|
||||
token := strings.TrimSpace(source[start+2 : end])
|
||||
if token == "#"+blockName || strings.HasPrefix(token, "#"+blockName+" ") {
|
||||
depth++
|
||||
} else if token == "/"+blockName {
|
||||
depth--
|
||||
if depth == 0 {
|
||||
return source[bodyStart:start], end + 2, nil
|
||||
}
|
||||
}
|
||||
offset = end + 2
|
||||
}
|
||||
return "", offset, fmt.Errorf("render wiki template %s for %s: missing {{/%s}}", path, pageID, blockName)
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) evalWikiTemplateExpression(expr string, state *wikiTemplateRenderState) (any, error) {
|
||||
parts := splitWikiTemplatePipes(expr)
|
||||
if len(parts) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
value, err := ctx.evalWikiTemplateBase(strings.TrimSpace(parts[0]), state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, rawFilter := range parts[1:] {
|
||||
value, err = ctx.applyWikiTemplateFilter(value, strings.TrimSpace(rawFilter), state)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func splitWikiTemplatePipes(expr string) []string {
|
||||
parts := []string{}
|
||||
start := 0
|
||||
inQuote := false
|
||||
escaped := false
|
||||
for i := 0; i < len(expr); i++ {
|
||||
switch {
|
||||
case escaped:
|
||||
escaped = false
|
||||
case expr[i] == '\\' && inQuote:
|
||||
escaped = true
|
||||
case expr[i] == '"':
|
||||
inQuote = !inQuote
|
||||
case expr[i] == '|' && !inQuote:
|
||||
parts = append(parts, expr[start:i])
|
||||
start = i + 1
|
||||
}
|
||||
}
|
||||
parts = append(parts, expr[start:])
|
||||
return parts
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) evalWikiTemplateBase(expr string, state *wikiTemplateRenderState) (any, error) {
|
||||
if expr == "." {
|
||||
return state.dot, nil
|
||||
}
|
||||
if value, ok := ctx.resolveWikiTemplateScopedValue(expr, state); ok {
|
||||
return value, nil
|
||||
}
|
||||
renderCtx := wikiTableRenderContext{Page: state.page, Path: state.path}
|
||||
row := state.page.Row
|
||||
if state.row != nil {
|
||||
row = state.row
|
||||
}
|
||||
parser := wikiExprParser{tokens: tokenizeWikiExpr(expr), row: row, ctx: ctx, renderCtx: renderCtx}
|
||||
value, err := parser.parseComparison()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("render wiki template %s for %s expression %q: %w", state.path, state.page.PageID, expr, err)
|
||||
}
|
||||
if parser.peek().kind != wikiExprEOF {
|
||||
return nil, fmt.Errorf("render wiki template %s for %s expression %q: unexpected token %q", state.path, state.page.PageID, expr, parser.peek().text)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) resolveWikiTemplateScopedValue(name string, state *wikiTemplateRenderState) (any, bool) {
|
||||
name = strings.TrimSpace(name)
|
||||
if strings.HasPrefix(name, "page.") {
|
||||
field := strings.TrimPrefix(name, "page.")
|
||||
switch field {
|
||||
case "title", "Title":
|
||||
return state.page.Title, true
|
||||
case "page_id", "PageID":
|
||||
return state.page.PageID, true
|
||||
case "status", "Status":
|
||||
return state.page.Status, true
|
||||
case "category", "Category":
|
||||
return state.page.Category, true
|
||||
case "key", "Key":
|
||||
return state.page.Key, true
|
||||
default:
|
||||
if value, ok := lookupField(state.page.Row, field); ok {
|
||||
return value, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
if state.row != nil {
|
||||
if value, ok := lookupField(state.row, name); ok {
|
||||
return value, true
|
||||
}
|
||||
}
|
||||
if value, ok := lookupField(state.page.Row, name); ok {
|
||||
return value, true
|
||||
}
|
||||
switch name {
|
||||
case "title":
|
||||
return state.page.Title, true
|
||||
case "page_id":
|
||||
return state.page.PageID, true
|
||||
case "status":
|
||||
return state.page.Status, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) applyWikiTemplateFilter(value any, filter string, state *wikiTemplateRenderState) (any, error) {
|
||||
name, argText, hasArg := strings.Cut(filter, ":")
|
||||
name = strings.TrimSpace(name)
|
||||
args, err := parseWikiTemplateFilterArgs(argText)
|
||||
if hasArg && err != nil {
|
||||
return nil, fmt.Errorf("render wiki template %s for %s filter %q: %w", state.path, state.page.PageID, filter, err)
|
||||
}
|
||||
switch name {
|
||||
case "text":
|
||||
return ctx.resolveTextValue(value, nil), nil
|
||||
case "trim":
|
||||
return strings.TrimSpace(stringifyWikiTemplatePlain(value)), nil
|
||||
case "after":
|
||||
return afterFirstMarker(stringifyWikiTemplatePlain(value), firstArg(args)), nil
|
||||
case "before":
|
||||
return beforeFirstMarker(stringifyWikiTemplatePlain(value), firstArg(args)), nil
|
||||
case "between":
|
||||
text := stringifyWikiTemplatePlain(value)
|
||||
return beforeFirstMarker(afterFirstMarker(text, firstArg(args)), secondArg(args)), nil
|
||||
case "paragraph":
|
||||
return selectWikiTemplateParagraphs(stringifyWikiTemplatePlain(value), firstArg(args)), nil
|
||||
case "paragraphs":
|
||||
return selectWikiTemplateParagraphs(stringifyWikiTemplatePlain(value), firstArg(args)), nil
|
||||
case "linebreaks":
|
||||
return wikiTemplateHTML(strings.ReplaceAll(html.EscapeString(stringifyWikiTemplatePlain(value)), "\n", "<br>")), nil
|
||||
case "html":
|
||||
return wikiTemplateHTML(renderWikiTemplatePlainHTML(stringifyWikiTemplatePlain(value))), nil
|
||||
case "wiki":
|
||||
return wikiTemplateHTML(renderWikiLinks(stringifyWikiTemplatePlain(value))), nil
|
||||
case "lower":
|
||||
return strings.ToLower(stringifyWikiTemplatePlain(value)), nil
|
||||
case "title":
|
||||
return strings.Title(stringifyWikiTemplatePlain(value)), nil
|
||||
case "sentence":
|
||||
text := stringifyWikiTemplatePlain(value)
|
||||
for i, r := range text {
|
||||
if unicode.IsLetter(r) {
|
||||
return text[:i] + string(unicode.ToUpper(r)) + text[i+len(string(r)):], nil
|
||||
}
|
||||
}
|
||||
return text, nil
|
||||
case "join":
|
||||
return joinWikiTemplateValue(value, firstArg(args)), nil
|
||||
case "count":
|
||||
return countWikiTemplateValue(value), nil
|
||||
case "alignments":
|
||||
return ctx.formatWikiTemplateAlignments(value)
|
||||
case "list":
|
||||
return wikiTemplateHTML(renderWikiTemplateList(value, firstArg(args))), nil
|
||||
case "ordinal":
|
||||
return ordinalWikiTableValue(numericWikiTableValue(value)), nil
|
||||
case "default":
|
||||
if strings.TrimSpace(stringifyWikiTemplatePlain(value)) == "" {
|
||||
return firstArg(args), nil
|
||||
}
|
||||
return value, nil
|
||||
case "ref":
|
||||
return ctx.renderTemplateReference(value, firstArg(args)), nil
|
||||
case "link":
|
||||
return ctx.renderTemplateLink(value, firstArg(args), state), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("render wiki template %s for %s: unknown filter %q", state.path, state.page.PageID, name)
|
||||
}
|
||||
}
|
||||
|
||||
func parseWikiTemplateFilterArgs(raw string) ([]string, error) {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if raw == "" {
|
||||
return nil, nil
|
||||
}
|
||||
args := []string{}
|
||||
for len(raw) > 0 {
|
||||
raw = strings.TrimSpace(raw)
|
||||
if !strings.HasPrefix(raw, `"`) {
|
||||
if idx := strings.Index(raw, ","); idx >= 0 {
|
||||
args = append(args, strings.TrimSpace(raw[:idx]))
|
||||
raw = raw[idx+1:]
|
||||
continue
|
||||
}
|
||||
args = append(args, strings.TrimSpace(raw))
|
||||
break
|
||||
}
|
||||
var b strings.Builder
|
||||
escaped := false
|
||||
i := 1
|
||||
for ; i < len(raw); i++ {
|
||||
if escaped {
|
||||
b.WriteByte(raw[i])
|
||||
escaped = false
|
||||
continue
|
||||
}
|
||||
if raw[i] == '\\' {
|
||||
escaped = true
|
||||
continue
|
||||
}
|
||||
if raw[i] == '"' {
|
||||
break
|
||||
}
|
||||
b.WriteByte(raw[i])
|
||||
}
|
||||
if i >= len(raw) {
|
||||
return nil, fmt.Errorf("unterminated quoted argument")
|
||||
}
|
||||
args = append(args, b.String())
|
||||
raw = strings.TrimSpace(raw[i+1:])
|
||||
if strings.HasPrefix(raw, ",") {
|
||||
raw = raw[1:]
|
||||
} else if raw != "" {
|
||||
return nil, fmt.Errorf("unexpected argument text %q", raw)
|
||||
}
|
||||
}
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func firstArg(args []string) string {
|
||||
if len(args) == 0 {
|
||||
return ""
|
||||
}
|
||||
return args[0]
|
||||
}
|
||||
|
||||
func secondArg(args []string) string {
|
||||
if len(args) < 2 {
|
||||
return ""
|
||||
}
|
||||
return args[1]
|
||||
}
|
||||
|
||||
func afterFirstMarker(text, marker string) string {
|
||||
if marker == "" {
|
||||
return text
|
||||
}
|
||||
idx := strings.Index(text, marker)
|
||||
if idx < 0 {
|
||||
return ""
|
||||
}
|
||||
return text[idx+len(marker):]
|
||||
}
|
||||
|
||||
func beforeFirstMarker(text, marker string) string {
|
||||
if marker == "" {
|
||||
return text
|
||||
}
|
||||
idx := strings.Index(text, marker)
|
||||
if idx < 0 {
|
||||
return text
|
||||
}
|
||||
return text[:idx]
|
||||
}
|
||||
|
||||
func selectWikiTemplateParagraphs(text, selector string) string {
|
||||
paragraphs := splitWikiTemplateParagraphs(text)
|
||||
if len(paragraphs) == 0 {
|
||||
return ""
|
||||
}
|
||||
switch strings.TrimSpace(selector) {
|
||||
case "first":
|
||||
return paragraphs[0]
|
||||
case "last":
|
||||
return paragraphs[len(paragraphs)-1]
|
||||
case "all", "":
|
||||
return strings.Join(paragraphs, "\n\n")
|
||||
default:
|
||||
selected := []string{}
|
||||
for _, part := range strings.Split(selector, ",") {
|
||||
switch strings.TrimSpace(part) {
|
||||
case "first":
|
||||
selected = append(selected, paragraphs[0])
|
||||
case "last":
|
||||
selected = append(selected, paragraphs[len(paragraphs)-1])
|
||||
default:
|
||||
if idx, err := strconv.Atoi(strings.TrimSpace(part)); err == nil && idx > 0 && idx <= len(paragraphs) {
|
||||
selected = append(selected, paragraphs[idx-1])
|
||||
}
|
||||
}
|
||||
}
|
||||
return strings.Join(selected, "\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
func splitWikiTemplateParagraphs(text string) []string {
|
||||
normalized := strings.ReplaceAll(text, "\r\n", "\n")
|
||||
chunks := strings.Split(normalized, "\n\n")
|
||||
out := []string{}
|
||||
for _, chunk := range chunks {
|
||||
if trimmed := strings.TrimSpace(chunk); trimmed != "" {
|
||||
out = append(out, trimmed)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func renderWikiTemplatePlainHTML(text string) string {
|
||||
paragraphs := splitWikiTemplateParagraphs(text)
|
||||
if len(paragraphs) == 0 {
|
||||
return ""
|
||||
}
|
||||
out := strings.Builder{}
|
||||
for _, paragraph := range paragraphs {
|
||||
escaped := html.EscapeString(paragraph)
|
||||
escaped = strings.ReplaceAll(escaped, "\n", "<br>")
|
||||
out.WriteString("<p>")
|
||||
out.WriteString(escaped)
|
||||
out.WriteString("</p>")
|
||||
}
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func joinWikiTemplateValue(value any, sep string) string {
|
||||
switch typed := value.(type) {
|
||||
case []string:
|
||||
return strings.Join(typed, sep)
|
||||
case []any:
|
||||
parts := make([]string, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
if text := stringifyWikiTemplatePlain(item); text != "" {
|
||||
parts = append(parts, text)
|
||||
}
|
||||
}
|
||||
return strings.Join(parts, sep)
|
||||
default:
|
||||
return stringifyWikiTemplatePlain(value)
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) formatWikiTemplateAlignments(value any) (any, error) {
|
||||
values, err := wikiTemplateAlignmentValues(value)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(values) == 0 || (len(values) == 1 && values[0] == 0) {
|
||||
return "Any", nil
|
||||
}
|
||||
labels := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
if value == 0 {
|
||||
continue
|
||||
}
|
||||
label, ok := wikiTemplateAlignmentLabel(value)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unknown alignment value 0x%02X", value)
|
||||
}
|
||||
labels = append(labels, ctx.renderWikiTemplateAlignmentLabel(label))
|
||||
}
|
||||
if len(labels) == 0 {
|
||||
return "Any", nil
|
||||
}
|
||||
rendered := strings.Join(labels, ", ")
|
||||
if ctx != nil && strings.TrimSpace(ctx.alignmentLinkFormat) == "html_anchor" {
|
||||
return wikiTemplateHTML(rendered), nil
|
||||
}
|
||||
return rendered, nil
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) renderWikiTemplateAlignmentLabel(label string) string {
|
||||
pattern := ""
|
||||
format := "wiki_markup"
|
||||
if ctx != nil {
|
||||
pattern = strings.TrimSpace(ctx.alignmentLinkPattern)
|
||||
if strings.TrimSpace(ctx.alignmentLinkFormat) != "" {
|
||||
format = strings.TrimSpace(ctx.alignmentLinkFormat)
|
||||
}
|
||||
}
|
||||
if pattern == "" {
|
||||
return label
|
||||
}
|
||||
target := strings.ReplaceAll(pattern, "{alignment}", wikiTemplateAlignmentTargetSegment(label))
|
||||
target = strings.ReplaceAll(target, "{label}", label)
|
||||
if strings.TrimSpace(target) == "" {
|
||||
return label
|
||||
}
|
||||
if format == "html_anchor" {
|
||||
return `<a class="wiki-internal-link" href="` + html.EscapeString(target) + `">` + html.EscapeString(label) + `</a>`
|
||||
}
|
||||
return "[[" + target + "|" + label + "]]"
|
||||
}
|
||||
|
||||
func wikiTemplateAlignmentTargetSegment(label string) string {
|
||||
return strings.ReplaceAll(strings.TrimSpace(label), " ", "_")
|
||||
}
|
||||
|
||||
func wikiTemplateAlignmentValues(value any) ([]int, error) {
|
||||
if isNullLike(value) {
|
||||
return []int{0}, nil
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case map[string]any:
|
||||
_, hasAlignments := typed["alignments"]
|
||||
_, hasList := typed["list"]
|
||||
if hasAlignments && hasList {
|
||||
return nil, fmt.Errorf("alignment list object cannot contain both alignments and list")
|
||||
}
|
||||
for key := range typed {
|
||||
if key != "alignments" && key != "list" {
|
||||
return nil, fmt.Errorf("alignment list object contains unsupported key %q", key)
|
||||
}
|
||||
}
|
||||
if hasAlignments {
|
||||
return wikiTemplateAlignmentValues(typed["alignments"])
|
||||
}
|
||||
return wikiTemplateAlignmentValues(typed["list"])
|
||||
case string:
|
||||
trimmed := strings.TrimSpace(typed)
|
||||
if trimmed == "" || trimmed == "0" || strings.EqualFold(trimmed, "0x00") {
|
||||
return []int{0}, nil
|
||||
}
|
||||
if strings.HasPrefix(strings.ToLower(trimmed), "0x") {
|
||||
hexText := trimmed[2:]
|
||||
if len(hexText)%2 != 0 {
|
||||
return nil, fmt.Errorf("alignment hex list %q must contain two-character chunks", typed)
|
||||
}
|
||||
values := make([]int, 0, len(hexText)/2)
|
||||
for index := 0; index < len(hexText); index += 2 {
|
||||
parsed, err := strconv.ParseInt(hexText[index:index+2], 16, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("alignment hex list %q contains invalid chunk %q", typed, hexText[index:index+2])
|
||||
}
|
||||
values = append(values, int(parsed))
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
if strings.Contains(trimmed, ",") {
|
||||
parts := strings.Split(trimmed, ",")
|
||||
values := make([]int, 0, len(parts))
|
||||
for _, part := range parts {
|
||||
parsed, err := parseConfiguredAlignmentValue(part)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values = append(values, parsed)
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
parsed, err := parseConfiguredAlignmentValue(trimmed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []int{parsed}, nil
|
||||
case []any:
|
||||
values := make([]int, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
parsed, err := parseConfiguredAlignmentValue(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
values = append(values, parsed)
|
||||
}
|
||||
return values, nil
|
||||
default:
|
||||
parsed, err := parseConfiguredAlignmentValue(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []int{parsed}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func wikiTemplateAlignmentLabel(value int) (string, bool) {
|
||||
switch value {
|
||||
case 0x0A:
|
||||
return "Lawful Good", true
|
||||
case 0x09:
|
||||
return "Neutral Good", true
|
||||
case 0x0C:
|
||||
return "Chaotic Good", true
|
||||
case 0x03:
|
||||
return "Lawful Neutral", true
|
||||
case 0x01:
|
||||
return "True Neutral", true
|
||||
case 0x05:
|
||||
return "Chaotic Neutral", true
|
||||
case 0x12:
|
||||
return "Lawful Evil", true
|
||||
case 0x11:
|
||||
return "Neutral Evil", true
|
||||
case 0x14:
|
||||
return "Chaotic Evil", true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func renderWikiTemplateList(value any, class string) string {
|
||||
items := wikiTemplateListItems(value)
|
||||
if len(items) == 0 {
|
||||
return ""
|
||||
}
|
||||
var out strings.Builder
|
||||
out.WriteString("<ul")
|
||||
out.WriteString(attrClass(class))
|
||||
out.WriteString(">")
|
||||
for _, item := range items {
|
||||
out.WriteString("<li>")
|
||||
out.WriteString(renderWikiLinks(item))
|
||||
out.WriteString("</li>")
|
||||
}
|
||||
out.WriteString("</ul>")
|
||||
return out.String()
|
||||
}
|
||||
|
||||
func wikiTemplateListItems(value any) []string {
|
||||
switch typed := value.(type) {
|
||||
case []string:
|
||||
out := make([]string, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
if text := strings.TrimSpace(item); text != "" {
|
||||
out = append(out, text)
|
||||
}
|
||||
}
|
||||
return out
|
||||
case []any:
|
||||
out := make([]string, 0, len(typed))
|
||||
for _, item := range typed {
|
||||
if text := strings.TrimSpace(stringifyWikiTemplatePlain(item)); text != "" {
|
||||
out = append(out, text)
|
||||
}
|
||||
}
|
||||
return out
|
||||
default:
|
||||
if text := strings.TrimSpace(stringifyWikiTemplatePlain(value)); text != "" {
|
||||
return []string{text}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) renderTemplateReference(value any, dataset string) string {
|
||||
return ctx.renderReference(value, ctx.wikiIDMapForDataset(dataset), ctx.wikiNameResolverForDataset(dataset))
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) wikiNameResolverForDataset(dataset string) func(string) string {
|
||||
switch dataset {
|
||||
case "classes":
|
||||
return ctx.resolveClassName
|
||||
case "feat":
|
||||
return ctx.resolveFeatName
|
||||
case "skills":
|
||||
return ctx.resolveSkillName
|
||||
case "spells":
|
||||
return ctx.resolveSpellName
|
||||
case "racialtypes":
|
||||
return ctx.resolveRaceName
|
||||
case "baseitems":
|
||||
return ctx.resolveBaseItemName
|
||||
default:
|
||||
return func(string) string { return "" }
|
||||
}
|
||||
}
|
||||
|
||||
func (ctx *wikiContext) renderTemplateLink(value any, labelField string, state *wikiTemplateRenderState) string {
|
||||
key := stringifyWikiTemplatePlain(value)
|
||||
label := labelField
|
||||
if state.row != nil {
|
||||
if raw, ok := lookupField(state.row, labelField); ok {
|
||||
label = stringifyWikiTemplatePlain(raw)
|
||||
}
|
||||
}
|
||||
if label == "" {
|
||||
label = key
|
||||
}
|
||||
if key == "" {
|
||||
return label
|
||||
}
|
||||
if !ctx.canReferenceWikiKey(key) {
|
||||
return ""
|
||||
}
|
||||
if target := ctx.publicWikiTargetForKey(key, label); target != "" {
|
||||
key = target
|
||||
}
|
||||
return "[[" + key + "|" + label + "]]"
|
||||
}
|
||||
|
||||
func stringifyWikiTemplateOutput(value any) string {
|
||||
if htmlValue, ok := value.(wikiTemplateHTML); ok {
|
||||
return string(htmlValue)
|
||||
}
|
||||
return html.EscapeString(stringifyWikiTemplatePlain(value))
|
||||
}
|
||||
|
||||
func stringifyWikiTemplatePlain(value any) string {
|
||||
if htmlValue, ok := value.(wikiTemplateHTML); ok {
|
||||
return string(htmlValue)
|
||||
}
|
||||
return stringifyWikiTableValue(value)
|
||||
}
|
||||
Reference in New Issue
Block a user