from __future__ import annotations from typing import Any, Dict, List, Optional from export.models import ( IDExportCharacterStyle, IDExportDocument, IDExportImageFrame, IDExportPage, IDExportPageNumber, IDExportParagraph, IDExportParagraphStyle, IDExportRun, IDExportShape, IDExportTable, IDExportTableCell, IDExportTableRow, IDExportTextFrame, ) class IDExportDocumentFactory: """Builds IDExportDocument from a validated idconvert_export.json dict.""" @staticmethod def from_dict(data: dict) -> IDExportDocument: doc_info = data.get('document', {}) styles_data = data.get('styles', {}) paragraph_styles = [ IDExportParagraphStyleFactory.from_dict(s) for s in styles_data.get('paragraph', []) ] character_styles = [ IDExportCharacterStyleFactory.from_dict(s) for s in styles_data.get('character', []) ] pages = [ IDExportPageFactory.from_dict(p) for p in data.get('pages', []) ] # Find first page_number item across all pages for footer use page_number_style: Optional[IDExportPageNumber] = None for page in pages: for item in page.items: if isinstance(item, IDExportPageNumber): page_number_style = item break if page_number_style: break return IDExportDocument( name=doc_info.get('name', 'Document'), page_width_pt=float(doc_info.get('page_width_pt', 595.28)), page_height_pt=float(doc_info.get('page_height_pt', 841.89)), page_count=int(doc_info.get('page_count', len(pages))), facing_pages=bool(doc_info.get('facing_pages', False)), paragraph_styles=paragraph_styles, character_styles=character_styles, colors=data.get('colors', {}), fonts_used=data.get('fonts_used', []), pages=pages, page_number_style=page_number_style, ) class IDExportParagraphStyleFactory: @staticmethod def from_dict(data: dict) -> IDExportParagraphStyle: return IDExportParagraphStyle( name=data.get('name', ''), font=data.get('font'), size_pt=_optional_float(data.get('size_pt')), leading_pt=_optional_float(data.get('leading_pt')), space_before_pt=float(data.get('space_before_pt', 0)), space_after_pt=float(data.get('space_after_pt', 0)), alignment=data.get('alignment', 'left'), color_hex=data.get('color_hex'), bold=bool(data.get('bold', False)), italic=bool(data.get('italic', False)), ) class IDExportCharacterStyleFactory: @staticmethod def from_dict(data: dict) -> IDExportCharacterStyle: return IDExportCharacterStyle( name=data.get('name', ''), font=data.get('font'), bold=bool(data.get('bold', False)), italic=bool(data.get('italic', False)), color_hex=data.get('color_hex'), ) class IDExportPageFactory: @staticmethod def from_dict(data: dict) -> IDExportPage: items = [ IDExportItemFactory.from_dict(item) for item in data.get('items', []) if item ] items = [i for i in items if i is not None] return IDExportPage( page_number=data.get('page_number', 1), width_pt=float(data.get('width_pt', 595.28)), height_pt=float(data.get('height_pt', 841.89)), items=items, ) class IDExportItemFactory: @staticmethod def from_dict(data: dict): item_type = data.get('type', '') if item_type == 'text_frame': return IDExportTextFrameFactory.from_dict(data) if item_type == 'image_frame': return IDExportImageFrameFactory.from_dict(data) if item_type == 'table': return IDExportTableFactory.from_dict(data) if item_type == 'page_number': return IDExportPageNumberFactory.from_dict(data) if item_type == 'shape': return IDExportShapeFactory.from_dict(data) # frame_group, column_break, and unknown types not converted return None class IDExportShapeFactory: @staticmethod def from_dict(data: dict) -> IDExportShape: return IDExportShape( id=str(data.get('id', '')), x_pt=float(data.get('x_pt', 0)), y_pt=float(data.get('y_pt', 0)), width_pt=float(data.get('width_pt', 0)), height_pt=float(data.get('height_pt', 0)), shape_type=data.get('shape_type', 'rect'), fill_hex=data.get('fill_hex'), stroke_hex=data.get('stroke_hex'), stroke_weight_pt=float(data.get('stroke_weight_pt', 0)), ) class IDExportTextFrameFactory: @staticmethod def from_dict(data: dict) -> IDExportTextFrame: paragraphs = [ IDExportParagraphFactory.from_dict(p) for p in data.get('paragraphs', []) ] return IDExportTextFrame( id=str(data.get('id', '')), thread_id=str(data.get('thread_id', '')), thread_position=int(data.get('thread_position', 1)), x_pt=float(data.get('x_pt', 0)), y_pt=float(data.get('y_pt', 0)), width_pt=float(data.get('width_pt', 0)), height_pt=float(data.get('height_pt', 0)), column_count=int(data.get('column_count', 1)), paragraphs=paragraphs, ) class IDExportParagraphFactory: @staticmethod def from_dict(data: dict) -> IDExportParagraph: runs = [ IDExportRunFactory.from_dict(r) for r in data.get('runs', []) ] return IDExportParagraph( style=data.get('style', ''), text=data.get('text', ''), runs=runs, ) class IDExportRunFactory: @staticmethod def from_dict(data: dict) -> IDExportRun: return IDExportRun( text=data.get('text', ''), style=data.get('style'), bold=bool(data.get('bold', False)), italic=bool(data.get('italic', False)), color_hex=data.get('color_hex'), font=data.get('font'), size_pt=_optional_float(data.get('size_pt')), ) class IDExportImageFrameFactory: @staticmethod def from_dict(data: dict) -> IDExportImageFrame: return IDExportImageFrame( id=str(data.get('id', '')), x_pt=float(data.get('x_pt', 0)), y_pt=float(data.get('y_pt', 0)), width_pt=float(data.get('width_pt', 0)), height_pt=float(data.get('height_pt', 0)), image_name=data.get('image_name', 'image'), image_data_b64=data.get('image_data_b64'), fit_mode=data.get('fit_mode', 'fill_proportionally'), ) class IDExportTableFactory: @staticmethod def from_dict(data: dict) -> IDExportTable: rows = [ IDExportTableRowFactory.from_dict(r) for r in data.get('rows', []) ] return IDExportTable( id=str(data.get('id', '')), x_pt=float(data.get('x_pt', 0)), y_pt=float(data.get('y_pt', 0)), width_pt=float(data.get('width_pt', 0)), column_widths_pt=[float(w) for w in data.get('column_widths_pt', [])], rows=rows, ) class IDExportTableRowFactory: @staticmethod def from_dict(data: dict) -> IDExportTableRow: cells = [ IDExportTableCellFactory.from_dict(c) for c in data.get('cells', []) ] return IDExportTableRow( is_header=bool(data.get('is_header', False)), cells=cells, ) class IDExportTableCellFactory: @staticmethod def from_dict(data: dict) -> IDExportTableCell: return IDExportTableCell( text=data.get('text', ''), style=data.get('style'), col_span=int(data.get('col_span', 1)), row_span=int(data.get('row_span', 1)), background_hex=data.get('background_hex'), text_color_hex=data.get('text_color_hex'), ) class IDExportPageNumberFactory: @staticmethod def from_dict(data: dict) -> IDExportPageNumber: return IDExportPageNumber( id=str(data.get('id', '')), x_pt=float(data.get('x_pt', 0)), y_pt=float(data.get('y_pt', 0)), font=data.get('font'), size_pt=float(data.get('size_pt', 9)), color_hex=data.get('color_hex', '000000') or '000000', alignment=data.get('alignment', 'center'), ) def _optional_float(value) -> Optional[float]: if value is None: return None try: return float(value) except (TypeError, ValueError): return None