# DataGrid Formatting - User Guide

## Introduction

The DataGrid Formatting system provides a powerful way to apply conditional styling and value formatting to your data grids. It allows you to create rich, readable data presentations with minimal code.

**Key features:**

- Apply styles (colors, fonts, decorations) based on cell values
- Format values for display (currencies, dates, percentages, text transformations)
- Create conditional rules that respond to data
- Reference other cells for cross-column/row comparisons
- Define formatting at multiple levels (cell, row, column, table, or globally)

**Common use cases:**

- Highlight negative values in financial reports
- Format currency and percentage values
- Color-code status indicators
- Display custom labels for enumerated values
- Apply different styles based on data thresholds
- Create visual hierarchies with conditional formatting

## Quick Start

Here's a simple example to highlight negative values in red:

```python
column amount:
    style("error") if value < 0
    format("EUR")
```

This creates a formatting rule that:
- Applies to all cells in the "amount" column
- Colors negative values with the "error" style (red background)
- Formats all values as Euro currency

## Understanding Formatting

### Formatting Levels

Formatting can be applied at five different levels, each with its own specificity:

| Level      | Cells Targeted                    | Condition Evaluated On | Specificity |
|------------|-----------------------------------|------------------------|-------------|
| **Cell**   | 1 specific cell                   | The cell value         | Highest (1) |
| **Row**    | All cells in the row              | Each cell value        | High (2)    |
| **Column** | All cells in the column           | Each cell value        | Medium (3)  |
| **Table**  | All cells in a specific table     | Each cell value        | Low (4)     |
| **Tables** | All cells in all tables (global)  | Each cell value        | Lowest (5)  |

**Visual hierarchy:**

```
Cell (amount, 5)        ← Highest priority
    ↓
Row 5                   ← Overridden by cell
    ↓
Column amount           ← Overridden by row and cell
    ↓
Table "products"        ← Overridden by column, row, and cell
    ↓
Tables (global)         ← Lowest priority, applies everywhere
```

### How Rules Are Evaluated

Rules are processed from **highest to lowest specificity**. When multiple rules match:

1. **Cell-level rules** win over all others
2. **Row-level rules** win over column, table, and global rules
3. **Column-level rules** win over table and global rules
4. **Table-level rules** win over global rules
5. **Global rules** apply only if no other rules match

### Conflict Resolution

When multiple rules at the **same level** match a cell, style and formatter are resolved **independently**:

**Style rules compete among themselves:**
1. **Specificity** = number of conditions in the rule (0 or 1)
2. **Higher specificity wins**
3. **At equal specificity, last rule wins**

**Formatter rules compete among themselves:**
1. Same specificity logic as styles
2. Resolved independently from styles

**Style and formatter can fuse:**
- If one rule provides the style and another provides the formatter, **both are applied**
- This allows separating concerns (e.g., unconditional formatting + conditional styling)

**Example 1: Style competition**

```python
column amount:
    style(color="gray")                       # Specificity: 0 (no condition)
    style("error") if value < 0               # Specificity: 1
    style("success", bold=True) if value > 0  # Specificity: 1
```

- For `value = -10`: "error" style wins (specificity 1 > 0)
- For `value = 50`: "success" style wins (specificity 1 > 0)
- For `value = 0`: gray color applies (no other rules match)

**Example 2: Style and formatter fusion**

```python
column amount:
    format("EUR")                                     # Unconditional formatter
    style("error") if value < 0                       # Conditional style
```

- For `value = -100`:
  - ✅ Style "error" applied (from conditional rule)
  - ✅ Format "EUR" applied (from unconditional rule)
  - Result: `-100,00 €` with red background

- For `value = 100`:
  - ❌ No style (condition not met)
  - ✅ Format "EUR" applied
  - Result: `100,00 €` with default style

**Example 3: Multiple styles and formatters**

```python
column amount:
    style("neutral")                                  # Unconditional style
    format("EUR")                                     # Unconditional formatter
    style("error") if value < 0                       # Conditional style (higher specificity)
    format.number(precision=0, suffix=" €") if value < 0  # Conditional formatter (higher specificity)
```

- For `value = -5.67`:
  - Style: "error" wins (specificity 1 > 0)
  - Formatter: precision=0 wins (specificity 1 > 0)
  - Result: `-6 €` with red background

