Download google-workspace/upstream/source/gdocs/docs_tables.py from smlflg/Connector: direct link, hf CLI and curl.
- Browser
- Download file 13.7 kB
-
https://huggingface.co/smlflg/Connector/resolve/main/google-workspace/upstream/source/gdocs/docs_tables.py
- Command line
-
hf download hf://smlflg/Connector/google-workspace/upstream/source/gdocs/docs_tables.py
-
curl -L -o docs_tables.py https://huggingface.co/smlflg/Connector/resolve/main/google-workspace/upstream/source/gdocs/docs_tables.py
13.7 kB
| """ | |
| Google Docs Table Operations | |
| This module provides utilities for creating and manipulating tables | |
| in Google Docs, including population with data and formatting. | |
| """ | |
| import logging | |
| from typing import Dict, Any, List, Optional, Union, Tuple | |
| from gdocs.docs_helpers import create_update_table_cell_style_request | |
| logger = logging.getLogger(__name__) | |
| def build_table_population_requests( | |
| table_info: Dict[str, Any], data: List[List[str]], bold_headers: bool = True | |
| ) -> List[Dict[str, Any]]: | |
| """ | |
| Build batch requests to populate a table with data. | |
| Args: | |
| table_info: Table information from document structure including cell indices | |
| data: 2D array of data to insert into table | |
| bold_headers: Whether to make the first row bold | |
| Returns: | |
| List of request dictionaries for batch update | |
| """ | |
| requests = [] | |
| cells = table_info.get("cells", []) | |
| if not cells: | |
| logger.warning("No cell information found in table_info") | |
| return requests | |
| # Process each cell - ONLY INSERT, DON'T DELETE | |
| for row_idx, row_data in enumerate(data): | |
| if row_idx >= len(cells): | |
| logger.warning( | |
| f"Data has more rows ({len(data)}) than table ({len(cells)})" | |
| ) | |
| break | |
| for col_idx, cell_text in enumerate(row_data): | |
| if col_idx >= len(cells[row_idx]): | |
| logger.warning( | |
| f"Data has more columns ({len(row_data)}) than table row {row_idx} ({len(cells[row_idx])})" | |
| ) | |
| break | |
| cell = cells[row_idx][col_idx] | |
| # For new empty tables, use the insertion index | |
| # For tables with existing content, check if cell only contains newline | |
| existing_content = cell.get("content", "").strip() | |
| # Only insert if we have text to insert | |
| if cell_text: | |
| # Use the specific insertion index for this cell | |
| insertion_index = cell.get("insertion_index", cell["start_index"] + 1) | |
| # If cell only contains a newline, replace it | |
| if existing_content == "" or existing_content == "\n": | |
| # Cell is empty (just newline), insert at the insertion index | |
| requests.append( | |
| { | |
| "insertText": { | |
| "location": {"index": insertion_index}, | |
| "text": cell_text, | |
| } | |
| } | |
| ) | |
| # Apply bold formatting to first row if requested | |
| if bold_headers and row_idx == 0: | |
| requests.append( | |
| { | |
| "updateTextStyle": { | |
| "range": { | |
| "startIndex": insertion_index, | |
| "endIndex": insertion_index + len(cell_text), | |
| }, | |
| "textStyle": {"bold": True}, | |
| "fields": "bold", | |
| } | |
| } | |
| ) | |
| else: | |
| # Cell has content, append after existing content | |
| # Find the end of existing content | |
| cell_end = cell["end_index"] - 1 # Don't include cell end marker | |
| requests.append( | |
| { | |
| "insertText": { | |
| "location": {"index": cell_end}, | |
| "text": cell_text, | |
| } | |
| } | |
| ) | |
| # Apply bold formatting to first row if requested | |
| if bold_headers and row_idx == 0: | |
| requests.append( | |
| { | |
| "updateTextStyle": { | |
| "range": { | |
| "startIndex": cell_end, | |
| "endIndex": cell_end + len(cell_text), | |
| }, | |
| "textStyle": {"bold": True}, | |
| "fields": "bold", | |
| } | |
| } | |
| ) | |
| return requests | |
| def calculate_cell_positions( | |
| table_start_index: int, | |
| rows: int, | |
| cols: int, | |
| existing_table_data: Optional[Dict[str, Any]] = None, | |
| ) -> List[List[Dict[str, int]]]: | |
| """ | |
| Calculate estimated positions for each cell in a table. | |
| Args: | |
| table_start_index: Starting index of the table | |
| rows: Number of rows | |
| cols: Number of columns | |
| existing_table_data: Optional existing table data with actual positions | |
| Returns: | |
| 2D list of cell position dictionaries | |
| """ | |
| if existing_table_data and "cells" in existing_table_data: | |
| # Use actual positions from existing table | |
| return existing_table_data["cells"] | |
| # Estimate positions for a new table | |
| # Note: These are estimates; actual positions depend on content | |
| cells = [] | |
| current_index = table_start_index + 2 # Account for table start | |
| for row_idx in range(rows): | |
| row_cells = [] | |
| for col_idx in range(cols): | |
| # Each cell typically starts with a paragraph marker | |
| cell_start = current_index | |
| cell_end = current_index + 2 # Minimum cell size | |
| row_cells.append( | |
| { | |
| "row": row_idx, | |
| "column": col_idx, | |
| "start_index": cell_start, | |
| "end_index": cell_end, | |
| } | |
| ) | |
| current_index = cell_end + 1 | |
| cells.append(row_cells) | |
| return cells | |
| def format_table_data( | |
| raw_data: Union[List[List[str]], List[str], str], | |
| ) -> List[List[str]]: | |
| """ | |
| Normalize various data formats into a 2D array for table insertion. | |
| Args: | |
| raw_data: Data in various formats (2D list, 1D list, or delimited string) | |
| Returns: | |
| Normalized 2D list of strings | |
| """ | |
| if isinstance(raw_data, str): | |
| # Parse delimited string (detect delimiter) | |
| lines = raw_data.strip().split("\n") | |
| if "\t" in raw_data: | |
| # Tab-delimited | |
| return [line.split("\t") for line in lines] | |
| elif "," in raw_data: | |
| # Comma-delimited (simple CSV) | |
| return [line.split(",") for line in lines] | |
| else: | |
| # Space-delimited or single column | |
| return [[cell.strip() for cell in line.split()] for line in lines] | |
| elif isinstance(raw_data, list): | |
| if not raw_data: | |
| return [[]] | |
| # Check if it's already a 2D list | |
| if isinstance(raw_data[0], list): | |
| # Ensure all cells are strings | |
| return [[str(cell) for cell in row] for row in raw_data] | |
| else: | |
| # Convert 1D list to single-column table | |
| return [[str(cell)] for cell in raw_data] | |
| else: | |
| # Convert single value to 1x1 table | |
| return [[str(raw_data)]] | |
| def create_table_with_data( | |
| index: int, | |
| data: List[List[str]], | |
| headers: Optional[List[str]] = None, | |
| bold_headers: bool = True, | |
| ) -> List[Dict[str, Any]]: | |
| """ | |
| Create a table and populate it with data in one operation. | |
| Args: | |
| index: Position to insert the table | |
| data: 2D array of table data | |
| headers: Optional header row (will be prepended to data) | |
| bold_headers: Whether to make headers bold | |
| Returns: | |
| List of request dictionaries for batch update | |
| """ | |
| requests = [] | |
| # Prepare data with headers if provided | |
| if headers: | |
| full_data = [headers] + data | |
| else: | |
| full_data = data | |
| # Normalize the data | |
| full_data = format_table_data(full_data) | |
| if not full_data or not full_data[0]: | |
| raise ValueError("Cannot create table with empty data") | |
| rows = len(full_data) | |
| cols = len(full_data[0]) | |
| # Ensure all rows have the same number of columns | |
| for row in full_data: | |
| while len(row) < cols: | |
| row.append("") | |
| # Create the table | |
| requests.append( | |
| {"insertTable": {"location": {"index": index}, "rows": rows, "columns": cols}} | |
| ) | |
| # Build text insertion requests for each cell | |
| # Note: In practice, we'd need to get the actual document structure | |
| # after table creation to get accurate indices | |
| return requests | |
| def build_table_style_requests( | |
| table_start_index: int, style_options: Dict[str, Any] | |
| ) -> List[Dict[str, Any]]: | |
| """ | |
| Build requests to style a table. | |
| Args: | |
| table_start_index: Starting index of the table | |
| style_options: Dictionary of style options | |
| - border_width: Width of borders in points | |
| - border_color: RGB color for borders | |
| - background_color: RGB color for cell backgrounds | |
| - header_background: RGB color for header row background | |
| Returns: | |
| List of request dictionaries for styling | |
| """ | |
| requests = [] | |
| style_request = create_update_table_cell_style_request( | |
| table_start_index=table_start_index, | |
| background_color=style_options.get("background_color"), | |
| border_color=style_options.get("border_color"), | |
| border_width=style_options.get("border_width"), | |
| ) | |
| if style_request: | |
| requests.append(style_request) | |
| # Header row specific styling | |
| if "header_background" in style_options: | |
| header_request = create_update_table_cell_style_request( | |
| table_start_index=table_start_index, | |
| background_color=style_options["header_background"], | |
| row_index=0, | |
| column_index=0, | |
| row_span=1, | |
| column_span=100, # Large number to cover all columns | |
| ) | |
| if header_request: | |
| requests.append(header_request) | |
| return requests | |
| def extract_table_as_data(table_info: Dict[str, Any]) -> List[List[str]]: | |
| """ | |
| Extract table content as a 2D array of strings. | |
| Args: | |
| table_info: Table information from document structure | |
| Returns: | |
| 2D list of cell contents | |
| """ | |
| data = [] | |
| cells = table_info.get("cells", []) | |
| for row in cells: | |
| row_data = [] | |
| for cell in row: | |
| row_data.append(cell.get("content", "").strip()) | |
| data.append(row_data) | |
| return data | |
| def find_table_by_content( | |
| tables: List[Dict[str, Any]], search_text: str, case_sensitive: bool = False | |
| ) -> Optional[int]: | |
| """ | |
| Find a table index by searching for content within it. | |
| Args: | |
| tables: List of table information from document | |
| search_text: Text to search for in table cells | |
| case_sensitive: Whether to do case-sensitive search | |
| Returns: | |
| Index of the first matching table, or None | |
| """ | |
| search_text = search_text if case_sensitive else search_text.lower() | |
| for idx, table in enumerate(tables): | |
| for row in table.get("cells", []): | |
| for cell in row: | |
| cell_content = cell.get("content", "") | |
| if not case_sensitive: | |
| cell_content = cell_content.lower() | |
| if search_text in cell_content: | |
| return idx | |
| return None | |
| def validate_table_data(data: List[List[str]]) -> Tuple[bool, str]: | |
| """ | |
| Validates table data format and provides specific error messages for LLMs. | |
| WHAT THIS CHECKS: | |
| - Data is a 2D list (list of lists) | |
| - All rows have consistent column counts | |
| - Dimensions are within Google Docs limits | |
| - No None or undefined values | |
| VALID FORMAT EXAMPLE: | |
| [ | |
| ["Header1", "Header2"], # Row 0 - 2 columns | |
| ["Data1", "Data2"], # Row 1 - 2 columns | |
| ["Data3", "Data4"] # Row 2 - 2 columns | |
| ] | |
| INVALID FORMATS: | |
| - [["col1"], ["col1", "col2"]] # Inconsistent column counts | |
| - ["col1", "col2"] # Not 2D (missing inner lists) | |
| - [["col1", None]] # Contains None values | |
| - [] or [[]] # Empty data | |
| Args: | |
| data: 2D array of data to validate | |
| Returns: | |
| Tuple of (is_valid, error_message_with_examples) | |
| """ | |
| if not data: | |
| return ( | |
| False, | |
| "Data is empty. Use format: [['col1', 'col2'], ['row1col1', 'row1col2']]", | |
| ) | |
| if not isinstance(data, list): | |
| return ( | |
| False, | |
| f"Data must be a list, got {type(data).__name__}. Use format: [['col1', 'col2'], ['row1col1', 'row1col2']]", | |
| ) | |
| if not all(isinstance(row, list) for row in data): | |
| return ( | |
| False, | |
| f"Data must be a 2D list (list of lists). Each row must be a list. Check your format: {data}", | |
| ) | |
| # Check for consistent column count | |
| col_counts = [len(row) for row in data] | |
| if len(set(col_counts)) > 1: | |
| return ( | |
| False, | |
| f"All rows must have same number of columns. Found: {col_counts}. Fix your data format.", | |
| ) | |
| # Check for reasonable size | |
| rows = len(data) | |
| cols = col_counts[0] if col_counts else 0 | |
| if rows > 1000: | |
| return False, f"Too many rows ({rows}). Google Docs limit is 1000 rows." | |
| if cols > 20: | |
| return False, f"Too many columns ({cols}). Google Docs limit is 20 columns." | |
| return True, f"Valid table data: {rows}x{cols} table format" | |