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