- For `value = 1234.56`:
  - Style: "neutral" applies (no conditional matches)
  - Formatter: "EUR" applies (no conditional matches)
  - Result: `1 234,56 €` with neutral background

### How Rules Work

A formatting rule consists of three optional parts:

```
[style(...)] [format(...)] [if <condition>]
```

**Rules:**

- At least one of `style()` or `format()` must be present
- `condition` is optional
- If a condition is present, the rule applies only when the condition is met
- Multiple rules can be defined for the same scope

**Examples:**

```python
# Style only
style("error")

# Format only
format("EUR")

# Style + Format
style("error") format("EUR")

# With condition
style("error") if value < 0

# All combined
style("error") format("EUR") if value < 0
```

## Writing Formatting Rules

### Basic Syntax

A DSL document consists of **scopes**, each containing one or more **rules**:

```python
<scope>:
    <rule>
    <rule>
    ...

<scope>:
    <rule>
    ...
```

**Scopes define which cells a rule applies to:**

| Scope Syntax | Applies To | Example |
|--------------|------------|---------|
| `column <name>:` | All cells in the column | `column amount:` |
| `column "<name>":` | Column with spaces | `column "total amount":` |
| `row <index>:` | All cells in the row | `row 0:` |
| `cell (<col>, <row>):` | Single cell by coordinates | `cell (amount, 3):` |
| `cell <cell_id>:` | Single cell by ID | `cell tcell_grid1-3-2:` |
| `table "<name>":` | All cells in a specific table | `table "products":` |
| `tables:` | All cells in all tables | `tables:` |

**Indentation and comments:**

```python
# This is a comment

column amount:
    # Rules must be indented under their scope
    style("error") if value < 0
    format("EUR")

# Column names with spaces need quotes
column "total amount":
    style("neutral", bold=True)
```

### Styling Cells

The `style()` function applies visual formatting to cells.

**Syntax:**

```python
style(<preset>)
style(<preset>, <options>)
style(<options>)
```

**Available style presets (DaisyUI 5):**

| Preset | Background | Text | Use Case |
|--------|------------|------|----------|
| `primary` | Primary theme color | Primary content | Main highlights |
| `secondary` | Secondary theme color | Secondary content | Secondary highlights |
| `accent` | Accent theme color | Accent content | Accent highlights |
| `neutral` | Neutral theme color | Neutral content | Headers, totals |
| `info` | Info (blue) | Info content | Information |
| `success` | Success (green) | Success content | Positive values |
| `warning` | Warning (yellow) | Warning content | Warnings |
| `error` | Error (red) | Error content | Negative values, errors |

**Style properties:**

| Property | Type | Description | Example |
|----------|------|-------------|---------|
| `background_color` | string | Background color | `"#ffeeee"`, `"red"`, `"var(--color-primary)"` |
| `color` | string | Text color | `"#cc0000"`, `"white"` |
| `bold` | boolean | Bold text | `True`, `False` |
| `italic` | boolean | Italic text | `True`, `False` |
| `underline` | boolean | Underlined text | `True`, `False` |
| `strikethrough` | boolean | Strikethrough text | `True`, `False` |
| `font_size` | string | Font size | `"12px"`, `"0.9em"` |

**Examples:**

```python
# Preset only
column status:
    style("error")

# Preset with overrides
column total:
    style("neutral", bold=True)

# Multiple properties
column name:
    style("success", italic=True, underline=True)

# No preset, direct properties
column notes:
    style(color="red", bold=True)
    style(background_color="#ffeeee", color="#cc0000")
```

### Formatting Values

The `format()` function transforms cell values for display without changing the underlying data.

**Using presets:**

```python
# Preset only
format("EUR")

# Preset with overrides
format("EUR", precision=3)
```

**Available formatter presets:**

| Preset | Type | Description | Example Output |
|--------|------|-------------|----------------|
| `EUR` | number | Euro currency | 1 234,56 € |
| `USD` | number | US Dollar | $1,234.56 |
| `percentage` | number | Percentage (×100) | 45.2% |
| `short_date` | date | DD/MM/YYYY | 29/01/2026 |
| `iso_date` | date | YYYY-MM-DD | 2026-01-29 |
| `yes_no` | boolean | Yes/No | Yes |

