tinycss2 icon indicating copy to clipboard operation
tinycss2 copied to clipboard

Add `tinycss2.ast.Node.get_child_nodes` to allow walking the tree

Open encukou opened this issue 4 months ago • 0 comments

It would be nice to allow walking the AST even in code that does not understand all the node types, for example to:

  • gather all ParseError nodes
  • gather all URLs
  • display the AST in a GUI tree widget

... and so on.

Currently, it seems that code to gather URLs would look like this (click to expand)
def get_urls_from_tinycss2_nodes(nodes):
    if nodes is None:
        return
    for node in nodes:
        match node:
            case tinycss2.ast.QualifiedRule():
                yield from get_urls_from_tinycss2_nodes(node.prelude)
                yield from get_urls_from_tinycss2_nodes(node.content)
            case tinycss2.ast.AtRule():
                yield from get_urls_from_tinycss2_nodes(node.prelude)
                yield from get_urls_from_tinycss2_nodes(node.content)
            case tinycss2.ast.Declaration():
                yield from get_urls_from_tinycss2_nodes(node.value)
            case tinycss2.ast.ParseError():
                pass
            case tinycss2.ast.Comment():
                pass
            case tinycss2.ast.WhitespaceToken():
                pass
            case tinycss2.ast.LiteralToken():
                pass
            case tinycss2.ast.IdentToken():
                pass
            case tinycss2.ast.AtKeywordToken():
                pass
            case tinycss2.ast.HashToken():
                pass
            case tinycss2.ast.StringToken():
                pass
            case tinycss2.ast.URLToken():
                # TODO: unescape
                yield node.value
            case tinycss2.ast.UnicodeRangeToken():
                pass
            case tinycss2.ast.NumberToken():
                pass
            case tinycss2.ast.PercentageToken():
                pass
            case tinycss2.ast.DimensionToken():
                pass
            case tinycss2.ast.ParenthesesBlock():
                yield from get_urls_from_tinycss2_nodes(node.content)
            case tinycss2.ast.SquareBracketsBlock():
                yield from get_urls_from_tinycss2_nodes(node.content)
            case tinycss2.ast.CurlyBracketsBlock():
                yield from get_urls_from_tinycss2_nodes(node.content)
            case tinycss2.ast.FunctionBlock():
                yield from get_urls_from_tinycss2_nodes(node.arguments)
                if node.name == 'url':
                    match node.arguments:
                        case [tinycss2.ast.StringToken() as string]:
                            # TODO: unescape
                            yield string.value
            case _:
                raise TypeError(node)

Would it make sense to add a method like get_child_nodes() (or child_nodes iterable property) to all Node classes, to make this both easier and resistant to tinycss2 adding new node types?

I'm willing to do the work :)

encukou avatar Sep 23 '25 17:09 encukou