Skip to content

Styling

momapy.styling

Classes and functions for styling layout elements using style sheets.

This module provides a CSS-like styling system for layout elements, supporting: - Style sheets with selectors (type, class, id, child, descendant, compound) - Style collections that group related style properties - CSS parsing from files or strings - Style application to layout elements

Example

from momapy.styling import StyleSheet

Load a CSS file

stylesheet = StyleSheet.from_file("styles.css")

Apply to a layout element

styled_element = apply_style_sheet(element, stylesheet)

Classes:

Name Description
ChildSelector

Selector that matches elements that are direct children of a parent.

ClassSelector

Selector that matches elements by class (including subclasses).

CompoundSelector

Selector that matches only if all of its component selectors match (AND logic).

DescendantSelector

Selector that matches elements that are descendants of an ancestor.

IdSelector

Selector that matches elements by their id attribute.

NotSelector

Selector that matches if none of its component selectors match (NOT logic).

OrSelector

Selector that matches if any of its component selectors match (OR logic).

Selector

Abstract base class for CSS-like selectors.

StyleCollection

A dictionary-based collection of style properties.

StyleSheet

A dictionary-based stylesheet mapping selectors to style collections.

TypeSelector

Selector that matches elements by their exact class name.

Functions:

Name Description
apply_style_collection

Apply a StyleCollection to a layout element.

apply_style_sheet

Apply a StyleSheet to a layout element or map layout recursively.

combine_style_sheets

Merge multiple StyleSheets into a single StyleSheet.

ChildSelector dataclass

ChildSelector(parent_selector: Selector, child_selector: Selector)

Bases: Selector

Selector that matches elements that are direct children of a parent.

Attributes:

Name Type Description
parent_selector Selector

The selector for the parent element.

child_selector Selector

The selector for the child element.

Example

selector = ChildSelector(TypeSelector("Group"), TypeSelector("Rectangle")) selector.match(rect, [group]) # True if rect is direct child of group

Methods:

Name Description
select

Check if the object is a direct child matching the criteria.

select

select(obj: LayoutElement | LayoutElementBuilder, ancestors: Collection[LayoutElement | LayoutElementBuilder])

Check if the object is a direct child matching the criteria.

Parameters:

Name Type Description Default
obj LayoutElement | LayoutElementBuilder

The layout element or builder to test.

required
ancestors Collection[LayoutElement | LayoutElementBuilder]

List of ancestor elements.

required

Returns:

Type Description

True if the object matches child_selector and its immediate

parent matches parent_selector.

Source code in src/momapy/styling.py
def select(
    self,
    obj: momapy.core.LayoutElement | momapy.core.LayoutElementBuilder,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ],
):
    """Check if the object is a direct child matching the criteria.

    Args:
        obj: The layout element or builder to test.
        ancestors: List of ancestor elements.

    Returns:
        True if the object matches child_selector and its immediate
        parent matches parent_selector.
    """
    if not ancestors:
        return False
    return self.child_selector.select(
        obj, ancestors
    ) and self.parent_selector.select(ancestors[-1], ancestors[:-1])

ClassSelector dataclass

ClassSelector(class_name: str)

Bases: Selector

Selector that matches elements by class (including subclasses).

Attributes:

Name Type Description
class_name str

The class name to match (matches subclasses too).

Example

selector = ClassSelector("Shape") selector.match(some_rectangle) # True if Rectangle is a Shape subclass

Methods:

Name Description
select

Check if the object is an instance of the specified class.

select

select(obj: LayoutElement | LayoutElementBuilder, ancestors: Collection[LayoutElement | LayoutElementBuilder])

Check if the object is an instance of the specified class.

Parameters:

Name Type Description Default
obj LayoutElement | LayoutElementBuilder

The layout element or builder to test.

required
ancestors Collection[LayoutElement | LayoutElementBuilder]

List of ancestor elements (unused for class matching).

required

Returns:

Type Description