**Using explicit types:**

When not using a preset, specify the type explicitly:

```python
format.number(precision=2, suffix=" €", thousands_sep=" ")
format.date(format="%d/%m/%Y")
format.boolean(true_value="Oui", false_value="Non")
format.text(max_length=50, ellipsis="...")
format.enum(source={"draft": "Draft", "published": "Published"})
format.constant(value="N/A")
```

#### Number Formatter

Formats numbers, currencies, and percentages.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `prefix` | string | `""` | Text before value (e.g., "$") |
| `suffix` | string | `""` | Text after value (e.g., " €", "%") |
| `thousands_sep` | string | `""` | Thousands separator (e.g., ",", " ") |
| `decimal_sep` | string | `"."` | Decimal separator (e.g., ".", ",") |
| `precision` | int | `0` | Number of decimal places |
| `multiplier` | number | `1` | Multiply value before display (e.g., 100 for %) |

**Examples:**

```python
# Euro currency
column amount:
    format.number(suffix=" €", thousands_sep=" ", decimal_sep=",", precision=2)

# US Dollar
column price:
    format.number(prefix="$", thousands_sep=",", precision=2)

# Percentage
column rate:
    format.number(suffix="%", multiplier=100, precision=1)

# Large numbers with thousands separator
column population:
    format.number(thousands_sep=",", precision=0)
```

#### Date Formatter

Formats dates and datetimes using strftime patterns.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `format` | string | `"%Y-%m-%d"` | strftime format pattern |

**Common format patterns:**

| Pattern | Description | Example |
|---------|-------------|---------|
| `"%Y-%m-%d"` | ISO format | 2026-01-29 |
| `"%d/%m/%Y"` | European | 29/01/2026 |
| `"%m/%d/%Y"` | US format | 01/29/2026 |
| `"%d %b %Y"` | Short month | 29 Jan 2026 |
| `"%d %B %Y"` | Full month | 29 January 2026 |
| `"%Y-%m-%d %H:%M"` | With time | 2026-01-29 14:30 |

**Examples:**

```python
# European format
column created_at:
    format.date(format="%d/%m/%Y")

# Full date with month name
column updated_at:
    format.date(format="%d %B %Y")

# ISO format with time
column timestamp:
    format.date(format="%Y-%m-%d %H:%M:%S")
```

#### Boolean Formatter

Formats boolean and binary values.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `true_value` | string | `"true"` | Display for true/1 |
| `false_value` | string | `"false"` | Display for false/0 |
| `null_value` | string | `""` | Display for null/None |

**Examples:**

```python
# Yes/No
column active:
    format.boolean(true_value="Yes", false_value="No")

# Checked/Unchecked
column completed:
    format.boolean(true_value="✓", false_value="✗")

# Custom labels
column status:
    format.boolean(true_value="Enabled", false_value="Disabled", null_value="Unknown")
```

#### Text Formatter

Formats and transforms text values.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `transform` | string | - | `"uppercase"`, `"lowercase"`, `"capitalize"` |
| `max_length` | int | - | Truncate if exceeded |
| `ellipsis` | string | `"..."` | Suffix when truncated |

**Examples:**

```python
# Uppercase
column code:
    format.text(transform="uppercase")

# Truncate long text
column description:
    format.text(max_length=50, ellipsis="...")

# Capitalize first letter
column name:
    format.text(transform="capitalize")
```

#### Enum Formatter

Maps values to display labels. Useful for status codes, categories, and dropdown lists.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `source` | object | - | Mapping or datagrid reference |
| `default` | string | `""` | Label for unknown values |

**Source types:**

**Static mapping:**

```python
column status:
    format.enum(
        source={
            "draft": "Draft",
            "pending": "Pending Review",
            "approved": "Approved",
            "rejected": "Rejected"
        },
        default="Unknown"
    )
```

**From another DataGrid:**

```python
column category_id:
    format.enum(
        source={
            "type": "datagrid",
            "value": "categories_grid",
            "value_column": "id",
            "display_column": "name"
        }
    )
```

This looks up the `category_id` value in the "categories_grid", finds the matching row by "id", and displays the "name" column.

#### Constant Formatter

Displays a fixed value regardless of the cell's actual value. Useful for placeholder text or fixed labels.

