package html_builder import "core:fmt" import "core:slice" import "core:strings" Element :: struct { tag_name: string, id: string, children: [dynamic]Node, attributes: map[string]string, class_list: [dynamic]string, } Node :: union { Element, string, } empty_tags: map[string]bool = { "img" = true, "br" = true, "hr" = true, } indent :: proc(sb: ^strings.Builder, depth: int) { for i in 0 ..< depth { fmt.sbprintf(sb, "\t") } } element_to_string :: proc(sb: ^strings.Builder, e: Element, depth: int = 0) -> string { indent(sb, depth) fmt.sbprintf(sb, "<%s", e.tag_name) for name, attr in e.attributes { fmt.sbprintf(sb, " %s=\"%s\"", name, attr) } fmt.sbprintf(sb, ">") children_len := len(e.children) if children_len == 1 { switch ch in e.children[0] { case string: fmt.sbprintf(sb, "%s", ch) case Element: fmt.sbprintf(sb, "\n") element_to_string(sb, ch, depth + 1) fmt.sbprintf(sb, "\n") indent(sb, depth) } } if children_len > 1 { for child, idx in e.children { fmt.sbprintf(sb, "\n") switch ch in child { case string: indent(sb, depth + 1) fmt.sbprintf(sb, "%s", ch) case Element: element_to_string(sb, ch, depth + 1) } } fmt.sbprintf(sb, "\n") indent(sb, depth) } if !empty_tags[e.tag_name] do fmt.sbprintf(sb, "", e.tag_name) return strings.to_string(sb^) } parent_element: ^Element elements_by_id: map[string]^Element elements_by_classes: map[string][dynamic]^Element _element_scope_start :: proc(tag_name: string, id: string = "") -> (previous_parent: ^Element) { children := &parent_element.children new_idx := len(children^) new_element := Element { tag_name = tag_name, children = make([dynamic]Node), } append(children, new_element) previous_parent = parent_element parent_element = &children[new_idx].(Element) return previous_parent } _element_scope_end :: proc(previous_parent: ^Element) { parent_element = previous_parent } // @(deferred_out = free_html) html :: proc() -> ^Element { parent_element = new(Element) parent_element.tag_name = "html" parent_element.children = make([dynamic]Node) return parent_element } text :: proc(txt: string) { append(&parent_element.children, txt) } attr :: proc(name: string, value: string) { if name == "class" { class(value) return } map_insert(&parent_element.attributes, name, value) } id :: proc(id: string) { if id != "" { parent_element.id = id parent_element.attributes["id"] = id elements_by_id[id] = parent_element } } class :: proc(class_names: string) { classes: [dynamic]string for class in strings.split(class_names, " ") { _, found := slice.linear_search(parent_element.class_list[:], class) if elements_by_classes[class] == nil do elements_by_classes[class] = make([dynamic]^Element) if !found { append(&classes, class) append(&elements_by_classes[class], parent_element) } } append(&parent_element.class_list, ..classes[:]) map_insert( &parent_element.attributes, "class", strings.join(parent_element.class_list[:], " "), ) } free_html :: proc(el: ^Element) { defer parent_element = nil handle_children :: proc(children: [dynamic]Node) { for e in children { _e, ok := e.(Element) if ok do handle_children(_e.children) } delete(children) } handle_children(el.children) free(el) } @(deferred_out = _element_scope_end) body :: proc(id: string = "") -> (previous_parent: ^Element) { return _element_scope_start("body", id) } @(deferred_out = _element_scope_end) head :: proc(id: string = "") -> (previous_parent: ^Element) { return _element_scope_start("head", id) } @(deferred_out = _element_scope_end) title :: proc(txt: string = "") -> (previous_parent: ^Element) { el := _element_scope_start("title") text(txt) return el } @(deferred_out = _element_scope_end) h1 :: proc(txt: string = "") -> (previous_parent: ^Element) { el := _element_scope_start("h1") text(txt) return el } @(deferred_out = _element_scope_end) h2 :: proc(txt: string = "") -> (previous_parent: ^Element) { el := _element_scope_start("h2") text(txt) return el } @(deferred_out = _element_scope_end) h3 :: proc(txt: string = "") -> (previous_parent: ^Element) { el := _element_scope_start("h3") text(txt) return el } @(deferred_out = _element_scope_end) h4 :: proc(txt: string = "") -> (previous_parent: ^Element) { el := _element_scope_start("h4") text(txt) return el } @(deferred_out = _element_scope_end) h5 :: proc(txt: string = "") -> (previous_parent: ^Element) { el := _element_scope_start("h5") text(txt) return el } @(deferred_out = _element_scope_end) h6 :: proc(txt: string = "") -> (previous_parent: ^Element) { el := _element_scope_start("h6") text(txt) return el } @(deferred_out = _element_scope_end) div :: proc(id: string = "") -> (previous_parent: ^Element) { previous_parent = _element_scope_start("div", id) return previous_parent } @(deferred_out = _element_scope_end) img :: proc(src: string = "", alt: string = "") -> (previous_parent: ^Element) { previous_parent = _element_scope_start("img") parent_element.children = nil parent_element.attributes["src"] = src if alt != "" do parent_element.attributes["alt"] = alt return previous_parent } @(deferred_out = _element_scope_end) a :: proc(txt: string, href: string = "") -> (previous_parent: ^Element) { previous_parent = _element_scope_start("a") append(&parent_element.children, txt) parent_element.attributes["href"] = href return previous_parent }