True if the object is an instance of class_name or its subclasses.

Source code in src/momapy/styling.py
def select(
    self,
    obj: momapy.core.LayoutElement | momapy.core.LayoutElementBuilder,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ],
):
    """Check if the object is an instance of the specified class.

    Args:
        obj: The layout element or builder to test.
        ancestors: List of ancestor elements (unused for class matching).

    Returns:
        True if the object is an instance of class_name or its subclasses.
    """
    for cls in type(obj).__mro__:
        cls_name = cls.__name__
        if cls_name == self.class_name or cls_name == f"{self.class_name}Builder":
            return True
    return False

CompoundSelector dataclass

CompoundSelector(selectors: tuple[Selector, ...])

Bases: Selector

Selector that matches only if all of its component selectors match (AND logic).

Attributes:

Name Type Description
selectors tuple[Selector, ...]

Tuple of selectors, all of which must match.

Example

selector = CompoundSelector((TypeSelector("Rectangle"), ClassSelector("Colored"))) selector.match(element) # True if element is Rectangle AND Colored

Methods:

Name Description
select

Check if all selectors in the tuple match.

select

select(obj: LayoutElement | LayoutElementBuilder, ancestors: Collection[LayoutElement | LayoutElementBuilder])

Check if all selectors in the tuple match.

Parameters:

Name Type Description Default
obj LayoutElement | LayoutElementBuilder

The layout element or builder to test.

required
ancestors Collection[LayoutElement | LayoutElementBuilder]

List of ancestor elements.

required

Returns:

Type Description

True if all selectors match the object.

Source code in src/momapy/styling.py
def select(
    self,
    obj: momapy.core.LayoutElement | momapy.core.LayoutElementBuilder,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ],
):
    """Check if all selectors in the tuple match.

    Args:
        obj: The layout element or builder to test.
        ancestors: List of ancestor elements.

    Returns:
        True if all selectors match the object.
    """
    return all([selector.select(obj, ancestors) for selector in self.selectors])

DescendantSelector dataclass

DescendantSelector(ancestor_selector: Selector, descendant_selector: Selector)

Bases: Selector

Selector that matches elements that are descendants of an ancestor.

Attributes:

Name Type Description
ancestor_selector Selector

The selector for any ancestor element.

descendant_selector Selector

The selector for the descendant element.

Example

selector = DescendantSelector(TypeSelector("Group"), TypeSelector("Text")) selector.match(text, [subgroup, group]) # True if text is somewhere inside group

Methods:

Name Description
select

Check if the object is a descendant matching the criteria.

select

select(obj: LayoutElement | LayoutElementBuilder, ancestors: Collection[LayoutElement | LayoutElementBuilder])

Check if the object is a descendant matching the criteria.

Parameters:

Name Type Description Default
obj LayoutElement | LayoutElementBuilder

The layout element or builder to test.

required
ancestors Collection[LayoutElement | LayoutElementBuilder]

List of ancestor elements.

required

Returns:

Type Description

True if the object matches descendant_selector and any ancestor

matches ancestor_selector.

Source code in src/momapy/styling.py
def select(
    self,
    obj: momapy.core.LayoutElement | momapy.core.LayoutElementBuilder,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ],
):
    """Check if the object is a descendant matching the criteria.

    Args:
        obj: The layout element or builder to test.
        ancestors: List of ancestor elements.

    Returns:
        True if the object matches descendant_selector and any ancestor
        matches ancestor_selector.
    """
    if not ancestors:
        return False
    return self.descendant_selector.select(obj, ancestors) and any(
        [
            self.ancestor_selector.select(ancestor, ancestors[:i])
            for i, ancestor in enumerate(ancestors)
        ]
    )

IdSelector dataclass

IdSelector(id_: str)

Bases: Selector

Selector that matches elements by their id attribute.

Attributes:

Name Type Description
id_ str

The identifier to match.

Example

selector = IdSelector("main_node") selector.match(element) # True if element.id_ == "main_node"