**Parameters:**

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `value` | string | - | The constant text to display |

**Examples:**

```python
# Display "N/A" for all cells
column placeholder:
    format.constant(value="N/A")

# Display fixed label
column type:
    format.constant(value="Product")

# Combined with condition
column optional_field:
    format.constant(value="—") if value isempty
```

### Conditional Formatting

Conditions determine when a rule applies. They use the syntax:

```python
if <left> <operator> <right>
if <operand> <unary_operator>
```

#### Comparison Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `==` | Equal | `value == 0` |
| `!=` | Not equal | `value != ""` |
| `<` | Less than | `value < 0` |
| `<=` | Less or equal | `value <= 100` |
| `>` | Greater than | `value > 1000` |
| `>=` | Greater or equal | `value >= 0` |

**Examples:**

```python
column amount:
    style("error") if value < 0
    style("success") if value > 1000
    style("warning") if value == 0
```

#### String Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `contains` | String contains | `value contains "error"` |
| `startswith` | String starts with | `value startswith "ERR"` |
| `endswith` | String ends with | `value endswith ".pdf"` |

**Examples:**

```python
column message:
    style("error") if value contains "error"
    style("info") if value startswith "INFO"

column filename:
    style("accent") if value endswith ".pdf"
```

**Note:** String comparisons are **case-insensitive by default**. See "Case Sensitivity" below for case-sensitive matching.

#### List Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `in` | Value in list | `value in ["A", "B", "C"]` |
| `between` | Value in range | `value between 0 and 100` |

**Examples:**

```python
column status:
    style("success") if value in ["approved", "completed"]
    style("warning") if value in ["pending", "review"]

column score:
    style("error") if value between 0 and 30
    style("warning") if value between 31 and 70
    style("success") if value between 71 and 100
```

#### Empty and NaN Checks

| Operator | Description | Example |
|----------|-------------|---------|
| `isempty` | Is null or empty string | `value isempty` |
| `isnotempty` | Is not null or empty | `value isnotempty` |
| `isnan` | Is NaN (Not a Number) | `value isnan` |

**Examples:**

```python
column notes:
    style("neutral") if value isempty
    format.constant(value="—") if value isempty

column calculation:
    style("error") if value isnan
    format.constant(value="Error") if value isnan

column required_field:
    style("warning") if value isempty
```

**Note:** `isempty` checks for `None` or empty string (`""`). `isnan` specifically checks for invalid float values (NaN).

#### Cell References

References allow comparing a cell's value with values from other cells.

| Reference | Description | Example |
|-----------|-------------|---------|
| `value` | Current cell value | `value < 0` |
| `col.<name>` | Value from another column (same row) | `value > col.budget` |
| `col."<name>"` | Column with spaces | `value > col."max amount"` |
| `row.<index>` | Value from another row (same column) | `value != row.0` |
| `cell.<col>-<row>` | Specific cell by coordinates | `value == cell.status-0` |

**Examples:**

```python
# Compare with another column
column actual:
    style("error") if value > col.budget
    style("warning") if value > col.budget * 0.9

# Compare with header row
column total:
    style("neutral", bold=True) if value == row.0

# Compare with specific cell
column status:
    style("success") if value == cell.status-0
```

**Arithmetic in references:**

You can perform simple arithmetic with column references:

```python
column spending:
    style("warning") if value > col.budget * 0.8
    style("error") if value > col.budget * 1.1
```

#### Negation

Use `not` to negate any condition:

```python
column status:
    style("error") if not value in ["valid", "approved"]
    style("warning") if not value contains "OK"

column amount:
    style("success") if not value < 0
```

#### Case Sensitivity

String comparisons are **case-insensitive by default**. Use the `(case)` modifier for case-sensitive matching:

```python
column code:
    style("error") if value == "Error" (case)
    style("warning") if value contains "WARN" (case)

column status:
    # This matches "approved", "APPROVED", "Approved"
    style("success") if value == "approved"

    # This matches only "APPROVED"
    style("info") if value == "APPROVED" (case)
```

### Complete Examples

#### Example 1: Highlight Negative Values

Simple financial data with negative values highlighted:

```python
column amount:
    style("error") if value < 0
    format("EUR")
```

**Result:**
- Negative values: red background, formatted as Euro
- Positive values: normal display, formatted as Euro

