Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion jupyterbook/basics/selection-formatting.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.13.0"
"version": "3.13.7"
}
},
"nbformat": 4,
Expand Down
362 changes: 362 additions & 0 deletions jupyterbook/examples/students.ipynb

Large diffs are not rendered by default.

Binary file added tests/fixtures/xlsx/Students.xlsx
Binary file not shown.
2 changes: 2 additions & 0 deletions tidychef/acquire/excel_time.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
"M/D/YY": "%m/%d/%y",
# Month/Year with 4-digit year (e.g., 5/2023)
"m/yyyy": "%-m/%Y",
# Month as three letter abbreviation and 2 digit year (e.g., May-23)
"mmm-yy": "%b-%y",
# Year/Month with 4-digit year (e.g., 2023/5)
"yyyy/m": "%Y/%-m",
# Day/Month/Year with 2-digit year (e.g., 1/5/23)
Expand Down
41 changes: 37 additions & 4 deletions tidychef/acquire/xls/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,17 +90,50 @@ def sheets_from_workbook(
is_hyperlink = True
break

# Get indentation level from XF alignment
# Get alignment information from XF
indent_level = 0
if hasattr(xf, 'alignment') and hasattr(xf.alignment, 'indent_level'):
indent_level = xf.alignment.indent_level
horizontal_alignment = None
vertical_alignment = None

if hasattr(xf, 'alignment'):
# Get indentation level
if hasattr(xf.alignment, 'indent_level'):
indent_level = xf.alignment.indent_level

# Get horizontal alignment - XLS uses integers:
# 0 = general, 1 = left, 2 = center, 3 = right, 4 = fill, 5 = justify
if hasattr(xf.alignment, 'hor_align'):
hor_align = xf.alignment.hor_align
alignment_map = {
0: None, # general - let Excel decide
1: 'left',
2: 'center',
3: 'right',
4: 'fill', # not common, treat as general
5: 'justify'
}
horizontal_alignment = alignment_map.get(hor_align)

# Get vertical alignment - XLS uses integers:
# 0 = top, 1 = center, 2 = bottom, 3 = justify
if hasattr(xf.alignment, 'vert_align'):
vert_align = xf.alignment.vert_align
vertical_map = {
0: 'top',
1: 'center',
2: 'bottom',
3: 'justify'
}
vertical_alignment = vertical_map.get(vert_align)

cell_formatting = CellFormatting(
bold=is_bold,
italic=is_italic,
underline=is_underline,
hyperlink=is_hyperlink,
indent_level=indent_level
indent_level=indent_level,
horizontal_alignment=horizontal_alignment,
vertical_alignment=vertical_alignment
)

if cell.ctype == 3: # Date Cell
Expand Down
19 changes: 16 additions & 3 deletions tidychef/acquire/xlsx/shared.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,26 @@ def sheets_from_workbook(
is_underline = False
is_hyperlink = False
indent_level = 0
horizontal_alignment = None
vertical_alignment = None

if opycell.font:
is_bold = opycell.font.bold if opycell.font.bold is not None else False
is_italic = opycell.font.italic if opycell.font.italic is not None else False
# Check for underline - openpyxl uses 'single', 'double', etc. or None
is_underline = opycell.font.underline is not None and opycell.font.underline != 'none'

if opycell.alignment and opycell.alignment.indent is not None:
indent_level = int(opycell.alignment.indent)
if opycell.alignment:
if opycell.alignment.indent is not None:
indent_level = int(opycell.alignment.indent)

# Extract horizontal alignment (None means 'general' in Excel)
if opycell.alignment.horizontal is not None:
horizontal_alignment = opycell.alignment.horizontal

# Extract vertical alignment (None means 'bottom' in Excel)
if opycell.alignment.vertical is not None:
vertical_alignment = opycell.alignment.vertical

# Check if cell is a hyperlink
is_hyperlink = opycell.hyperlink is not None
Expand All @@ -74,7 +85,9 @@ def sheets_from_workbook(
italic=is_italic,
underline=is_underline,
hyperlink=is_hyperlink,
indent_level=indent_level
indent_level=indent_level,
horizontal_alignment=horizontal_alignment,
vertical_alignment=vertical_alignment
)

if opycell.is_date and opycell.internal_value is not None:
Expand Down
49 changes: 49 additions & 0 deletions tidychef/models/source/cellformat.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ class CellFormatting:
underline: Optional[bool] = None
hyperlink: Optional[bool] = None
indent_level: Optional[int] = None
horizontal_alignment: Optional[str] = None # 'left', 'center', 'right', 'justify', 'general'
vertical_alignment: Optional[str] = None # 'top', 'center', 'bottom'

def is_bold(self) -> bool:
"""
Expand Down Expand Up @@ -107,3 +109,50 @@ def is_indented(self) -> bool:
"Indentation level is unknown. Cannot determine if cell is indented."
)
return self.indent_level > 0

def get_horizontal_alignment(self) -> str:
"""
Get the horizontal alignment of the cell.

Returns:
str: The horizontal alignment ('left', 'center', 'right', 'justify', 'general')
Returns 'general' if alignment is not specified (Excel default behavior)
"""
return self.horizontal_alignment or 'general'

def get_vertical_alignment(self) -> str:
"""
Get the vertical alignment of the cell.

Returns:
str: The vertical alignment ('top', 'center', 'bottom')
Returns 'bottom' if alignment is not specified (Excel default behavior)
"""
return self.vertical_alignment or 'bottom'

def is_left_aligned(self) -> bool:
"""
Check if the cell is left-aligned.

Returns:
bool: True if cell is explicitly left-aligned, False otherwise
"""
return self.horizontal_alignment == 'left'

def is_center_aligned(self) -> bool:
"""
Check if the cell is center-aligned.

Returns:
bool: True if cell is center-aligned, False otherwise
"""
return self.horizontal_alignment == 'center'

def is_right_aligned(self) -> bool:
"""
Check if the cell is right-aligned.

Returns:
bool: True if cell is right-aligned, False otherwise
"""
return self.horizontal_alignment == 'right'
38 changes: 37 additions & 1 deletion tidychef/notebook/preview/html/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ def as_html(self):
Create the html representation of this cell with formatting.
"""
content = str(self.value)
cell_styles = [f"background-color:{self.colour}"]

# Apply text formatting if cell formatting is available
if self.cell and self.cell.cellformat:
Expand Down Expand Up @@ -140,4 +141,39 @@ def as_html(self):
except Exception:
logger.error("Error checking underline formatting", exc_info=True)

return f'<td style="background-color:{self.colour}">{content}</td>'
# Apply alignment and indentation formatting - handle each separately
# Handle indentation first (takes precedence and implies left alignment)
try:
if (self.cell.cellformat.indent_level is not None and
self.cell.cellformat.indent_level > 0):
indent_level = self.cell.cellformat.indent_level
# Apply padding-left based on indent level - make it more pronounced than Excel's subtle 8px
# Using 20px per level to make indentation clearly visible in HTML previews
padding_left = indent_level * 20
cell_styles.append(f"padding-left: {padding_left}px")
# Indented cells are always left-aligned in Excel
# Use !important to override Jupyter notebook CSS
cell_styles.append("text-align: left !important")
else:
# Handle horizontal alignment for non-indented cells
alignment = self.cell.cellformat.get_horizontal_alignment()
if alignment != 'general':
# Apply explicit alignment (but not for 'general')
# Use !important to override Jupyter notebook CSS
cell_styles.append(f"text-align: {alignment} !important")
else:
# For 'general' alignment, override the CSS center alignment
# Excel's general alignment: text left, numbers right
# Since we don't easily distinguish types here, default to left
# which is more appropriate for most data
# Use !important to override Jupyter notebook CSS
cell_styles.append("text-align: left !important")
except Exception:
logger.error("Error checking alignment/indentation formatting", exc_info=True)
# Fallback - at least override the center alignment from CSS
# Use !important to override Jupyter notebook CSS
cell_styles.append("text-align: left !important")

# Combine all styles
style_attr = "; ".join(cell_styles)
return f'<td style="{style_attr}">{content}</td>'
17 changes: 17 additions & 0 deletions tidychef/notebook/preview/html/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@
"#b380ff",
]

# Separate color palette for multiple selection combinations
# These colors are distinct from individual selection colors to avoid confusion
MULTIPLE_SELECTION_COLOURS = [
"#ffb3b3", # Light red
"#ffd9b3", # Light orange
"#ffffb3", # Light yellow
"#d9ffb3", # Light lime
"#b3ffb3", # Light green
"#b3ffff", # Light cyan
"#c6e6ff", # Very light blue (different from #b3d9ff)
"#d9b3ff", # Light purple
"#ffb3ff", # Light magenta
"#ffb3d9", # Light pink
"#e6ccb3", # Light brown
"#cccccc", # Light gray
]

# Simple CSS to make it pretty-ish
INLINE_CSS = """
<style>
Expand Down
Loading
Loading