Methods:

Name Description
select

Check if the object has the specified id.

select

select(obj: LayoutElement | LayoutElementBuilder, ancestors: Collection[LayoutElement | LayoutElementBuilder])

Check if the object has the specified id.

Parameters:

Name Type Description Default
obj LayoutElement | LayoutElementBuilder

The layout element or builder to test.

required
ancestors Collection[LayoutElement | LayoutElementBuilder]

List of ancestor elements (unused for id matching).

required

Returns:

Type Description

True if the object has an id_ attribute matching the selector.

Source code in src/momapy/styling.py
def select(
    self,
    obj: momapy.core.LayoutElement | momapy.core.LayoutElementBuilder,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ],
):
    """Check if the object has the specified id.

    Args:
        obj: The layout element or builder to test.
        ancestors: List of ancestor elements (unused for id matching).

    Returns:
        True if the object has an id_ attribute matching the selector.
    """
    return hasattr(obj, "id_") and obj.id_ == self.id_

NotSelector dataclass

NotSelector(selectors: tuple[Selector, ...])

Bases: Selector

Selector that matches if none of its component selectors match (NOT logic).

Attributes:

Name Type Description
selectors tuple[Selector, ...]

Tuple of selectors, none of which should match.

Example

selector = NotSelector((TypeSelector("Hidden"),)) selector.match(element) # True if element is NOT of type Hidden

Methods:

Name Description
select

Check if none of the selectors in the tuple match.

select

select(obj: LayoutElement | LayoutElementBuilder, ancestors: Collection[LayoutElement | LayoutElementBuilder])

Check if none of the selectors in the tuple match.

Parameters:

Name Type Description Default
obj LayoutElement | LayoutElementBuilder

The layout element or builder to test.

required
ancestors Collection[LayoutElement | LayoutElementBuilder]

List of ancestor elements.

required

Returns:

Type Description

True if no selector matches the object.

Source code in src/momapy/styling.py
def select(
    self,
    obj: momapy.core.LayoutElement | momapy.core.LayoutElementBuilder,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ],
):
    """Check if none of the selectors in the tuple match.

    Args:
        obj: The layout element or builder to test.
        ancestors: List of ancestor elements.

    Returns:
        True if no selector matches the object.
    """
    return not any([selector.select(obj, ancestors) for selector in self.selectors])

OrSelector dataclass

OrSelector(selectors: tuple[Selector, ...])

Bases: Selector

Selector that matches if any of its component selectors match (OR logic).

Attributes:

Name Type Description
selectors tuple[Selector, ...]

Tuple of selectors, any of which can match.

Example

selector = OrSelector((TypeSelector("Rectangle"), TypeSelector("Circle"))) selector.match(some_shape) # True if shape is Rectangle OR Circle

Methods:

Name Description
select

Check if any selector in the tuple matches.

select

select(obj: LayoutElement | LayoutElementBuilder, ancestors: Collection[LayoutElement | LayoutElementBuilder])

Check if any selector in the tuple matches.

Parameters:

Name Type Description Default
obj LayoutElement | LayoutElementBuilder

The layout element or builder to test.

required
ancestors Collection[LayoutElement | LayoutElementBuilder]

List of ancestor elements.

required

Returns:

Type Description

True if any selector matches the object.

Source code in src/momapy/styling.py
def select(
    self,
    obj: momapy.core.LayoutElement | momapy.core.LayoutElementBuilder,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ],
):
    """Check if any selector in the tuple matches.

    Args:
        obj: The layout element or builder to test.
        ancestors: List of ancestor elements.

    Returns:
        True if any selector matches the object.
    """
    return any([selector.select(obj, ancestors) for selector in self.selectors])

Selector dataclass

Selector()

Bases: object

Abstract base class for CSS-like selectors.

Selectors determine whether a layout element matches specific criteria. All selector types inherit from this class and implement the select() method.

Methods:

Name Description
select

Check if the layout element matches this selector.

