Files
TypoML/src/html.odin
T
2026-07-15 13:09:56 +02:00

469 lines
10 KiB
Odin

package main
import "core:fmt"
import "core:strings"
import "core:unicode"
escape_html :: proc(s: string, allocator := context.temp_allocator) -> string {
escaped := decode_escapes(s, allocator)
b := strings.builder_make(allocator)
for ch in escaped {
switch ch {
case '&':
strings.write_string(&b, "&")
case '<':
strings.write_string(&b, "&lt;")
case '>':
strings.write_string(&b, "&gt;")
case '"':
strings.write_string(&b, "&quot;")
case:
strings.write_rune(&b, ch)
}
}
return strings.to_string(b)
}
get_text_content :: proc(token: ^Token, b: ^strings.Builder) {
if token.type == .Text {
if v, ok := token.value.(string); ok {
strings.write_string(b, v)
}
}
for child in token.children {
get_text_content(child, b)
}
}
check_only_var_defs :: proc(token: ^Token) -> bool {
for child in token.children {
if child.type == .VariableDef || child.type == .BreakLine {
continue
}
if child.type == .Text {
if child.value != nil {
if v, ok := child.value.(string); ok && v != "" {
return false
}
}
has_non_var_def := false
for gc in child.children {
if gc.type != .VariableDef {
has_non_var_def = true
break
}
}
if has_non_var_def {
return false
}
continue
}
return false
}
return true
}
slugifyifyifyify :: proc(text: string) -> string {
b := strings.builder_make(context.temp_allocator)
for ch in text {
lower_ch := unicode.to_lower(ch)
if unicode.is_letter(lower_ch) || unicode.is_digit(lower_ch) {
strings.write_rune(&b, lower_ch)
} else if lower_ch == ' ' || lower_ch == '_' || lower_ch == '-' {
strings.write_rune(&b, '-')
}
}
res := strings.to_string(b)
for {
new_res, replaced := strings.replace_all(res, "--", "-", context.temp_allocator)
if !replaced {
break
}
res = new_res
}
res = strings.trim(res, "-")
return res
}
print_html :: proc(token: ^Token) {
switch token.type {
case TokenType.Root:
has_footnotes := false
for child in token.children {
if child.type == .FootnoteDef && !has_footnotes {
fmt.print("<hr class=\"footnotes-sep\">\n")
has_footnotes = true
}
print_html(child)
}
case TokenType.Header:
level := 1
if v, ok := token.value.(int); ok {
level = v
}
b := strings.builder_make(context.temp_allocator)
for child in token.children {
get_text_content(child, &b)
}
slug := slugifyifyifyify(strings.to_string(b))
fmt.printf("<h%d id=\"%s\">", level, escape_html(slug))
for child in token.children {
print_html(child)
}
fmt.printf("</h%d>\n", level)
case TokenType.Paragraph:
only_var_defs := check_only_var_defs(token)
if !only_var_defs {
fmt.print("<p>")
for child in token.children {
print_html(child)
}
fmt.print("</p>\n")
} else {
for child in token.children {
if child.type != .BreakLine {
print_html(child)
}
}
}
case TokenType.Blockquote:
fmt.print("<blockquote>\n")
for child in token.children {
print_html(child)
}
fmt.print("</blockquote>\n")
case TokenType.UnorderedList:
fmt.print("<ul>\n")
for child in token.children {
print_html(child)
}
fmt.print("</ul>\n")
case TokenType.OrderedList:
fmt.print("<ol>\n")
for child in token.children {
print_html(child)
}
fmt.print("</ol>\n")
case TokenType.ListItem:
fmt.print("<li>")
for child in token.children {
print_html(child)
}
fmt.print("</li>\n")
case TokenType.Bold:
fmt.print("<strong>")
for child in token.children {
print_html(child)
}
fmt.print("</strong>")
case TokenType.Italics:
fmt.print("<em>")
for child in token.children {
print_html(child)
}
fmt.print("</em>")
case TokenType.Strikethrough:
fmt.print("<strike>")
for child in token.children {
print_html(child)
}
fmt.print("</strike>")
case TokenType.Table:
fmt.print("<table>\n")
for child in token.children {
print_html(child)
}
fmt.print("</table>\n")
case TokenType.TableHeaderRow:
fmt.print("<tr>")
for child in token.children {
align := ""
if len(child.children) > 0 {
first_child := child.children[0]
if first_child.type == .Left {
align = "left"
} else if first_child.type == .Right {
align = "right"
} else if first_child.type == .Middle {
align = "center"
}
}
if align != "" {
fmt.printf("<th align=\"%s\">", align)
for grandchild in child.children[0].children {
print_html(grandchild)
}
} else {
fmt.print("<th>")
for grandchild in child.children {
print_html(grandchild)
}
}
fmt.print("</th>")
}
fmt.print("</tr>\n")
case TokenType.TableRow:
fmt.print("<tr>")
for child in token.children {
print_html(child)
}
fmt.print("</tr>\n")
case TokenType.TableCell:
align := ""
if len(token.children) > 0 {
first_child := token.children[0]
if first_child.type == .Left {
align = "left"
} else if first_child.type == .Right {
align = "right"
} else if first_child.type == .Middle {
align = "center"
}
}
if align != "" {
fmt.printf("<td align=\"%s\">", align)
for child in token.children[0].children {
print_html(child)
}
} else {
fmt.print("<td>")
for child in token.children {
print_html(child)
}
}
fmt.print("</td>")
case TokenType.TableSeparator:
fmt.print("<tr class=\"table-sep\"><td colspan=\"100\"></td></tr>\n")
case TokenType.Left:
fmt.print("<div align=\"left\">")
for child in token.children {
print_html(child)
}
fmt.print("</div>")
case TokenType.Right:
fmt.print("<div align=\"right\">")
for child in token.children {
print_html(child)
}
fmt.print("</div>")
case TokenType.Middle:
fmt.print("<div align=\"center\">")
for child in token.children {
print_html(child)
}
fmt.print("</div>")
case TokenType.FootnoteRef:
if id, ok := token.value.(string); ok {
fmt.printf(
"<a href=\"#fn-%s\" id=\"ref-%s\"><sup>%s</sup></a>",
escape_html(id),
escape_html(id),
escape_html(id),
)
}
case TokenType.FootnoteDef:
if id, ok := token.value.(string); ok {
fmt.printf(
"<div class=\"footnote\" id=\"fn-%s\"><a href=\"#ref-%s\">^</a> ",
escape_html(id),
escape_html(id),
)
for child in token.children {
print_html(child)
}
fmt.print("</div>\n")
}
case TokenType.CodeBlock:
lang := ""
if v, ok := token.value.(string); ok && v != "" {
lang = v
}
if lang != "" {
fmt.printf("<pre><code class=\"lang-%s\">", lang)
} else {
fmt.print("<pre><code>")
}
for i := 0; i < len(token.children); i += 1 {
print_html(token.children[i])
if i + 1 < len(token.children) {
fmt.print("\n")
}
}
fmt.print("\n</code></pre>\n")
case TokenType.InlineCode:
if code, ok := token.value.(string); ok {
fmt.printf("<code>%s</code>", escape_html(code))
}
case TokenType.HorizontalRule:
fmt.print("<hr>\n")
case TokenType.Underline:
fmt.print("<u>")
for child in token.children {
print_html(child)
}
fmt.print("</u>")
case TokenType.Superscript:
fmt.print("<sup>")
for child in token.children {
print_html(child)
}
fmt.print("</sup>")
case TokenType.Subscript:
fmt.print("<sub>")
for child in token.children {
print_html(child)
}
fmt.print("</sub>")
case TokenType.HeaderLink:
if target, ok := token.value.(string); ok {
slug := slugifyifyifyify(target)
fmt.printf("<a href=\"#%s\">", escape_html(slug))
for child in token.children {
print_html(child)
}
fmt.print("</a>")
}
case TokenType.Link:
if url, ok := token.value.(string); ok {
fmt.printf("<a href=\"%s\">", escape_html(url))
for child in token.children {
print_html(child)
}
fmt.print("</a>")
}
case TokenType.Image:
if url, ok := token.value.(string); ok {
b := strings.builder_make(context.temp_allocator)
for child in token.children {
get_text_content(child, &b)
}
alt_text := strings.to_string(b)
fmt.printf("<img src=\"%s\" alt=\"%s\">", escape_html(url), escape_html(alt_text))
}
case TokenType.Audio:
if url, ok := token.value.(string); ok {
mime := detect_mime_type(url)
b := strings.builder_make(context.temp_allocator)
for child in token.children {
get_text_content(child, &b)
}
alt_text := strings.to_string(b)
if alt_text == "" {
alt_text = url
}
fmt.printf("<object data=\"%s\" type=\"%s\">\n", escape_html(url), escape_html(mime))
fmt.printf("<param name=\"src\" value=\"%s\">\n", escape_html(url))
fmt.printf("<param name=\"autoplay\" value=\"false\">\n")
fmt.printf("<param name=\"controller\" value=\"true\">\n")
fmt.printf("<a href=\"%s\">%s</a>\n", escape_html(url), escape_html(alt_text))
fmt.print("</object>\n")
}
case TokenType.Video:
if url, ok := token.value.(string); ok {
mime := detect_mime_type(url)
b := strings.builder_make(context.temp_allocator)
for child in token.children {
get_text_content(child, &b)
}
alt_text := strings.to_string(b)
if alt_text == "" {
alt_text = url
}
fmt.printf("<object data=\"%s\" type=\"%s\">\n", escape_html(url), escape_html(mime))
fmt.printf("<param name=\"src\" value=\"%s\">\n", escape_html(url))
fmt.printf("<param name=\"autoplay\" value=\"false\">\n")
fmt.printf("<param name=\"controller\" value=\"true\">\n")
fmt.printf("<a href=\"%s\">%s</a>\n", escape_html(url), escape_html(alt_text))
fmt.print("</object>\n")
}
case TokenType.Iframe:
if url, ok := token.value.(string); ok {
fmt.printf("<iframe src=\"%s\"></iframe>", escape_html(url))
}
case TokenType.Script:
if url, ok := token.value.(string); ok {
fmt.printf("<script src=\"%s\"></script>", escape_html(url))
}
case TokenType.ScriptBlock:
if content, ok := token.value.(string); ok {
fmt.printf("<script>%s</script>", content)
}
case TokenType.Variable:
if name, ok := token.value.(string); ok {
fmt.printf("<span data-tlm-var=\"%s\">", escape_html(name))
for child in token.children {
print_html(child)
}
fmt.print("</span>")
}
case TokenType.VariableDef:
if name, ok := token.value.(string); ok {
b := strings.builder_make(context.temp_allocator)
for child in token.children {
get_text_content(child, &b)
}
val := strings.to_string(b)
fmt.printf(
"<template data-tlm-def=\"%s\" data-tlm-value=\"%s\"></template>",
escape_html(name),
escape_html(val),
)
}
case TokenType.BreakLine:
fmt.print("<br>\n")
case TokenType.Text:
if v, ok := token.value.(string); ok {
fmt.print(escape_html(v))
}
for child in token.children {
print_html(child)
}
}
}