Skip to main content
Glama
formatting.pyโ€ข17.3 kB
"""Table and cell formatting operations.""" from typing import Optional, Dict, Any, Union from docx.shared import RGBColor, Pt from docx.enum.text import WD_PARAGRAPH_ALIGNMENT from docx.enum.table import WD_TABLE_ALIGNMENT from docx.oxml.shared import OxmlElement, qn from ...models.responses import OperationResponse from ...models.formatting import ( TextFormat, CellAlignment, CellBorders, CellFormatting, HorizontalAlignment, VerticalAlignment, BorderStyle, BorderWidth, hex_to_rgb, validate_color ) from ...utils.exceptions import ( TableNotFoundError, InvalidCellPositionError, TableOperationError, DataFormatError ) from ...utils.validation import validate_table_index, validate_cell_position class TableFormattingOperations: """Handles table and cell formatting operations.""" def __init__(self, document_manager): """Initialize with document manager.""" self.document_manager = document_manager def format_cell_text( self, file_path: str, table_index: int, row_index: int, column_index: int, text_format: Union[TextFormat, Dict[str, Any]] ) -> OperationResponse: """ Apply text formatting to a specific cell. Args: file_path: Path to the document table_index: Index of the table row_index: Row index of the cell column_index: Column index of the cell text_format: TextFormat object or dictionary with formatting options Returns: OperationResponse indicating success or failure """ try: document = self.document_manager.get_or_load_document(file_path) # Validate parameters validate_table_index(table_index, len(document.tables)) table = document.tables[table_index] validate_cell_position(row_index, column_index, len(table.rows), len(table.columns)) # Convert dict to TextFormat if needed if isinstance(text_format, dict): text_format = TextFormat.from_dict(text_format) # Get the cell cell = table.rows[row_index].cells[column_index] # Apply formatting to all paragraphs in the cell for paragraph in cell.paragraphs: for run in paragraph.runs: self._apply_text_formatting(run, text_format) # If no runs exist, create one if not paragraph.runs: run = paragraph.runs[0] if paragraph.runs else paragraph.add_run() self._apply_text_formatting(run, text_format) return OperationResponse.success( f"Text formatting applied to cell [{row_index}, {column_index}]", { "table_index": table_index, "row_index": row_index, "column_index": column_index, "formatting_applied": text_format.to_dict() } ) except Exception as e: return OperationResponse.error(f"Failed to format cell text: {str(e)}") def format_cell_alignment( self, file_path: str, table_index: int, row_index: int, column_index: int, alignment: Union[CellAlignment, Dict[str, Any]] ) -> OperationResponse: """ Set text alignment for a specific cell. Args: file_path: Path to the document table_index: Index of the table row_index: Row index of the cell column_index: Column index of the cell alignment: CellAlignment object or dictionary with alignment settings Returns: OperationResponse indicating success or failure """ try: document = self.document_manager.get_or_load_document(file_path) # Validate parameters validate_table_index(table_index, len(document.tables)) table = document.tables[table_index] validate_cell_position(row_index, column_index, len(table.rows), len(table.columns)) # Convert dict to CellAlignment if needed if isinstance(alignment, dict): alignment = CellAlignment.from_dict(alignment) # Get the cell cell = table.rows[row_index].cells[column_index] # Apply horizontal alignment to paragraphs if alignment.horizontal: wd_alignment = self._get_paragraph_alignment(alignment.horizontal) for paragraph in cell.paragraphs: paragraph.alignment = wd_alignment # Apply vertical alignment to cell if alignment.vertical: self._set_cell_vertical_alignment(cell, alignment.vertical) return OperationResponse.success( f"Alignment applied to cell [{row_index}, {column_index}]", { "table_index": table_index, "row_index": row_index, "column_index": column_index, "alignment_applied": alignment.to_dict() } ) except Exception as e: return OperationResponse.error(f"Failed to set cell alignment: {str(e)}") def format_cell_background( self, file_path: str, table_index: int, row_index: int, column_index: int, color: str ) -> OperationResponse: """ Set background color for a specific cell. Args: file_path: Path to the document table_index: Index of the table row_index: Row index of the cell column_index: Column index of the cell color: Hex color string (with or without #) Returns: OperationResponse indicating success or failure """ try: document = self.document_manager.get_or_load_document(file_path) # Validate parameters validate_table_index(table_index, len(document.tables)) table = document.tables[table_index] validate_cell_position(row_index, column_index, len(table.rows), len(table.columns)) # Validate and clean color color = color.lstrip('#') if not validate_color(color): return OperationResponse.error(f"Invalid color format: {color}") # Get the cell cell = table.rows[row_index].cells[column_index] # Set background color using shading self._set_cell_background_color(cell, color) return OperationResponse.success( f"Background color applied to cell [{row_index}, {column_index}]", { "table_index": table_index, "row_index": row_index, "column_index": column_index, "background_color": color } ) except Exception as e: return OperationResponse.error(f"Failed to set cell background: {str(e)}") def format_cell_borders( self, file_path: str, table_index: int, row_index: int, column_index: int, borders: Union[CellBorders, Dict[str, Any]] ) -> OperationResponse: """ Set borders for a specific cell. Args: file_path: Path to the document table_index: Index of the table row_index: Row index of the cell column_index: Column index of the cell borders: CellBorders object or dictionary with border settings Returns: OperationResponse indicating success or failure """ try: document = self.document_manager.get_or_load_document(file_path) # Validate parameters validate_table_index(table_index, len(document.tables)) table = document.tables[table_index] validate_cell_position(row_index, column_index, len(table.rows), len(table.columns)) # Convert dict to CellBorders if needed if isinstance(borders, dict): borders = CellBorders.from_dict(borders) # Get the cell cell = table.rows[row_index].cells[column_index] # Apply borders self._set_cell_borders(cell, borders) return OperationResponse.success( f"Borders applied to cell [{row_index}, {column_index}]", { "table_index": table_index, "row_index": row_index, "column_index": column_index, "borders_applied": borders.to_dict() } ) except Exception as e: return OperationResponse.error(f"Failed to set cell borders: {str(e)}") def format_cell_complete( self, file_path: str, table_index: int, row_index: int, column_index: int, formatting: Union[CellFormatting, Dict[str, Any]] ) -> OperationResponse: """ Apply complete formatting to a cell (text, alignment, background, borders). Args: file_path: Path to the document table_index: Index of the table row_index: Row index of the cell column_index: Column index of the cell formatting: CellFormatting object or dictionary with all formatting options Returns: OperationResponse indicating success or failure """ try: # Convert dict to CellFormatting if needed if isinstance(formatting, dict): formatting = CellFormatting.from_dict(formatting) results = [] # Apply text formatting if formatting.text_format: result = self.format_cell_text( file_path, table_index, row_index, column_index, formatting.text_format ) if result.status.value != "success": return result results.append("text_format") # Apply alignment if formatting.alignment: result = self.format_cell_alignment( file_path, table_index, row_index, column_index, formatting.alignment ) if result.status.value != "success": return result results.append("alignment") # Apply background color if formatting.background_color: result = self.format_cell_background( file_path, table_index, row_index, column_index, formatting.background_color ) if result.status.value != "success": return result results.append("background_color") # Apply borders if formatting.borders: result = self.format_cell_borders( file_path, table_index, row_index, column_index, formatting.borders ) if result.status.value != "success": return result results.append("borders") return OperationResponse.success( f"Complete formatting applied to cell [{row_index}, {column_index}]", { "table_index": table_index, "row_index": row_index, "column_index": column_index, "applied_formats": results, "formatting": formatting.to_dict() } ) except Exception as e: return OperationResponse.error(f"Failed to apply complete cell formatting: {str(e)}") # Helper methods def _apply_text_formatting(self, run, text_format: TextFormat): """Apply text formatting to a run.""" if text_format.font_family: run.font.name = text_format.font_family if text_format.font_size: run.font.size = Pt(text_format.font_size) if text_format.font_color: color = text_format.font_color.lstrip('#') if validate_color(color): r, g, b = hex_to_rgb(color) run.font.color.rgb = RGBColor(r, g, b) if text_format.bold is not None: run.font.bold = text_format.bold if text_format.italic is not None: run.font.italic = text_format.italic if text_format.underline is not None: run.font.underline = text_format.underline if text_format.strikethrough is not None: run.font.strike = text_format.strikethrough if text_format.subscript is not None: run.font.subscript = text_format.subscript if text_format.superscript is not None: run.font.superscript = text_format.superscript def _get_paragraph_alignment(self, alignment: HorizontalAlignment): """Convert HorizontalAlignment to WD_PARAGRAPH_ALIGNMENT.""" alignment_map = { HorizontalAlignment.LEFT: WD_PARAGRAPH_ALIGNMENT.LEFT, HorizontalAlignment.CENTER: WD_PARAGRAPH_ALIGNMENT.CENTER, HorizontalAlignment.RIGHT: WD_PARAGRAPH_ALIGNMENT.RIGHT, HorizontalAlignment.JUSTIFY: WD_PARAGRAPH_ALIGNMENT.JUSTIFY } return alignment_map.get(alignment, WD_PARAGRAPH_ALIGNMENT.LEFT) def _set_cell_vertical_alignment(self, cell, alignment: VerticalAlignment): """Set vertical alignment for a cell.""" # This requires direct XML manipulation as python-docx doesn't have direct support tc = cell._tc tcPr = tc.get_or_add_tcPr() # Remove existing vAlign if present for vAlign in tcPr.xpath('.//w:vAlign'): vAlign.getparent().remove(vAlign) # Add new vAlign vAlign = OxmlElement('w:vAlign') alignment_map = { VerticalAlignment.TOP: "top", VerticalAlignment.MIDDLE: "center", VerticalAlignment.BOTTOM: "bottom" } vAlign.set(qn('w:val'), alignment_map.get(alignment, "top")) tcPr.append(vAlign) def _set_cell_background_color(self, cell, color: str): """Set background color for a cell.""" # Direct XML manipulation for cell shading tc = cell._tc tcPr = tc.get_or_add_tcPr() # Remove existing shd if present for shd in tcPr.xpath('.//w:shd'): shd.getparent().remove(shd) # Add new shading shd = OxmlElement('w:shd') shd.set(qn('w:val'), 'clear') shd.set(qn('w:color'), 'auto') shd.set(qn('w:fill'), color.upper()) tcPr.append(shd) def _set_cell_borders(self, cell, borders: CellBorders): """Set borders for a cell.""" # Direct XML manipulation for cell borders tc = cell._tc tcPr = tc.get_or_add_tcPr() # Remove existing tcBorders if present for tcBorders in tcPr.xpath('.//w:tcBorders'): tcBorders.getparent().remove(tcBorders) # Add new borders tcBorders = OxmlElement('w:tcBorders') border_sides = { 'top': borders.top, 'bottom': borders.bottom, 'left': borders.left, 'right': borders.right } for side_name, border_props in border_sides.items(): if border_props: border_element = OxmlElement(f'w:{side_name}') # Set border style style_map = { BorderStyle.NONE: "none", BorderStyle.SOLID: "single", BorderStyle.DASHED: "dashed", BorderStyle.DOTTED: "dotted", BorderStyle.DOUBLE: "double" } border_element.set(qn('w:val'), style_map.get(border_props.style, "single")) # Set border width width_map = { BorderWidth.THIN: "4", BorderWidth.MEDIUM: "8", BorderWidth.THICK: "12" } border_element.set(qn('w:sz'), width_map.get(border_props.width, "4")) # Set border color border_element.set(qn('w:color'), border_props.color.upper()) tcBorders.append(border_element) tcPr.append(tcBorders)

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Rookie0x80/docx-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server