select abstractmethod

select(obj: LayoutElement | LayoutElementBuilder, ancestors: Collection[LayoutElement | LayoutElementBuilder]) -> bool

Check if the layout element matches this selector.

Parameters:

Name Type Description Default
obj LayoutElement | LayoutElementBuilder

The layout element or builder to test.

required
ancestors Collection[LayoutElement | LayoutElementBuilder]

List of ancestor elements.

required

Returns:

Type Description
bool

True if the element matches, False otherwise.

Source code in src/momapy/styling.py
@abc.abstractmethod
def select(
    self,
    obj: momapy.core.LayoutElement | momapy.core.LayoutElementBuilder,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ],
) -> bool:
    """Check if the layout element matches this selector.

    Args:
        obj: The layout element or builder to test.
        ancestors: List of ancestor elements.

    Returns:
        True if the element matches, False otherwise.
    """
    pass

StyleCollection

Bases: dict

A dictionary-based collection of style properties.

StyleCollection maps attribute names to their values, similar to a CSS declaration block. It is used within StyleSheets to store styles for specific selectors.

Example

styles = StyleCollection({"fill": "red", "stroke_width": 2}) styles["fill"] 'red'

StyleSheet

Bases: dict

A dictionary-based stylesheet mapping selectors to style collections.

StyleSheet extends dict to map Selector objects to StyleCollection objects. Supports merging stylesheets using the | operator.

Example

from momapy.styling import StyleSheet, TypeSelector ss = StyleSheet({TypeSelector("Rectangle"): StyleCollection({"fill": "blue"})})

Merge stylesheets

combined = ss | another_stylesheet

Methods:

Name Description
from_file

Parse and return a StyleSheet from a CSS file.

from_files

Parse and combine multiple CSS files into a single StyleSheet.

from_string

Parse and return a StyleSheet from a CSS string.

from_file classmethod

from_file(file_path: str) -> StyleSheet

Parse and return a StyleSheet from a CSS file.

Parameters:

Name Type Description Default
file_path str

Path to the CSS file to parse.

required

Returns:

Type Description
StyleSheet

A StyleSheet containing the parsed selectors and style collections.

Raises:

Type Description
ParseException

If the CSS file is malformed.

Source code in src/momapy/styling.py
@classmethod
def from_file(cls, file_path: str) -> "StyleSheet":
    """Parse and return a StyleSheet from a CSS file.

    Args:
        file_path: Path to the CSS file to parse.

    Returns:
        A StyleSheet containing the parsed selectors and style collections.

    Raises:
        pyparsing.ParseException: If the CSS file is malformed.
    """
    style_sheet = _css_document.parse_file(file_path, parse_all=True)[0]
    return style_sheet

from_files classmethod

from_files(file_paths: Collection[str]) -> StyleSheet

Parse and combine multiple CSS files into a single StyleSheet.

Parameters:

Name Type Description Default
file_paths Collection[str]

Collection of paths to CSS files.

required

Returns:

Type Description
StyleSheet

A merged StyleSheet containing all parsed styles.

Source code in src/momapy/styling.py
@classmethod
def from_files(cls, file_paths: collections.abc.Collection[str]) -> "StyleSheet":
    """Parse and combine multiple CSS files into a single StyleSheet.

    Args:
        file_paths: Collection of paths to CSS files.

    Returns:
        A merged StyleSheet containing all parsed styles.
    """
    style_sheets = []
    for file_path in file_paths:
        style_sheet = StyleSheet.from_file(file_path)
        style_sheets.append(style_sheet)
    style_sheet = combine_style_sheets(style_sheets)
    return style_sheet

from_string classmethod

from_string(s: str) -> StyleSheet

Parse and return a StyleSheet from a CSS string.

Parameters:

Name Type Description Default
s str

CSS string to parse.

required

Returns:

Type Description
StyleSheet

A StyleSheet containing the parsed selectors and style collections.

Raises:

Type Description
ParseException

