orinium_browser/engine/css/
parser.rs

1//! CSS Parser
2//!
3//! Consumes tokens produced by the tokenizer and builds
4//! higher-level CSS syntax structures.
5//!
6//! ## Responsibilities
7//! - Parse token streams into structured CSS data
8//!   (selectors, declarations, component values)
9//! - Handle nesting such as blocks and functions
10//!
11//! ## Non-responsibilities
12//! - Tokenization of raw input
13//! - Semantic interpretation (length resolution, color computation, etc.)
14//!
15//! ## Design notes
16//! - No property-specific validation is performed here
17//! - Semantic meaning is assigned in later stages (style computation, layout)
18use std::collections::VecDeque;
19use std::fmt;
20
21use super::tokenizer::{Token, Tokenizer};
22use super::values::{CssValue, Unit};
23
24/// Node kinds used in the CSS syntax tree.
25///
26/// These nodes represent **syntactic structure only**.
27/// No semantic validation or value resolution is performed here.
28#[derive(Debug, Clone)]
29pub enum CssNodeType {
30    /// Root node of a CSS document
31    Stylesheet,
32
33    /// Qualified rule (e.g. `div { ... }`)
34    Rule {
35        /// Selectors associated with this rule
36        selectors: Vec<ComplexSelector>,
37    },
38
39    /// At-rule (e.g. `@media`, `@supports`)
40    AtRule {
41        /// At-rule name without `@`
42        name: String,
43
44        params: AtQuery,
45    },
46
47    /// Declaration inside a rule block (e.g. `color: red`)
48    Declaration {
49        /// Property name
50        name: String,
51
52        value: CssValue,
53    },
54}
55
56#[derive(Debug, Clone)]
57pub enum AtQuery {
58    Keyword(String), // screen, and, not
59    Condition {
60        name: String,    // max-width
61        value: CssValue, // 600px
62    },
63    Group(Vec<AtQuery>), // ( ... )
64}
65
66/// Node in the CSS syntax tree.
67///
68/// Each node represents a syntactic construct such as a rule,
69/// at-rule, or declaration, and may contain child nodes.
70#[derive(Debug)]
71pub struct CssNode {
72    /// Kind of this CSS node
73    node: CssNodeType,
74
75    /// Child nodes forming the tree structure
76    children: Vec<CssNode>,
77}
78
79impl CssNode {
80    pub fn node(&self) -> &CssNodeType {
81        &self.node
82    }
83    pub fn children(&self) -> &Vec<CssNode> {
84        &self.children
85    }
86}
87
88#[derive(Debug, Clone, PartialEq, Eq, Hash)]
89pub struct Selector {
90    /// Type selector (e.g. `div`)
91    ///
92    /// `None` represents the absence of a type selector
93    /// (e.g. `.class`, `#id`).
94    pub tag: Option<String>,
95
96    /// ID selector (e.g. `#main`)
97    pub id: Option<String>,
98
99    /// Class selectors (e.g. `.container`)
100    pub classes: Vec<String>,
101
102    /// Pseudo-class (e.g. `:hover`)
103    pub pseudo_class: Option<String>,
104
105    /// Pseudo-element (e.g. `::before`)
106    pub pseudo_element: Option<String>,
107}
108
109/// Combinator defining the relationship between selectors.
110///
111/// Additional combinators (`>`, `+`, `~`) may be added later.
112#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
113pub enum Combinator {
114    /// Descendant combinator (` `)
115    Descendant,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq, Hash)]
119pub struct SelectorPart {
120    /// Simple selector matched at this step
121    pub selector: Selector,
122
123    /// Relationship to the next selector on the left.
124    ///
125    /// `None` indicates this is the leftmost selector
126    /// in the selector sequence.
127    pub combinator: Option<Combinator>,
128}
129
130/// A complex CSS selector composed of multiple selector parts.
131///
132/// Selector parts are stored **from right to left** to match
133/// the order used during selector matching.
134///
135/// Example:
136/// ```text
137/// A B
138/// ```
139/// is stored as:
140/// ```text
141/// [
142///   B (Descendant),
143///   A (None)
144/// ]
145/// ```
146#[derive(Debug, Clone, PartialEq, Eq, Hash)]
147pub struct ComplexSelector {
148    pub parts: Vec<SelectorPart>,
149}
150
151/// CSS parser consuming tokens and producing syntax structures.
152pub struct Parser<'a> {
153    /// Source of tokens produced by the tokenizer
154    tokenizer: Tokenizer<'a>,
155
156    /// Used to detect the start and end of rule blocks (`{}`).
157    brace_depth: usize,
158
159    /// Lookahead token (optional)
160    ///
161    /// Parser may need to peek the next token without consuming it.
162    lookahead: VecDeque<Token>,
163}
164
165/// Parser error kinds
166#[derive(Debug, Clone, PartialEq, Eq)]
167pub enum ParserErrorKind {
168    /// Expected a token but found something else
169    UnexpectedToken {
170        expected: &'static str,
171        found: String, // Token debug or value
172    },
173
174    /// Unexpected end of file
175    UnexpectedEOF,
176
177    /// Invalid or unsupported CSS syntax
178    InvalidSyntax,
179
180    /// Mismatched braces or parentheses
181    MismatchedDelimiter { expected: char, found: char },
182}
183
184impl fmt::Display for ParserErrorKind {
185    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
186        write!(f, "{:?}", self)
187    }
188}
189
190/// Parser error
191#[derive(Debug, Clone, PartialEq, Eq)]
192pub struct ParserError {
193    /// Kind of the error
194    pub kind: ParserErrorKind,
195    /// Context
196    pub context: Vec<String>,
197}
198
199impl ParserError {
200    pub fn with_context(mut self, ctx: impl Into<String>) -> Self {
201        self.context.push(ctx.into());
202        self
203    }
204}
205
206impl fmt::Display for ParserError {
207    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208        let mut ctx = self.context.clone();
209        ctx.reverse();
210        write!(
211            f,
212            "CssParserError: {}, (Context:[{}])",
213            self.kind,
214            ctx.join(" <-")
215        )
216    }
217}
218
219impl std::error::Error for ParserError {}
220
221/// Result type for parser functions
222pub type ParseResult<T> = Result<T, ParserError>;
223
224impl<'a> Parser<'a> {
225    /// Create a new CSS parser from a source string.
226    pub fn new(input: &'a str) -> Self {
227        Self {
228            tokenizer: Tokenizer::new(input),
229            brace_depth: 0,
230            lookahead: VecDeque::new(),
231        }
232    }
233
234    fn ensure_lookahead(&mut self, n: usize) {
235        while self.lookahead.len() <= n {
236            let tok = self.tokenizer.next_token();
237            self.lookahead.push_back(tok);
238        }
239    }
240
241    fn peek_next_token(&mut self, cursor_size: usize) -> &Token {
242        self.ensure_lookahead(cursor_size);
243        &self.lookahead[cursor_size]
244    }
245
246    /// Consume and return the next token.
247    fn peek_token(&mut self) -> &Token {
248        self.peek_next_token(0)
249    }
250
251    fn consume_token(&mut self) -> Token {
252        if let Some(tok) = self.lookahead.pop_front() {
253            tok
254        } else {
255            self.tokenizer.next_token()
256        }
257    }
258
259    /// Parse the entire CSS source into a syntax tree.
260    ///
261    /// This method consumes tokens until `Token::EOF` is reached and constructs
262    /// a `CssNode` representing the stylesheet root.
263    ///
264    /// Parsing behavior:
265    /// - Whitespace tokens are ignored
266    /// - Qualified rules and at-rules are parsed into child nodes
267    /// - No semantic validation is performed
268    pub fn parse(&mut self) -> ParseResult<CssNode> {
269        let mut stylesheet = CssNode {
270            node: CssNodeType::Stylesheet,
271            children: vec![],
272        };
273
274        loop {
275            let token = self.peek_token().clone();
276
277            match token {
278                Token::EOF => break,
279                Token::Whitespace | Token::Comment(_) => {
280                    self.consume_token();
281                }
282                Token::AtKeyword(_) => {
283                    let node = self
284                        .parse_at_rule()
285                        .map_err(|e| e.with_context("parse: failed to parse at-rule"))?;
286                    log::debug!(target: "CssParser", "AtRule parsed: {:?}", &node);
287                    stylesheet.children.push(node);
288                }
289                _ => {
290                    let node = self
291                        .parse_rule()
292                        .map_err(|e| e.with_context("parse: failed to parse rule"))?;
293                    log::debug!(target: "CssParser", "Rule parsed: {:?}", &node);
294                    stylesheet.children.push(node);
295                }
296            }
297        }
298
299        Ok(stylesheet)
300    }
301
302    fn parse_at_rule(&mut self) -> ParseResult<CssNode> {
303        // 1. consume '@' token
304        let at_name = if let Token::AtKeyword(name) = self.consume_token() {
305            name
306        } else {
307            return Err(ParserError {
308                kind: ParserErrorKind::UnexpectedToken {
309                    expected: "@keyword",
310                    found: format!("{:?}", self.peek_token()),
311                },
312                context: vec![],
313            });
314        };
315
316        // 2. Collect prelude tokens (until '{' or ';'), handling nested parentheses
317        let mut prelude = vec![];
318        let mut paren_depth = 0;
319
320        loop {
321            match self.peek_token() {
322                Token::Delim('{') if paren_depth == 0 => break,
323                Token::Delim(';') if paren_depth == 0 => break,
324                Token::Delim('(') => {
325                    paren_depth += 1;
326                    prelude.push(self.consume_token());
327                }
328                Token::Delim(')') => {
329                    paren_depth -= 1;
330                    prelude.push(self.consume_token());
331                }
332                Token::EOF => break,
333                _ => prelude.push(self.consume_token()),
334            }
335        }
336
337        // 3. Convert prelude tokens to CssValue (handles functions and nested parentheses)
338        let params = Self::parse_at_query(prelude).map_err(|e| {
339            e.with_context("parse_at_rule: failed to parse params via parse_at_query")
340        })?;
341
342        // 4. Block vs semicolon
343        let children = if self.peek_token() == &Token::Delim('{') {
344            self.consume_token();
345            self.brace_depth += 1;
346
347            let mut children = vec![];
348            while self.peek_token() != &Token::Delim('}') {
349                match self.peek_token() {
350                    Token::EOF => {
351                        return Err(ParserError {
352                            kind: ParserErrorKind::UnexpectedEOF,
353                            context: vec![],
354                        });
355                    }
356                    Token::Whitespace => {
357                        self.consume_token();
358                    }
359                    Token::AtKeyword(_) => {
360                        let node = self.parse_at_rule().map_err(|e| {
361                            e.with_context("parse_at_rule: failed to parse nested at-rule")
362                        })?;
363                        children.push(node);
364                    }
365                    _ => {
366                        let mut cursor = 0;
367                        let mut is_declaration = false;
368
369                        loop {
370                            match self.peek_next_token(cursor) {
371                                Token::Delim('{') => {
372                                    break;
373                                }
374                                Token::Delim('}') => {
375                                    is_declaration = true;
376                                    break;
377                                }
378                                Token::EOF => {
379                                    return Err(ParserError {
380                                        kind: ParserErrorKind::UnexpectedEOF,
381                                        context: vec![],
382                                    });
383                                }
384                                _ => {}
385                            }
386                            cursor += 1;
387                        }
388
389                        let nodes = if is_declaration {
390                            self.parse_declaration_list().map_err(|e| {
391                                e.with_context(
392                                    "parse_at_rule: failed to parse declaration in block",
393                                )
394                            })?
395                        } else {
396                            vec![self.parse_rule().map_err(|e| {
397                                e.with_context("parse_at_rule: failed to parse rule in block")
398                            })?]
399                        };
400
401                        children.extend(nodes);
402                    }
403                }
404            }
405
406            self.consume_token(); // consume '}'
407            self.brace_depth -= 1;
408            children
409        } else {
410            if self.consume_token() != Token::Delim(';') {
411                return Err(ParserError {
412                    kind: ParserErrorKind::UnexpectedToken {
413                        expected: ";",
414                        found: format!("{:?}", self.peek_token()),
415                    },
416                    context: vec![],
417                });
418            }
419            vec![]
420        };
421
422        Ok(CssNode {
423            node: CssNodeType::AtRule {
424                name: at_name,
425                params,
426            },
427            children,
428        })
429    }
430
431    fn parse_at_query(tokens: Vec<Token>) -> ParseResult<AtQuery> {
432        let mut cursor = 0;
433        let items = Self::parse_at_query_list(&tokens, &mut cursor)?;
434        Ok(AtQuery::Group(items))
435    }
436
437    fn parse_at_query_list(tokens: &[Token], cursor: &mut usize) -> ParseResult<Vec<AtQuery>> {
438        let mut items = Vec::new();
439
440        while *cursor < tokens.len() {
441            match &tokens[*cursor] {
442                Token::Whitespace => {
443                    *cursor += 1;
444                }
445
446                Token::Delim('(') => {
447                    *cursor += 1;
448                    let group = Self::parse_at_query_list(tokens, cursor)?;
449                    items.push(AtQuery::Group(group));
450                }
451
452                Token::Delim(')') => {
453                    *cursor += 1;
454                    break;
455                }
456
457                Token::Ident(_) => {
458                    items.push(Self::parse_at_query_item(tokens, cursor)?);
459                }
460
461                _ => {
462                    *cursor += 1;
463                }
464            }
465        }
466
467        Ok(items)
468    }
469
470    fn parse_at_query_item(tokens: &[Token], cursor: &mut usize) -> ParseResult<AtQuery> {
471        let name = match &tokens[*cursor] {
472            Token::Ident(s) => s.clone(),
473            _ => unreachable!(),
474        };
475        *cursor += 1;
476
477        if matches!(tokens.get(*cursor), Some(Token::Delim(':'))) {
478            *cursor += 1;
479            let value = Self::parse_at_query_value(tokens, cursor)?;
480            Ok(AtQuery::Condition { name, value })
481        } else {
482            Ok(AtQuery::Keyword(name))
483        }
484    }
485
486    fn parse_at_query_value(tokens: &[Token], cursor: &mut usize) -> ParseResult<CssValue> {
487        let mut buf = Vec::new();
488        let mut paren_depth = 0;
489
490        while *cursor < tokens.len() {
491            match &tokens[*cursor] {
492                Token::Delim('(') => {
493                    paren_depth += 1;
494                    buf.push(tokens[*cursor].clone());
495                    *cursor += 1;
496                }
497                Token::Delim(')') if paren_depth == 0 => break,
498                Token::Delim(')') => {
499                    paren_depth -= 1;
500                    buf.push(tokens[*cursor].clone());
501                    *cursor += 1;
502                }
503                _ => {
504                    buf.push(tokens[*cursor].clone());
505                    *cursor += 1;
506                }
507            }
508        }
509
510        Self::parse_tokens_to_css_value(buf)
511    }
512
513    /// Parse a qualified rule (e.g., `div { color: red; }`).
514    ///
515    /// Parses the selector list first, then the block of declarations.
516    fn parse_rule(&mut self) -> ParseResult<CssNode> {
517        // 1. Parse selectors
518        let selectors = self.parse_selector_list();
519
520        // 2. Expect `{`
521        match self.consume_token() {
522            Token::Delim('{') => self.brace_depth += 1,
523            token => {
524                return Err(ParserError {
525                    kind: ParserErrorKind::UnexpectedToken {
526                        expected: "{",
527                        found: format!("{:?}", token),
528                    },
529                    context: vec![format!(
530                        "While parsing rule with selectors: {}",
531                        selectors
532                            .iter()
533                            .map(|s| format!("{:?}", s))
534                            .collect::<Vec<_>>()
535                            .join(", ")
536                    )],
537                });
538            }
539        }
540
541        // 3. Parse declarations inside the block
542        let mut children = vec![];
543        loop {
544            let token = self.peek_token().clone();
545            match token {
546                Token::Delim('}') => {
547                    self.consume_token();
548                    self.brace_depth -= 1;
549                    break;
550                }
551                Token::EOF => {
552                    return Err(ParserError {
553                        kind: ParserErrorKind::UnexpectedEOF,
554                        context: vec![],
555                    });
556                }
557                _ => {
558                    let mut decls = self.parse_declaration_list().map_err(|e| {
559                        e.with_context("parse_rule: failed to parse declaration list")
560                    })?;
561                    children.append(&mut decls);
562                }
563            }
564        }
565
566        Ok(CssNode {
567            node: CssNodeType::Rule { selectors },
568            children,
569        })
570    }
571
572    /// Parse a comma-separated list of selectors for a rule.
573    ///
574    /// Each selector is represented as a `ComplexSelector`.
575    fn parse_selector_list(&mut self) -> Vec<ComplexSelector> {
576        let mut selectors = vec![];
577        let mut parts = vec![];
578
579        let mut current_selector: Option<Selector> = None;
580        let mut current_combinator: Option<Combinator> = None;
581
582        loop {
583            let token = self.peek_token().clone();
584            match token {
585                Token::Ident(name) => {
586                    let sel = current_selector.get_or_insert_with(|| Selector {
587                        tag: None,
588                        id: None,
589                        classes: vec![],
590                        pseudo_class: None,
591                        pseudo_element: None,
592                    });
593
594                    if sel.tag.is_none() {
595                        sel.tag = Some(name);
596                    }
597
598                    self.consume_token();
599                }
600
601                Token::Hash(id) => {
602                    let sel = current_selector.get_or_insert_with(|| Selector {
603                        tag: None,
604                        id: None,
605                        classes: vec![],
606                        pseudo_class: None,
607                        pseudo_element: None,
608                    });
609                    sel.id = Some(id);
610                    self.consume_token();
611                }
612
613                Token::Delim('.') => {
614                    self.consume_token();
615                    if let Token::Ident(class) = self.consume_token() {
616                        let sel = current_selector.get_or_insert_with(|| Selector {
617                            tag: None,
618                            id: None,
619                            classes: vec![],
620                            pseudo_class: None,
621                            pseudo_element: None,
622                        });
623                        sel.classes.push(class);
624                    }
625                }
626
627                Token::Delim(':') => {
628                    self.consume_token();
629                    if self.peek_token() == &Token::Delim(':') {
630                        // pseudo-element
631                        self.consume_token();
632                        if let Token::Ident(name) = self.consume_token() {
633                            let sel = current_selector.get_or_insert_with(|| Selector {
634                                tag: None,
635                                id: None,
636                                classes: vec![],
637                                pseudo_class: None,
638                                pseudo_element: None,
639                            });
640                            sel.pseudo_element = Some(name);
641                        }
642                    } else if let Token::Ident(name) = self.consume_token() {
643                        let sel = current_selector.get_or_insert_with(|| Selector {
644                            tag: None,
645                            id: None,
646                            classes: vec![],
647                            pseudo_class: None,
648                            pseudo_element: None,
649                        });
650                        sel.pseudo_class = Some(name);
651                    }
652                }
653
654                Token::Whitespace | Token::Comment(_) => {
655                    // descendant combinator
656                    if let Some(sel) = current_selector.take() {
657                        parts.push(SelectorPart {
658                            selector: sel,
659                            combinator: current_combinator.take(),
660                        });
661                    }
662                    current_combinator = Some(Combinator::Descendant);
663                    self.consume_token();
664                }
665
666                Token::Delim(',') => {
667                    if let Some(sel) = current_selector.take() {
668                        parts.push(SelectorPart {
669                            selector: sel,
670                            combinator: current_combinator.take(),
671                        });
672                    }
673                    parts.reverse();
674                    selectors.push(ComplexSelector {
675                        parts: parts.clone(),
676                    });
677                    parts.clear();
678                    current_combinator = None;
679                    self.consume_token();
680
681                    while matches!(self.peek_token(), Token::Whitespace | Token::Comment(_)) {
682                        self.consume_token();
683                    }
684                }
685
686                Token::Delim('{') | Token::EOF => {
687                    if let Some(sel) = current_selector.take() {
688                        parts.push(SelectorPart {
689                            selector: sel,
690                            combinator: current_combinator.take(),
691                        });
692                    }
693                    if !parts.is_empty() {
694                        parts.reverse();
695                        selectors.push(ComplexSelector { parts });
696                    }
697                    break;
698                }
699
700                _ => {
701                    self.consume_token();
702                }
703            }
704        }
705
706        selectors
707    }
708
709    /// Parse declaration until `Token::Delim('}')`.
710    fn parse_declaration_list(&mut self) -> ParseResult<Vec<CssNode>> {
711        let mut declarations = vec![];
712        let mut parsing_name = true;
713        let mut name = String::new();
714        let mut value_tokens = vec![];
715
716        loop {
717            let token = self.peek_token().clone();
718            match token {
719                Token::Delim(':') if parsing_name => {
720                    parsing_name = false;
721                    self.consume_token();
722                }
723                Token::Delim(';') if !parsing_name => {
724                    self.consume_token(); // consume ;
725                    declarations.push(CssNode {
726                        node: CssNodeType::Declaration {
727                            name: std::mem::take(&mut name),
728                            value: Self::parse_tokens_to_css_value(std::mem::take(
729                                &mut value_tokens,
730                            ))
731                            .map_err(|e| {
732                                e.with_context(
733                                    "parse_declaration: failed to parse declaration value list",
734                                )
735                            })?,
736                        },
737                        children: vec![],
738                    });
739                    parsing_name = true;
740                }
741                Token::Delim('}') | Token::EOF => {
742                    if !parsing_name && !name.is_empty() {
743                        declarations.push(CssNode {
744                            node: CssNodeType::Declaration {
745                                name: std::mem::take(&mut name),
746                                value: Self::parse_tokens_to_css_value(std::mem::take(
747                                    &mut value_tokens,
748                                ))?,
749                            },
750                            children: vec![],
751                        });
752                    }
753                    break;
754                }
755
756                Token::Ident(s) if parsing_name => {
757                    name.push_str(&s);
758                    self.consume_token();
759                }
760                _ => {
761                    if !parsing_name {
762                        value_tokens.push(self.consume_token());
763                    } else {
764                        self.consume_token(); // skip unsupported token in name
765                    }
766                }
767            }
768        }
769
770        Ok(declarations)
771    }
772
773    fn parse_tokens_to_css_value(tokens: Vec<Token>) -> ParseResult<CssValue> {
774        let mut values = vec![];
775        let mut iter = tokens.into_iter().peekable();
776
777        while let Some(token) = iter.next() {
778            log::debug!(target: "CssParser", "parse_tokens_to_css_value: token={:?}", token);
779
780            match token {
781                Token::Ident(s) => values.push(CssValue::Keyword(s)),
782
783                Token::Delim(',') => {
784                    // List separator
785                    continue;
786                }
787
788                Token::Delim('(') | Token::Delim(')') => {
789                    // Function の構文用なので無視
790                    continue;
791                }
792
793                Token::Delim(c) => {
794                    values.push(CssValue::Keyword(c.to_string()));
795                }
796
797                Token::Number(n) => values.push(CssValue::Number(n)),
798
799                Token::String(s) => values.push(CssValue::String(s)),
800
801                Token::Dimension(value, unit) => {
802                    let unit = match unit.as_str() {
803                        "px" => Unit::Px,
804                        "em" => Unit::Em,
805                        "rem" => Unit::Rem,
806                        "%" => Unit::Percent,
807                        "vw" => Unit::Vw,
808                        "vh" => Unit::Vh,
809                        _ => Unit::Px,
810                    };
811                    values.push(CssValue::Length(value, unit));
812                }
813
814                Token::Hash(s) => values.push(CssValue::Color(s)),
815
816                Token::Function(name) => {
817                    // () の中をそのまま集める
818                    let mut depth = 0;
819                    let mut func_tokens = vec![];
820
821                    for tok in iter.by_ref() {
822                        match &tok {
823                            Token::Delim('(') => {
824                                depth += 1;
825                                func_tokens.push(tok);
826                            }
827                            Token::Delim(')') => {
828                                func_tokens.push(tok);
829                                depth -= 1;
830                                if depth == 0 {
831                                    break;
832                                }
833                            }
834                            _ => func_tokens.push(tok),
835                        }
836                    }
837
838                    let arg_value = Self::parse_tokens_to_css_value(func_tokens)
839                        .map_err(|e| e.with_context("parse function args"))?;
840
841                    let args = match arg_value {
842                        CssValue::List(list) => list,
843                        other => vec![other],
844                    };
845
846                    values.push(CssValue::Function(name, args));
847                }
848
849                _ => continue,
850            }
851        }
852
853        // 複数値なら List、単数ならそのまま
854        Ok(match values.len() {
855            0 => CssValue::Keyword(String::new()),
856            1 => values.remove(0),
857            _ => CssValue::List(values),
858        })
859    }
860}
861
862// ====================
863impl fmt::Display for CssNode {
864    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
865        fmt_tree_node(self, f, &[])
866    }
867}
868
869/// 再帰的にツリーを表示するヘルパー関数
870fn fmt_tree_node(
871    node: &CssNode,
872    f: &mut fmt::Formatter<'_>,
873    ancestors_last: &[bool],
874) -> fmt::Result {
875    let is_last = *ancestors_last.last().unwrap_or(&true);
876    let connector = if ancestors_last.is_empty() {
877        ""
878    } else if is_last {
879        "└── "
880    } else {
881        "├── "
882    };
883
884    let mut prefix = String::new();
885    for &ancestor_last in &ancestors_last[..ancestors_last.len().saturating_sub(1)] {
886        prefix.push_str(if ancestor_last { "    " } else { "│   " });
887    }
888
889    writeln!(f, "{}{}{:?}", prefix, connector, node.node())?;
890
891    let child_count = node.children().len();
892    for (i, child) in node.children().iter().enumerate() {
893        let child_is_last = i == child_count - 1;
894        let mut new_ancestors = ancestors_last.to_vec();
895        new_ancestors.push(child_is_last);
896        fmt_tree_node(child, f, &new_ancestors)?;
897    }
898
899    Ok(())
900}