469 lines
10 KiB
Odin
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, "<")
|
|
case '>':
|
|
strings.write_string(&b, ">")
|
|
case '"':
|
|
strings.write_string(&b, """)
|
|
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)
|
|
}
|
|
}
|
|
}
|