If the CSS string is malformed.

Example

css = "Rectangle { fill: red; }" ss = StyleSheet.from_string(css)

Source code in src/momapy/styling.py
@classmethod
def from_string(cls, s: str) -> "StyleSheet":
    """Parse and return a StyleSheet from a CSS string.

    Args:
        s: CSS string to parse.

    Returns:
        A StyleSheet containing the parsed selectors and style collections.

    Raises:
        pyparsing.ParseException: If the CSS string is malformed.

    Example:
        >>> css = "Rectangle { fill: red; }"
        >>> ss = StyleSheet.from_string(css)
    """
    style_sheet = _css_document.parse_string(s, parse_all=True)[0]
    return style_sheet

TypeSelector dataclass

TypeSelector(class_name: str)

Bases: Selector

Selector that matches elements by their exact class name.

Attributes:

Name Type Description
class_name str

The exact class name to match (e.g., "Rectangle").

Example

selector = TypeSelector("Rectangle") selector.match(some_rectangle) # True if type is Rectangle

Methods:

Name Description
select

Check if the object's class name matches exactly.

select

select(obj: LayoutElement | LayoutElementBuilder, ancestors: Collection[LayoutElement | LayoutElementBuilder])

Check if the object's class name matches exactly.

Parameters:

Name Type Description Default
obj LayoutElement | LayoutElementBuilder

The layout element or builder to test.

required
ancestors Collection[LayoutElement | LayoutElementBuilder]

List of ancestor elements (unused for type matching).

required

Returns:

Type Description

True if the object's class name or builder name matches.

Source code in src/momapy/styling.py
def select(
    self,
    obj: momapy.core.LayoutElement | momapy.core.LayoutElementBuilder,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ],
):
    """Check if the object's class name matches exactly.

    Args:
        obj: The layout element or builder to test.
        ancestors: List of ancestor elements (unused for type matching).

    Returns:
        True if the object's class name or builder name matches.
    """
    obj_cls_name = type(obj).__name__
    return (
        obj_cls_name == self.class_name
        or obj_cls_name == f"{self.class_name}Builder"
    )

apply_style_collection

apply_style_collection(layout_element: LayoutElement | LayoutElementBuilder, style_collection: StyleCollection, strict: bool = True) -> LayoutElement | LayoutElementBuilder

Apply a StyleCollection to a layout element.

Parameters:

Name Type Description Default
layout_element LayoutElement | LayoutElementBuilder

The element or builder to apply styles to.

required
style_collection StyleCollection

The styles to apply.

required
strict bool

If True, raises AttributeError for invalid attributes. If False, silently ignores them.

True

Returns:

Type Description
LayoutElement | LayoutElementBuilder

The modified layout element or builder.

Raises:

Type Description
AttributeError

If strict=True and an attribute doesn't exist on the element.

Source code in src/momapy/styling.py
def apply_style_collection(
    layout_element: (momapy.core.LayoutElement | momapy.core.LayoutElementBuilder),
    style_collection: StyleCollection,
    strict: bool = True,
) -> momapy.core.LayoutElement | momapy.core.LayoutElementBuilder:
    """Apply a StyleCollection to a layout element.

    Args:
        layout_element: The element or builder to apply styles to.
        style_collection: The styles to apply.
        strict: If True, raises AttributeError for invalid attributes.
            If False, silently ignores them.

    Returns:
        The modified layout element or builder.

    Raises:
        AttributeError: If strict=True and an attribute doesn't exist on the element.
    """
    if not isinstance(layout_element, momapy.builder.Builder):
        layout_element = momapy.builder.builder_from_object(layout_element)
        is_builder = False
    else:
        is_builder = True
    for attribute, value in style_collection.items():
        if hasattr(layout_element, attribute):
            setattr(layout_element, attribute, value)
        else:
            if strict:
                raise AttributeError(
                    f"{type(layout_element)} object has no attribute '{attribute}'"
                )
    if is_builder:
        return layout_element
    return momapy.builder.object_from_builder(layout_element)