#### Example 2: Status Indicators with Color Coding

Map status codes to colors and labels:

```python
column status:
    format.enum(
        source={
            "draft": "Draft",
            "pending": "Pending Review",
            "approved": "Approved",
            "rejected": "Rejected"
        },
        default="Unknown"
    )
    style("neutral") if value == "draft"
    style("warning") if value == "pending"
    style("success") if value == "approved"
    style("error") if value == "rejected"
```

**Result:**
- "draft" → "Draft" with neutral background
- "pending" → "Pending Review" with yellow background
- "approved" → "Approved" with green background
- "rejected" → "Rejected" with red background

#### Example 3: Budget vs Actual with Thresholds

Compare actual spending against budget with warning thresholds:

```python
column actual:
    format("EUR")
    style("success") if value <= col.budget * 0.8
    style("warning") if value > col.budget * 0.8
    style("error") if value > col.budget

column budget:
    format("EUR")
```

**Result:**
- Actual ≤ 80% of budget: green background
- Actual > 80% but ≤ 100%: yellow background
- Actual > 100%: red background

#### Example 4: Financial Report with Multiple Rules

Complete financial report with headers, formatting, and conditional highlighting:

```python
# Global styling for all tables
tables:
    style(font_size="14px")

# Header row
row 0:
    style("neutral", bold=True)

# Amount column
column amount:
    format.number(precision=2, suffix=" €", thousands_sep=" ")
    style("error") if value < 0
    style("success") if value > col.target

# Percentage column
column progress:
    format("percentage")
    style("error") if value < 0.5
    style("warning") if value between 0.5 and 0.8
    style("success") if value > 0.8

# Status column
column status:
    format.enum(
        source={
            "draft": "Draft",
            "review": "In Review",
            "approved": "Approved",
            "rejected": "Rejected"
        }
    )
    style("neutral") if value == "draft"
    style("info") if value == "review"
    style("success") if value == "approved"
    style("error") if value == "rejected"

# Date column
column created_at:
    format.date(format="%d %b %Y")

# Highlight specific important cell
cell (amount, 10):
    style("accent", bold=True)
```

**Result:** A fully formatted financial report with:
- Bold headers with neutral background
- Euro amounts with conditional coloring
- Progress percentages with traffic light colors
- Status labels with appropriate colors
- Formatted dates
- Highlighted total row

## Using the DSL Editor

### Editor Features

The DSL editor provides a comfortable writing experience with:

**Syntax highlighting:**
- Keywords (column, row, if, not) are highlighted
- Functions (style, format) are colored differently
- Strings, numbers, and operators have distinct colors

**Line numbers:**
- Easy reference for error messages
- Quick navigation in large documents

**Auto-indentation:**
- Automatic indentation after scope declarations
- Tab key for manual indentation
- Maintains Python-style indentation

### Autocompletion

The editor provides context-aware suggestions to help you write rules quickly and correctly.

**How to trigger:**

- **Ctrl+Space** - Manual trigger at any time
- **Automatic** - Triggers after typing `.`, `(`, `"`, or space in certain contexts

**What can be autocompleted:**

| Context | Suggestions |
|---------|-------------|
| Start of line | `column`, `row`, `cell`, `table`, `tables` |
| After `column ` | Column names from your DataGrid |
| After `row ` | Row indices (0, 1, 2, ...) |
| After `table ` | Table name from current DataGrid |
| Indented line | `style(`, `format(`, `format.` |
| Inside `style("` | Style presets (primary, error, success, ...) |
| After `style(` | Preset names and parameters (bold=, color=, ...) |
| Inside `format("` | Format presets (EUR, USD, percentage, ...) |
| After `format.` | Format types (number, date, boolean, text, enum, constant) |
| After `if ` | `value`, `col.`, `row.`, `not` |
| After operand | Operators (==, <, >, contains, in, ...) |
| After `col.` | Column names |
| After operator | `col.`, literal values, `True`, `False` |

**Example workflow:**

```
User types     │ Suggestions
───────────────┼────────────────────────────────────
col            │ column
column         │ [column names from grid]
column amount: │ (new line, indent)
    st         │ style
    style(     │ "error", "warning", "success", ...
    style("e   │ "error"
    style("error", │ bold=, italic=, color=, ...
    style("error", bold=│ True, False
    style("error") │ format(, format., if
    style("error") if │ value, col., not
    style("error") if value │ ==, !=, <, >, contains, in, ...
    style("error") if value < │ [number input]
```

