diff --git a/any_parser/any_parser.py b/any_parser/any_parser.py index a97f8f9..ac52c92 100644 --- a/any_parser/any_parser.py +++ b/any_parser/any_parser.py @@ -10,7 +10,13 @@ from any_parser.async_parser import AsyncParser from any_parser.constants import ProcessType -from any_parser.sync_parser import SyncParser +from any_parser.sync_parser import ( + ExtractKeyValueSyncParser, + ExtractPIISyncParser, + ExtractResumeKeyValueSyncParser, + ExtractTablesSyncParser, + ParseSyncParser, +) from any_parser.utils import validate_file_inputs PUBLIC_SHARED_BASE_URL = "https://public-api.cambio-ai.com" @@ -119,8 +125,14 @@ def __init__(self, api_key: str, base_url: str = PUBLIC_SHARED_BASE_URL) -> None api_key: Authentication key for API access base_url: API endpoint URL, defaults to public endpoint """ - self._sync_parser = SyncParser(api_key, base_url) self._async_parser = AsyncParser(api_key, base_url) + self._sync_parse = ParseSyncParser(api_key, base_url) + self._sync_extract_key_value = ExtractKeyValueSyncParser(api_key, base_url) + self._sync_extract_resume_key_value = ExtractResumeKeyValueSyncParser( + api_key, base_url + ) + self._sync_extract_pii = ExtractPIISyncParser(api_key, base_url) + self._sync_extract_tables = ExtractTablesSyncParser(api_key, base_url) @handle_file_processing def parse( @@ -141,23 +153,13 @@ def parse( Returns: tuple: (result, timing_info) or (error_message, "") """ - response, info = self._sync_parser.get_sync_response( - self._sync_parser._sync_parse_url, - file_content=file_content, # type: ignore - file_type=file_type, # type: ignore + return self._sync_parse.parse( + file_path=file_path, + file_content=file_content, + file_type=file_type, extract_args=extract_args, ) - if response is None: - return info, "" - - try: - response_data = response.json() - result = response_data["markdown"] - return result, f"Time Elapsed: {info}" - except json.JSONDecodeError: - return f"Error: Invalid JSON response: {response.text}", "" - @handle_file_processing def extract_pii( self, @@ -168,23 +170,12 @@ def extract_pii( """ Extract PII data from a file synchronously. """ - response, info = self._sync_parser.get_sync_response( - self._sync_parser._sync_extract_pii, - file_content=file_content, # type: ignore - file_type=file_type, # type: ignore - extract_args=None, + return self._sync_extract_pii.extract( + file_path=file_path, + file_content=file_content, + file_type=file_type, ) - if response is None: - return info, "" - - try: - response_data = response.json() - result = response_data["pii_extraction"] - return result, f"Time Elapsed: {info}" - except json.JSONDecodeError: - return f"Error: Invalid JSON response: {response.text}", "" - @handle_file_processing def extract_tables( self, @@ -199,23 +190,12 @@ def extract_tables( Returns: tuple(str, str): The extracted data and the time taken. """ - response, info = self._sync_parser.get_sync_response( - self._sync_parser._sync_extract_tables, - file_content=file_content, # type: ignore - file_type=file_type, # type: ignore - extract_args=None, + return self._sync_extract_tables.extract( + file_path=file_path, + file_content=file_content, + file_type=file_type, ) - if response is None: - return info, "" - - try: - response_data = response.json() - result = response_data["markdown"] - return result, f"Time Elapsed: {info}" - except json.JSONDecodeError: - return f"Error: Invalid JSON response: {response.text}", "" - @handle_file_processing def extract_key_value( self, @@ -233,23 +213,13 @@ def extract_key_value( Returns: tuple(str, str): The extracted data and the time taken. """ - response, info = self._sync_parser.get_sync_response( - self._sync_parser._sync_extract_key_value, - file_content=file_content, # type: ignore - file_type=file_type, # type: ignore + return self._sync_extract_key_value.extract( + file_path=file_path, + file_content=file_content, + file_type=file_type, extract_args={"extract_instruction": extract_instruction}, ) - if response is None: - return info, "" - - try: - response_data = response.json() - result = response_data["json"] - return result, f"Time Elapsed: {info}" - except json.JSONDecodeError: - return f"Error: Invalid JSON response: {response.text}", "" - @handle_file_processing def extract_resume_key_value( self, file_path=None, file_content=None, file_type=None @@ -270,23 +240,12 @@ def extract_resume_key_value( - "pii": Personally Identifiable Information - includes only name, email, and phone """ - response, info = self._sync_parser.get_sync_response( - self._sync_parser._sync_extract_resume_key_value, - file_content=file_content, # type: ignore - file_type=file_type, # type: ignore - extract_args=None, + return self._sync_extract_resume_key_value.extract( + file_path=file_path, + file_content=file_content, + file_type=file_type, ) - if response is None: - return info, "" - - try: - response_data = response.json() - result = response_data["extraction_result"] - return result, f"Time Elapsed: {info}" - except json.JSONDecodeError: - return f"Error: Invalid JSON response: {response.text}", "" - # Example of decorated methods: @handle_file_processing def async_parse( @@ -425,19 +384,4 @@ def async_fetch( timeout=TIMEOUT, ) - if response is None: - return "Error: timeout, no response received" - if response.status_code == 200: - result = response.json() - if "json" in result: - return result["json"] - elif "resume_extraction" in result: - return result["resume_extraction"] - elif "pii_extraction" in result: - return result["pii_extraction"] - elif "markdown" in result: - return result["markdown"] - return f"Error: Invalid response format\n {result}" - if response.status_code == 202: - return "" - return f"Error: {response.status_code} {response.text}" + return self._async_parser.handle_async_response(response) diff --git a/any_parser/async_parser.py b/any_parser/async_parser.py index ad941c3..7ca194c 100644 --- a/any_parser/async_parser.py +++ b/any_parser/async_parser.py @@ -13,8 +13,48 @@ TIMEOUT = 60 +class BasePostProcessor: + def __init__(self, successor=None) -> None: + self.successor = successor + + def process(self, json_response: Dict) -> str: + if self.successor: + return self.successor.process(json_response) + return f"Error: Invalid JSON response: {json_response}" + + +class ParsePostProcessor(BasePostProcessor): + def process(self, json_response: Dict) -> str: + if "markdown" in json_response: + return json_response["markdown"] + return super().process(json_response) + + +class KeyValuePostProcessor(BasePostProcessor): + def process(self, json_response: Dict) -> str: + if "json" in json_response: + return json_response["json"] + return super().process(json_response) + + +class ExtractPIIPostProcessor(BasePostProcessor): + def process(self, json_response: Dict) -> str: + if "pii_extraction" in json_response: + return json_response["pii_extraction"] + return super().process(json_response) + + +class ExtractResumeKeyValuePostProcessor(BasePostProcessor): + + def process(self, json_response: Dict) -> str: + if "resume_extraction" in json_response: + return json_response["resume_extraction"] + return super().process(json_response) + + class AsyncParser(BaseParser): - def _setup_endpoints(self) -> None: + def __init__(self, api_key: str, base_url: str) -> None: + super().__init__(api_key, base_url) self._async_upload_url = f"{self._base_url}/async/upload" self._async_fetch_url = f"{self._base_url}/async/fetch" @@ -58,3 +98,20 @@ def send_async_request( # If response successful, upload the file return upload_file_to_presigned_url(file_content, response) + + def handle_async_response(self, response) -> str: + if response is None: + return "Error: timeout, no response received" + if response.status_code == 202: + return "" + if response.status_code == 200: + extract_resume_processor = ExtractResumeKeyValuePostProcessor() + key_value_processor = KeyValuePostProcessor(extract_resume_processor) + extract_pii_processor = ExtractPIIPostProcessor(key_value_processor) + handler = ParsePostProcessor(extract_pii_processor) + try: + return handler.process(response.json()) + except json.JSONDecodeError: + return f"Error: Invalid JSON response: {response.text}" + + return f"Error: {response.status_code} {response.text}" diff --git a/any_parser/base_parser.py b/any_parser/base_parser.py index 0c33034..963c025 100644 --- a/any_parser/base_parser.py +++ b/any_parser/base_parser.py @@ -9,8 +9,3 @@ def __init__(self, api_key: str, base_url: str) -> None: "Content-Type": "application/json", "x-api-key": self._api_key, } - self._setup_endpoints() - - def _setup_endpoints(self) -> None: - """Setup API endpoints - to be implemented by child classes.""" - raise NotImplementedError diff --git a/any_parser/sync_parser.py b/any_parser/sync_parser.py index 012d8e8..25d918e 100644 --- a/any_parser/sync_parser.py +++ b/any_parser/sync_parser.py @@ -11,16 +11,7 @@ TIMEOUT = 60 -class SyncParser(BaseParser): - def _setup_endpoints(self) -> None: - self._sync_parse_url = f"{self._base_url}/parse" - self._sync_extract_pii = f"{self._base_url}/extract_pii" - self._sync_extract_tables = f"{self._base_url}/extract_tables" - self._sync_extract_key_value = f"{self._base_url}/extract_key_value" - self._sync_extract_resume_key_value = ( - f"{self._base_url}/extract_resume_key_value" - ) - self._sync_parse_with_ocr = f"{self._base_url}/parse_with_ocr" +class BaseSyncParser(BaseParser): def get_sync_response( self, @@ -49,3 +40,163 @@ def get_sync_response( return None, f"Error: {response.status_code} {response.text}" return response, f"{end_time - start_time:.2f} seconds" + + def parse( + self, + file_path=None, + file_content=None, + file_type=None, + extract_args=None, + ): + """Converts the given file to markdown.""" + raise NotImplementedError + + def extract( + self, + file_path=None, + file_content=None, + file_type=None, + extract_args=None, + ): + """Extracts information from the given file.""" + raise NotImplementedError + + +class ParseSyncParser(BaseSyncParser): + """Parse parser implementation.""" + + def parse( + self, + file_path=None, + file_content=None, + file_type=None, + extract_args=None, + ): + response, info = self.get_sync_response( + f"{self._base_url}/parse", + file_content=file_content, # type: ignore + file_type=file_type, # type: ignore + extract_args=extract_args, + ) + + if response is None: + return info, "" + + try: + response_data = response.json() + result = response_data["markdown"] + return result, f"Time Elapsed: {info}" + except json.JSONDecodeError: + return f"Error: Invalid JSON response: {response.text}", "" + + +class ExtractPIISyncParser(BaseSyncParser): + """Extract PII parser implementation.""" + + def extract( + self, + file_path=None, + file_content=None, + file_type=None, + extract_args=None, + ): + response, info = self.get_sync_response( + f"{self._base_url}/extract_pii", + file_content=file_content, # type: ignore + file_type=file_type, # type: ignore + extract_args=None, + ) + + if response is None: + return info, "" + + try: + response_data = response.json() + result = response_data["pii_extraction"] + return result, f"Time Elapsed: {info}" + except json.JSONDecodeError: + return f"Error: Invalid JSON response: {response.text}", "" + + +class ExtractTablesSyncParser(BaseSyncParser): + """Extract tables parser implementation.""" + + def extract( + self, + file_path=None, + file_content=None, + file_type=None, + extract_args=None, + ): + response, info = self.get_sync_response( + f"{self._base_url}/extract_tables", + file_content=file_content, # type: ignore + file_type=file_type, # type: ignore + extract_args=None, + ) + + if response is None: + return info, "" + + try: + response_data = response.json() + result = response_data["markdown"] + return result, f"Time Elapsed: {info}" + except json.JSONDecodeError: + return f"Error: Invalid JSON response: {response.text}", "" + + +class ExtractKeyValueSyncParser(BaseSyncParser): + """Extract key-value parser implementation.""" + + def extract( + self, + file_path=None, + file_content=None, + file_type=None, + extract_args=None, + ): + response, info = self.get_sync_response( + f"{self._base_url}/extract_key_value", + file_content=file_content, # type: ignore + file_type=file_type, # type: ignore + extract_args={"extract_instruction": extract_args}, + ) + + if response is None: + return info, "" + + try: + response_data = response.json() + result = response_data["json"] + return result, f"Time Elapsed: {info}" + except json.JSONDecodeError: + return f"Error: Invalid JSON response: {response.text}", "" + + +class ExtractResumeKeyValueSyncParser(BaseSyncParser): + """Extract resume key-value parser implementation.""" + + def extract( + self, + file_path=None, + file_content=None, + file_type=None, + extract_args=None, + ): + response, info = self.get_sync_response( + f"{self._base_url}/extract_resume_key_value", + file_content=file_content, # type: ignore + file_type=file_type, # type: ignore + extract_args=None, + ) + + if response is None: + return info, "" + + try: + response_data = response.json() + result = response_data["extraction_result"] + return result, f"Time Elapsed: {info}" + except json.JSONDecodeError: + return f"Error: Invalid JSON response: {response.text}", "" diff --git a/tests/outputs/correct_docx_output.txt b/tests/outputs/correct_docx_output.txt index eae702b..22eecd9 100644 --- a/tests/outputs/correct_docx_output.txt +++ b/tests/outputs/correct_docx_output.txt @@ -1,16 +1,18 @@ ## Test document -## Here is an example chart: +Here is an example chart: +<|table_start|> | Investor Metrics | FY23 Q1 | FY23 Q2 | FY23 Q3 | FY23 Q4 | FY24 Q1 | -|------------------|---------|---------|---------|---------|---------| +|---|---|---|---|---|---| | Office Commercial products and cloud services revenue growth (y/y) | 7% / 13% | 7% / 14% | 13% / 17% | 12% / 14% | 15% / 14% | | Office Consumer products and cloud services revenue growth (y/y) | 7% / 11% | (2)% / 3% | 1% / 4% | 3% / 6% | 3% / 4% | | Office 365 Commercial seat growth (y/y) | 14% | 12% | 11% | 11% | 10% | | Microsoft 365 Consumer subscribers (in millions) | 65.1 | 67.7 | 70.8 | 74.9 | 76.7 | | Dynamics products and cloud services revenue growth (y/y) | 15% / 22% | 13% / 20% | 17% / 21% | 19% / 21% | 22% / 21% | | LinkedIn revenue growth (y/y) | 17% / 21% | 10% / 14% | 8% / 11% | 6% / 8% | 8% | +<|table_end|> -Growth rates include non-GAAP CC growth (GAP % / CC %) +Growth rates include non-GAAP CC growth (FY23 Q1) Done. \ No newline at end of file diff --git a/tests/outputs/correct_pdf_output.txt b/tests/outputs/correct_pdf_output.txt index e02f768..c2eb7e2 100644 --- a/tests/outputs/correct_pdf_output.txt +++ b/tests/outputs/correct_pdf_output.txt @@ -1,137 +1,118 @@ -STOXX INDEX METHODOLOGY GUIDE CONTENTS - -3/529 - -## 7. STOXX BENCHMARK INDICES (BMI) - -| 7.1. STOXX GLOBAL INDICES | 52 | -|---------------------------|-----| -| 7.1.1. OVERVIEW | 52 | -| 7.1.2. INDEX REVIEW | 53 | -| 7.1.3. ONGOING MAINTENANCE | 55 | - -| 7.2 STOXX GLOBAL 1800 AND DERIVED INDICES | 56 | -|-------------------------------------------|-----| -| 7.2.1. OVERVIEW | 56 | -| 7.2.2. INDEX REVIEW | 56 | -| 7.2.3. ONGOING MAINTENANCE | 58 | - -| 7.3 SIZE INDICES BASED ON THE STOXX GLOBAL INDICES | 60 | -|---------------------------------------------------|-----| -| 7.3.1. OVERVIEW | 60 | -| 7.3.2. INDEX REVIEW | 60 | -| 7.3.3. ONGOING MAINTENANCE | 62 | - -| 7.4 SECTOR INDICES BASED ON THE STOXX GLOBAL INDICES | 63 | -|-----------------------------------------------------|-----| -| 7.4.1. OVERVIEW | 63 | -| 7.4.2. INDEX REVIEW | 63 | -| 7.4.3. ONGOING MAINTENANCE | 64 | - -| 7.5 STOXX EUROPE 600 AND EURO STOXX SUPERSECTOR INDICES: 30% / 15% CAPS | 65 | -|------------------------------------------------------------------------|-----| -| 7.5.1. OVERVIEW | 65 | -| 7.5.2. INDEX REVIEW | 65 | -| 7.5.3. ONGOING MAINTENANCE | 66 | - -| 7.6 STOXX REGIONAL REAL ESTATE INDICES: 20% CAPS67 | 67 | -|-----------------------------------------------------|-----| -| 7.6.1. OVERVIEW | 67 | -| 7.6.2. INDEX REVIEW | 67 | -| 7.6.3. ONGOING MAINTENANCE | 67 | - -| 7.7 STOXX EMERGING MARKETS 800 LO | 68 | -|-------------------------------------|-----| -| 7.7.1. OVERVIEW | 68 | -| 7.7.2. INDEX REVIEW | 68 | -| 7.7.3. ONGOING MAINTENANCE | 68 | - -| 7.8 STOXX INDUSTRY AND SUPERSECTOR LEGACY INDICES | 70 | -|---------------------------------------------------|-----| -| 7.8.1. OVERVIEW | 70 | -| 7.8.2. INDEX REVIEW | 71 | -| 7.8.3. ONGOING MAINTENANCE | 71 | - -| 7.9 EURO STOXX SUPERSECTOR 5/10/40 INDICES | 72 | -|---------------------------------------------|-----| -| 7.9.1. OVERVIEW | 72 | -| 7.9.2. INDEX REVIEW | 72 | -| 7.9.3. ONGOING MAINTENANCE | 73 | - -| 7.10 STOXX EUROPE 600 INDUSTRY 30-15 INDICES | 74 | -|----------------------------------------------|-----| -| 7.10.1. OVERVIEW | 74 | -| 7.10.2. INDEX REVIEW | 74 | -| 7.10.3. ONGOING MAINTENANCE | 75 | - -| 7.11. STOXX SEMICONDUCTOR 30 INDEX | 76 | -|-------------------------------------|-----| -| 7.11.1. OVERVIEW | 76 | -| 7.11.2. INDEX REVIEW | 76 | -| 7.11.3. ONGOING MAINTENANCE | 77 | - -## 8. STOXX EQUAL WEIGHT INDICES - -| 8.1. STOXX EQUAL WEIGHT INDICES | 78 | -|--------------------------------|-----| -| 8.1.1. OVERVIEW | 78 | -| 8.1.2. INDEX REVIEW | 78 | -| 8.1.3. ONGOING MAINTENANCE | 78 | +<|header_start|>STOXX INDEX METHODOLOGY GUIDE<|header_end|> + +## CONTENTS + +<|table_start|> +| 6.5.1. | OVERVIEW | 49 | +|---|---|---| +| 6.5.2. | INDEX REVIEW | 49 | +| 6.5.3. | ONGOING MAINTENANCE | 51 | +<|table_end|> + +<|table_start|> +| 7. | STOXX BENCHMARK INDICES (BMI) | 52 | +|---|---|---| +| 7.1. | STOXX GLOBAL INDICES | 52 | +| 7.1.1. | OVERVIEW | 52 | +| 7.1.2. | INDEX REVIEW | 53 | +| 7.1.3. | ONGOING MAINTENANCE | 55 | +| 7.2 | STOXX GLOBAL 1800 AND DERIVED INDICES | 56 | +| 7.2.1. | OVERVIEW | 56 | +| 7.2.2. | INDEX REVIEW | 56 | +| 7.2.3. | ONGOING MAINTENANCE | 58 | +| 7.3 | SIZE INDICES BASED ON THE STOXX GLOBAL INDICES | 60 | +| 7.3.1. | OVERVIEW | 60 | +| 7.3.2. | INDEX REVIEW | 60 | +| 7.3.3. | ONGOING MAINTENANCE | 62 | +| 7.4 | SECTOR INDICES BASED ON THE STOXX GLOBAL INDICES | 63 | +| 7.4.1. | OVERVIEW | 63 | +| 7.4.2. | INDEX REVIEW | 63 | +| 7.4.3. | ONGOING MAINTENANCE | 64 | +| 7.5 | STOXX EUROPE 600 AND EURO STOXX SUPERSECTOR INDICES: 30% / 15% CAPS | 65 | +| 7.5.1. | OVERVIEW | 65 | +| 7.5.2. | INDEX REVIEW | 65 | +| 7.5.3. | ONGOING MAINTENANCE | 66 | +| 7.6 | STOXX REGIONAL REAL ESTATE INDICES: 20% CAPS67 | 67 | +| 7.6.1. | OVERVIEW | 67 | +| 7.6.2. | INDEX REVIEW | 67 | +| 7.6.3. | ONGOING MAINTENANCE | 67 | +| 7.7 | STOXX EMERGING MARKETS 800 LO | 68 | +| 7.7.1. | OVERVIEW | 68 | +| 7.7.2. | INDEX REVIEW | 68 | +| 7.7.3. | ONGOING MAINTENANCE | 68 | +| 7.8 | STOXX INDUSTRY AND SUPERSECTOR LEGACY INDICES | 70 | +| 7.8.1. | OVERVIEW | 70 | +| 7.8.2. | INDEX REVIEW | 71 | +| 7.8.3. | ONGOING MAINTENANCE | 71 | +| 7.9 | EURO STOXX SUPERSECTOR 5/10/40 INDICES | 72 | +| 7.9.1. | OVERVIEW | 72 | +| 7.9.2. | INDEX REVIEW | 72 | +| 7.9.3. | ONGOING MAINTENANCE | 73 | +| 7.10 | STOXX EUROPE 600 INDUSTRY 30-15 INDICES | 74 | +| 7.10.1. | OVERVIEW | 74 | +| 7.10.2. | INDEX REVIEW | 74 | +| 7.10.3. | ONGOING MAINTENANCE | 75 | +| 7.11 | STOXX SEMICONDUCTOR 30 INDEX | 76 | +| 7.11.1. | OVERVIEW | 76 | +| 7.11.2. | INDEX REVIEW | 76 | +| 7.11.3. | ONGOING MAINTENANCE | 77 | +<|table_end|> + +<|table_start|> +| 8. | STOXX EQUAL WEIGHT INDICES | 78 | +|---|---|---| +| 8.1. | STOXX EQUAL WEIGHT INDICES | 78 | +| 8.1.1. | OVERVIEW | 78 | +| 8.1.2. | INDEX REVIEW | 78 | +| 8.1.3. | ONGOING MAINTENANCE | 78 | +<|table_end|> ## 9. STOXX BLUE-CHIP INDICES -| 9.1 STOXX GLOBAL AND COUNTRY BLUE-CHIP INDICES | 80 | -|------------------------------------------------|-----| -| 9.1.1. OVERVIEW | 80 | -| 9.1.2. INDEX REVIEW | 81 | -| 9.1.3. ONGOING MAINTENANCE | 84 | - -| 9.2 EURO STOXX 50 | 85 | -|-------------------|-----| -| 9.2.1. OVERVIEW | 85 | -| 9.2.2. INDEX REVIEW | 85 | -| 9.2.3. ONGOING MAINTENANCE | 86 | - -| 9.3 STOXX REGIONAL BLUE-CHIP INDICES | 88 | -|-------------------------------------|-----| -| 9.3.1. OVERVIEW | 88 | -| 9.3.2. INDEX REVIEW | 88 | -| 9.3.3. ONGOING MAINTENANCE | 89 | - -| 9.4 STOXX GLOBAL 150 | 91 | -|----------------------|-----| -| 9.4.1. OVERVIEW | 91 | -| 9.4.2. INDEX REVIEW | 91 | -| 9.4.3. ONGOING MAINTENANCE | 91 | - -| 9.5 STOXX BALKAN 50 EQUAL WEIGHT | 92 | -|-----------------------------------|-----| -| 9.5.1. OVERVIEW | 92 | -| 9.5.2. INDEX REVIEW | 92 | -| 9.5.3. ONGOING MAINTENANCE | 93 | - -| 9.6 STOXX CANADA 60 | 94 | -|---------------------|-----| -| 9.6.1. OVERVIEW | 94 | -| 9.6.2. INDEX REVIEW | 94 | -| 9.6.3. ONGOING MAINTENANCE | 95 | +<|table_start|> +| 9.1 | STOXX GLOBAL AND COUNTRY BLUE-CHIP INDICES | 80 | +|---|---|---| +| 9.1.1. | OVERVIEW | 80 | +| 9.1.2. | INDEX REVIEW | 81 | +| 9.1.3. | ONGOING MAINTENANCE | 84 | +| 9.2 | EURO STOXX 50 | 85 | +| 9.2.1. | OVERVIEW | 85 | +| 9.2.2. | INDEX REVIEW | 85 | +| 9.2.3. | ONGOING MAINTENANCE | 86 | +| 9.3 | STOXX REGIONAL BLUE-CHIP INDICES | 88 | +| 9.3.1. | OVERVIEW | 88 | +| 9.3.2. | INDEX REVIEW | 88 | +| 9.3.3. | ONGOING MAINTENANCE | 89 | +| 9.4 | STOXX GLOBAL 150 | 91 | +| 9.4.1. | OVERVIEW | 91 | +| 9.4.2. | INDEX REVIEW | 91 | +| 9.4.3. | ONGOING MAINTENANCE | 91 | +| 9.5 | STOXX BALKAN 50 EQUAL WEIGHT | 92 | +| 9.5.1. | OVERVIEW | 92 | +| 9.5.2. | INDEX REVIEW | 92 | +| 9.5.3. | ONGOING MAINTENANCE | 93 | +| 9.6 | STOXX CANADA 60 | 94 | +| 9.6.1. | OVERVIEW | 94 | +| 9.6.2. | INDEX REVIEW | 94 | +| 9.6.3. | ONGOING MAINTENANCE | 95 | +<|table_end|> ## 10. STOXX DIVIDEND INDICES -| 10.1 STOXX SELECT DIVIDEND INDICES | 96 | -|-----------------------------------|-----| -| 10.1.1. OVERVIEW | 96 | -| 10.1.2. INDEX REVIEW | 96 | -| 10.1.3. STOXX SELECT DIVIDEND INDICES | 99 | -| 10.1.4. ONGOING MAINTENANCE | 101 | - -| 10.2 STOXX ASEAN-FIVE SELECT DIVIDEND 50 | 104 | -|---------------------------------------------|-----| -| 10.2.1. OVERVIEW | 104 | -| 10.2.2. INDEX REVIEW | 104 | -| 10.2.3. ONGOING MAINTENANCE | 105 | - -| 10.3 STOXX ASEAN SELECT DIVIDEND 30 | 106 | -|-------------------------------------|-----| - -STOXX logo with text "Part of DEUTSCHE BÖRSE GROUP" \ No newline at end of file +<|table_start|> +| 10.1 | STOXX SELECT DIVIDEND INDICES | 96 | +|---|---|---| +| 10.1.1. | OVERVIEW | 96 | +| 10.1.2. | INDEX REVIEW | 96 | +| 10.1.3. | STOXX SELECT DIVIDEND INDICES | 99 | +| 10.1.4. | ONGOING MAINTENANCE | 101 | +| 10.2 | STOXX ASEAN-FIVE SELECT DIVIDEND 50 | 104 | +| 10.2.1. | OVERVIEW | 104 | +| 10.2.2. | INDEX REVIEW | 104 | +| 10.2.3. | ONGOING MAINTENANCE | 105 | +| 10.3 | STOXX ASEAN SELECT DIVIDEND 30 | 106 | +<|table_end|> + +<|footer_start|> +<|figure_start|>STOXX logo<|figure_end|> +<|figure_start|>DEUTSCHE BÖRSE GROUP logo<|figure_end|> diff --git a/tests/outputs/correct_png_output.txt b/tests/outputs/correct_png_output.txt index 43be0fb..c86bd8e 100644 --- a/tests/outputs/correct_png_output.txt +++ b/tests/outputs/correct_png_output.txt @@ -1,3 +1,4 @@ +<|table_start|> | Investor Metrics | FY23 Q1 | FY23 Q2 | FY23 Q3 | FY23 Q4 | FY24 Q1 | |---|---|---|---|---|---| | Office Commercial products and cloud services revenue growth (y/y) | 7% / 13% | 7% / 14% | 13% / 17% | 12% / 14% | 15% / 14% | @@ -6,5 +7,4 @@ | Microsoft 365 Consumer subscribers (in millions) | 65.1 | 67.7 | 70.8 | 74.9 | 76.7 | | Dynamics products and cloud services revenue growth (y/y) | 15% / 22% | 13% / 20% | 17% / 21% | 19% / 21% | 22% / 21% | | LinkedIn revenue growth (y/y) | 17% / 21% | 10% / 14% | 8% / 11% | 6% / 8% | 8% | - -Growth rates include non-GAAP CC growth (GAAP % / CC %). \ No newline at end of file +<|table_end|> \ No newline at end of file diff --git a/tests/outputs/correct_pptx_output.txt b/tests/outputs/correct_pptx_output.txt index 8dd5c22..53f5742 100644 --- a/tests/outputs/correct_pptx_output.txt +++ b/tests/outputs/correct_pptx_output.txt @@ -3,14 +3,16 @@ • Chart 1 example +<|table_start|> | Investor Metrics | FY23 Q1 | FY23 Q2 | FY23 Q3 | FY23 Q4 | FY24 Q1 | -|-----------------|---------|---------|---------|---------|---------| +|---|---|---|---|---|---| | Office Commercial products and cloud services revenue growth (y/y) | 7% / 13% | 7% / 14% | 13% / 17% | 12% / 14% | 15% / 14% | | Office Consumer products and cloud services revenue growth (y/y) | 7% / 11% | (2)% / 3% | 1% / 4% | 3% / 6% | 3% / 4% | | Office 365 Commercial seat growth (y/y) | 14% | 12% | 11% | 11% | 10% | | Microsoft 365 Consumer subscribers (in millions) | 65.1 | 67.7 | 70.8 | 74.9 | 76.7 | | Dynamics products and cloud services revenue growth (y/y) | 15% / 22% | 13% / 20% | 17% / 21% | 19% / 21% | 22% / 21% | | LinkedIn revenue growth (y/y) | 17% / 21% | 10% / 14% | 8% / 11% | 6% / 8% | 8% | +<|table_end|> Growth rates include non-GAAP CC growth (GAAP % / CC %). -## Thanks \ No newline at end of file +Thanks \ No newline at end of file