apply_style_sheet

apply_style_sheet(map_or_layout_element: Map | LayoutElement | MapBuilder | LayoutElementBuilder, style_sheet: StyleSheet, strict: bool = True, ancestors: Collection[LayoutElement | LayoutElementBuilder] = None) -> Map | LayoutElement | LayoutElementBuilder | MapBuilder

Apply a StyleSheet to a layout element or map layout recursively.

Parameters:

Name Type Description Default
map_or_layout_element Map | LayoutElement | MapBuilder | LayoutElementBuilder

The map, element, or builder to style.

required
style_sheet StyleSheet

The stylesheet to apply.

required
strict bool

If True, raises errors for invalid attributes.

True
ancestors Collection[LayoutElement | LayoutElementBuilder]

Internal list of ancestor elements for selector matching.

None

Returns:

Type Description
Map | LayoutElement | LayoutElementBuilder | MapBuilder

The modified map, layout element, or builder.

Source code in src/momapy/styling.py
def apply_style_sheet(
    map_or_layout_element: (
        momapy.core.Map
        | momapy.core.LayoutElement
        | momapy.core.MapBuilder
        | momapy.core.LayoutElementBuilder
    ),
    style_sheet: StyleSheet,
    strict: bool = True,
    ancestors: collections.abc.Collection[
        momapy.core.LayoutElement | momapy.core.LayoutElementBuilder
    ] = None,
) -> (
    momapy.core.Map
    | momapy.core.LayoutElement
    | momapy.core.LayoutElementBuilder
    | momapy.core.MapBuilder
):
    """Apply a StyleSheet to a layout element or map layout recursively.

    Args:
        map_or_layout_element: The map, element, or builder to style.
        style_sheet: The stylesheet to apply.
        strict: If True, raises errors for invalid attributes.
        ancestors: Internal list of ancestor elements for selector matching.

    Returns:
        The modified map, layout element, or builder.
    """
    if not isinstance(map_or_layout_element, momapy.builder.Builder):
        map_or_layout_element = momapy.builder.builder_from_object(
            map_or_layout_element
        )
        is_builder = False
    else:
        is_builder = True
    if isinstance(map_or_layout_element, momapy.core.MapBuilder):
        layout_element = map_or_layout_element.layout
    else:
        layout_element = map_or_layout_element
    if style_sheet is not None:
        if ancestors is None:
            ancestors = []
        for selector, style_collection in style_sheet.items():
            if selector.select(layout_element, ancestors):
                apply_style_collection(
                    layout_element=layout_element,
                    style_collection=style_collection,
                    strict=strict,
                )
        ancestors = ancestors + [layout_element]
        for child in layout_element.children():
            apply_style_sheet(
                map_or_layout_element=child,
                style_sheet=style_sheet,
                strict=strict,
                ancestors=ancestors,
            )
    if is_builder:
        return map_or_layout_element
    return momapy.builder.object_from_builder(map_or_layout_element)

combine_style_sheets

combine_style_sheets(style_sheets: Collection[StyleSheet]) -> StyleSheet

Merge multiple StyleSheets into a single StyleSheet.

Parameters:

Name Type Description Default
style_sheets Collection[StyleSheet]

Collection of StyleSheets to merge.

required

Returns:

Type Description
StyleSheet

A combined StyleSheet, or None if the input is empty.

Source code in src/momapy/styling.py
def combine_style_sheets(
    style_sheets: collections.abc.Collection[StyleSheet],
) -> StyleSheet:
    """Merge multiple StyleSheets into a single StyleSheet.

    Args:
        style_sheets: Collection of StyleSheets to merge.

    Returns:
        A combined StyleSheet, or None if the input is empty.
    """
    if not style_sheets:
        return None
    output_style_sheet = style_sheets[0]
    for style_sheet in style_sheets[1:]:
        output_style_sheet |= style_sheet
    return output_style_sheet