Multi-row Headers

Some spreadsheets use two or more header rows — typically a category row followed by a sub-header row. xlea handles this with the @config decorator.

Example spreadsheet layout

Consider a file where the header spans two rows:

Row 1 (category):  │ Profile       │ Profile       │ Contact │
Row 2 (sub-header):│ Last Name     │ First Name    │ Email   │
Row 3 (data):      │ Smith         │ Alice         │ a@x.com │
Row 4 (data):      │ Jones         │ Bob           │ b@x.com │

xlea flattens these into "Profile;Last Name", "Profile;First Name", "Contact;Email" (using ; as the default delimiter).

Configuration

Use @config(header_rows=2) on your schema class:

from xlea import Schema, Column, config

@config(header_rows=2)
class Person(Schema):
    last_name: str  = Column("Profile;Last Name")
    first_name: str = Column("Profile;First Name")
    email: str      = Column("Contact;Email")

The first segment is carried forward when a category cell is empty (merged cells). So the spreadsheet above is equivalent to:

Profile;Last Name  │ Profile;First Name │ Contact;Email

Custom delimiter

The default delimiter between header levels is ;. Override it with delimiter:

@config(header_rows=2, delimiter=" / ")
class Report(Schema):
    revenue: float = Column("Finance / Revenue (USD)")

You can combine a custom delimiter with ignore_case on individual columns:

@config(header_rows=2, delimiter="|")
class Inventory(Schema):
    sku: str   = Column("Product|SKU", ignore_case=True)
    qty: int   = Column("Stock|Quantity")

How carry-forward works

When a category cell is empty (or contains only whitespace / None), xlea reuses the last seen non-empty value for that column position. This mirrors how merged cells look when exported from Excel.

Given:

│ Sales  │        │ Finance │        │
│ Q1     │ Q2     │ Q1      │ Q2     │

xlea produces: ["Sales;Q1", "Sales;Q2", "Finance;Q1", "Finance;Q2"]

@config(header_rows=2)
class Quarterly(Schema):
    sales_q1:   float = Column("Sales;Q1")
    sales_q2:   float = Column("Sales;Q2")
    finance_q1: float = Column("Finance;Q1")
    finance_q2: float = Column("Finance;Q2")