### Syntax Validation

The editor provides real-time syntax checking as you type.

**Error indicators:**

```
┌─────────────────────────────────────────────────────┐
│  1  column amount:                                  │
│  2      style("error" if value < 0                  │
│         ▲▲▲▲▲▲▲▲▲▲▲▲▲                              │
│         ╰─ Missing closing parenthesis              │
│  3                                                  │
└─────────────────────────────────────────────────────┘
```

**Visual feedback:**

- **Red underline** - Syntax error at that position
- **Gutter marker** - Error icon in the line number area
- **Tooltip** - Hover over the error for details

**Common errors and fixes:**

| Error | Cause | Fix |
|-------|-------|-----|
| `Expected ':'` | Missing colon after scope | Add `:` after scope name |
| `Expected indentation` | Rule not indented | Indent rule under scope |
| `Unexpected token` | Typo or invalid syntax | Check spelling, operators |
| `Missing closing quote` | Unclosed string | Add closing `"` or `'` |
| `Missing closing parenthesis` | Unclosed function call | Add closing `)` |
| `Unknown column` | Column doesn't exist | Check column name spelling |

**Example - Before and after fixing:**

**Before (error):**

```python
column amount
    style("error") if value < 0
```

Error: `Expected ':' after column name`

**After (fixed):**

```python
column amount:
    style("error") if value < 0
```

## Managing Presets

### Using Built-in Presets

**Style presets (DaisyUI 5):**

| Preset | Background | Text | Typical Use |
|--------|------------|------|-------------|
| `primary` | Primary theme color | Primary content | Main highlights, important data |
| `secondary` | Secondary theme color | Secondary content | Secondary highlights |
| `accent` | Accent theme color | Accent content | Accent highlights, special items |
| `neutral` | Neutral theme color | Neutral content | Headers, totals, summaries |
| `info` | Info (blue) | Info content | Information, notes |
| `success` | Success (green) | Success content | Positive values, completed items |
| `warning` | Warning (yellow) | Warning content | Warnings, thresholds exceeded |
| `error` | Error (red) | Error content | Negative values, errors, failures |

**Formatter presets:**

| Preset | Type | Configuration | Example Output |
|--------|------|---------------|----------------|
| `EUR` | number | suffix: " €", thousands_sep: " ", decimal_sep: ",", precision: 2 | 1 234,56 € |
| `USD` | number | prefix: "$", thousands_sep: ",", decimal_sep: ".", precision: 2 | $1,234.56 |
| `percentage` | number | suffix: "%", precision: 1, multiplier: 100 | 45.2% |
| `short_date` | date | format: "%d/%m/%Y" | 29/01/2026 |
| `iso_date` | date | format: "%Y-%m-%d" | 2026-01-29 |
| `yes_no` | boolean | true_value: "Yes", false_value: "No" | Yes |

### Creating Custom Presets

You can add your own presets to use across all DataGrids.

**Adding a custom style preset:**

```python
from myfasthtml.controls.DataGridsManager import DataGridsManager

# Get the global manager
manager = DataGridsManager.get_instance()

# Add custom style preset
manager.add_style_preset("highlight", {
    "background_color": "yellow",
    "color": "black",
    "font_weight": "bold"
})

# Use in DSL
column important:
    style("highlight")
```

**Adding a custom formatter preset:**

```python
# Swiss Franc currency
manager.add_formatter_preset("CHF", {
    "type": "number",
    "prefix": "CHF ",
    "thousands_sep": "'",
    "decimal_sep": ".",
    "precision": 2
})

# Use in DSL
column price:
    format("CHF")
```

**Adding multiple presets:**

```python
# Custom style presets
manager.add_style_preset("urgent", {
    "background_color": "#ff6b6b",
    "color": "white",
    "font_weight": "bold"
})

manager.add_style_preset("completed", {
    "background_color": "#51cf66",
    "color": "white",
    "text_decoration": "line-through"
})

# Custom formatter presets
manager.add_formatter_preset("GBP", {
    "type": "number",
    "prefix": "£",
    "thousands_sep": ",",
    "precision": 2
})

manager.add_formatter_preset("compact_date", {
    "type": "date",
    "format": "%d/%m/%y"
})
```

