1use fhp_core::hash::{class_bloom_bit, selector_hash};
8use fhp_core::tag::Tag;
9
10use crate::node::{Node, NodeFlags, NodeId};
11
12#[derive(Clone, Debug)]
18pub struct Attribute {
19 name_offset: u32,
20 name_len: u16,
21 value_offset: u32,
22 value_len: u16,
23}
24
25const TAG_INDEX_SIZE: usize = 256;
27
28pub struct Arena {
34 pub(crate) nodes: Vec<Node>,
36 pub(crate) text_slab: Vec<u8>,
38 pub(crate) attr_slab: Vec<Attribute>,
40 pub(crate) attr_str_slab: Vec<u8>,
42 pub(crate) source: Vec<u8>,
48 pub(crate) tag_index: Option<Box<[Vec<NodeId>; TAG_INDEX_SIZE]>>,
54}
55
56impl Arena {
57 pub fn new() -> Self {
59 Self {
60 nodes: Vec::new(),
61 text_slab: Vec::new(),
62 attr_slab: Vec::new(),
63 attr_str_slab: Vec::new(),
64 source: Vec::new(),
65 tag_index: None,
66 }
67 }
68
69 pub fn with_capacity(node_cap: usize, text_cap: usize, attr_cap: usize) -> Self {
71 Self {
72 nodes: Vec::with_capacity(node_cap),
73 text_slab: Vec::with_capacity(text_cap),
74 attr_slab: Vec::with_capacity(attr_cap),
75 attr_str_slab: Vec::with_capacity(attr_cap * 32),
76 source: Vec::new(),
77 tag_index: None,
78 }
79 }
80
81 pub fn enable_tag_index(&mut self) {
87 if self.tag_index.is_none() {
88 self.tag_index = Some(Box::new(std::array::from_fn(|_| Vec::new())));
90 }
91 }
92
93 pub fn tag_index(&self) -> Option<&[Vec<NodeId>; TAG_INDEX_SIZE]> {
95 self.tag_index.as_deref()
96 }
97
98 pub fn new_element(&mut self, tag: Tag, depth: u16) -> NodeId {
100 let id = NodeId(self.nodes.len() as u32);
101 self.nodes.push(Node::new_element(tag, depth));
102 if let Some(ref mut idx) = self.tag_index {
104 idx[tag as u8 as usize].push(id);
105 }
106 id
107 }
108
109 pub fn set_unknown_tag_name(&mut self, node: NodeId, tag_name: &str) {
111 if tag_name.is_empty() || self.nodes[node.index()].tag != Tag::Unknown {
112 return;
113 }
114 let offset = self.text_slab.len() as u32;
115 let len = tag_name.len() as u32;
116 self.text_slab.extend_from_slice(tag_name.as_bytes());
117 let n = &mut self.nodes[node.index()];
118 n.text_offset = offset;
119 n.text_len = len;
120 }
121
122 pub fn new_text(&mut self, depth: u16, text: &str) -> NodeId {
124 let offset = self.text_slab.len() as u32;
125 let len = text.len() as u32;
126 self.text_slab.extend_from_slice(text.as_bytes());
127 let id = NodeId(self.nodes.len() as u32);
128 self.nodes.push(Node::new_text(depth, offset, len));
129 id
130 }
131
132 pub fn new_text_ref(&mut self, depth: u16, source_offset: u32, len: u32) -> NodeId {
139 let id = NodeId(self.nodes.len() as u32);
140 let mut node = Node::new_text(depth, source_offset, len);
141 node.flags.set(NodeFlags::IS_TEXT_FROM_SOURCE);
142 self.nodes.push(node);
143 id
144 }
145
146 pub fn set_source(&mut self, input: &str) {
148 self.source = input.as_bytes().to_vec();
149 }
150
151 pub fn set_source_owned(&mut self, source: String) {
156 self.source = source.into_bytes();
157 }
158
159 pub fn new_comment(&mut self, depth: u16, text: &str) -> NodeId {
161 let offset = self.text_slab.len() as u32;
162 let len = text.len() as u32;
163 self.text_slab.extend_from_slice(text.as_bytes());
164 let id = NodeId(self.nodes.len() as u32);
165 self.nodes.push(Node::new_comment(depth, offset, len));
166 id
167 }
168
169 pub fn new_doctype(&mut self, depth: u16, text: &str) -> NodeId {
171 let offset = self.text_slab.len() as u32;
172 let len = text.len() as u32;
173 self.text_slab.extend_from_slice(text.as_bytes());
174 let id = NodeId(self.nodes.len() as u32);
175 self.nodes.push(Node::new_doctype(depth, offset, len));
176 id
177 }
178
179 pub fn set_attrs(&mut self, node: NodeId, attrs: &[fhp_tokenizer::token::Attribute<'_>]) {
181 if attrs.is_empty() {
182 return;
183 }
184 let offset = self.attr_slab.len() as u32;
185 let count = attrs.len().min(u16::MAX as usize) as u16;
186
187 for attr in &attrs[..count as usize] {
188 let name_offset = self.attr_str_slab.len() as u32;
189 self.attr_str_slab.extend_from_slice(attr.name.as_bytes());
190 let name_len = attr.name.len() as u16;
191
192 let (value_offset, value_len) = if let Some(ref v) = attr.value {
193 let vo = self.attr_str_slab.len() as u32;
194 self.attr_str_slab.extend_from_slice(v.as_bytes());
195 (vo, v.len() as u16)
196 } else {
197 (0, 0)
198 };
199
200 self.attr_slab.push(Attribute {
201 name_offset,
202 name_len,
203 value_offset,
204 value_len,
205 });
206 }
207
208 let n = &mut self.nodes[node.index()];
209 n.attr_offset = offset;
210 n.attr_count = count;
211 n.flags.set(NodeFlags::HAS_ATTRS);
212
213 self.compute_node_hashes(node, offset, count);
215 }
216
217 pub fn set_attrs_from_raw(&mut self, node: NodeId, attr_raw: &str) {
223 let bytes = attr_raw.as_bytes();
224 let end = bytes.len();
225 if end == 0 {
226 return;
227 }
228
229 let slab_offset = self.attr_slab.len() as u32;
230 let mut count: u16 = 0;
231 let mut pos = 0;
232
233 loop {
234 pos += bytes[pos..end]
236 .iter()
237 .position(|&b| !is_attr_whitespace(b))
238 .unwrap_or(end - pos);
239 if pos >= end || count == u16::MAX {
240 break;
241 }
242
243 let name_start = pos;
245 while pos < end && !is_attr_name_end(bytes[pos]) {
246 pos += 1;
247 }
248 if name_start == pos {
249 pos += 1;
251 continue;
252 }
253
254 let name_slab_offset = self.attr_str_slab.len() as u32;
255 self.attr_str_slab
256 .extend_from_slice(&bytes[name_start..pos]);
257 let name_len = (pos - name_start) as u16;
258
259 pos += bytes[pos..end]
261 .iter()
262 .position(|&b| !is_attr_whitespace(b))
263 .unwrap_or(end - pos);
264
265 if pos < end && bytes[pos] == b'=' {
267 pos += 1;
268
269 pos += bytes[pos..end]
271 .iter()
272 .position(|&b| !is_attr_whitespace(b))
273 .unwrap_or(end - pos);
274
275 if pos < end && (bytes[pos] == b'"' || bytes[pos] == b'\'') {
277 let quote = bytes[pos];
279 pos += 1;
280 let val_start = pos;
281 if let Some(found) = memchr::memchr(quote, &bytes[pos..end]) {
282 pos += found;
283 } else {
284 pos = end;
285 }
286 let val_end = pos;
287 if pos < end {
288 pos += 1; }
290 let raw_value = &attr_raw[val_start..val_end];
291 let (value_offset, value_len) = self.push_attr_value(raw_value);
292 self.attr_slab.push(Attribute {
293 name_offset: name_slab_offset,
294 name_len,
295 value_offset,
296 value_len,
297 });
298 } else {
299 let val_start = pos;
301 while pos < end && !is_attr_whitespace(bytes[pos]) && bytes[pos] != b'>' {
302 pos += 1;
303 }
304 let raw_value = &attr_raw[val_start..pos];
305 let (value_offset, value_len) = self.push_attr_value(raw_value);
306 self.attr_slab.push(Attribute {
307 name_offset: name_slab_offset,
308 name_len,
309 value_offset,
310 value_len,
311 });
312 }
313 } else {
314 self.attr_slab.push(Attribute {
316 name_offset: name_slab_offset,
317 name_len,
318 value_offset: 0,
319 value_len: 0,
320 });
321 }
322
323 count += 1;
324 }
325
326 if count > 0 {
327 let n = &mut self.nodes[node.index()];
328 n.attr_offset = slab_offset;
329 n.attr_count = count;
330 n.flags.set(NodeFlags::HAS_ATTRS);
331
332 self.compute_node_hashes(node, slab_offset, count);
334 }
335 }
336
337 fn compute_node_hashes(&mut self, node: NodeId, slab_offset: u32, count: u16) {
342 let mut class_hash: u64 = 0;
343 let mut id_hash: u32 = 0;
344 let start = slab_offset as usize;
345 let end = start + count as usize;
346
347 for i in start..end {
348 let attr = &self.attr_slab[i];
349 let name_start = attr.name_offset as usize;
350 let name_end = name_start + attr.name_len as usize;
351 let name_bytes = &self.attr_str_slab[name_start..name_end];
352
353 if name_bytes.eq_ignore_ascii_case(b"class") && attr.value_len > 0 {
354 let val_start = attr.value_offset as usize;
355 let val_end = val_start + attr.value_len as usize;
356 let val = &self.attr_str_slab[val_start..val_end];
357 let mut pos = 0;
359 while pos < val.len() {
360 while pos < val.len() && val[pos].is_ascii_whitespace() {
362 pos += 1;
363 }
364 let token_start = pos;
365 while pos < val.len() && !val[pos].is_ascii_whitespace() {
366 pos += 1;
367 }
368 if pos > token_start {
369 class_hash |= class_bloom_bit(&val[token_start..pos]);
370 }
371 }
372 } else if name_bytes.eq_ignore_ascii_case(b"id") && attr.value_len > 0 {
373 let val_start = attr.value_offset as usize;
374 let val_end = val_start + attr.value_len as usize;
375 id_hash = selector_hash(&self.attr_str_slab[val_start..val_end]);
376 }
377 }
378
379 let n = &mut self.nodes[node.index()];
380 n.class_hash = class_hash;
381 n.id_hash = id_hash;
382 }
383
384 #[cfg(feature = "entity-decode")]
386 fn push_attr_value(&mut self, raw_value: &str) -> (u32, u16) {
387 let offset = self.attr_str_slab.len() as u32;
388 let decoded = fhp_tokenizer::entity::decode_entities(raw_value);
389 self.attr_str_slab.extend_from_slice(decoded.as_bytes());
390 (offset, decoded.len() as u16)
391 }
392
393 #[cfg(not(feature = "entity-decode"))]
395 fn push_attr_value(&mut self, raw_value: &str) -> (u32, u16) {
396 let offset = self.attr_str_slab.len() as u32;
397 self.attr_str_slab.extend_from_slice(raw_value.as_bytes());
398 (offset, raw_value.len() as u16)
399 }
400
401 #[inline]
405 pub fn set_element_index(&mut self, node: NodeId, index: u16) {
406 self.nodes[node.index()].element_index = index;
407 }
408
409 pub fn set_self_closing(&mut self, node: NodeId) {
411 self.nodes[node.index()]
412 .flags
413 .set(NodeFlags::IS_SELF_CLOSING);
414 }
415
416 pub fn append_child(&mut self, parent: NodeId, child: NodeId) {
426 debug_assert!(
427 parent != child,
428 "append_child: parent and child must be distinct nodes (aliasing &mut)"
429 );
430 let nodes = self.nodes.as_mut_ptr();
431 unsafe {
438 let p = &mut *nodes.add(parent.index());
439 let c = &mut *nodes.add(child.index());
440 let last = p.last_child;
441 c.parent = parent;
442 if last.is_null() {
443 p.first_child = child;
444 } else {
445 debug_assert!(
446 last != parent && last != child,
447 "append_child: parent.last_child must differ from parent and child"
448 );
449 (*nodes.add(last.index())).next_sibling = child;
450 c.prev_sibling = last;
451 }
452 p.last_child = child;
453 p.flags.set(NodeFlags::HAS_CHILDREN);
454 }
455 }
456
457 #[inline]
459 pub fn attr_name(&self, attr: &Attribute) -> &str {
460 let start = attr.name_offset as usize;
461 let end = start + attr.name_len as usize;
462 unsafe { std::str::from_utf8_unchecked(&self.attr_str_slab[start..end]) }
464 }
465
466 #[inline]
468 pub fn attr_value(&self, attr: &Attribute) -> Option<&str> {
469 if attr.value_len == 0 {
470 return None;
471 }
472 let start = attr.value_offset as usize;
473 let end = start + attr.value_len as usize;
474 Some(unsafe { std::str::from_utf8_unchecked(&self.attr_str_slab[start..end]) })
476 }
477
478 #[inline]
483 pub fn attrs(&self, node: NodeId) -> &[Attribute] {
484 let n = &self.nodes[node.index()];
485 if n.attr_count == 0 {
486 return &[];
487 }
488 let start = n.attr_offset as usize;
489 let end = start + n.attr_count as usize;
490 &self.attr_slab[start..end]
491 }
492
493 #[inline]
495 pub fn text(&self, node: NodeId) -> &str {
496 let n = &self.nodes[node.index()];
497 if n.text_len == 0 {
498 return "";
499 }
500 let start = n.text_offset as usize;
501 let end = start + n.text_len as usize;
502 if n.flags.has(NodeFlags::IS_TEXT) && n.flags.has(NodeFlags::IS_TEXT_FROM_SOURCE) {
503 unsafe { std::str::from_utf8_unchecked(&self.source[start..end]) }
505 } else {
506 unsafe { std::str::from_utf8_unchecked(&self.text_slab[start..end]) }
508 }
509 }
510
511 #[inline]
513 pub fn unknown_tag_name(&self, node: NodeId) -> Option<&str> {
514 let n = &self.nodes[node.index()];
515 if n.tag != Tag::Unknown || n.text_len == 0 {
516 return None;
517 }
518 let start = n.text_offset as usize;
519 let end = start + n.text_len as usize;
520 Some(unsafe { std::str::from_utf8_unchecked(&self.text_slab[start..end]) })
522 }
523
524 #[inline]
526 pub fn get(&self, id: NodeId) -> &Node {
527 &self.nodes[id.index()]
528 }
529
530 #[inline]
532 pub fn get_mut(&mut self, id: NodeId) -> &mut Node {
533 &mut self.nodes[id.index()]
534 }
535
536 #[inline]
538 pub fn len(&self) -> usize {
539 self.nodes.len()
540 }
541
542 #[inline]
544 pub fn is_empty(&self) -> bool {
545 self.nodes.is_empty()
546 }
547}
548
549impl Default for Arena {
550 fn default() -> Self {
551 Self::new()
552 }
553}
554
555#[inline(always)]
557fn is_attr_whitespace(b: u8) -> bool {
558 matches!(b, b' ' | b'\t' | b'\n' | b'\r')
559}
560
561#[inline(always)]
563fn is_attr_name_end(b: u8) -> bool {
564 matches!(b, b' ' | b'\t' | b'\n' | b'\r' | b'=' | b'/' | b'>')
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570 use std::borrow::Cow;
571
572 #[test]
573 fn new_element_and_get() {
574 let mut arena = Arena::new();
575 let id = arena.new_element(Tag::Div, 0);
576 assert_eq!(id, NodeId(0));
577 assert_eq!(arena.get(id).tag, Tag::Div);
578 assert_eq!(arena.get(id).depth, 0);
579 }
580
581 #[test]
582 fn new_text_and_read() {
583 let mut arena = Arena::new();
584 let id = arena.new_text(1, "hello world");
585 assert!(arena.get(id).flags.has(NodeFlags::IS_TEXT));
586 assert_eq!(arena.text(id), "hello world");
587 }
588
589 #[test]
590 fn append_child_single() {
591 let mut arena = Arena::new();
592 let parent = arena.new_element(Tag::Div, 0);
593 let child = arena.new_element(Tag::Span, 1);
594 arena.append_child(parent, child);
595
596 assert_eq!(arena.get(parent).first_child, child);
597 assert_eq!(arena.get(parent).last_child, child);
598 assert_eq!(arena.get(child).parent, parent);
599 assert!(arena.get(child).next_sibling.is_null());
600 assert!(arena.get(child).prev_sibling.is_null());
601 }
602
603 #[test]
604 fn append_child_multiple() {
605 let mut arena = Arena::new();
606 let parent = arena.new_element(Tag::Div, 0);
607 let c1 = arena.new_element(Tag::Span, 1);
608 let c2 = arena.new_element(Tag::P, 1);
609 let c3 = arena.new_element(Tag::A, 1);
610
611 arena.append_child(parent, c1);
612 arena.append_child(parent, c2);
613 arena.append_child(parent, c3);
614
615 assert_eq!(arena.get(parent).first_child, c1);
616 assert_eq!(arena.get(parent).last_child, c3);
617
618 assert_eq!(arena.get(c1).next_sibling, c2);
619 assert!(arena.get(c1).prev_sibling.is_null());
620
621 assert_eq!(arena.get(c2).prev_sibling, c1);
622 assert_eq!(arena.get(c2).next_sibling, c3);
623
624 assert_eq!(arena.get(c3).prev_sibling, c2);
625 assert!(arena.get(c3).next_sibling.is_null());
626 }
627
628 #[test]
629 fn attrs_roundtrip() {
630 use fhp_tokenizer::token::Attribute as TokAttr;
631
632 let mut arena = Arena::new();
633 let id = arena.new_element(Tag::A, 0);
634
635 let tok_attrs = vec![
636 TokAttr {
637 name: Cow::Borrowed("href"),
638 value: Some(Cow::Borrowed("https://example.com")),
639 },
640 TokAttr {
641 name: Cow::Borrowed("class"),
642 value: Some(Cow::Borrowed("link")),
643 },
644 ];
645 arena.set_attrs(id, &tok_attrs);
646
647 let attrs = arena.attrs(id);
648 assert_eq!(attrs.len(), 2);
649 assert_eq!(arena.attr_name(&attrs[0]), "href");
650 assert_eq!(arena.attr_value(&attrs[0]), Some("https://example.com"));
651 assert_eq!(arena.attr_name(&attrs[1]), "class");
652 assert_eq!(arena.attr_value(&attrs[1]), Some("link"));
653 }
654
655 #[test]
656 fn empty_attrs() {
657 let mut arena = Arena::new();
658 let id = arena.new_element(Tag::Div, 0);
659 assert!(arena.attrs(id).is_empty());
660 }
661
662 #[test]
663 fn arena_len() {
664 let mut arena = Arena::new();
665 assert!(arena.is_empty());
666 arena.new_element(Tag::Div, 0);
667 arena.new_text(1, "hi");
668 assert_eq!(arena.len(), 2);
669 }
670}