html.odin (5101B)
1 package html 2 3 // Node_Ref is an index into Document.nodes. 4 // -1 means "none" (no parent, no child, no sibling). 5 Node_Ref :: int 6 7 // Attribute_Ref is an index into Document.attrs. 8 Attribute_Ref :: int 9 10 // Node_Kind classifies a node in the document tree. 11 Node_Kind :: enum u8 { 12 Element, // an HTML tag: <div>, <p>, ... 13 Text, // raw text content 14 Comment, // <!-- ... --> 15 Doctype, // <!DOCTYPE ...> 16 } 17 18 // Node is a single node in the document tree. 19 // 20 // Nodes are stored in a #soa[] slice on the Document (see Document.nodes). 21 // All cross-references use integer indices, never pointers, so the arena 22 // can be frozen (immutable) after parse and traversed cache-efficiently. 23 // 24 // Index conventions: -1 means "none". 25 Node :: struct { 26 kind: Node_Kind, 27 parent: Node_Ref, // index of parent node; -1 for root 28 first_child: Node_Ref, // index of first child; -1 if leaf 29 last_child: Node_Ref, // index of last child; -1 if leaf 30 next_sibling: Node_Ref, // index of next sibling; -1 if last 31 prev_sibling: Node_Ref, // index of prev sibling; -1 if first 32 child_count: u16, 33 attrs_offset: Attribute_Ref, // index into Document.attrs; -1 if none 34 attrs_count: u16, 35 name: string, // tag name for Element; "" otherwise (slices source) 36 text: string, // text/comment body for Text/Comment (slices source) 37 } 38 39 // Attribute is a single name="value" pair on an Element node. 40 // Stored in a flat []Attribute on the Document; a node references a 41 // contiguous run [attrs_offset, attrs_offset+attrs_count). 42 Attribute :: struct { 43 name: string, // slices source 44 value: string, // slices source; "" for boolean attributes 45 } 46 47 // Document is the immutable, fully-parsed HTML document. 48 // 49 // All strings on nodes and attributes are slices into `source`, which the 50 // Document owns. Delete with document_delete when done. 51 Document :: struct { 52 source: string, // owned copy of the input 53 nodes: #soa[]Node, // frozen arena; index 0 is the root 54 attrs: []Attribute, 55 preamble: string, // anything before the first tag (e.g. doctype), slices source 56 } 57 58 // node_name returns the tag name of an Element, or "" for other kinds. 59 node_name :: proc(doc: ^Document, ref: Node_Ref) -> string { 60 return doc.nodes[ref].name 61 } 62 63 // node_kind returns the Node_Kind of the node at ref. 64 node_kind :: proc(doc: ^Document, ref: Node_Ref) -> Node_Kind { 65 return doc.nodes[ref].kind 66 } 67 68 // node_text returns the text/comment body for Text and Comment nodes. 69 node_text :: proc(doc: ^Document, ref: Node_Ref) -> string { 70 return doc.nodes[ref].text 71 } 72 73 // node_parent returns the parent node ref, or -1 for the root. 74 node_parent :: proc(doc: ^Document, ref: Node_Ref) -> Node_Ref { 75 return doc.nodes[ref].parent 76 } 77 78 // node_first_child returns the first child ref, or -1 if leaf. 79 node_first_child :: proc(doc: ^Document, ref: Node_Ref) -> Node_Ref { 80 return doc.nodes[ref].first_child 81 } 82 83 // node_last_child returns the last child ref, or -1 if leaf. 84 node_last_child :: proc(doc: ^Document, ref: Node_Ref) -> Node_Ref { 85 return doc.nodes[ref].last_child 86 } 87 88 // node_next_sibling returns the next sibling ref, or -1 if last. 89 node_next_sibling :: proc(doc: ^Document, ref: Node_Ref) -> Node_Ref { 90 return doc.nodes[ref].next_sibling 91 } 92 93 // node_prev_sibling returns the previous sibling ref, or -1 if first. 94 node_prev_sibling :: proc(doc: ^Document, ref: Node_Ref) -> Node_Ref { 95 return doc.nodes[ref].prev_sibling 96 } 97 98 // node_child_count returns the number of children. 99 node_child_count :: proc(doc: ^Document, ref: Node_Ref) -> int { 100 return int(doc.nodes[ref].child_count) 101 } 102 103 // Node_Children is a zero-allocation iterator over a node's children. 104 // It lives on the caller's stack and walks the sibling chain. 105 Node_Children :: struct { 106 doc: ^Document, 107 current: Node_Ref, 108 } 109 110 // node_children returns an iterator over the children of a node. 111 // The iterator walks the sibling chain starting from first_child. 112 node_children :: proc(doc: ^Document, ref: Node_Ref) -> Node_Children { 113 return {doc = doc, current = doc.nodes[ref].first_child} 114 } 115 116 // next yields the next child ref, or false when exhausted. 117 next :: proc(it: ^Node_Children) -> (ref: Node_Ref, ok: bool) { 118 if it.current < 0 { 119 return 120 } 121 ref = it.current 122 it.current = it.doc.nodes[ref].next_sibling 123 return ref, true 124 } 125 126 // node_attr returns the value of the named attribute and whether it exists. 127 node_attr :: proc(doc: ^Document, ref: Node_Ref, name: string) -> (string, bool) { 128 offset := doc.nodes[ref].attrs_offset 129 count := int(doc.nodes[ref].attrs_count) 130 for k in 0 ..< count { 131 attr := &doc.attrs[offset + k] 132 if attr.name == name { 133 return attr.value, true 134 } 135 } 136 return "", false 137 } 138 139 // node_has_attr reports whether the node has the named attribute. 140 node_has_attr :: proc(doc: ^Document, ref: Node_Ref, name: string) -> bool { 141 _, ok := node_attr(doc, ref, name) 142 return ok 143 } 144 145 // document_delete frees the arena, attribute array, and owned source. 146 document_delete :: proc(doc: ^Document) { 147 delete(doc.nodes) 148 delete(doc.attrs) 149 delete(doc.source) 150 doc.nodes = nil 151 doc.attrs = nil 152 doc.source = "" 153 doc.preamble = "" 154 } 155