**Removing presets:**

```python
# Remove style preset
manager.remove_style_preset("highlight")

# Remove formatter preset
manager.remove_formatter_preset("CHF")
```

## Reference

### Style Properties

Complete reference of all style properties:

| Property | Type | Values | Description |
|----------|------|--------|-------------|
| `preset` | string | See Style Presets below | Preset name (applied first) |
| `background_color` | string | Hex, CSS name, or variable | Background color |
| `color` | string | Hex, CSS name, or variable | Text color |
| `bold` | boolean | `True`, `False` | Bold text |
| `italic` | boolean | `True`, `False` | Italic text |
| `underline` | boolean | `True`, `False` | Underlined text |
| `strikethrough` | boolean | `True`, `False` | Strikethrough text |
| `font_size` | string | CSS size value | Font size (e.g., "12px", "0.9em") |

**Color values:**

```python
# Hex colors
style(color="#cc0000")

# CSS color names
style(color="red")

# DaisyUI CSS variables
style(color="var(--color-primary)")
style(background_color="var(--color-base-200)")
```

**Combining properties:**

```python
# Preset + overrides
style("error", bold=True, font_size="16px")

# Multiple properties
style(background_color="#fffacd", color="#856404", bold=True, italic=True)
```

### Style Presets

DaisyUI 5 color presets:

| Preset | Background CSS Variable | Text CSS Variable |
|--------|------------------------|-------------------|
| `primary` | `var(--color-primary)` | `var(--color-primary-content)` |
| `secondary` | `var(--color-secondary)` | `var(--color-secondary-content)` |
| `accent` | `var(--color-accent)` | `var(--color-accent-content)` |
| `neutral` | `var(--color-neutral)` | `var(--color-neutral-content)` |
| `info` | `var(--color-info)` | `var(--color-info-content)` |
| `success` | `var(--color-success)` | `var(--color-success-content)` |
| `warning` | `var(--color-warning)` | `var(--color-warning-content)` |
| `error` | `var(--color-error)` | `var(--color-error-content)` |

**Note:** All presets use `font_weight: "normal"`, `font_style: "normal"`, `text_decoration: "none"` by default.

### Formatter Types

#### Number Formatter

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `prefix` | string | `""` | Text before value |
| `suffix` | string | `""` | Text after value |
| `thousands_sep` | string | `""` | Thousands separator |
| `decimal_sep` | string | `"."` | Decimal separator |
| `precision` | int | `0` | Decimal places |
| `multiplier` | number | `1` | Multiply before display |

#### Date Formatter

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `format` | string | `"%Y-%m-%d"` | strftime pattern |

#### Boolean Formatter

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `true_value` | string | `"true"` | Display for true/1 |
| `false_value` | string | `"false"` | Display for false/0 |
| `null_value` | string | `""` | Display for null/None |

#### Text Formatter

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `transform` | string | - | `"uppercase"`, `"lowercase"`, `"capitalize"` |
| `max_length` | int | - | Truncate if exceeded |
| `ellipsis` | string | `"..."` | Suffix when truncated |

#### Enum Formatter

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `source` | object | - | Mapping or datagrid reference |
| `default` | string | `""` | Label for unknown values |

#### Constant Formatter

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `value` | string | - | The constant text to display |

### Formatter Presets

| Preset | Type | Configuration |
|--------|------|---------------|
| `EUR` | number | `suffix: " €", thousands_sep: " ", decimal_sep: ",", precision: 2` |
| `USD` | number | `prefix: "$", thousands_sep: ",", decimal_sep: ".", precision: 2` |
| `percentage` | number | `suffix: "%", precision: 1, multiplier: 100` |
| `short_date` | date | `format: "%d/%m/%Y"` |
| `iso_date` | date | `format: "%Y-%m-%d"` |
| `yes_no` | boolean | `true_value: "Yes", false_value: "No"` |

### Operators Reference

#### Comparison Operators

| Operator | Description | Value Type | Example |
|----------|-------------|------------|---------|
| `==` | Equal | Any | `value == 0` |
| `!=` | Not equal | Any | `value != ""` |
| `<` | Less than | Number, Date | `value < 0` |
| `<=` | Less or equal | Number, Date | `value <= 100` |
| `>` | Greater than | Number, Date | `value > 1000` |
| `>=` | Greater or equal | Number, Date | `value >= 0` |

