Spaces:
Runtime error
Runtime error
| # Word Generator Module | |
| # Contains create_word_document() and all Word-related functions | |
| import re | |
| import os | |
| import tempfile | |
| import time | |
| import io | |
| import sys | |
| from typing import List, Dict, Any | |
| # Add the parent directory to sys.path to import from models.py | |
| sys.path.append(os.path.dirname(os.path.dirname(__file__))) | |
| from models import ExportRequest | |
| # Import from the same directory (Document Generation) | |
| from document_helpers import ( | |
| filter_empty_sections, | |
| extract_business_name_from_content | |
| ) | |
| # from table_of_content import generate_table_of_contents # Not used in word generator | |
| from chart_generator import parse_embedded_chart_data, create_chart_from_data | |
| from markdown_renderer import ( | |
| render_markdown_to_docx_grouped, | |
| process_content_with_inline_charts, | |
| clean_text_for_pdf | |
| ) | |
| # Add Currency Mapping path | |
| sys.path.append(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'CurrencyMapping')) | |
| from CurrencyMapping.currency_mapping import convert_currency_symbols_to_iso | |
| # Import sort_sections_by_order from utils module | |
| from utils import sort_sections_by_order | |
| def create_word_document(request: ExportRequest): | |
| """Create a Word document from the business plan data with front page, TOC, and proper page breaks""" | |
| try: | |
| from docx import Document | |
| from docx.shared import Inches, Pt, RGBColor | |
| from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_TAB_ALIGNMENT | |
| from docx.oxml import OxmlElement | |
| from docx.oxml.ns import qn | |
| except ImportError: | |
| raise Exception("python-docx is not installed. Please install it with: pip install python-docx") | |
| # Filter out empty sections to prevent blank pages | |
| try: | |
| filtered_sections = filter_empty_sections(request.sections) | |
| except Exception as e: | |
| raise Exception(f"Failed to filter sections: {str(e)}") | |
| if not filtered_sections: | |
| raise Exception("No valid sections found after filtering. All sections appear to be empty.") | |
| doc = Document() | |
| # Add confidentiality footer to every page | |
| section = doc.sections[0] | |
| footer = section.footer | |
| footer_para = footer.paragraphs[0] | |
| footer_para.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| footer_run = footer_para.add_run("CONFIDENTIAL - This document contains proprietary information and may not be shared without consent") | |
| footer_run.font.size = Pt(8) | |
| footer_run.font.color.rgb = RGBColor(100, 100, 100) # Gray text | |
| # ===== FRONT PAGE ===== | |
| # Add company logo at the top | |
| try: | |
| # Use the MBD logo PNG file - now in app folder for HF deployment | |
| app_root = os.path.dirname(os.path.dirname(__file__)) | |
| logo_path = os.path.join(app_root, 'assets', 'logos', 'MBD logo.png') | |
| if os.path.exists(logo_path): | |
| # Add logo to Word document (centered, top of page) | |
| logo_para = doc.add_paragraph() | |
| logo_para.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| logo_run = logo_para.add_run() | |
| logo_run.add_picture(logo_path, width=Inches(3.0)) # Even larger size to show full logo | |
| print(f"Logo added successfully from: {logo_path}") | |
| else: | |
| print(f"Logo file not found at: {logo_path}") | |
| except Exception as e: | |
| # Log any errors with logo | |
| print(f"Logo error: {e}") | |
| pass | |
| # Add top spacing | |
| doc.add_paragraph() | |
| doc.add_paragraph() | |
| # Extract business name from content, fallback to businessIdea | |
| extracted_business_name = extract_business_name_from_content(filtered_sections) | |
| business_name = extracted_business_name or request.businessIdea or "Business Plan" | |
| # Business name as main title with enhanced styling - PURPLE THEME | |
| title = doc.add_heading(business_name, 0) | |
| title.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| # Style the title with larger font and PURPLE color | |
| for run in title.runs: | |
| run.font.size = Pt(36) | |
| run.font.color.rgb = RGBColor(139, 92, 246) # PURPLE | |
| # Removed decorative line under title | |
| # Add subtitle with enhanced styling - PURPLE | |
| doc.add_paragraph() | |
| subtitle = doc.add_paragraph("Professional Business Plan Document") | |
| subtitle.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| subtitle_run = subtitle.runs[0] | |
| subtitle_run.font.size = Pt(18) | |
| subtitle_run.font.color.rgb = RGBColor(124, 58, 237) # DARKER PURPLE | |
| subtitle_run.italic = True | |
| # Add more spacing | |
| doc.add_paragraph() | |
| doc.add_paragraph() | |
| doc.add_paragraph() | |
| # Add document info in a styled box - PURPLE BORDER | |
| info_para = doc.add_paragraph() | |
| info_para.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| # Add background color and border styling | |
| info_run = info_para.add_run("Prepared by: Business Plan Generator") | |
| info_run.font.size = Pt(14) | |
| info_run.font.color.rgb = RGBColor(139, 92, 246) # PURPLE | |
| doc.add_paragraph() | |
| date_para = doc.add_paragraph() | |
| date_para.paragraph_format.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| date_run = date_para.add_run(f"Date: {time.strftime('%B %Y')}") | |
| date_run.font.size = Pt(14) | |
| date_run.font.color.rgb = RGBColor(139, 92, 246) # PURPLE | |
| # Confidentiality statement removed - now in footer of every page | |
| # Add more spacing before footer | |
| doc.add_paragraph() | |
| doc.add_paragraph() | |
| doc.add_paragraph() | |
| doc.add_paragraph() | |
| # Add footer with enhanced styling - PURPLE | |
| footer = doc.add_paragraph("Confidential Business Information") | |
| footer.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| footer_run = footer.runs[0] | |
| footer_run.font.size = Pt(12) | |
| footer_run.font.color.rgb = RGBColor(139, 92, 246) # PURPLE | |
| footer_run.bold = True | |
| # Removed decorative line above footer | |
| # ===== TABLE OF CONTENTS ===== | |
| # Add page break before TOC | |
| doc.add_page_break() | |
| # Add TOC title | |
| toc_title = doc.add_heading("Table of Contents", level=1) | |
| toc_title.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| # Removed decorative line under TOC title | |
| # Add spacing before TOC entries | |
| doc.add_paragraph() | |
| # Sort sections according to predefined order (do this before TOC generation) | |
| try: | |
| sorted_sections = sort_sections_by_order(filtered_sections) | |
| except Exception as e: | |
| # Fallback: use filtered order if sorting fails | |
| sorted_sections = filtered_sections | |
| # Generate TOC entries with actual page numbers | |
| # Since each section starts on a new page, we can calculate page numbers | |
| toc_items = [] | |
| current_page = 3 # Start after cover (1) and TOC (2) | |
| for section in sorted_sections: | |
| if section.content and section.content.strip(): | |
| # Add main section | |
| toc_items.append({ | |
| "title": section.title, | |
| "level": 1, | |
| "page": current_page | |
| }) | |
| # Skip subheadings and chart titles - only include main section headings | |
| current_page += 1 # Each section starts on a new page | |
| # Add the actual TOC entries | |
| for item in toc_items: | |
| # Create TOC entry paragraph | |
| toc_para = doc.add_paragraph() | |
| toc_para.paragraph_format.space_after = Pt(6) | |
| # Add section title | |
| title_run = toc_para.add_run(item["title"]) | |
| title_run.font.size = Pt(11) | |
| # Add dots and page number | |
| if item["level"] == 1: | |
| # Main section - bold | |
| title_run.bold = True | |
| # Add dots | |
| dots_run = toc_para.add_run(" " + "." * (60 - len(item["title"])) + " ") | |
| dots_run.font.size = Pt(11) | |
| dots_run.font.color.rgb = RGBColor(128, 128, 128) # Gray | |
| # Add page number | |
| page_run = toc_para.add_run(str(item["page"])) | |
| page_run.font.size = Pt(11) | |
| page_run.bold = True | |
| else: | |
| # Subsection - indented | |
| toc_para.paragraph_format.left_indent = Inches(0.3) | |
| # Add dots | |
| dots_run = toc_para.add_run(" " + "." * (50 - len(item["title"])) + " ") | |
| dots_run.font.size = Pt(11) | |
| dots_run.font.color.rgb = RGBColor(128, 128, 128) # Gray | |
| # Add page number | |
| page_run = toc_para.add_run(str(item["page"])) | |
| page_run.font.size = Pt(11) | |
| # logging.info(f"Generated {len(toc_items)} TOC items with actual page numbers") | |
| # Executive Summary is now handled as a regular section above | |
| # ===== MAIN SECTIONS ===== | |
| for i, section in enumerate(sorted_sections): | |
| if section.content.strip(): | |
| # Add page break before each section (including Executive Summary) | |
| doc.add_page_break() | |
| # Don't add section heading here - it's already in the TOC | |
| # The section title is already displayed in the Table of Contents | |
| # Process content with inline charts - use ORIGINAL content to preserve chart markers | |
| process_content_with_inline_charts(doc, section.content, request.businessIdea or "Business") | |
| doc.add_paragraph() | |
| # Charts are now handled inline with the sections above | |
| # Save to bytes | |
| buffer = io.BytesIO() | |
| doc.save(buffer) | |
| buffer.seek(0) | |
| doc_bytes = buffer.getvalue() | |
| buffer.close() | |
| filename = f"business_plan_{request.businessIdea or 'export'}.docx" | |
| return doc_bytes, filename |