Exercise 3: Naive vs. Fixed, Side by Side — Possible Solution ==================================================================== THE TEST ------------------------------ p = Element('p', {'id': 'greet'}) p.children = [TextNode('Hello '), CommentNode(' a note '), b] b.children = [TextNode('world')] naive_tree = build_style_tree_naive(p, sheet) fixed_tree = build_style_tree(p, sheet) collect_text(naive_tree) collect_text(fixed_tree) RESULT ------------------------------ collect_text(naive_tree) -> [] collect_text(fixed_tree) -> ['Hello ', 'world'] THE EXACT LINE RESPONSIBLE ------------------------------ build_style_tree_naive's own children-building loop is: for child in dom_node.children: if child.kind == 'element': styled_child = build_style_tree_naive(child, stylesheet, style) if styled_child is not None: children.append(styled_child) The line `if child.kind == 'element':` is the single point of failure. It's a filter applied BEFORE any recursive call even happens -- a TextNode is rejected right here, on the spot, and build_style_tree_naive is never even invoked on it at all. This isn't a bug inside the recursive call itself; it's that the recursive call is never reached for anything that isn't already an element. Because this same filtered loop runs at every level of the recursion, the effect compounds:
's own 'Hello ' TextNode child is rejected here directly. passes the filter (it's an element) and recurses normally -- but when THAT recursive call reaches its own children- building loop, 'world' (a TextNode) is rejected by the identical check, one level down. Every text node in the whole tree, at any depth, gets caught by this same line, independently, each time the loop runs. WHY THIS WORKS AS AN ANSWER ------------------------------ Comparing the fixed version's own three-way dispatch -- `if dom_node.kind == 'comment': ... elif dom_node.kind == 'text': ... else: (element handling)` -- shows the actual difference in design: the fixed version decides what to do based on the CHILD's own kind, producing a real (if simple) StyledNode for a text node instead of skipping the recursive call altogether. The naive version's mistake isn't a typo or an off-by-one error; it's a design choice (only elements are worth recursing into) that happens to be correct for determining which nodes get their own COMPUTED STYLE from cascade() matching, but wrong for determining which nodes belong in the style tree at all -- text needs to appear in the tree even though it can never be the TARGET of a CSS selector.