#### String Operators

| Operator | Description | Value Type | Example |
|----------|-------------|------------|---------|
| `contains` | String contains | String | `value contains "error"` |
| `startswith` | String starts with | String | `value startswith "ERR"` |
| `endswith` | String ends with | String | `value endswith ".pdf"` |

**Note:** String operators are case-insensitive by default. Use `(case)` modifier for case-sensitive matching.

#### List Operators

| Operator | Description | Value Type | Example |
|----------|-------------|------------|---------|
| `in` | Value in list | List | `value in ["A", "B", "C"]` |
| `between` | Value in range | List [min, max] | `value between 0 and 100` |

#### Unary Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `isempty` | Is null or empty string | `value isempty` |
| `isnotempty` | Is not null or empty | `value isnotempty` |
| `isnan` | Is NaN (Not a Number) | `value isnan` |

### DSL Grammar (EBNF)

Formal syntax reference for the DSL:

```ebnf
// Top-level structure
program         : scope+

// Scopes
scope           : scope_header NEWLINE INDENT rule+ DEDENT
scope_header    : column_scope | row_scope | cell_scope | table_scope | tables_scope
column_scope    : "column" column_name ":"
row_scope       : "row" INTEGER ":"
cell_scope      : "cell" cell_ref ":"
table_scope     : "table" QUOTED_STRING ":"
tables_scope    : "tables" ":"
column_name     : NAME | QUOTED_STRING
cell_ref        : "(" column_name "," INTEGER ")" | CELL_ID

// Rules
rule            : (style_expr format_expr? | format_expr style_expr?) condition? NEWLINE
condition       : "if" comparison

// Comparisons
comparison      : "not"? (binary_comp | unary_comp) case_modifier?
binary_comp     : operand operator operand
                | operand "in" list
                | operand "between" operand "and" operand
unary_comp      : operand ("isempty" | "isnotempty" | "isnan")
case_modifier   : "(" "case" ")"

// Operators
operator        : "==" | "!=" | "<" | "<=" | ">" | ">="
                | "contains" | "startswith" | "endswith"

// Operands
operand         : value_ref | column_ref | row_ref | cell_ref_expr | literal | arithmetic
value_ref       : "value"
column_ref      : "col." (NAME | QUOTED_STRING)
row_ref         : "row." INTEGER
cell_ref_expr   : "cell." NAME "-" INTEGER
literal         : STRING | NUMBER | BOOLEAN
arithmetic      : operand ("*" | "/" | "+" | "-") operand
list            : "[" (literal ("," literal)*)? "]"

// Style expression
style_expr      : "style" "(" style_args ")"
style_args      : (QUOTED_STRING ("," style_kwargs)?) | style_kwargs
style_kwargs    : style_kwarg ("," style_kwarg)*
style_kwarg     : NAME "=" (QUOTED_STRING | BOOLEAN | NUMBER)

// Format expression
format_expr     : format_preset | format_typed
format_preset   : "format" "(" QUOTED_STRING ("," format_kwargs)? ")"
format_typed    : "format" "." FORMAT_TYPE "(" format_kwargs? ")"
format_kwargs   : format_kwarg ("," format_kwarg)*
format_kwarg    : NAME "=" (QUOTED_STRING | BOOLEAN | NUMBER | dict)
dict            : "{" (dict_entry ("," dict_entry)*)? "}"
dict_entry      : QUOTED_STRING ":" QUOTED_STRING

// Tokens
FORMAT_TYPE     : "number" | "date" | "boolean" | "text" | "enum" | "constant"
NAME            : /[a-zA-Z_][a-zA-Z0-9_]*/
QUOTED_STRING   : /"[^"]*"/ | /'[^']*'/
INTEGER         : /[0-9]+/
NUMBER          : /[0-9]+(\.[0-9]+)?/
BOOLEAN         : "True" | "False" | "true" | "false"
CELL_ID         : /tcell_[a-zA-Z0-9_-]+/
NEWLINE         : /\n/
COMMENT         : /#.*/
```

---

**End of User Guide**

For technical details about the implementation, see the developer documentation.
