From 3f6f2e998c40aee5fdd5dc58b3a7a5b95af2bd65 Mon Sep 17 00:00:00 2001 From: UncleCode Date: Fri, 6 Jun 2025 17:16:53 +0800 Subject: [PATCH] feat(script): add new scripting capabilities and documentation This commit introduces a comprehensive set of new scripts and examples to enhance the scripting capabilities of the crawl4ai project. The changes include the addition of several Python scripts for compiling and executing scripts, as well as a variety of example scripts demonstrating different functionalities such as login flows, data extraction, and multi-step workflows. Additionally, detailed documentation has been created to guide users on how to utilize these new features effectively. The following significant modifications were made: - Added core scripting files: , , and . - Created a new documentation file to provide an overview of the new features. - Introduced multiple example scripts in the directory to showcase various use cases. - Updated and to integrate the new functionalities. - Added font assets for improved documentation presentation. These changes significantly expand the functionality of the crawl4ai project, allowing users to create more complex and varied scripts with ease. --- crawl4ai/__init__.py | 17 + crawl4ai/async_configs.py | 55 ++ crawl4ai/script/__init__.py | 35 + crawl4ai/script/c4a_compile.py | 328 +++++++++ crawl4ai/script/c4a_result.py | 219 ++++++ crawl4ai/script/c4ai_script.py | 623 ++++++++++++++++++ docs/examples/c4a_script/C4A_SCRIPT_DOCS.md | 312 +++++++++ .../examples/c4a_script/api_usage_examples.py | 217 ++++++ .../c4a_script/c4a_script_hello_world.py | 53 ++ .../c4a_script_hello_world_error.py | 53 ++ docs/examples/c4a_script/demo_c4a_crawl4ai.py | 285 ++++++++ .../c4a_script/examples/add_to_cart.c4a | 7 + .../examples/advanced_control_flow.c4a | 43 ++ .../c4a_script/examples/conditional_login.c4a | 8 + .../c4a_script/examples/data_extraction.c4a | 56 ++ .../c4a_script/examples/fill_contact.c4a | 8 + .../c4a_script/examples/load_more_content.c4a | 7 + .../c4a_script/examples/login_flow.c4a | 36 + .../examples/multi_step_workflow.c4a | 106 +++ .../c4a_script/examples/navigate_tabs.c4a | 8 + .../c4a_script/examples/quick_login.c4a | 8 + .../examples/responsive_actions.c4a | 7 + .../c4a_script/examples/scroll_and_click.c4a | 8 + .../c4a_script/examples/search_product.c4a | 7 + .../c4a_script/examples/simple_form.c4a | 19 + .../c4a_script/examples/smart_form_fill.c4a | 11 + .../tutorial/assets/DankMono-Bold.woff2 | Bin 0 -> 33480 bytes .../tutorial/assets/DankMono-Italic.woff2 | Bin 0 -> 32468 bytes .../tutorial/assets/DankMono-Regular.woff2 | Bin 0 -> 30528 bytes 29 files changed, 2536 insertions(+) create mode 100644 crawl4ai/script/__init__.py create mode 100644 crawl4ai/script/c4a_compile.py create mode 100644 crawl4ai/script/c4a_result.py create mode 100644 crawl4ai/script/c4ai_script.py create mode 100644 docs/examples/c4a_script/C4A_SCRIPT_DOCS.md create mode 100644 docs/examples/c4a_script/api_usage_examples.py create mode 100644 docs/examples/c4a_script/c4a_script_hello_world.py create mode 100644 docs/examples/c4a_script/c4a_script_hello_world_error.py create mode 100644 docs/examples/c4a_script/demo_c4a_crawl4ai.py create mode 100644 docs/examples/c4a_script/examples/add_to_cart.c4a create mode 100644 docs/examples/c4a_script/examples/advanced_control_flow.c4a create mode 100644 docs/examples/c4a_script/examples/conditional_login.c4a create mode 100644 docs/examples/c4a_script/examples/data_extraction.c4a create mode 100644 docs/examples/c4a_script/examples/fill_contact.c4a create mode 100644 docs/examples/c4a_script/examples/load_more_content.c4a create mode 100644 docs/examples/c4a_script/examples/login_flow.c4a create mode 100644 docs/examples/c4a_script/examples/multi_step_workflow.c4a create mode 100644 docs/examples/c4a_script/examples/navigate_tabs.c4a create mode 100644 docs/examples/c4a_script/examples/quick_login.c4a create mode 100644 docs/examples/c4a_script/examples/responsive_actions.c4a create mode 100644 docs/examples/c4a_script/examples/scroll_and_click.c4a create mode 100644 docs/examples/c4a_script/examples/search_product.c4a create mode 100644 docs/examples/c4a_script/examples/simple_form.c4a create mode 100644 docs/examples/c4a_script/examples/smart_form_fill.c4a create mode 100644 docs/examples/c4a_script/tutorial/assets/DankMono-Bold.woff2 create mode 100644 docs/examples/c4a_script/tutorial/assets/DankMono-Italic.woff2 create mode 100644 docs/examples/c4a_script/tutorial/assets/DankMono-Regular.woff2 diff --git a/crawl4ai/__init__.py b/crawl4ai/__init__.py index c0d2b424..c8f6648a 100644 --- a/crawl4ai/__init__.py +++ b/crawl4ai/__init__.py @@ -69,6 +69,16 @@ from .deep_crawling import ( # NEW: Import AsyncUrlSeeder from .async_url_seeder import AsyncUrlSeeder +# C4A Script Language Support +from .script import ( + compile as c4a_compile, + validate as c4a_validate, + compile_file as c4a_compile_file, + CompilationResult, + ValidationResult, + ErrorDetail +) + from .utils import ( start_colab_display_server, setup_colab_environment @@ -139,6 +149,13 @@ __all__ = [ "ProxyConfig", "start_colab_display_server", "setup_colab_environment", + # C4A Script additions + "c4a_compile", + "c4a_validate", + "c4a_compile_file", + "CompilationResult", + "ValidationResult", + "ErrorDetail", ] diff --git a/crawl4ai/async_configs.py b/crawl4ai/async_configs.py index e63af8cc..8600dea4 100644 --- a/crawl4ai/async_configs.py +++ b/crawl4ai/async_configs.py @@ -911,6 +911,7 @@ class CrawlerRunConfig(): semaphore_count: int = 5, # Page Interaction Parameters js_code: Union[str, List[str]] = None, + c4a_script: Union[str, List[str]] = None, js_only: bool = False, ignore_body_visibility: bool = True, scan_full_page: bool = False, @@ -1009,6 +1010,7 @@ class CrawlerRunConfig(): # Page Interaction Parameters self.js_code = js_code + self.c4a_script = c4a_script self.js_only = js_only self.ignore_body_visibility = ignore_body_visibility self.scan_full_page = scan_full_page @@ -1084,6 +1086,59 @@ class CrawlerRunConfig(): # Experimental Parameters self.experimental = experimental or {} + + # Compile C4A scripts if provided + if self.c4a_script and not self.js_code: + self._compile_c4a_script() + + + def _compile_c4a_script(self): + """Compile C4A script to JavaScript""" + try: + # Try importing the compiler + try: + from .script import compile + except ImportError: + from crawl4ai.script import compile + + # Handle both string and list inputs + if isinstance(self.c4a_script, str): + scripts = [self.c4a_script] + else: + scripts = self.c4a_script + + # Compile each script + compiled_js = [] + for i, script in enumerate(scripts): + result = compile(script) + + if result.success: + compiled_js.extend(result.js_code) + else: + # Format error message following existing patterns + error = result.first_error + error_msg = ( + f"C4A Script compilation error (script {i+1}):\n" + f" Line {error.line}, Column {error.column}: {error.message}\n" + f" Code: {error.source_line}" + ) + if error.suggestions: + error_msg += f"\n Suggestion: {error.suggestions[0].message}" + + raise ValueError(error_msg) + + self.js_code = compiled_js + + except ImportError: + raise ValueError( + "C4A script compiler not available. " + "Please ensure crawl4ai.script module is properly installed." + ) + except Exception as e: + # Re-raise with context + if "compilation error" not in str(e).lower(): + raise ValueError(f"Failed to compile C4A script: {str(e)}") + raise def __getattr__(self, name): diff --git a/crawl4ai/script/__init__.py b/crawl4ai/script/__init__.py new file mode 100644 index 00000000..ecbffc39 --- /dev/null +++ b/crawl4ai/script/__init__.py @@ -0,0 +1,35 @@ +""" +C4A-Script: A domain-specific language for web automation in Crawl4AI +""" + +from .c4a_compile import C4ACompiler, compile, validate, compile_file +from .c4a_result import ( + CompilationResult, + ValidationResult, + ErrorDetail, + WarningDetail, + ErrorType, + Severity, + Suggestion +) + +__all__ = [ + # Main compiler + "C4ACompiler", + + # Convenience functions + "compile", + "validate", + "compile_file", + + # Result types + "CompilationResult", + "ValidationResult", + "ErrorDetail", + "WarningDetail", + + # Enums + "ErrorType", + "Severity", + "Suggestion" +] \ No newline at end of file diff --git a/crawl4ai/script/c4a_compile.py b/crawl4ai/script/c4a_compile.py new file mode 100644 index 00000000..2ee41cfe --- /dev/null +++ b/crawl4ai/script/c4a_compile.py @@ -0,0 +1,328 @@ +""" +Clean C4A-Script API with Result pattern +No exceptions - always returns results +""" + +from __future__ import annotations +import pathlib +import re +from typing import Union, List, Optional + +from .c4a_result import ( + CompilationResult, ValidationResult, ErrorDetail, WarningDetail, + ErrorType, Severity, Suggestion +) +from .c4ai_script import Compiler +from lark.exceptions import UnexpectedToken, UnexpectedCharacters, VisitError + + +class C4ACompiler: + """Main compiler with result-based API""" + + # Error code mapping + ERROR_CODES = { + "missing_then": "E001", + "missing_paren": "E002", + "missing_comma": "E003", + "missing_endproc": "E004", + "undefined_proc": "E005", + "missing_backticks": "E006", + "invalid_command": "E007", + "syntax_error": "E999" + } + + @classmethod + def compile(cls, script: Union[str, List[str]], root: Optional[pathlib.Path] = None) -> CompilationResult: + """ + Compile C4A-Script to JavaScript + + Args: + script: C4A-Script as string or list of lines + root: Root directory for includes + + Returns: + CompilationResult with success status and JS code or errors + """ + # Normalize input + if isinstance(script, list): + script_text = '\n'.join(script) + script_lines = script + else: + script_text = script + script_lines = script.split('\n') + + try: + # Try compilation + compiler = Compiler(root) + js_code = compiler.compile(script_text) + + # Success! + result = CompilationResult( + success=True, + js_code=js_code, + metadata={ + "lineCount": len(script_lines), + "statementCount": len(js_code) + } + ) + + # Add any warnings (future feature) + # result.warnings = cls._check_warnings(script_text) + + return result + + except Exception as e: + # Convert exception to ErrorDetail + error = cls._exception_to_error(e, script_lines) + return CompilationResult( + success=False, + errors=[error], + metadata={ + "lineCount": len(script_lines) + } + ) + + @classmethod + def validate(cls, script: Union[str, List[str]]) -> ValidationResult: + """ + Validate script syntax without generating code + + Args: + script: C4A-Script to validate + + Returns: + ValidationResult with validity status and any errors + """ + result = cls.compile(script) + + return ValidationResult( + valid=result.success, + errors=result.errors, + warnings=result.warnings + ) + + @classmethod + def compile_file(cls, path: Union[str, pathlib.Path]) -> CompilationResult: + """ + Compile a C4A-Script file + + Args: + path: Path to the file + + Returns: + CompilationResult + """ + path = pathlib.Path(path) + + if not path.exists(): + error = ErrorDetail( + type=ErrorType.RUNTIME, + code="E100", + severity=Severity.ERROR, + message=f"File not found: {path}", + line=0, + column=0, + source_line="" + ) + return CompilationResult(success=False, errors=[error]) + + try: + script = path.read_text() + return cls.compile(script, root=path.parent) + except Exception as e: + error = ErrorDetail( + type=ErrorType.RUNTIME, + code="E101", + severity=Severity.ERROR, + message=f"Error reading file: {str(e)}", + line=0, + column=0, + source_line="" + ) + return CompilationResult(success=False, errors=[error]) + + @classmethod + def _exception_to_error(cls, exc: Exception, script_lines: List[str]) -> ErrorDetail: + """Convert an exception to ErrorDetail""" + + if isinstance(exc, UnexpectedToken): + return cls._handle_unexpected_token(exc, script_lines) + elif isinstance(exc, UnexpectedCharacters): + return cls._handle_unexpected_chars(exc, script_lines) + elif isinstance(exc, ValueError): + return cls._handle_value_error(exc, script_lines) + else: + # Generic error + return ErrorDetail( + type=ErrorType.SYNTAX, + code=cls.ERROR_CODES["syntax_error"], + severity=Severity.ERROR, + message=str(exc), + line=1, + column=1, + source_line=script_lines[0] if script_lines else "" + ) + + @classmethod + def _handle_unexpected_token(cls, exc: UnexpectedToken, script_lines: List[str]) -> ErrorDetail: + """Handle UnexpectedToken errors""" + line = exc.line + column = exc.column + + # Get context lines + source_line = script_lines[line - 1] if 0 < line <= len(script_lines) else "" + line_before = script_lines[line - 2] if line > 1 and line <= len(script_lines) + 1 else None + line_after = script_lines[line] if 0 < line < len(script_lines) else None + + # Determine error type and suggestions + if exc.token.type == 'CLICK' and 'THEN' in str(exc.expected): + code = cls.ERROR_CODES["missing_then"] + message = "Missing 'THEN' keyword after IF condition" + suggestions = [ + Suggestion( + "Add 'THEN' after the condition", + source_line.replace("CLICK", "THEN CLICK") if source_line else None + ) + ] + elif exc.token.type == '$END': + code = cls.ERROR_CODES["missing_endproc"] + message = "Unexpected end of script" + suggestions = [ + Suggestion("Check for missing ENDPROC"), + Suggestion("Ensure all procedures are properly closed") + ] + elif 'RPAR' in str(exc.expected): + code = cls.ERROR_CODES["missing_paren"] + message = "Missing closing parenthesis ')'" + suggestions = [ + Suggestion("Add closing parenthesis at the end of the condition") + ] + elif 'COMMA' in str(exc.expected): + code = cls.ERROR_CODES["missing_comma"] + message = "Missing comma ',' in command" + suggestions = [ + Suggestion("Add comma between arguments") + ] + else: + # Check if this might be missing backticks + if exc.token.type == 'NAME' and 'BACKTICK_STRING' in str(exc.expected): + code = cls.ERROR_CODES["missing_backticks"] + message = "Selector must be wrapped in backticks" + suggestions = [ + Suggestion( + "Wrap the selector in backticks", + f"`{exc.token.value}`" + ) + ] + else: + code = cls.ERROR_CODES["syntax_error"] + message = f"Unexpected '{exc.token.value}'" + if exc.expected: + expected_list = [str(e) for e in exc.expected if not str(e).startswith('_')][:3] + if expected_list: + message += f". Expected: {', '.join(expected_list)}" + suggestions = [] + + return ErrorDetail( + type=ErrorType.SYNTAX, + code=code, + severity=Severity.ERROR, + message=message, + line=line, + column=column, + source_line=source_line, + line_before=line_before, + line_after=line_after, + suggestions=suggestions + ) + + @classmethod + def _handle_unexpected_chars(cls, exc: UnexpectedCharacters, script_lines: List[str]) -> ErrorDetail: + """Handle UnexpectedCharacters errors""" + line = exc.line + column = exc.column + + source_line = script_lines[line - 1] if 0 < line <= len(script_lines) else "" + + # Check for missing backticks + if "CLICK" in source_line and column > source_line.find("CLICK"): + code = cls.ERROR_CODES["missing_backticks"] + message = "Selector must be wrapped in backticks" + suggestions = [ + Suggestion( + "Wrap the selector in backticks", + re.sub(r'CLICK\s+([^\s]+)', r'CLICK `\1`', source_line) + ) + ] + else: + code = cls.ERROR_CODES["syntax_error"] + message = f"Invalid character at position {column}" + suggestions = [] + + return ErrorDetail( + type=ErrorType.SYNTAX, + code=code, + severity=Severity.ERROR, + message=message, + line=line, + column=column, + source_line=source_line, + suggestions=suggestions + ) + + @classmethod + def _handle_value_error(cls, exc: ValueError, script_lines: List[str]) -> ErrorDetail: + """Handle ValueError (runtime errors)""" + message = str(exc) + + # Check for undefined procedure + if "Unknown procedure" in message: + proc_match = re.search(r"'([^']+)'", message) + if proc_match: + proc_name = proc_match.group(1) + + # Find the line with the procedure call + for i, line in enumerate(script_lines): + if proc_name in line and not line.strip().startswith('PROC'): + return ErrorDetail( + type=ErrorType.RUNTIME, + code=cls.ERROR_CODES["undefined_proc"], + severity=Severity.ERROR, + message=f"Undefined procedure '{proc_name}'", + line=i + 1, + column=line.find(proc_name) + 1, + source_line=line, + suggestions=[ + Suggestion( + f"Define the procedure before using it", + f"PROC {proc_name}\n # commands here\nENDPROC" + ) + ] + ) + + # Generic runtime error + return ErrorDetail( + type=ErrorType.RUNTIME, + code="E999", + severity=Severity.ERROR, + message=message, + line=1, + column=1, + source_line=script_lines[0] if script_lines else "" + ) + + +# Convenience functions for direct use +def compile(script: Union[str, List[str]], root: Optional[pathlib.Path] = None) -> CompilationResult: + """Compile C4A-Script to JavaScript""" + return C4ACompiler.compile(script, root) + + +def validate(script: Union[str, List[str]]) -> ValidationResult: + """Validate C4A-Script syntax""" + return C4ACompiler.validate(script) + + +def compile_file(path: Union[str, pathlib.Path]) -> CompilationResult: + """Compile C4A-Script file""" + return C4ACompiler.compile_file(path) \ No newline at end of file diff --git a/crawl4ai/script/c4a_result.py b/crawl4ai/script/c4a_result.py new file mode 100644 index 00000000..c0047697 --- /dev/null +++ b/crawl4ai/script/c4a_result.py @@ -0,0 +1,219 @@ +""" +Result classes for C4A-Script compilation +Clean API design with no exceptions +""" + +from __future__ import annotations +from dataclasses import dataclass, field +from enum import Enum +from typing import List, Dict, Any, Optional +import json + + +class ErrorType(Enum): + SYNTAX = "syntax" + SEMANTIC = "semantic" + RUNTIME = "runtime" + + +class Severity(Enum): + ERROR = "error" + WARNING = "warning" + INFO = "info" + + +@dataclass +class Suggestion: + """A suggestion for fixing an error""" + message: str + fix: Optional[str] = None + + def to_dict(self) -> dict: + return { + "message": self.message, + "fix": self.fix + } + + +@dataclass +class ErrorDetail: + """Detailed information about a compilation error""" + # Core info + type: ErrorType + code: str # E001, E002, etc. + severity: Severity + message: str + + # Location + line: int + column: int + + # Context + source_line: str + + # Optional fields with defaults + end_line: Optional[int] = None + end_column: Optional[int] = None + line_before: Optional[str] = None + line_after: Optional[str] = None + + # Help + suggestions: List[Suggestion] = field(default_factory=list) + documentation_url: Optional[str] = None + + def to_dict(self) -> dict: + """Convert to dictionary for JSON serialization""" + return { + "type": self.type.value, + "code": self.code, + "severity": self.severity.value, + "message": self.message, + "location": { + "line": self.line, + "column": self.column, + "endLine": self.end_line, + "endColumn": self.end_column + }, + "context": { + "sourceLine": self.source_line, + "lineBefore": self.line_before, + "lineAfter": self.line_after, + "marker": { + "start": self.column - 1, + "length": (self.end_column - self.column) if self.end_column else 1 + } + }, + "suggestions": [s.to_dict() for s in self.suggestions], + "documentationUrl": self.documentation_url + } + + def to_json(self) -> str: + """Convert to JSON string""" + return json.dumps(self.to_dict(), indent=2) + + @property + def formatted_message(self) -> str: + """Returns the nice text format for terminals""" + lines = [] + lines.append(f"\n{'='*60}") + lines.append(f"{self.type.value.title()} Error [{self.code}]") + lines.append(f"{'='*60}") + lines.append(f"Location: Line {self.line}, Column {self.column}") + lines.append(f"Error: {self.message}") + + if self.source_line: + marker = " " * (self.column - 1) + "^" + if self.end_column: + marker += "~" * (self.end_column - self.column - 1) + lines.append(f"\nCode:") + if self.line_before: + lines.append(f" {self.line - 1: >3} | {self.line_before}") + lines.append(f" {self.line: >3} | {self.source_line}") + lines.append(f" | {marker}") + if self.line_after: + lines.append(f" {self.line + 1: >3} | {self.line_after}") + + if self.suggestions: + lines.append("\nSuggestions:") + for i, suggestion in enumerate(self.suggestions, 1): + lines.append(f" {i}. {suggestion.message}") + if suggestion.fix: + lines.append(f" Fix: {suggestion.fix}") + + lines.append("="*60) + return "\n".join(lines) + + @property + def simple_message(self) -> str: + """Returns just the error message without formatting""" + return f"Line {self.line}: {self.message}" + + +@dataclass +class WarningDetail: + """Information about a compilation warning""" + code: str + message: str + line: int + column: int + + def to_dict(self) -> dict: + return { + "code": self.code, + "message": self.message, + "line": self.line, + "column": self.column + } + + +@dataclass +class CompilationResult: + """Result of C4A-Script compilation""" + success: bool + js_code: Optional[List[str]] = None + errors: List[ErrorDetail] = field(default_factory=list) + warnings: List[WarningDetail] = field(default_factory=list) + metadata: Dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict: + """Convert to dictionary for JSON serialization""" + return { + "success": self.success, + "jsCode": self.js_code, + "errors": [e.to_dict() for e in self.errors], + "warnings": [w.to_dict() for w in self.warnings], + "metadata": self.metadata + } + + def to_json(self) -> str: + """Convert to JSON string""" + return json.dumps(self.to_dict(), indent=2) + + @property + def has_errors(self) -> bool: + """Check if there are any errors""" + return len(self.errors) > 0 + + @property + def has_warnings(self) -> bool: + """Check if there are any warnings""" + return len(self.warnings) > 0 + + @property + def first_error(self) -> Optional[ErrorDetail]: + """Get the first error if any""" + return self.errors[0] if self.errors else None + + def __str__(self) -> str: + """String representation for debugging""" + if self.success: + msg = f"✓ Compilation successful" + if self.js_code: + msg += f" - {len(self.js_code)} statements generated" + if self.warnings: + msg += f" ({len(self.warnings)} warnings)" + return msg + else: + return f"✗ Compilation failed - {len(self.errors)} error(s)" + + +@dataclass +class ValidationResult: + """Result of script validation""" + valid: bool + errors: List[ErrorDetail] = field(default_factory=list) + warnings: List[WarningDetail] = field(default_factory=list) + + def to_dict(self) -> dict: + return { + "valid": self.valid, + "errors": [e.to_dict() for e in self.errors], + "warnings": [w.to_dict() for w in self.warnings] + } + + def to_json(self) -> str: + return json.dumps(self.to_dict(), indent=2) + + @property + def first_error(self) -> Optional[ErrorDetail]: + return self.errors[0] if self.errors else None \ No newline at end of file diff --git a/crawl4ai/script/c4ai_script.py b/crawl4ai/script/c4ai_script.py new file mode 100644 index 00000000..f0691538 --- /dev/null +++ b/crawl4ai/script/c4ai_script.py @@ -0,0 +1,623 @@ +""" +2025-06-03 +By Unclcode: +C4A-Script Language Documentation +Feeds Crawl4AI via CrawlerRunConfig(js_code=[ ... ]) – no core modifications. +""" + +from __future__ import annotations +import pathlib, re, sys, textwrap +from dataclasses import dataclass +from typing import Any, Dict, List, Union + +from lark import Lark, Transformer, v_args +from lark.exceptions import UnexpectedToken, UnexpectedCharacters, VisitError + + +# --------------------------------------------------------------------------- # +# Custom Error Classes +# --------------------------------------------------------------------------- # +class C4AScriptError(Exception): + """Custom error class for C4A-Script compilation errors""" + + def __init__(self, message: str, line: int = None, column: int = None, + error_type: str = "Syntax Error", details: str = None): + self.message = message + self.line = line + self.column = column + self.error_type = error_type + self.details = details + super().__init__(self._format_message()) + + def _format_message(self) -> str: + """Format a clear error message""" + lines = [f"\n{'='*60}"] + lines.append(f"C4A-Script {self.error_type}") + lines.append(f"{'='*60}") + + if self.line: + lines.append(f"Location: Line {self.line}" + (f", Column {self.column}" if self.column else "")) + + lines.append(f"Error: {self.message}") + + if self.details: + lines.append(f"\nDetails: {self.details}") + + lines.append("="*60) + return "\n".join(lines) + + @classmethod + def from_exception(cls, exc: Exception, script: Union[str, List[str]]) -> 'C4AScriptError': + """Create C4AScriptError from another exception""" + script_text = script if isinstance(script, str) else '\n'.join(script) + script_lines = script_text.split('\n') + + if isinstance(exc, UnexpectedToken): + # Extract line and column from UnexpectedToken + line = exc.line + column = exc.column + + # Get the problematic line + if 0 < line <= len(script_lines): + problem_line = script_lines[line - 1] + marker = " " * (column - 1) + "^" + + details = f"\nCode:\n {problem_line}\n {marker}\n" + + # Improve error message based on context + if exc.token.type == 'CLICK' and 'THEN' in str(exc.expected): + message = "Missing 'THEN' keyword after IF condition" + elif exc.token.type == '$END': + message = "Unexpected end of script. Check for missing ENDPROC or incomplete commands" + elif 'RPAR' in str(exc.expected): + message = "Missing closing parenthesis ')'" + elif 'COMMA' in str(exc.expected): + message = "Missing comma ',' in command" + else: + message = f"Unexpected '{exc.token}'" + if exc.expected: + expected_list = [str(e) for e in exc.expected if not e.startswith('_')] + if expected_list: + message += f". Expected: {', '.join(expected_list[:3])}" + + details += f"Token: {exc.token.type} ('{exc.token.value}')" + else: + message = str(exc) + details = None + + return cls(message, line, column, "Syntax Error", details) + + elif isinstance(exc, UnexpectedCharacters): + # Extract line and column + line = exc.line + column = exc.column + + if 0 < line <= len(script_lines): + problem_line = script_lines[line - 1] + marker = " " * (column - 1) + "^" + + details = f"\nCode:\n {problem_line}\n {marker}\n" + message = f"Invalid character or unexpected text at position {column}" + else: + message = str(exc) + details = None + + return cls(message, line, column, "Syntax Error", details) + + elif isinstance(exc, ValueError): + # Handle runtime errors like undefined procedures + message = str(exc) + + # Try to find which line caused the error + if "Unknown procedure" in message: + proc_name = re.search(r"'([^']+)'", message) + if proc_name: + proc_name = proc_name.group(1) + for i, line in enumerate(script_lines, 1): + if proc_name in line and not line.strip().startswith('PROC'): + details = f"\nCode:\n {line.strip()}\n\nMake sure the procedure '{proc_name}' is defined with PROC...ENDPROC" + return cls(f"Undefined procedure '{proc_name}'", i, None, "Runtime Error", details) + + return cls(message, None, None, "Runtime Error", None) + + else: + # Generic error + return cls(str(exc), None, None, "Compilation Error", None) + + +# --------------------------------------------------------------------------- # +# 1. Grammar +# --------------------------------------------------------------------------- # +GRAMMAR = r""" +start : line* +?line : command | proc_def | include | comment + +command : wait | nav | click_cmd | double_click | right_click | move | drag | scroll + | type | press | key_down | key_up + | eval_cmd | set_var | proc_call | if_cmd | repeat_cmd + +wait : "WAIT" (ESCAPED_STRING|BACKTICK_STRING|NUMBER) NUMBER? -> wait_cmd +nav : "GO" URL -> go + | "RELOAD" -> reload + | "BACK" -> back + | "FORWARD" -> forward + +click_cmd : "CLICK" (BACKTICK_STRING|NUMBER NUMBER) -> click +double_click : "DOUBLE_CLICK" (BACKTICK_STRING|NUMBER NUMBER) -> double_click +right_click : "RIGHT_CLICK" (BACKTICK_STRING|NUMBER NUMBER) -> right_click + +move : "MOVE" coords -> move +drag : "DRAG" coords coords -> drag +scroll : "SCROLL" DIR NUMBER? -> scroll + +type : "TYPE" (ESCAPED_STRING | NAME) -> type +press : "PRESS" WORD -> press +key_down : "KEY_DOWN" WORD -> key_down +key_up : "KEY_UP" WORD -> key_up + +eval_cmd : "EVAL" BACKTICK_STRING -> eval_cmd +set_var : "SET" NAME "=" value -> set_var +proc_call : NAME -> proc_call +proc_def : "PROC" NAME line* "ENDPROC" -> proc_def +include : "USE" ESCAPED_STRING -> include +comment : /#.*/ -> comment + +if_cmd : "IF" "(" condition ")" "THEN" command ("ELSE" command)? -> if_cmd +repeat_cmd : "REPEAT" "(" command "," repeat_count ")" -> repeat_cmd + +condition : exists_cond | js_cond +exists_cond : "EXISTS" BACKTICK_STRING -> exists_cond +js_cond : BACKTICK_STRING -> js_cond + +repeat_count : NUMBER | BACKTICK_STRING + +coords : NUMBER NUMBER +value : ESCAPED_STRING | NUMBER +DIR : /(UP|DOWN|LEFT|RIGHT)/i +REST : /[^\n]+/ + +URL : /(http|https):\/\/[^\s]+/ +NAME : /\$?[A-Za-z_][A-Za-z0-9_]*/ +WORD : /[A-Za-z0-9+]+/ +BACKTICK_STRING : /`[^`]*`/ + +%import common.NUMBER +%import common.ESCAPED_STRING +%import common.WS_INLINE +%import common.NEWLINE +%ignore WS_INLINE +%ignore NEWLINE +""" + +# --------------------------------------------------------------------------- # +# 2. IR dataclasses +# --------------------------------------------------------------------------- # +@dataclass +class Cmd: + op: str + args: List[Any] + +@dataclass +class Proc: + name: str + body: List[Cmd] + +# --------------------------------------------------------------------------- # +# 3. AST → IR +# --------------------------------------------------------------------------- # +@v_args(inline=True) +class ASTBuilder(Transformer): + # helpers + def _strip(self, s): + if s.startswith('"') and s.endswith('"'): + return s[1:-1] + elif s.startswith('`') and s.endswith('`'): + return s[1:-1] + return s + def start(self,*i): return list(i) + def line(self,i): return i + def command(self,i): return i + + # WAIT + def wait_cmd(self, rest, timeout=None): + rest_str = str(rest) + # Check if it's a number (including floats) + try: + num_val = float(rest_str) + payload = (num_val, "seconds") + except ValueError: + if rest_str.startswith('"') and rest_str.endswith('"'): + payload = (self._strip(rest_str), "text") + elif rest_str.startswith('`') and rest_str.endswith('`'): + payload = (self._strip(rest_str), "selector") + else: + payload = (rest_str, "selector") + return Cmd("WAIT", [payload, int(timeout) if timeout else None]) + + # NAV + def go(self,u): return Cmd("GO",[str(u)]) + def reload(self): return Cmd("RELOAD",[]) + def back(self): return Cmd("BACK",[]) + def forward(self): return Cmd("FORWARD",[]) + + # CLICK, DOUBLE_CLICK, RIGHT_CLICK + def click(self, *args): + return self._handle_click("CLICK", args) + + def double_click(self, *args): + return self._handle_click("DBLCLICK", args) + + def right_click(self, *args): + return self._handle_click("RIGHTCLICK", args) + + def _handle_click(self, op, args): + if len(args) == 1: + # Single argument - backtick string + target = self._strip(str(args[0])) + return Cmd(op, [("selector", target)]) + else: + # Two arguments - coordinates + x, y = args + return Cmd(op, [("coords", int(x), int(y))]) + + + # MOVE / DRAG / SCROLL + def coords(self,x,y): return ("coords",int(x),int(y)) + def move(self,c): return Cmd("MOVE",[c]) + def drag(self,c1,c2): return Cmd("DRAG",[c1,c2]) + def scroll(self,dir_tok,amt=None): + return Cmd("SCROLL",[dir_tok.upper(), int(amt) if amt else 500]) + + # KEYS + def type(self,tok): return Cmd("TYPE",[self._strip(str(tok))]) + def press(self,w): return Cmd("PRESS",[str(w)]) + def key_down(self,w): return Cmd("KEYDOWN",[str(w)]) + def key_up(self,w): return Cmd("KEYUP",[str(w)]) + + # FLOW + def eval_cmd(self,txt): return Cmd("EVAL",[self._strip(str(txt))]) + def set_var(self,n,v): + # v might be a Token or a Tree, extract value properly + if hasattr(v, 'value'): + value = v.value + elif hasattr(v, 'children') and len(v.children) > 0: + value = v.children[0].value + else: + value = str(v) + return Cmd("SET",[str(n), self._strip(value)]) + def proc_call(self,n): return Cmd("CALL",[str(n)]) + def proc_def(self,n,*body): return Proc(str(n),[b for b in body if isinstance(b,Cmd)]) + def include(self,p): return Cmd("INCLUDE",[self._strip(p)]) + def comment(self,*_): return Cmd("NOP",[]) + + # IF-THEN-ELSE and EXISTS + def if_cmd(self, condition, then_cmd, else_cmd=None): + return Cmd("IF", [condition, then_cmd, else_cmd]) + + def condition(self, cond): + return cond + + def exists_cond(self, selector): + return ("EXISTS", self._strip(str(selector))) + + def js_cond(self, expr): + return ("JS", self._strip(str(expr))) + + # REPEAT + def repeat_cmd(self, cmd, count): + return Cmd("REPEAT", [cmd, count]) + + def repeat_count(self, value): + return str(value) + +# --------------------------------------------------------------------------- # +# 4. Compiler +# --------------------------------------------------------------------------- # +class Compiler: + def __init__(self, root: pathlib.Path|None=None): + self.parser = Lark(GRAMMAR,start="start",parser="lalr") + self.root = pathlib.Path(root or ".").resolve() + self.vars: Dict[str,Any] = {} + self.procs: Dict[str,Proc]= {} + + def compile(self, text: Union[str, List[str]]) -> List[str]: + # Handle list input by joining with newlines + if isinstance(text, list): + text = '\n'.join(text) + + ir = self._parse_with_includes(text) + ir = self._collect_procs(ir) + ir = self._inline_calls(ir) + ir = self._apply_set_vars(ir) + return [self._emit_js(c) for c in ir if isinstance(c,Cmd) and c.op!="NOP"] + + # passes + def _parse_with_includes(self,txt,seen=None): + seen=seen or set() + cmds=ASTBuilder().transform(self.parser.parse(txt)) + out=[] + for c in cmds: + if isinstance(c,Cmd) and c.op=="INCLUDE": + p=(self.root/c.args[0]).resolve() + if p in seen: raise ValueError(f"Circular include {p}") + seen.add(p); out+=self._parse_with_includes(p.read_text(),seen) + else: out.append(c) + return out + + def _collect_procs(self,ir): + out=[] + for i in ir: + if isinstance(i,Proc): self.procs[i.name]=i + else: out.append(i) + return out + + def _inline_calls(self,ir): + out=[] + for c in ir: + if isinstance(c,Cmd) and c.op=="CALL": + if c.args[0] not in self.procs: + raise ValueError(f"Unknown procedure {c.args[0]!r}") + out+=self._inline_calls(self.procs[c.args[0]].body) + else: out.append(c) + return out + + def _apply_set_vars(self,ir): + def sub(s): return re.sub(r"\$(\w+)",lambda m:str(self.vars.get(m.group(1),m.group(0))) ,s) if isinstance(s,str) else s + out=[] + for c in ir: + if isinstance(c,Cmd): + if c.op=="SET": self.vars[c.args[0].lstrip('$')]=c.args[1] + else: + if c.op in("TYPE","EVAL"): c.args=[sub(a) for a in c.args] + out.append(c) + return out + + # JS emitter + def _emit_js(self, cmd: Cmd) -> str: + op, a = cmd.op, cmd.args + if op == "GO": return f"location.href = '{a[0]}';" + if op == "RELOAD": return "location.reload();" + if op == "BACK": return "history.back();" + if op == "FORWARD": return "history.forward();" + + if op == "WAIT": + arg, kind = a[0] + timeout = a[1] or 10 + if kind == "seconds": + return f"await new Promise(r=>setTimeout(r,{arg}*1000));" + if kind == "selector": + sel = arg.replace("\\","\\\\").replace("'","\\'") + return textwrap.dedent(f""" + await new Promise((res,rej)=>{{ + const max = {timeout*1000}, t0 = performance.now(); + const id = setInterval(()=>{{ + if(document.querySelector('{sel}')){{clearInterval(id);res();}} + else if(performance.now()-t0>max){{clearInterval(id);rej('WAIT selector timeout');}} + }},100); + }}); + """).strip() + if kind == "text": + txt = arg.replace('`', '\\`') + return textwrap.dedent(f""" + await new Promise((res,rej)=>{{ + const max={timeout*1000},t0=performance.now(); + const id=setInterval(()=>{{ + if(document.body.innerText.includes(`{txt}`)){{clearInterval(id);res();}} + else if(performance.now()-t0>max){{clearInterval(id);rej('WAIT text timeout');}} + }},100); + }}); + """).strip() + + # click-style helpers + def _js_click(sel, evt="click", button=0, detail=1): + sel = sel.replace("'", "\\'") + return f"document.querySelector('{sel}')?.dispatchEvent(new MouseEvent('{evt}',{{bubbles:true,button:{button},detail:{detail}}}));" + + def _js_click_xy(x, y, evt="click", button=0, detail=1): + return textwrap.dedent(f""" + (()=>{{ + const el=document.elementFromPoint({x},{y}); + el&&el.dispatchEvent(new MouseEvent('{evt}',{{bubbles:true,button:{button},detail:{detail}}})); + }})(); + """).strip() + + if op in ("CLICK", "DBLCLICK", "RIGHTCLICK"): + evt = {"CLICK":"click","DBLCLICK":"dblclick","RIGHTCLICK":"contextmenu"}[op] + btn = 2 if op=="RIGHTCLICK" else 0 + det = 2 if op=="DBLCLICK" else 1 + kind,*rest = a[0] + return _js_click_xy(*rest) if kind=="coords" else _js_click(rest[0],evt,btn,det) + + if op == "MOVE": + _, x, y = a[0] + return textwrap.dedent(f""" + document.dispatchEvent(new MouseEvent('mousemove',{{clientX:{x},clientY:{y},bubbles:true}})); + """).strip() + + if op == "DRAG": + (_, x1, y1), (_, x2, y2) = a + return textwrap.dedent(f""" + (()=>{{ + const s=document.elementFromPoint({x1},{y1}); + if(!s) return; + s.dispatchEvent(new MouseEvent('mousedown',{{bubbles:true,clientX:{x1},clientY:{y1}}})); + document.dispatchEvent(new MouseEvent('mousemove',{{bubbles:true,clientX:{x2},clientY:{y2}}})); + document.dispatchEvent(new MouseEvent('mouseup', {{bubbles:true,clientX:{x2},clientY:{y2}}})); + }})(); + """).strip() + + if op == "SCROLL": + dir_, amt = a + dx, dy = {"UP":(0,-amt),"DOWN":(0,amt),"LEFT":(-amt,0),"RIGHT":(amt,0)}[dir_] + return f"window.scrollBy({dx},{dy});" + + if op == "TYPE": + txt = a[0].replace("'", "\\'") + return textwrap.dedent(f""" + (()=>{{ + const el=document.activeElement; + if(el){{ + el.value += '{txt}'; + el.dispatchEvent(new Event('input',{{bubbles:true}})); + }} + }})(); + """).strip() + + if op in ("PRESS","KEYDOWN","KEYUP"): + key = a[0] + evs = {"PRESS":("keydown","keyup"),"KEYDOWN":("keydown",),"KEYUP":("keyup",)}[op] + return ";".join([f"document.dispatchEvent(new KeyboardEvent('{e}',{{key:'{key}',bubbles:true}}))" for e in evs]) + ";" + + if op == "EVAL": + return a[0] + + if op == "IF": + condition, then_cmd, else_cmd = a + cond_type, cond_value = condition + + # Generate condition JavaScript + if cond_type == "EXISTS": + js_condition = f"!!document.querySelector('{cond_value}')" + else: # JS condition + js_condition = cond_value + + # Generate commands - handle both regular commands and procedure calls + then_js = self._handle_cmd_or_proc(then_cmd) + else_js = self._handle_cmd_or_proc(else_cmd) if else_cmd else "" + + if else_cmd: + return textwrap.dedent(f""" + if ({js_condition}) {{ + {then_js} + }} else {{ + {else_js} + }} + """).strip() + else: + return textwrap.dedent(f""" + if ({js_condition}) {{ + {then_js} + }} + """).strip() + + if op == "REPEAT": + cmd, count = a + + # Handle the count - could be number or JS expression + if count.isdigit(): + # Simple number + repeat_js = self._handle_cmd_or_proc(cmd) + return textwrap.dedent(f""" + for (let _i = 0; _i < {count}; _i++) {{ + {repeat_js} + }} + """).strip() + else: + # JS expression (from backticks) + count_expr = count[1:-1] if count.startswith('`') and count.endswith('`') else count + repeat_js = self._handle_cmd_or_proc(cmd) + return textwrap.dedent(f""" + (()=>{{ + const _count = {count_expr}; + if (typeof _count === 'number') {{ + for (let _i = 0; _i < _count; _i++) {{ + {repeat_js} + }} + }} else if (_count) {{ + {repeat_js} + }} + }})(); + """).strip() + + raise ValueError(f"Unhandled op {op}") + + def _handle_cmd_or_proc(self, cmd): + """Handle a command that might be a regular command or a procedure call""" + if not cmd: + return "" + + if isinstance(cmd, Cmd): + if cmd.op == "CALL": + # Inline the procedure + if cmd.args[0] not in self.procs: + raise ValueError(f"Unknown procedure {cmd.args[0]!r}") + proc_body = self.procs[cmd.args[0]].body + return "\n".join([self._emit_js(c) for c in proc_body if c.op != "NOP"]) + else: + return self._emit_js(cmd) + return "" + +# --------------------------------------------------------------------------- # +# 5. Helpers + demo +# --------------------------------------------------------------------------- # + +def compile_string(script: Union[str, List[str]], *, root: Union[pathlib.Path, None] = None) -> List[str]: + """Compile C4A-Script from string or list of strings to JavaScript. + + Args: + script: C4A-Script as a string or list of command strings + root: Root directory for resolving includes (optional) + + Returns: + List of JavaScript command strings + + Raises: + C4AScriptError: When compilation fails with detailed error information + """ + try: + return Compiler(root).compile(script) + except Exception as e: + # Wrap the error with better formatting + raise C4AScriptError.from_exception(e, script) + +def compile_file(path: pathlib.Path) -> List[str]: + """Compile C4A-Script from file to JavaScript. + + Args: + path: Path to C4A-Script file + + Returns: + List of JavaScript command strings + """ + return compile_string(path.read_text(), root=path.parent) + +def compile_lines(lines: List[str], *, root: Union[pathlib.Path, None] = None) -> List[str]: + """Compile C4A-Script from list of lines to JavaScript. + + Args: + lines: List of C4A-Script command lines + root: Root directory for resolving includes (optional) + + Returns: + List of JavaScript command strings + """ + return compile_string(lines, root=root) + +DEMO = """ +# quick sanity demo +PROC login + CLICK `input[name="username"]` + TYPE $user + PRESS Tab + TYPE $pass + CLICK `button.submit` +ENDPROC + +SET user = "tom@crawl4ai.com" +SET pass = "hunter2" + +GO https://example.com/login +WAIT `input[name="username"]` 10 +login +WAIT 3 +EVAL `console.log('logged in')` +""" + +if __name__ == "__main__": + if len(sys.argv) == 2: + for js in compile_file(pathlib.Path(sys.argv[1])): + print(js) + else: + print("=== DEMO ===") + for js in compile_string(DEMO): + print(js) diff --git a/docs/examples/c4a_script/C4A_SCRIPT_DOCS.md b/docs/examples/c4a_script/C4A_SCRIPT_DOCS.md new file mode 100644 index 00000000..f388f113 --- /dev/null +++ b/docs/examples/c4a_script/C4A_SCRIPT_DOCS.md @@ -0,0 +1,312 @@ +# C4A-Script Language Documentation + +C4A-Script (Crawl4AI Script) is a simple, powerful language for web automation. Write human-readable commands that compile to JavaScript for browser automation. + +## Quick Start + +```python +from c4a_compile import compile + +# Write your script +script = """ +GO https://example.com +WAIT `#content` 5 +CLICK `button.submit` +""" + +# Compile to JavaScript +result = compile(script) + +if result.success: + # Use with Crawl4AI + config = CrawlerRunConfig(js_code=result.js_code) +else: + print(f"Error at line {result.first_error.line}: {result.first_error.message}") +``` + +## Language Basics + +- **One command per line** +- **Selectors in backticks**: `` `button.submit` `` +- **Strings in quotes**: `"Hello World"` +- **Variables with $**: `$username` +- **Comments with #**: `# This is a comment` + +## Commands Reference + +### Navigation + +```c4a +GO https://example.com # Navigate to URL +RELOAD # Reload current page +BACK # Go back in history +FORWARD # Go forward in history +``` + +### Waiting + +```c4a +WAIT 3 # Wait 3 seconds +WAIT `#content` 10 # Wait for element (max 10 seconds) +WAIT "Loading complete" 5 # Wait for text to appear +``` + +### Mouse Actions + +```c4a +CLICK `button.submit` # Click element +DOUBLE_CLICK `.item` # Double-click element +RIGHT_CLICK `#menu` # Right-click element +CLICK 100 200 # Click at coordinates + +MOVE 500 300 # Move mouse to position +DRAG 100 100 500 300 # Drag from one point to another + +SCROLL DOWN 500 # Scroll down 500 pixels +SCROLL UP # Scroll up (default 500px) +SCROLL LEFT 200 # Scroll left 200 pixels +SCROLL RIGHT # Scroll right +``` + +### Keyboard + +```c4a +TYPE "hello@example.com" # Type text +TYPE $email # Type variable value + +PRESS Tab # Press and release key +PRESS Enter +PRESS Escape + +KEY_DOWN Shift # Hold key down +KEY_UP Shift # Release key +``` + +### Control Flow + +#### IF-THEN-ELSE + +```c4a +# Check if element exists +IF (EXISTS `.cookie-banner`) THEN CLICK `.accept` +IF (EXISTS `#user`) THEN CLICK `.logout` ELSE CLICK `.login` + +# JavaScript conditions +IF (`window.innerWidth < 768`) THEN CLICK `.mobile-menu` +IF (`document.querySelectorAll('.item').length > 10`) THEN SCROLL DOWN +``` + +#### REPEAT + +```c4a +# Repeat fixed number of times +REPEAT (CLICK `.next`, 5) + +# Repeat based on JavaScript expression +REPEAT (SCROLL DOWN 300, `document.querySelectorAll('.item').length`) + +# Repeat while condition is true (like while loop) +REPEAT (CLICK `.load-more`, `document.querySelector('.load-more') !== null`) +``` + +### Variables & JavaScript + +```c4a +# Set variables +SET username = "john@example.com" +SET count = "10" + +# Use variables +TYPE $username + +# Execute JavaScript +EVAL `console.log('Hello')` +EVAL `localStorage.setItem('key', 'value')` +``` + +### Procedures + +```c4a +# Define reusable procedure +PROC login + CLICK `#email` + TYPE $email + CLICK `#password` + TYPE $password + CLICK `button[type="submit"]` +ENDPROC + +# Use procedure +SET email = "user@example.com" +SET password = "secure123" +login + +# Procedures work with control flow +IF (EXISTS `.login-form`) THEN login +REPEAT (process_item, 10) +``` + +## API Reference + +### Functions + +```python +from c4a_compile import compile, validate, compile_file + +# Compile script +result = compile("GO https://example.com") + +# Validate syntax only +result = validate(script) + +# Compile from file +result = compile_file("script.c4a") +``` + +### Working with Results + +```python +result = compile(script) + +if result.success: + # Access generated JavaScript + js_code = result.js_code # List[str] + + # Use with Crawl4AI + config = CrawlerRunConfig(js_code=js_code) +else: + # Handle errors + error = result.first_error + print(f"Line {error.line}, Column {error.column}: {error.message}") + + # Get suggestions + for suggestion in error.suggestions: + print(f"Fix: {suggestion.message}") + + # Get JSON for UI integration + error_json = result.to_json() +``` + +## Examples + +### Basic Automation + +```c4a +GO https://example.com +WAIT `#content` 5 +IF (EXISTS `.cookie-notice`) THEN CLICK `.accept` +CLICK `.main-button` +``` + +### Form Filling + +```c4a +SET email = "user@example.com" +SET message = "Hello, I need help with my order" + +GO https://example.com/contact +WAIT `form` 5 +CLICK `input[name="email"]` +TYPE $email +CLICK `textarea[name="message"]` +TYPE $message +CLICK `button[type="submit"]` +WAIT "Thank you" 10 +``` + +### Dynamic Content Loading + +```c4a +GO https://shop.example.com +WAIT `.product-list` 10 + +# Load all products +REPEAT (CLICK `.load-more`, `document.querySelector('.load-more') !== null`) + +# Extract data +EVAL ` + const count = document.querySelectorAll('.product').length; + console.log('Found ' + count + ' products'); +` +``` + +### Smart Navigation + +```c4a +PROC handle_popups + IF (EXISTS `.cookie-banner`) THEN CLICK `.accept-all` + IF (EXISTS `.newsletter-modal`) THEN CLICK `.close` +ENDPROC + +GO https://example.com +handle_popups +WAIT `.main-content` 5 + +# Navigate based on login state +IF (EXISTS `.user-avatar`) THEN CLICK `.dashboard` ELSE CLICK `.login` +``` + +## Error Messages + +C4A-Script provides clear, helpful error messages: + +``` +============================================================ +Syntax Error [E001] +============================================================ +Location: Line 3, Column 23 +Error: Missing 'THEN' keyword after IF condition + +Code: + 3 | IF (EXISTS `.button`) CLICK `.button` + | ^ + +Suggestions: + 1. Add 'THEN' after the condition +============================================================ +``` + +Common error codes: +- **E001**: Missing 'THEN' keyword +- **E002**: Missing closing parenthesis +- **E003**: Missing comma in REPEAT +- **E004**: Missing ENDPROC +- **E005**: Undefined procedure +- **E006**: Missing backticks for selector + +## Best Practices + +1. **Always use backticks for selectors**: `` CLICK `button` `` not `CLICK button` +2. **Check element existence before interaction**: `IF (EXISTS `.modal`) THEN CLICK `.close` +3. **Set appropriate wait times**: Don't wait too long or too short +4. **Use procedures for repeated actions**: Keep your code DRY +5. **Add comments for clarity**: `# Check if user is logged in` + +## Integration with Crawl4AI + +```python +from c4a_compile import compile +from crawl4ai import CrawlerRunConfig, WebCrawler + +# Compile your script +script = """ +GO https://example.com +WAIT `.content` 5 +CLICK `.load-more` +""" + +result = compile(script) + +if result.success: + # Create crawler config with compiled JS + config = CrawlerRunConfig( + js_code=result.js_code, + wait_for="css:.results" + ) + + # Run crawler + async with WebCrawler() as crawler: + result = await crawler.arun(config=config) +``` + +That's it! You're ready to automate the web with C4A-Script. \ No newline at end of file diff --git a/docs/examples/c4a_script/api_usage_examples.py b/docs/examples/c4a_script/api_usage_examples.py new file mode 100644 index 00000000..c34f5ddd --- /dev/null +++ b/docs/examples/c4a_script/api_usage_examples.py @@ -0,0 +1,217 @@ +""" +C4A-Script API Usage Examples +Shows how to use the new Result-based API in various scenarios +""" + +from c4a_compile import compile, validate, compile_file +from c4a_result import CompilationResult, ValidationResult +import json + + +print("C4A-Script API Usage Examples") +print("=" * 80) + +# Example 1: Basic compilation +print("\n1. Basic Compilation") +print("-" * 40) + +script = """ +GO https://example.com +WAIT 2 +IF (EXISTS `.cookie-banner`) THEN CLICK `.accept` +REPEAT (SCROLL DOWN 300, 3) +""" + +result = compile(script) +print(f"Success: {result.success}") +print(f"Statements generated: {len(result.js_code) if result.js_code else 0}") + +# Example 2: Error handling +print("\n\n2. Error Handling") +print("-" * 40) + +error_script = """ +GO https://example.com +IF (EXISTS `.modal`) CLICK `.close` +undefined_procedure +""" + +result = compile(error_script) +if not result.success: + # Access error details + error = result.first_error + print(f"Error on line {error.line}: {error.message}") + print(f"Error code: {error.code}") + + # Show suggestions if available + if error.suggestions: + print("Suggestions:") + for suggestion in error.suggestions: + print(f" - {suggestion.message}") + +# Example 3: Validation only +print("\n\n3. Validation (no code generation)") +print("-" * 40) + +validation_script = """ +PROC validate_form + IF (EXISTS `#email`) THEN TYPE "test@example.com" + PRESS Tab +ENDPROC + +validate_form +""" + +validation = validate(validation_script) +print(f"Valid: {validation.valid}") +if validation.errors: + print(f"Errors found: {len(validation.errors)}") + +# Example 4: JSON output for UI +print("\n\n4. JSON Output for UI Integration") +print("-" * 40) + +ui_script = """ +CLICK button.submit +""" + +result = compile(ui_script) +if not result.success: + # Get JSON for UI + error_json = result.to_dict() + print("Error data for UI:") + print(json.dumps(error_json["errors"][0], indent=2)) + +# Example 5: File compilation +print("\n\n5. File Compilation") +print("-" * 40) + +# Create a test file +test_file = "test_script.c4a" +with open(test_file, "w") as f: + f.write(""" +GO https://example.com +WAIT `.content` 5 +CLICK `.main-button` +""") + +result = compile_file(test_file) +print(f"File compilation: {'Success' if result.success else 'Failed'}") +if result.success: + print(f"Generated {len(result.js_code)} JavaScript statements") + +# Clean up +import os +os.remove(test_file) + +# Example 6: Batch processing +print("\n\n6. Batch Processing Multiple Scripts") +print("-" * 40) + +scripts = [ + "GO https://example1.com\nCLICK `.button`", + "GO https://example2.com\nWAIT 2", + "GO https://example3.com\nINVALID_CMD" +] + +results = [] +for i, script in enumerate(scripts, 1): + result = compile(script) + results.append(result) + status = "✓" if result.success else "✗" + print(f"Script {i}: {status}") + +# Summary +successful = sum(1 for r in results if r.success) +print(f"\nBatch result: {successful}/{len(scripts)} successful") + +# Example 7: Custom error formatting +print("\n\n7. Custom Error Formatting") +print("-" * 40) + +def format_error_for_ide(error): + """Format error for IDE integration""" + return f"{error.source_line}:{error.line}:{error.column}: {error.type.value}: {error.message} [{error.code}]" + +error_script = "IF EXISTS `.button` THEN CLICK `.button`" +result = compile(error_script) + +if not result.success: + error = result.first_error + print("IDE format:", format_error_for_ide(error)) + print("Simple format:", error.simple_message) + print("Full format:", error.formatted_message) + +# Example 8: Working with warnings (future feature) +print("\n\n8. Handling Warnings") +print("-" * 40) + +# In the future, we might have warnings +result = compile("GO https://example.com\nWAIT 100") # Very long wait +print(f"Success: {result.success}") +print(f"Warnings: {len(result.warnings)}") + +# Example 9: Metadata usage +print("\n\n9. Using Metadata") +print("-" * 40) + +complex_script = """ +PROC helper1 + CLICK `.btn1` +ENDPROC + +PROC helper2 + CLICK `.btn2` +ENDPROC + +GO https://example.com +helper1 +helper2 +""" + +result = compile(complex_script) +if result.success: + print(f"Script metadata:") + for key, value in result.metadata.items(): + print(f" {key}: {value}") + +# Example 10: Integration patterns +print("\n\n10. Integration Patterns") +print("-" * 40) + +# Web API endpoint simulation +def api_compile(request_body): + """Simulate API endpoint""" + script = request_body.get("script", "") + result = compile(script) + + response = { + "status": "success" if result.success else "error", + "data": result.to_dict() + } + return response + +# CLI tool simulation +def cli_compile(script, output_format="text"): + """Simulate CLI tool""" + result = compile(script) + + if output_format == "json": + return result.to_json() + elif output_format == "simple": + if result.success: + return f"OK: {len(result.js_code)} statements" + else: + return f"ERROR: {result.first_error.simple_message}" + else: + return str(result) + +# Test the patterns +api_response = api_compile({"script": "GO https://example.com"}) +print(f"API response status: {api_response['status']}") + +cli_output = cli_compile("WAIT 2", "simple") +print(f"CLI output: {cli_output}") + +print("\n" + "=" * 80) +print("All examples completed successfully!") \ No newline at end of file diff --git a/docs/examples/c4a_script/c4a_script_hello_world.py b/docs/examples/c4a_script/c4a_script_hello_world.py new file mode 100644 index 00000000..9c71d2e0 --- /dev/null +++ b/docs/examples/c4a_script/c4a_script_hello_world.py @@ -0,0 +1,53 @@ +""" +C4A-Script Hello World +A concise example showing how to use the C4A-Script compiler +""" + +from c4a_compile import compile + +# Define your C4A-Script +script = """ +GO https://example.com +WAIT `#content` 5 +IF (EXISTS `.cookie-banner`) THEN CLICK `.accept` +CLICK `button.submit` +""" + +# Compile the script +result = compile(script) + +# Check if compilation was successful +if result.success: + # Success! Use the generated JavaScript + print("✅ Compilation successful!") + print(f"Generated {len(result.js_code)} JavaScript statements:\n") + + for i, js in enumerate(result.js_code, 1): + print(f"{i}. {js}\n") + + # In real usage, you'd pass result.js_code to Crawl4AI: + # config = CrawlerRunConfig(js_code=result.js_code) + +else: + # Error! Handle the compilation error + print("❌ Compilation failed!") + + # Get the first error (there might be multiple) + error = result.first_error + + # Show error details + print(f"Error at line {error.line}, column {error.column}") + print(f"Message: {error.message}") + + # Show the problematic code + print(f"\nCode: {error.source_line}") + print(" " * (6 + error.column) + "^") + + # Show suggestions if available + if error.suggestions: + print("\n💡 How to fix:") + for suggestion in error.suggestions: + print(f" {suggestion.message}") + + # For debugging or logging, you can also get JSON + # error_json = result.to_json() \ No newline at end of file diff --git a/docs/examples/c4a_script/c4a_script_hello_world_error.py b/docs/examples/c4a_script/c4a_script_hello_world_error.py new file mode 100644 index 00000000..895d7fe8 --- /dev/null +++ b/docs/examples/c4a_script/c4a_script_hello_world_error.py @@ -0,0 +1,53 @@ +""" +C4A-Script Hello World - Error Example +Shows how error handling works +""" + +from c4a_compile import compile + +# Define a script with an error (missing THEN) +script = """ +GO https://example.com +WAIT `#content` 5 +IF (EXISTS `.cookie-banner`) CLICK `.accept` +CLICK `button.submit` +""" + +# Compile the script +result = compile(script) + +# Check if compilation was successful +if result.success: + # Success! Use the generated JavaScript + print("✅ Compilation successful!") + print(f"Generated {len(result.js_code)} JavaScript statements:\n") + + for i, js in enumerate(result.js_code, 1): + print(f"{i}. {js}\n") + + # In real usage, you'd pass result.js_code to Crawl4AI: + # config = CrawlerRunConfig(js_code=result.js_code) + +else: + # Error! Handle the compilation error + print("❌ Compilation failed!") + + # Get the first error (there might be multiple) + error = result.first_error + + # Show error details + print(f"Error at line {error.line}, column {error.column}") + print(f"Message: {error.message}") + + # Show the problematic code + print(f"\nCode: {error.source_line}") + print(" " * (6 + error.column) + "^") + + # Show suggestions if available + if error.suggestions: + print("\n💡 How to fix:") + for suggestion in error.suggestions: + print(f" {suggestion.message}") + + # For debugging or logging, you can also get JSON + # error_json = result.to_json() \ No newline at end of file diff --git a/docs/examples/c4a_script/demo_c4a_crawl4ai.py b/docs/examples/c4a_script/demo_c4a_crawl4ai.py new file mode 100644 index 00000000..53e4e830 --- /dev/null +++ b/docs/examples/c4a_script/demo_c4a_crawl4ai.py @@ -0,0 +1,285 @@ +""" +Demonstration of C4A-Script integration with Crawl4AI +Shows various use cases and features +""" + +import asyncio +from crawl4ai import AsyncWebCrawler, CrawlerRunConfig +from crawl4ai import c4a_compile, CompilationResult + +async def example_basic_usage(): + """Basic C4A-Script usage with Crawl4AI""" + print("\n" + "="*60) + print("Example 1: Basic C4A-Script Usage") + print("="*60) + + # Define your automation script + c4a_script = """ + # Wait for page to load + WAIT `body` 2 + + # Handle cookie banner if present + IF (EXISTS `.cookie-banner`) THEN CLICK `.accept-btn` + + # Scroll down to load more content + SCROLL DOWN 500 + WAIT 1 + + # Click load more button if exists + IF (EXISTS `.load-more`) THEN CLICK `.load-more` + """ + + # Create crawler config with C4A script + config = CrawlerRunConfig( + url="https://example.com", + c4a_script=c4a_script, + wait_for="css:.content", + verbose=False + ) + + print("✅ C4A Script compiled successfully!") + print(f"Generated {len(config.js_code)} JavaScript commands") + + # In production, you would run: + # async with AsyncWebCrawler() as crawler: + # result = await crawler.arun(config=config) + + +async def example_form_filling(): + """Form filling with C4A-Script""" + print("\n" + "="*60) + print("Example 2: Form Filling with C4A-Script") + print("="*60) + + # Form automation script + form_script = """ + # Set form values + SET email = "test@example.com" + SET message = "This is a test message" + + # Fill the form + CLICK `#email-input` + TYPE $email + + CLICK `#message-textarea` + TYPE $message + + # Submit the form + CLICK `button[type="submit"]` + + # Wait for success message + WAIT `.success-message` 10 + """ + + config = CrawlerRunConfig( + url="https://example.com/contact", + c4a_script=form_script + ) + + print("✅ Form filling script ready") + print("Script will:") + print(" - Fill email field") + print(" - Fill message textarea") + print(" - Submit form") + print(" - Wait for confirmation") + + +async def example_dynamic_loading(): + """Handle dynamic content loading""" + print("\n" + "="*60) + print("Example 3: Dynamic Content Loading") + print("="*60) + + # Script for infinite scroll or pagination + pagination_script = """ + # Initial wait + WAIT `.product-list` 5 + + # Load all products by clicking "Load More" repeatedly + REPEAT (CLICK `.load-more`, `document.querySelector('.load-more') !== null`) + + # Alternative: Scroll to load (infinite scroll) + # REPEAT (SCROLL DOWN 1000, `document.querySelectorAll('.product').length < 100`) + + # Extract count + EVAL `console.log('Products loaded: ' + document.querySelectorAll('.product').length)` + """ + + config = CrawlerRunConfig( + url="https://example.com/products", + c4a_script=pagination_script, + screenshot=True # Capture final state + ) + + print("✅ Dynamic loading script ready") + print("Script will load all products by repeatedly clicking 'Load More'") + + +async def example_multi_step_workflow(): + """Complex multi-step workflow with procedures""" + print("\n" + "="*60) + print("Example 4: Multi-Step Workflow with Procedures") + print("="*60) + + # Complex workflow with reusable procedures + workflow_script = """ + # Define login procedure + PROC login + CLICK `#username` + TYPE "demo_user" + CLICK `#password` + TYPE "demo_pass" + CLICK `#login-btn` + WAIT `.dashboard` 10 + ENDPROC + + # Define search procedure + PROC search_product + CLICK `.search-box` + TYPE "laptop" + PRESS Enter + WAIT `.search-results` 5 + ENDPROC + + # Main workflow + GO https://example.com + login + search_product + + # Process results + IF (EXISTS `.no-results`) THEN EVAL `console.log('No products found')` + ELSE REPEAT (CLICK `.add-to-cart`, 3) + """ + + # Compile to check for errors + result = c4a_compile(workflow_script) + + if result.success: + print("✅ Complex workflow compiled successfully!") + print("Workflow includes:") + print(" - Login procedure") + print(" - Product search") + print(" - Conditional cart additions") + + config = CrawlerRunConfig( + url="https://example.com", + c4a_script=workflow_script + ) + else: + print("❌ Compilation error:") + error = result.first_error + print(f" Line {error.line}: {error.message}") + + +async def example_error_handling(): + """Demonstrate error handling""" + print("\n" + "="*60) + print("Example 5: Error Handling") + print("="*60) + + # Script with intentional error + bad_script = """ + WAIT body 2 + CLICK button + IF (EXISTS .modal) CLICK .close + """ + + try: + config = CrawlerRunConfig( + url="https://example.com", + c4a_script=bad_script + ) + except ValueError as e: + print("✅ Error caught as expected:") + print(f" {e}") + + # Fixed version + good_script = """ + WAIT `body` 2 + CLICK `button` + IF (EXISTS `.modal`) THEN CLICK `.close` + """ + + config = CrawlerRunConfig( + url="https://example.com", + c4a_script=good_script + ) + + print("\n✅ Fixed script compiled successfully!") + + +async def example_combining_with_extraction(): + """Combine C4A-Script with extraction strategies""" + print("\n" + "="*60) + print("Example 6: C4A-Script + Extraction Strategies") + print("="*60) + + from crawl4ai import JsonCssExtractionStrategy + + # Script to prepare page for extraction + prep_script = """ + # Expand all collapsed sections + REPEAT (CLICK `.expand-btn`, `document.querySelectorAll('.expand-btn:not(.expanded)').length > 0`) + + # Load all comments + IF (EXISTS `.load-comments`) THEN CLICK `.load-comments` + WAIT `.comments-section` 5 + + # Close any popups + IF (EXISTS `.popup-close`) THEN CLICK `.popup-close` + """ + + # Define extraction schema + schema = { + "name": "article", + "selector": "article.main", + "fields": { + "title": {"selector": "h1", "type": "text"}, + "content": {"selector": ".content", "type": "text"}, + "comments": { + "selector": ".comment", + "type": "list", + "fields": { + "author": {"selector": ".author", "type": "text"}, + "text": {"selector": ".text", "type": "text"} + } + } + } + } + + config = CrawlerRunConfig( + url="https://example.com/article", + c4a_script=prep_script, + extraction_strategy=JsonCssExtractionStrategy(schema), + wait_for="css:.comments-section" + ) + + print("✅ Combined C4A + Extraction ready") + print("Workflow:") + print(" 1. Expand collapsed sections") + print(" 2. Load comments") + print(" 3. Extract structured data") + + +async def main(): + """Run all examples""" + print("\n🚀 C4A-Script + Crawl4AI Integration Demo\n") + + # Run all examples + await example_basic_usage() + await example_form_filling() + await example_dynamic_loading() + await example_multi_step_workflow() + await example_error_handling() + await example_combining_with_extraction() + + print("\n" + "="*60) + print("✅ All examples completed successfully!") + print("="*60) + + print("\nTo run actual crawls, uncomment the AsyncWebCrawler sections") + print("or create your own scripts using these examples as templates.") + + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/add_to_cart.c4a b/docs/examples/c4a_script/examples/add_to_cart.c4a new file mode 100644 index 00000000..0625478c --- /dev/null +++ b/docs/examples/c4a_script/examples/add_to_cart.c4a @@ -0,0 +1,7 @@ +GO https://store.example.com/product/laptop +WAIT `.product-details` 8 +CLICK `button.add-to-cart` +WAIT `.cart-notification` 3 +CLICK `.cart-icon` +WAIT `.checkout-btn` 5 +CLICK `.checkout-btn` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/advanced_control_flow.c4a b/docs/examples/c4a_script/examples/advanced_control_flow.c4a new file mode 100644 index 00000000..4fd3dc06 --- /dev/null +++ b/docs/examples/c4a_script/examples/advanced_control_flow.c4a @@ -0,0 +1,43 @@ +# Advanced control flow with IF, EXISTS, and REPEAT + +# Define reusable procedures +PROC handle_cookie_banner + IF (EXISTS `.cookie-banner`) THEN CLICK `.accept-cookies` + IF (EXISTS `.privacy-notice`) THEN CLICK `.dismiss-privacy` +ENDPROC + +PROC scroll_to_load + SCROLL DOWN 500 + WAIT 0.5 +ENDPROC + +PROC try_login + CLICK `#email` + TYPE "user@example.com" + CLICK `#password` + TYPE "secure123" + CLICK `button[type="submit"]` + WAIT 2 +ENDPROC + +# Main script +GO https://example.com +WAIT 2 + +# Handle popups +handle_cookie_banner + +# Conditional navigation based on login state +IF (EXISTS `.user-menu`) THEN CLICK `.dashboard-link` ELSE try_login + +# Repeat scrolling based on content count +REPEAT (scroll_to_load, 5) + +# Load more content while button exists +REPEAT (CLICK `.load-more`, `document.querySelector('.load-more') && !document.querySelector('.no-more-content')`) + +# Process items conditionally +IF (`document.querySelectorAll('.item').length > 10`) THEN EVAL `console.log('Found ' + document.querySelectorAll('.item').length + ' items')` + +# Complex condition with viewport check +IF (`window.innerWidth < 768 && document.querySelector('.mobile-menu')`) THEN CLICK `.mobile-menu-toggle` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/conditional_login.c4a b/docs/examples/c4a_script/examples/conditional_login.c4a new file mode 100644 index 00000000..d6817433 --- /dev/null +++ b/docs/examples/c4a_script/examples/conditional_login.c4a @@ -0,0 +1,8 @@ +GO https://myapp.com +WAIT 2 +IF (EXISTS `.user-avatar`) THEN CLICK `.logout` ELSE CLICK `.login` +WAIT `#auth-form` 5 +IF (EXISTS `#auth-form`) THEN TYPE "user@example.com" +IF (EXISTS `#auth-form`) THEN PRESS Tab +IF (EXISTS `#auth-form`) THEN TYPE "password123" +IF (EXISTS `#auth-form`) THEN CLICK `button[type="submit"]` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/data_extraction.c4a b/docs/examples/c4a_script/examples/data_extraction.c4a new file mode 100644 index 00000000..1c87352a --- /dev/null +++ b/docs/examples/c4a_script/examples/data_extraction.c4a @@ -0,0 +1,56 @@ +# Data extraction example +# Scrapes product information from an e-commerce site + +# Navigate to products page +GO https://shop.example.com/products +WAIT `.product-list` 10 + +# Scroll to load lazy-loaded content +SCROLL DOWN 500 +WAIT 1 +SCROLL DOWN 500 +WAIT 1 +SCROLL DOWN 500 +WAIT 2 + +# Extract product data +EVAL ` + // Extract all product information + const products = Array.from(document.querySelectorAll('.product-card')).map((card, index) => { + return { + id: index + 1, + name: card.querySelector('.product-title')?.textContent?.trim() || 'N/A', + price: card.querySelector('.price')?.textContent?.trim() || 'N/A', + rating: card.querySelector('.rating')?.textContent?.trim() || 'N/A', + availability: card.querySelector('.in-stock') ? 'In Stock' : 'Out of Stock', + image: card.querySelector('img')?.src || 'N/A' + }; + }); + + // Log results + console.log('=== Product Extraction Results ==='); + console.log('Total products found:', products.length); + console.log(JSON.stringify(products, null, 2)); + + // Save to localStorage for retrieval + localStorage.setItem('scraped_products', JSON.stringify(products)); +` + +# Optional: Click on first product for details +CLICK `.product-card:first-child` +WAIT `.product-details` 5 + +# Extract detailed information +EVAL ` + const details = { + description: document.querySelector('.product-description')?.textContent?.trim(), + specifications: Array.from(document.querySelectorAll('.spec-item')).map(spec => ({ + label: spec.querySelector('.spec-label')?.textContent, + value: spec.querySelector('.spec-value')?.textContent + })), + reviews: document.querySelector('.review-count')?.textContent + }; + + console.log('=== Product Details ==='); + console.log(JSON.stringify(details, null, 2)); +` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/fill_contact.c4a b/docs/examples/c4a_script/examples/fill_contact.c4a new file mode 100644 index 00000000..07e74572 --- /dev/null +++ b/docs/examples/c4a_script/examples/fill_contact.c4a @@ -0,0 +1,8 @@ +GO https://company.com/contact +WAIT `form#contact` 10 +TYPE "John Smith" +PRESS Tab +TYPE "john@email.com" +PRESS Tab +TYPE "Need help with my order" +CLICK `button[type="submit"]` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/load_more_content.c4a b/docs/examples/c4a_script/examples/load_more_content.c4a new file mode 100644 index 00000000..c8441064 --- /dev/null +++ b/docs/examples/c4a_script/examples/load_more_content.c4a @@ -0,0 +1,7 @@ +GO https://news.example.com +WAIT `.article-list` 5 +REPEAT (SCROLL DOWN 500, 3) +WAIT 1 +REPEAT (CLICK `.load-more`, `document.querySelector('.load-more') !== null`) +WAIT 2 +IF (`document.querySelectorAll('.article').length > 20`) THEN EVAL `console.log('Loaded enough articles')` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/login_flow.c4a b/docs/examples/c4a_script/examples/login_flow.c4a new file mode 100644 index 00000000..c2224994 --- /dev/null +++ b/docs/examples/c4a_script/examples/login_flow.c4a @@ -0,0 +1,36 @@ +# Login flow with error handling +# Demonstrates procedures, variables, and conditional checks + +# Define login procedure +PROC perform_login + CLICK `input#email` + TYPE $email + CLICK `input#password` + TYPE $password + CLICK `button.login-submit` +ENDPROC + +# Set credentials +SET email = "user@example.com" +SET password = "securePassword123" + +# Navigate to login page +GO https://app.example.com/login +WAIT `.login-container` 15 + +# Attempt login +perform_login + +# Wait for page to load +WAIT 3 + +# Check if login was successful +EVAL ` + if (document.querySelector('.dashboard')) { + console.log('Login successful - on dashboard'); + } else if (document.querySelector('.error-message')) { + console.log('Login failed:', document.querySelector('.error-message').textContent); + } else { + console.log('Unknown state after login'); + } +` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/multi_step_workflow.c4a b/docs/examples/c4a_script/examples/multi_step_workflow.c4a new file mode 100644 index 00000000..40bebfeb --- /dev/null +++ b/docs/examples/c4a_script/examples/multi_step_workflow.c4a @@ -0,0 +1,106 @@ +# Multi-step e-commerce workflow +# Complete purchase flow with procedures and error handling + +# Reusable procedures +PROC search_product + CLICK `input.search-bar` + TYPE $search_term + PRESS Enter + WAIT `.search-results` 10 +ENDPROC + +PROC add_first_item_to_cart + CLICK `.product-item:first-child .add-to-cart` + WAIT ".added-to-cart-notification" 3 +ENDPROC + +PROC go_to_checkout + CLICK `.cart-icon` + WAIT `.cart-drawer` 5 + CLICK `button.proceed-to-checkout` + WAIT `.checkout-page` 10 +ENDPROC + +PROC fill_customer_info + # Billing information + CLICK `#billing-firstname` + TYPE $first_name + CLICK `#billing-lastname` + TYPE $last_name + CLICK `#billing-email` + TYPE $email + CLICK `#billing-phone` + TYPE $phone + + # Address + CLICK `#billing-address` + TYPE $address + CLICK `#billing-city` + TYPE $city + CLICK `#billing-state` + TYPE $state + CLICK `#billing-zip` + TYPE $zip +ENDPROC + +PROC select_shipping + CLICK `input[value="standard"]` + WAIT 1 +ENDPROC + +# Set all required variables +SET search_term = "wireless headphones" +SET first_name = "John" +SET last_name = "Doe" +SET email = "john.doe@example.com" +SET phone = "555-0123" +SET address = "123 Main Street" +SET city = "San Francisco" +SET state = "CA" +SET zip = "94105" + +# Main workflow starts here +GO https://shop.example.com +WAIT `.homepage-loaded` 10 + +# Step 1: Search and add to cart +search_product +EVAL `console.log('Found', document.querySelectorAll('.product-item').length, 'products')` +add_first_item_to_cart + +# Add a second item +CLICK `.product-item:nth-child(2) .add-to-cart` +WAIT 2 + +# Step 2: Go to checkout +go_to_checkout + +# Step 3: Fill customer information +fill_customer_info + +# Step 4: Select shipping method +select_shipping + +# Step 5: Continue to payment +CLICK `button.continue-to-payment` +WAIT `.payment-section` 10 + +# Log order summary +EVAL ` + const orderTotal = document.querySelector('.order-total')?.textContent; + const itemCount = document.querySelectorAll('.order-item').length; + console.log('=== Order Summary ==='); + console.log('Items:', itemCount); + console.log('Total:', orderTotal); + + // Get all items + const items = Array.from(document.querySelectorAll('.order-item')).map(item => ({ + name: item.querySelector('.item-name')?.textContent, + quantity: item.querySelector('.item-quantity')?.textContent, + price: item.querySelector('.item-price')?.textContent + })); + console.log('Items:', JSON.stringify(items, null, 2)); +` + +# Note: Stopping here before actual payment submission +EVAL `console.log('Workflow completed - stopped before payment submission')` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/navigate_tabs.c4a b/docs/examples/c4a_script/examples/navigate_tabs.c4a new file mode 100644 index 00000000..180645a2 --- /dev/null +++ b/docs/examples/c4a_script/examples/navigate_tabs.c4a @@ -0,0 +1,8 @@ +GO https://app.example.com +WAIT `.nav-menu` 8 +CLICK `a[href="/products"]` +WAIT 2 +CLICK `a[href="/about"]` +WAIT 2 +BACK +WAIT 1 \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/quick_login.c4a b/docs/examples/c4a_script/examples/quick_login.c4a new file mode 100644 index 00000000..c9e925f5 --- /dev/null +++ b/docs/examples/c4a_script/examples/quick_login.c4a @@ -0,0 +1,8 @@ +GO https://myapp.com/login +WAIT `input#email` 5 +CLICK `input#email` +TYPE "user@example.com" +PRESS Tab +TYPE "password123" +CLICK `button.login-btn` +WAIT `.dashboard` 10 \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/responsive_actions.c4a b/docs/examples/c4a_script/examples/responsive_actions.c4a new file mode 100644 index 00000000..1e08dffb --- /dev/null +++ b/docs/examples/c4a_script/examples/responsive_actions.c4a @@ -0,0 +1,7 @@ +GO https://responsive.site.com +WAIT 2 +IF (`window.innerWidth < 768`) THEN CLICK `.mobile-menu` +IF (`window.innerWidth < 768`) THEN WAIT `.mobile-nav` 3 +IF (`window.innerWidth >= 768`) THEN CLICK `.desktop-menu li:nth-child(2)` +REPEAT (CLICK `.next-slide`, 5) +IF (EXISTS `.cookie-banner`) THEN CLICK `.accept-cookies` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/scroll_and_click.c4a b/docs/examples/c4a_script/examples/scroll_and_click.c4a new file mode 100644 index 00000000..fe9aa57e --- /dev/null +++ b/docs/examples/c4a_script/examples/scroll_and_click.c4a @@ -0,0 +1,8 @@ +GO https://news.site.com +WAIT `.article-list` 10 +SCROLL DOWN 500 +WAIT 1 +SCROLL DOWN 500 +WAIT 1 +CLICK `.article:nth-child(5)` +WAIT `.article-content` 5 \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/search_product.c4a b/docs/examples/c4a_script/examples/search_product.c4a new file mode 100644 index 00000000..030915b8 --- /dev/null +++ b/docs/examples/c4a_script/examples/search_product.c4a @@ -0,0 +1,7 @@ +GO https://shop.example.com +WAIT `.search-bar` 10 +CLICK `.search-bar` +TYPE "wireless headphones" +PRESS Enter +WAIT `.results` 5 +CLICK `.product-card:first-child` \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/simple_form.c4a b/docs/examples/c4a_script/examples/simple_form.c4a new file mode 100644 index 00000000..93b9fbb9 --- /dev/null +++ b/docs/examples/c4a_script/examples/simple_form.c4a @@ -0,0 +1,19 @@ +# Simple form submission example +# This script fills out a contact form and submits it + +GO https://example.com/contact +WAIT `form#contact-form` 10 + +# Fill out the form fields +CLICK `input[name="name"]` +TYPE "Alice Smith" +PRESS Tab +TYPE "alice@example.com" +PRESS Tab +TYPE "I'd like to learn more about your services" + +# Submit the form +CLICK `button[type="submit"]` + +# Wait for success message +WAIT "Thank you for your message" 5 \ No newline at end of file diff --git a/docs/examples/c4a_script/examples/smart_form_fill.c4a b/docs/examples/c4a_script/examples/smart_form_fill.c4a new file mode 100644 index 00000000..1219a7e6 --- /dev/null +++ b/docs/examples/c4a_script/examples/smart_form_fill.c4a @@ -0,0 +1,11 @@ +PROC fill_field + TYPE "test@example.com" + PRESS Tab +ENDPROC + +GO https://forms.example.com +WAIT `form` 5 +IF (EXISTS `input[type="email"]`) THEN CLICK `input[type="email"]` +IF (EXISTS `input[type="email"]`) THEN fill_field +REPEAT (PRESS Tab, `document.activeElement.type !== 'submit'`) +CLICK `button[type="submit"]` \ No newline at end of file diff --git a/docs/examples/c4a_script/tutorial/assets/DankMono-Bold.woff2 b/docs/examples/c4a_script/tutorial/assets/DankMono-Bold.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..3072fd8567c7f38769e8fa161b92417f2630f902 GIT binary patch literal 33480 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 MK{-$E|6vLM0LSWEPXGV_ literal 0 HcmV?d00001 diff --git a/docs/examples/c4a_script/tutorial/assets/DankMono-Italic.woff2 b/docs/examples/c4a_script/tutorial/assets/DankMono-Italic.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..1d01ea6d73be14be9ac9014865475ee73c597fb5 GIT binary patch literal 32468 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 j0=7(^{K1R0G5BwXVt^ZBLDyZBYynI literal 0 HcmV?d00001 diff --git a/docs/examples/c4a_script/tutorial/assets/DankMono-Regular.woff2 b/docs/examples/c4a_script/tutorial/assets/DankMono-Regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..99c1425ce4f3f324c29c0d4ac465bf73ffa363e9 GIT binary patch literal 30528 zcmV)fK&8KTPew9NR8&s@0CzwD3;+NC0L~Nu0Cw&G0RR9100000000000000000000 z0000Df{YFtY8%R49G6H2U;vIV0X7081Cta41_g(D2Ot|e%^4Ip#_kbmO~Y2Y4dGz9 znWbjWrh<(t%WelL(%O8RQAkZ%ZTA2F|NrZf4%h^|qtr*22!$HRDLkBok|C?wxhFMH zBQycy25Xa=sdrr6sUvrL-N=e%No(r60Hkuo03A3Xn2|B1+DQQ#_J)EPjgniT{#yJis)5iC9;AOa##`1_NZ&uliEwJ9?*Gg%!XY7r2jh5u9O z@IT!T>_skDVxkmN@jzM3OLJ=SqS5KY^V|G$?|U{<(*{3t(I!i5Oxk8fVq<~5C^J}; zi()RiUHpn7{$U*7&$(+PF^P#y$aLNg#kU(Jr1cllrf+~0OIniMFOA|#li*RH8H~W< zkZ3%{f-+cF`VH`I`+wFJ1Vsed;(^?38BUy#i1QkfE3>ktX2Zr*N!yk?G2517Hm$8K z?03CQ!-luKW7)kA%li4x^K1H_yjMa#=XF=kzcpkC!~`0m#GQmDUUgcZm~o@}0#QR^ z#DGAc$)H870|BD4_GRDU!4B9!7W4nj*{@B@N&Z-$dvfoJ3X(u@j|mYGB!VY+A%c2K zsQ9*2S-PS+?3=f*YHynst-oCxlj(}CHQ6c649Wch!YI?IM z4y}pSQyQ(N7u;hMrzlz5S#V@p@=Y^mMt2nj=M?3X*l(;aEZhG-HC+~;kXx6}HV2<7 zKRDEoTd&#b+mkKZ82|ri%{slEu43x%_Dtw)cIlp~A`n#~XoL_)yfKZL6CDyKN{~!Y z9S9L`LOQOu*w@){g9G_0?N6h%=Kl~+V}9&qzYDW#MW3ea+!xdDW7`lS_}sW6c20XKh2DW#O6!p;43I-O3Z z)9Eydq9}@@D2k#eilQirqNrG?1u-``&?t(gDT<;Zpa(q0*t)@iTNFi6G)>bq?FQ!- zMNu?O(=<&%ZOoAGlPt)BEXaZ^$bu}$JjNtzZb%)u-U?IJ?jHKTO9rfi z1)vnb)Tm?OwF#_C0BYx7RdfBHbRng@QLRcuR&viY@m(@m_5+A1@x`! zlaUp!R_5Oi&=zPs0B8gwYb3iuxsqkt^3P_I({dFNhp}0YwZ7KPr6`IZ_GUE3-5+DK zR6ingZ`9f-5GHXaJ{$hC-_e@&zn39$u`xu%WQmBFh}e*^dcVC>2)01-*@D+>o?nFJ zM-o4@1Zr?<#4j3u?z=udb$@?;nLqCB+}&$v=bx-c4*ieVCno`)v1UZ@|K`rq)3Xyj zapy6f4hZh~t)mG^CE0%o?C}VB@-<5knJ5W#(&Z>(WY%JpP6O82YPbDPyWonO9vJt^ zo8I%0&wcBAKl<5U{_+1+6Fb5s0x42u$ktmxKZBI1R;$i96>3d#v03I?s8O@aYRb&T$+;0D8JIGYiL=m^)%f2M%&ZDPISI& zT`jAE%B${fPqKItDsiN!S)Gbhlay7V92KidED0r-PF8sY5>f=w#gkMTS(TDok1Q>Y zY&>JW&GwsQsh!U94L?c#_L-r$EQJ?+{2@gVQ#{Gfo!H#)g<&L)G4fzx#tRZ29{#3= z2Y{og;(yRvd???WA1X$OQ^b$NU&KY?CUK{DSd15w#J~7_zMQY-@A8k4K!zw0(#fe; zMz+}GC|0EbpMr^l-zrgpz@s7YH7Q zh=QJtM@T|W)5Jy?>R*s>s4ZR5Ft{mHfCCwfu*17z)ApFX2NTYmPxY4^g(_6L>xmUm zOw05w-BqexnO9oF5i1S$CJ36u6v%Z}kFb=YhQ68YsZ~swTGvoD=9bo&9 z*VfL?m0Yeyy(X~BVAYHy;@&^0Y)?k zt|!Na#h*PAgV`;WJU4M?qLB8~f}s^ka_ZbS6;PE;!pZ)%TG2L?LcE*vB`j`^V$u9s z&yDGV#!`cV{(SM>z8XRDG)r!Ol)rBY6C2 z`B`IA7jpms2?dQ9DKg|J;IAs5lpwTn7|z&>a&>jPM-J#nUBbt6{QbEWOmqbiK>-F1 z4gm>;90j~8ohN|EZS-d;0p>=5%Lo8922Hk`_!}ojw|08j$radj(=}d+nPlO-=adG+ zn=a07mG%QFO{-(3GNZhAb-O{ed(bmZb|`;7r+TON*9=iw@~B|__70i$BNkHj$A%0k z8Py+-d5BiTA@O)QL2b9d?Ev^HS={xDmY{~D?DUITbZ1sZ^?c+d67WVhXg%)ILk>AUa7gaAw8Y+>CGCXo1CRJ^EeVG@wSIEQF|?@2p4RE$Z6goKk@iP6_Ndi~ z;>q=o-=kXbiFVgqW^gJDTJv(ueFCbzuF)C%@wFp$El&xz?wlq~bgiG|K!u&ydo!Z~ z=50^Iq9*-LP^|UXHPofwM|ac&?}~!l;Ym$Pg>7 z+p!i}wF>|Sh~axmPkkyPL#~S699|=H+anP`0bq&`>v*Kpnx_NmAeH3JSS{S28k3V1$J&r-VXCXg~m+ircENa!V-wf^8CtZ%S%f2 zE7gm<{Y%{Ph*~MIA(LLnQ!VSt{`TbTc&k_LN)%Fc6kDw|;}sWA5$-4^xt`yaUFCgQ zw-p_3ed&P6ob{|O={R`r=FTW}=PNf`Y<$v@UZoCguC6PNE9x5k37?|e$-P~A*BtPJ zqud7k@&tM{yaaKhQ(OXbp_vYQ&_5cIs)7dk?egvZ%GG1H;+a<`Y&z!5T$t;VHRbcH z_ic~wsy1w6SGTEKwVgY*XZHGDS=SbCusi9NE@+^(UP28gvEoJ)B z+GRT_FW+z0vIuFI*!s*?;jpNrES!9z(x#F<@#!Vgh*>~2gs}9Dx!7LuiaT-oVwxhi z)ukNPy19Fhy6N&-vEFR+0+B>ITdcLY1w^G}HFVDCj_B4_qf!5_L>J|9wK^_jux^5B zqf`1=l)_3R+C(6x5E>ud|N2~cYGSP%*{{r42|9S)8{C~zMBK9Uz~CT`pm?w-g*81L zNS|a~1UMS?-Nk6ZvAIJpIG?*%GDdK)HDKS)na$Z`-7@0YO%*ApWRs}3bbYFzW4_E; zNlTOp6lg_CuqT9{jXNNfnoLq_hojpA7evX6n_Ni*(T|@X0d;5vvra0y81gzX;6y%A zShM6`rp~uvrm^T4Dld^b?Lch5%#V%4>C}e^MEatHaEAPK{qk#_4V{OFk}wy)6UT&* z)qMYFe@z}IoLJGDQ8CtzO0>NtV+Pr zQkfp>agLRbyYxQZ?Rz(dKr>!`GcVo5$NjylU-$M|x)><=`%!;&#`Rm^PTsLZ9PP$g zlXPM*Z)sOdg;=u1xLDKx?3%a?1g7mj_|YGCpZR(RmcX=xNV=CclUVV@my*7&?tSMKDmMna7w7|h6B2O6MP8!jC+f?l7*ZcZiWT?b#15t1UfCSv;0 zRdPk30wk2DL5Q6aa}SMEg5d{8i5~1b{Nxc93>}NDf;qr7IyN!hH41x<fR>~&`tquSNSpth8;0-* zGSJ_{j0z5DFue|Aa*%^=3Q3KRtTr5wfi-Ia{w9>L%Bbq*wHv!_y>99kDWj%ouq`_S}e(-bSv6OhaQpPa0%$@MqX5W(9~8?nFf} zGtx&8RtD4Cbi9tAQNbDRLN+K9cL)2rB{Uum#VJM$db#v z-e#n`cJP{Jj1a}inp93_*u&#JW8i$YMK04hiYj%XX(Nh_#XcxsvTT==#l5+Hca0$# z5G5hzi-Wd1L(v%@h}(KGEl_l3H|pnR?~h50b&bHWV_xFGL=o9$VbZdy&H|&Z>n#dc z*R4n|@>v6WkTrXrr-~Q|GEoqp#9MXz3b25qM-bdAm{-nm6rULV3xsf=Nu|a$ zXtAV2-sM6UvD(E_#PBi6GDK*KsGSAkGt4a|Oc_@8g&~oiFN9sg%y(q#Hlv%!U#*!W*&^v9dn`^vSPUcNt8N}AJYoi>xA zRdSsF#A`(QQq*zrQWLZMZ$jA(NbHa$~4K7pSuimpQKr36j5$aG9KR!Qtrdf5S5 zCEZ+SZf#PqoQlFq=B62Z^Dqq+lV}Wih4Zp2SaV^s@xL}rv=XrFjP2~VjgKE4Efiu5 z?(8%m^wjBTvi7(6QhlYXtNxCnO>^PC(ImAR{Qg%I0eY2Q6$FKJ5flak8jS2I!!|6G zhD4yJx6cc5MJFR&+`Vn?zzQfTmNl>x($X@~^d{1pXIql9*E77uMxK8H+E7ZCy_Cy& zUe1{3grxAfwDBBSugPApGqff_F&D$6ns%Dkvy2%TxiUUqD-(#+)5|OIsYRrn38wL# z4Jm=n&OU#r8~GwV(Iq%nWzr$1t- z<#N)`J{x5Ikuwq!nvmql^P6 zpc{494qrmFN2_?$rJZTXB;-eysg8gX5NLFLyYah}VL4)HF~JG9VgkE6Qfwr~wrBle zxFuo?skx%Lu=M0#>uN8jR_Ton>|Sd3NuEEhSjl_Eo(J032_IClW=a9D(-cg zGET{Bz3bWN2;s}>SeJJ51vSBqy>Fa(SoRW>fuSZEitm*n!cqsRI3>8y4%AV65%|ry zH%y2f`fYAF+lpw7fOH-i2=70Fz%UbpF_A{n%jV|O6cn0dEpKC2PUkX+US1&8PdH1U zUL-# z0f%vr+z%bn0x9-7)Of^~;PORuC8aUVlO>x%=jedM?Q)eB8I3Ry3xv`PvRS2GfBfQw zURoxEP92EA8ln{>wW>rj`3fql{H9Txs4p+HE&*;`TrJp8Goji!k63~uMpvq{CJ1)&V6O{C_Nj47A$?EU1MohxWSRz;J-<5k8e%P10cHM%6k zk<*&Yo(){D8ZG=$qh@woIcM+CMi%Ql4an{T4AYp<$^tn09ZGFfOCv->bOdS@NaoG< z!4@p=cMiYi8j@Kae6BM%tR)i8lj&=5HLG~}UiwpFCN`1-lMX?eq7)p)uoR+tp#%)D z9g1NMj6pf9hdrZUoUsJlYsIoOCy7;^8X!CvH}PatZ@VFw&_ z$dNvVQ-|Y)b8xzF9+&GvaK$Az^IA2y1rOYvM{pkxJvM%LQVm|g^J?%q-p)7D;2n6& zhxuML_$Uo_b{|h8=z<$;Lf5^-Rd^RYb_=)BtAji2PmCHb43oX+oflYVckV}jyw_rW zGCPPVW|(OfjZz;(8|hu|*Q!O;wXW@@!kc!s%cM`5RiDOg>+hcC?fm5$4;Xf^G0M2# zRN0nZrkSDUmRjCBeYgZYt2&WutrjFw{f?0?SKj)K`j^US{1e^!mkz%SnU>||_9@La zzO?0~{9kH>mO>jL0#@}M)4mA#LZMK?nzJsfJDX?BV8!en6I_ZN{`v02Cl{rhqT;$- z{nw0_V_z=QcF=xc%KO#$wfnD^f6e)A;%^gvTiV*@wg0pp*Lp$gMXj@6DR`ymmAkfW zZ4+(lbnohtb%@Ii-y8l{-l@>aL*?t>tH2px0zqI2JV9De6qE$FgS)|f)DTT*pd0lg zg<>>@YS93aAq8@wTvUlTjD>^YZpaDUAtPjk+|UtLTGRe@`{QFmSNjq6qwL4pPq&|C zzq`lY9_98W_74m*3=<96VZ3jW@nG1q-@E?q`}>67kG=X)Y$|pTeZ>9vT6$FqluG3m z@^Cp&o+R&5jwNZRKl+Uu$qEb-2~0VUsnl*E_ayJosj3r-@E!Z{BTd zv>nm*+}rt{?q%MlijlQlEs<%d2s{*NmM!yS)4K<$PC~ zW$k)SDcfW1-_CXFQ=Ft(r^GU;N85Eyf2*QJy>%6@HY&5`Il-@Diai!%JN9EPHsKQH z+TOMJIrFuFT9Hg65)vdl5+ViUIo*wJG@>Qt(q^ip1^T}jzCk~Tp$1PNWV^G2VfK$% z*@(Z#HN2Obc~`tGxHuMG;gt^AC$XH8TxpSQk|jg(xjIh$Q6csEf0j^}=~LJBg|_Nh z-O}JoPj65DY;&jocEuepGjivRAoA$q&WdcyQyzK_s$e5Rg`HMbu%7KC6C^=_22(aT zMw-p^8!!`wV`fa++_4Squ!rrmk(GAB%B1@cjrF2;#^O6^kVZ`QhW3B%JORS zLc20c-+bpk`J8-3e!E-UYpQ8p_l7SXTksd`1>sNs`!5utP#0#J>81)r<46lzuo7Vl z-SqIwc8h*R+h^j*&7rei!Y}C`)mXkpUXQE%dncpvD^zQv`a-j_Sg1$-=R-l1{a{dv z`~u7OAVNttk=<}``X8u)qJ%cncZswEBu#JT@A7^gS?RCYGWju~h#LHLkbE~qncNwG zEPg%%$TW5QV-9<|PNC+gw)l`ixk{^Zc!n%f+EPRr5=^DlIXvSI7FxNLJ6bq|J@Q7^ zSV2b?QHtyiRm>$*q%gsk7aE8Cc}9K7_sDPo(zyLPu(r!d==4Q-AC(V2bJMk_3?qE4 z55n2wrZ}K6R-d>8l5W~5W47ni`?4I;J&LD!_9RTRb|G#Vtyg#HPPBPxMR}3?Y&Y3! zO7#up8Om&f6kMrpj6c6)a*tJFa-Oz8-iZ$cx*Mguj_;1IPUX)PXHPUvi{JU;4uZlx zt#WjIBynd@&BNiw27};EGGe9d{(62+0^SG$AR}JJ2Bw;|2-~)x;Ke}341qKaje!BXrV>`JhFY@9E)$JNnthXH>B*%27 zCYQ>WVLEN|&@ADz`;rZJrCM8FU#&-2xDJT}0)4nh2wzGFFaac^+VrC0z*9MwlM*GZ zV*uAA*=mH$A}%2;A`*BmMthAw35mFb#4?lPwT{q3g}`+H5Z;nbCAfS|l2Jy;OkTJ=@h@vL|^}e&q zbEf0DxBR(RvB1eQ4T9UX*6~qphvY9+`WwvrCejtUp0)r+p$I}S;0VJeY-S|FNO|>R z3MVaXN#YoOp~Nb6{S=}@;R^X{oAc701eR?8;}g@#`i-#P6v}Nw!Zw;sP6TiM-r6iAW?8K3X`$ zm11M{dV}6*xU-jX$hWe1=nxA75}`5x)4hS*+jvCxjFPJ2H3j*n|F~$pkuP8%?HNRp z73lMG&+pp;FUl_I(|v3(374b24(3%ubpPdm%5YXkz+yNuPwLB9#LVG3bxsbtqlKP2)C7p^oel%ztf^DGM28)%a%uNnUY0(P9FCyigP zEub)7xC6ToE@5nVNDf0sV?WkxFRmy@7_e|dz*j@DhPuzj>E)V4=M5k20VeIC0>mTQT?mrkv(H0`%&Acj(YO!O54BrfZihq?~#4$N0NGZ zfVwX}Wz1ydl#0yZR&(lEAjb|;ykO-Q`qBmOIh(y&bG~_9am997%afItcd)MjFN>Os z&7agKPEy2%y+$Y2kO;$b?rzTV+hxh*oD(^JdV=1qio6KkIo?YSE5kJ@7d|+?ZG3#? z=9gvK6%JR5&lq?G4tz9aTJpWf4n!s@iR5YA0(pO_u|x{v_9O&@Yf$iIz{3lO%KoC1 zeE7I%*-#xIP<0>Tkk4Jm7kyt2&d;Fn7PC|%XEgAnrA4Qah7cyf`t+?|`d8ea6}9nV zkk;uh7pUzb;1ON-uXe}UBN$C)fZ+gZ1E(f{y<41gW4zsX208<@$;=Emf2lV36kx>z zawJ=DNDV`aDGG7NZ#d~quPEVySY_h@HS1P-_%LF45_;1ppv~@wXd`&SAkxv;nR%!W zZ6CqeJ_q<>7Jp54=vbNxI~6Bhpl)fwY64Lc_oGz}2XFccX&kf#3PRKS;Owx1E+=^a z?gh}ci;D(NylgA8A-rD}+T$;P1~AXTc_DBv2HH_;Z3UJxuSDlGOFt885w~2n45xhy=^l09IsJl= zgFo!@FL7U}(NjPYxI

yh+Ty9rYa)6a)&ILKHOk9BM}q>RaII2j=cC*A5YzcZN03 z3@#{!N+;l}Ij41cfkJLL4xyz{4(;$tk6PV}wnoGM%2sBf}=&}oZZGL zERo$Q=&UwQwZ|s61JKNl(~lVAMx<5h`KS!=F^I)qia$p@^X97bwHXH88eOv^v;TeU zG2B|nLO92pn!7u6qeVFLyfOF=uR#ojKomGk$sPfxYueT$FN2P^=qZ*(54Lgs^a)Wq zdjEWf@^b6e!PZK}OTKUZeiw|CDHI4*c|+goU`-&f232Gmwfh-8r=cv*WoUXEq=ms_ zMfw3=-``8cj*b>g5SMOe7`)qCr`5E{p=iE4A1Ypyo{e=6v&b@bbl>EpVy z*Pkq(2smmjwX|VCi(8vVWX)%%9j@;dkjLK!hUwGOvvW$Daed;}4O`{38Lr%-u#>zT z5RsFgepI^YfJmDrUk)$CPMIGZ_VHXxy$+rJ#}Ya~ zm;PEk_21e-PeiEEN~w(hzP|;1_C*xaKXZE{Ftw7k4UTG;xP(JJf@=e5(@NGHg(7_d z_yV8aM}3byo3DLDzYS=wHx_UOm3gjw)zT~y524PyWX6b(=wbNQ0a}*DP2ut#NM0$)mJ5ys!ieULx-(NkAXs(IwjnLgz(H!=m$&AeOs$KKDuD z1W+9eeI~WUZ-td^W9o|}S#5^um-RLWdR_gtf4iZ@UOIxi?SWtzHt;)f*C?rS3@tU) z?;E&X=u+G}H6-QX;Y*50w4@btHt@ef2i^T@ba#ri`x?VJVzMoc_nB6!M0X3JEf(6G z)*tK#Wmk-aYLDC{M!HYi?{u*1@L{Km-g#7VTQVb)ft&V--Xu1%Z2@=Pq@xsG$dNM>HpOLi|>3C;E`A>FIpQ=y4@ z{Ilc=2XUyE#_xn$l=+iCQem4B>b(OS-bj=yV>Bu0bCV0;Os>ulLDP1gWGBnZi?5V? zgnVrzlQ=pZhjQw3fjSQ-h?I9afHqGeaAsW~5Io>}I~u0^+G%rl*F`>} z6hMU7D?kABF{qLoYi{z6OyXKneR^zK=Z!tS$eeg#%^prtNNmV?wXjyMZ#O$g4MZiz z2w-kGX0KoMt-%eO1Km9?TI^=gCbVf$P^-foI4Ze=(;HMkzku}@HSo%98t4DTD6e0l z9qgJ||IuYf#jtPR3jZfR$zRLYoe7Xz(>pqcp7u+v7~|H&mJh5S!SmxcbaT-1_`HhI zVVam|c5fYsh;{142rjWwQ+CDked9u385CRe<>NWWK9&ji>*k!((vKEetq8z{xLPFP zEx?06yx$-)pPq%yq1Ggfr1HJWV}yT38Kea$S8XG3W%cD-9@%Sx^*Fh{1Ryt zxmK>J0wX4`fBop=Qw{TGHQXLlegM0&IADVT+D7yy5Ze^@{-t-tf-Ad_HdA<@m;~O# zHAO_t>NeD72psDRu7`uhM{O8loS7rMY=b!ORNlLPIXN|e6wt0Fb&m3$L;#GRhVsA% z3RnnEj*c?%iAONYB#>%0-wMdHXMh6wthYc2+Peoq)fM^@Pzews=nKwOsF*p!A{$=9 zZCSLgorA|6L(w_BDm1+ocI9qI=+9+)lx5azWAilS{Zoo8pvi$ALxNY@{C9wNeviZz zPIVVlIf2S&&h*c^Tu*oPkul%&>icpR@3@PpJ!O$|=Jz&zTGqbe^XQ1*P;jM{?DFNI z-{hdHTh!n&Q7Q|&A;b@x_y+*Ta)49SF0Q9uEIhAilV@pwsI?_yBmlVeCXRG^c{55| z0|3A~x%o81x)r=Z%aSL5|5{`T9niOEASkU5uH<}?GiD>1t&cE&t0lN3D3-<%GUu`8jBiDZ8dz6R7+ zxJz>YhmG{{Nn!8KxtFQi?LxmQwn!zViKBnozuq%@R{7*qMaQd?VE>)^Y1l|FQ}cvt zUoIHWZgh?qsSg*=Q@)&!ZBtLl|2KAjIVJNy1g_GxY5)qiNF9uFa`_8Dg3PaIVD9*M zr+do+6i80bb?h9sg$lCg&gMYfx2d-8wg`lw7uO~(F})N9{l&$W`wT>X?F%o!-)HTd zf`0i}#Pru^i~%~u+jQX*8N19dm85PCmvEycD~K%&YT<%^9r)pY0s_5TtphD+C2WU?zu$2A&~#?qU9xueSF6->o;C()RrbCTWinw+CYr%^(QjEq)G^TC+u3j|)a;1(qxFDHdEDL8PB@2g6R zg1*t{IV=emBu_bNzYp;aqKsxTCl-g>~-^>*bOE*SNap&g4;Rw&V8|OYU?YmR>=in#^?#$FBU;UU#LKb`ih5=>CIQ zR`J)%DcjSl_tKU0qI5sOb{UVfY@>CvsCZSsKw?}8~B;aN=`6EiU$Byn$=z410LVS2i zx;e*Vo#6;VbwEn)$f3hOZXsN33N2$BEqYkqb6eG}kXxu)=?iwPx>F7a@FzYsq_7Nc z>E?k)X&aZ{{kW@3(mSj}`D7RK(Qk3H*3X)ZF6U4INt@umS>O+2rL(sF%aLJCdvg>U zSl_{@!K;g0O_j>@wC#7DEI+ZhaQ8>blotK8xmtTA=(~7=2&r<~v_wSVjUO`TjCxtA z5ehR*qzSV=FPii5`Q;UB7Q8D|_PFw$u?kv^nUb<5aaoG{D{w#B+nhAD)03c+3+O}W zJwxAI&(vg*VRWFvlz_^T>4a*5CX4gvkd+v)9C{P_3lhBfw~TCS7gD+e-dZ?)50>}? zw2CYJCxa~Oa&Wm#a26Bk7l;5FgI1eeYB-(?JhRksxCsi|8nUa^GD-*u75i+7k0?w; zVQW~ZI)kCt^)a|TzJC`$MzI6#fB?^`$(o$LEH7-;#>Av#<>0o z_P%PL=;;1iT};n!Ei#RNwYQq-qa^_*f>_iJ9O1RjEyvJNnrIZHW|pD>pDk+GQ#O^;twh_FZ`mXNYW zX_arawk|v~EMjQ-XwIXa-nhNro;s7P}qwm>E>lG8YLV-7?b93oNuyyqtcgs zGjk5Q1NHnEveM;=RH-#ySP(PV@f&yZ;W1VeYaZI3_CaQW+^kFT>~ZxJvyCC}TO@U} zywUJGbPX^>CJ;8}%iM?qvM%CT`dwav7CE=0sD!dPPRyWTBzTFe=rj;Zo3lwzgVleF z1h>M&fThaTC@r7~7rqOcfq{&<%F_tO0wKZ`d1k=nvHAQ+YAzTIA0pt~Dn>aJxekVs+_9o|46CAjP2X z7Hoa2|3b=*}^uGz!rl)>SB69OKzpTKVq-e zF<+eLtq1!LGVL15{mBIpUHipeD9TX(+iSU=Oy)?t2OZwb<1}M>sn>|uTqDX}8P~^Q ze=cT5w^oW<*93;b>uL8tI<&HFtZq0yNVYciE4wr_eSjl3X_5q#g8S!{DxPF>k0hy} zZkD8t*pikiLc|T&=51^8ZThW9dF>E^`%3?Ifk~A38rtxqo;0f`kWfcsn;0MvvRdH) zWFl3GvC}A!Tl$zg%D%9{n_$qqDBDB38OtVGAc87?tcc~I1kA_op65~!O`L)av7P6w zQ zVO5W*LLNT~wx_#&U0)jU_4=F~5E2~a?-EdUdQ_=Dt=*Y9P)@;1yxy77Y^r%$(o^<6 z?Kb-e?8ROXmIKP)j4ar9Pm1wA4&G4L0PYS57l!C%_2|N^kW{zGdE}ltDJp`Bi#)>;!>U_Xz2F7B)$x%pS37n zI>Q&)5_MHuQ%Zu7NQOZ_{j)Wr(NA-IzGqy#ZtuDz!T`ye}I>+Ct+rsAuTD!xjp+0;HL^LEy z)}38_N$Sk44e!V=!zty`hY2!UR(7VX6*_qTgfEemKzf=ow2q?Y4oX6PNkee-10_$0 zman6kc;?is=Y*dV7p#tvIZK)OEMhy69lFibI+qDW8(i20yqME`3CGMm>W$;4>QK~x z$HcjD*`vlZkao5$+3EH8-_}I?dPgs8tlMY9#AgBreIN;JeF6wMW5h?y!|HUJvoS*~ zDB8H21B;NYl;XMZ=y(v!Bew=5GIV^3BBO<9GWhMbSGE{er=sZ_Fr0D&xwJJ(J%v~$ zoAk`u97zQuymcinVD3@G4L20b1^`>N*kzAy#r#bI5AnJ6onYTd@Z1K zZ&w_igqjw*P9ix)AhmzZvX7NoKL#-k@n^)NVI00ox3A619f1cCp5^>&i52)w+PadE z-ypToy>NUCOqniB*eY1dalkilGr{qWv30s({G0>k55e!uF0ya)x7nAo+XgPPm~AlR z268C#o&4X#F7UmVsyUIT-)slA5o91Ay_<|Wr|L(m^uIgw>g`t#+$@A*zR|4PRCJbq zWE9_@I1n6w*k~|axdJ=wz~gP@uJS)a@a?4n(%TW940oWATJO9Ny)%QNIr8Rci^`DJ`0k%@>6zX?19^U983ejBR@(_!XH?3OFQ}n0p|N7f_ zj~NlF1n0e5AMHx1LK1ZGkJh{cpzk{1pA9sz{Dyn@k3oR>^zzd|`}QjZX$9^Dcd@CU z)#Fy%d+00s$)$=p8^SRf3kSX^-abiQP7-oTzXMav(lC}KB3xH321X1Irv1-lP^=h zbDg{>Y;{Zy%!B*_Woe#BQz}%1<4QDn685!w&9VWcD;Ulh0J(?6gu5A$u3$S+!GYJX z+x*bd7UW5n(pcc0OPnVC6Gm;?lPH_R@fU#Oj*h-Go-g;}s3S}HQLXO8kF+#F)*bll zbTvIeMg`|D*e!KC$3ZALDOU*F2t7pgdnK8$MR6tfKN7%35J4~PjGRf;5OUm_1w0=W zIOKx_H!OzDPOw@XNb=b#Db9rQsvJa3v}p9$EGBIsJ&TI>A=P_HO5#|EsoF4zDv&R? zyqj)!^xU}gQ@BKyo^uny`)b^mK;;YT~&KoU%r8YW4p!w6J4z zHVdOtl%2ElI87Yz4MFq7Tb)H2%hTe|k7srA-#8v%iT^3aGnn@0#X~JOzn8I({se>iP)8VuWXilp^2(;a`h~kSw#!ve~oQHMF;$+`*TgaxF)Lvn_;?bEGrX&Sufp zjI>mb5f+Ub^QBd;0Z?3_M+7##AHO^UsOi`dWts#B2s2+`DfqO2;z)kn=EzRc5z>V# zu-(7wn(Xev&=hC}vzBaVZbnO%_`P0F(CxzaNWK^5L9ERu4s&=SaeXk7+}pBvoNYkBlOn`Lppf=b4v@Epx_yg#&OlPGbDbX%3K7UzLdkb8&s^8y_rA2y|`?ctk1v?TfjJ>x$>8utfG9);;W zM2f*0rtRQiyXwuwG+ppTn)GIQB|~POGWwj8cyxZQ)(YNgn(55g(vbo{!Q@URCicff zFf{kp$(Kx9$8ZqdzXu_w+F_W3S_`5H4` zj>dkRmKnV4N2kOcKh(N#800Y?4kv`XF)}(x-m9UL=ce{%Hm3MT({?;F>pq?M~;mpAnM691&&5RXQN)hnn z;~!*R*@v|7ya~)r73xd@jULS_5TcR<%?Vdxa?wl?Q`xY7+(Oa)=D32y_OTdKZHYJz7yYgP}+*{Cb9Hfn2yxK*9V5wsJ6n~q-p*3V5|rQ!@xU9v^sb`cd zt|HNGA@%U+m(cuo~4ic0B7`Wi@ z!T?y~tbt005}6>WZ6?5>3Q_8c5R?1@MRt2j%j%yI{{Y}t&>-Baf?G6y-;QqG*|+NF zPcON|N`kZf+(IdcKYyPdWavnzX&YEq%Ow7ut|R_a-wWE18tTyxBRoN(o8qCEBxK54 zNlYIuy_a&s{Ltz@l}&W@@#}Y5l)bP$u~TCsU{#hK+Q1J>5F_|K_oU^YMFiaC5D^@H zuh8)7&Z6ejAPi6G+QsJT5p*wzIb4j?vbCroo+$v{Pvds3)_(e+kks=B@0Xnwbtg#Q z<)@z#l5+7v63zw&diIV<1evvC3ZcIo6u@MfEspB;HZLANnmBrR^XPS)8w>x(_7*r) z>7A0(7!V|qQ0bjFd|K3ch$BeMpXEmh>IhQvXM??BBR_26Vy!^iuI2H{X&mrd)ZT)v z{)vrIU=s)PsE(i3uiquFE4=&(VNDz>vcmfHGn2$E{$;wrcO;-nHvp_gk+v<$wF_gre>aI$zd*+S2aLZ@y~<>Dvn;_GmVd4O~yA%mXv( z$Er?|NM~sa41j>^rebovoQ->675WjhjdlrO8+9U>61qTiT&D`zs#giHDUM=Xcz`0k zE|8*EyBQle9cOAP#wxuV*0-h$4z&xVScicd!Iox?ZEqr@!ldHuTaMQU2Fna*aiMxA zns`(flS6SdzD$)l!!aUX?nG=fRqQo3at<&DYo#l%8P~eJ$j) z`exi@LqPzQ%TlIWYUBohlb9gZ1$c+uppJ-krwm*O`|ArV;TFP}2Ch2iYCdetf4>wF z3Cv;_O~6IBq5cO z;V-bW)fOJm67=c^gp%u3U8J%wo|_xnM3~Y#r`a&ZgAnrNAvZ^dg$#XJA|bDHCmYn& zh^F+k9PK6qr6rGwb7~t9eCaQ9K7`zv>_bQ3X%GA0-xO;cO2O)9yPB2L^H^bFS$V#7 zn${V&$SKntk2^Ey-RatelBBl6%c)yq9AZuJ1+g6^&(J043x}{Sn;ddUmY_vh>VRN6 zg`nI(+*#To?*)zLNp@WEc&4H(nSx!VQsZsP7VZgmSe5CN9se%_2u*R*II=p^EaWKjoNO=#Udioz$z z_<8?BAnEWmn6>V^J)g@|zlB@LDEACChiC}WHKIsV1U7h?%kBpby^rC;=H|0d3o-B8 zgL6)lo9frD>^AuISdQLQhv{MGv2VN{{!LJL@5Q~hDOrEe-gf`ta`N5|7^Daq_Hb^< zv4iFoaq!)TW}^s2Ss(Aa;%-x=qB104wR}UCdw|0BGTYv-$D?;}R9R-t`lCbPhL&~A zpITg1HQw?Elde>=lRw#B4Jg;zvaS=H@)vQO$OvEMMmq89WsVg4y{0{`+uGC>(v!VF zJo#RHNq$c&Q{T1Js%CQ{R}}?n$67rW7NX9CKyF^;HspK#$nD$5kL%V( zL#d@S#Vs|=pg|1g6DdXEI_u>s0wsMqaGm>TilU*59aGC)lHdF%AZ;qpd5hwHb&O65 z5664tjOlnpUbNx!^)L)wtgCpf--d|Mc<)hhbVye{Hqehv{Q}oJJq;)pmVhd+;wfdt7 z(94s1Aqm$)3a%nzk{LR<#ZIB6BQz*+88d+sQ#@J^UN_~nfZI1nWfO85&;Rh!u|+)# z7iFDD`b$%3VZf*ZXHrcY_O4Hyf1s^Tro1TWlQXbQ=1v_bohEe+G8b5dYu;bm6Ks^*g8wF@^j2*j*uUEq4v^XWicjY1aA& zM??W`|QoHd-)KI2)A|94l-%6|q?mE1|@2DXj2r;s+Tqrm67=*N2S4y0LC(=XESunw% z&i@a7(ppF{&5xy!`$5+H5sxd6cP-1066E3BRom4egjhA|rESx-an`DNH`ht>3ptNQ z(-i+B)z|{BJf_x4(uSU zIe%VlmYgzkQrZFDmrGr>7?@&Xe{8QPui{w&FMNnmeS#ZJ0~(h~Y(-7+=*^di^~kh> zGs9?JyeXw@8`&HSq~eg^T`x*kJGmETS;zM`tAt)G;3=ky zBK9FRrLU|e7!U`+g1vw{;NoKE&p<$I z)1dSq)n`9?4Rg?It+ks}9+erVpm-;>`FbWPdQcoDx(ZV&*lH&!C@cNxb)MY*bzsg~ z!p{V9j>ADs0tay7V&J&HsiHh@&Bc3TH*vr&A^lJ&?(%M)>GRn$d6?&t+2NvLqz{`q zTq}K~k9vVp+{*@PzPbHwP(yv+>JZb6c&X6GXguU{HUZ3)_wgJRKe52w+%p;` z6-=pKBF_=9$5aXC1GONWNotu^c{u!x^b^O+@6l`Jt1QwMGDuTA1|5)_H!=_IH|p0s z)&-Q;(9o+otc^T2k&IH17lN6aDW3g&HcSF|*e|QZg=Am^zr!E`gS5e$Q1d2KY=5nL zZ%MKJQcmcb1w6L>qy0aEg1%As20s0~@TS)&q8D{_lrErDlVm)!N14vzFVdYES&tXM z!6gtxR|MhDfv_8bM^|Xh!`yZNXpYrkAmAeQ?4OO&!ty*z7#u6c9;W`%208frQ<^|k zd?hZxfM3Dk(l|B0MzZ^L*a23U+N|c#1TejWR-xlpnD06GeUrZ5#snwS6tHv;+As}D z0nkcV;0DZ#Z*<4YFPxhcP&JR|)R)Pl+j1&AJMLZhFmq;6tUNrSP})#)bU^pk;7DQn z=WOR=8Z9CYKfT*5;bau1Nf3Zu1nNght2BKGG!MW3VT8R~p(c3gBWVUAMTvu^bba*Q zlGWpvvmq2D7{b*OL*emf=`u(Bgg(2n=GE;{w}bHJ(MCjtT7jV)@#sz{nfelou=y|_ z)62q+VudxV9~=t0bSmcHat3t$2ma>LHVlHtpLf}{xR6v-)BPTz=F|@+vXF_f!l)aHZ?~4Py9}|vj2&F z?vMUhrdk1+rJifDUdRIc4(wV6bh>(>tDte;Ew!*ZmpOrDF{4qlWpzaX_X0A?CG1gi ziP1n(r~hB5-T2qD{%0(^^CH^mx5`FHvm(x2e{cc&u_|FdUJfdEK!Qnt1|CoCYROwR zi?BVDYX`~!#9Y!8sFX)@3SC#8CRE3A$||=jCLt{hUlOX#ei<{Q_x1LM7%?uTFE-Gh z_eV(3jBn~gC0CNxBs4*(nOij1v;Z))4h)=l<&0uvhGOicS4pYaI7`wc00=P&Q{a$N zKlPI3R96C*Ww!Qd&F&FcVbY(TG+3C9!}+LY&guvmVM9jo9;i0YsR=s(M$7nXJBNL6 zs8(9#Q!DH=;#n!ly7%JDOB&S5$b1cf{2)bJVYkd4K8BwYUTj03n&{n=k}VK{T^M z{$9iQ_%2R@i^U^VKA4pc89p>hNR`)^BqCf|Ue1o4g%W~0RS<%-s!)reFp3H*GYBKb ze#&*fwn!MWyLHpgQR}#A-X8sa06<{WZtMI%Zm%S@HmJ1Rza2H*lBBjU%Ow=y!6z}4 zFg)+;1`)aDGk&6gDK|d!`kPFDf}q}0Fi z;#Yg{sA*JC>8}9V1bIKuH_a`-0!mtzjgg@2F664|oa3LxeiHWpV?Y|tYM9X9Kw0~R zj1!@{xHxNVY!{D}u+QK4dL5DNOekB!n-V1+rr%H7dTl!a=akbdT9g3|}T!^hPMtlNvx zjvf;=GveSa=O62ldc@&prJp!qXrzjMb93UBu)Msc+97RI4?Nt1a^30-@HfpkH?2DR z`+`_a>()xNdx-6zoLH5HBWOb&qxzUx)SK0G(36M@qs{7E&|6h*vKt15UA@tqrXt`X zclHkb8GDEP^4SDy$ft;Ym!#pF&Pp{?tHuyE#y|UuYk+;%YX{h0C~hc7M|1R2e`8b1 z-%j4d_-2MVBvZXn>Wmi<2<$mZ+6uZzrSO*VhufEWj=l&1gz9`OEji9ZjQOz?`*tUA ztEMX3+P_Af&>q=)PdW=g0gZY!k2|u&H_H09dJE%-v0LFhFjChT+15VLwT8|g}(H#p@AFwJG;d+|5^7X4QlLF)U@ zjX!OvoxlF@(?}j-c^;;>_0`vk7mFi|nA$aj2YhiL3fC8(N^Hy+p=L>^!4QPIyN#VT zLQ6d9#--PRENq0mp;`7qdQcdRWsn({(63Ue^@giGxY;*#*cSn=*Xl1pSkTt~=UlSRz6~BF@Ek2T3vu*HfYJU~<(0VKf)z$fV7dA- zVZ?lu1`Zfll(uRg(!pshqh?x$2$bv*y5|@&GBo27aTSV46tS>9Z`Dt0w=6C8w7dOz z7(gkMrF2nyHTD<&hzGr{|0a%j246DVy1NizU*zA<_;annXh?Ch&i$SwN+f&^azaZC z|17WFApwDCI}GCA)igd&y{?%7jUl)j^&w|IOFZEm8gV~75SRo1j_gMqECX4<(2oQu z*53OJE;!9{Jr*E^u!)5m)yd2C{+}-rE`Pj<{r}?YzyBy8O$vbqfA`5v+u(E%g@;nX zsuHpv+`bJE-qGcd-YyvNxj!obS1PVJGb_*qm9Y zY`}>P(JeBCAmi^`@1h5)tjCPl?@IOMC?$*cihoW{ZE?M_8xvL!dRdQpHe4Nac?2R* zD7^3y5@#N4qvH6a2o;2}KesVyPQD ztgaM2#m}?(T9(aDJ4C1zdlp#kp>YI>pHw5cOq0ZAOzk;@u=lOvUF21fA8T}=21REL zl&0#dIV-(4V6djCqL zr*tp%v8r9-T+O>wk5)W(27&E7+R#bIpqDAHy*=;YQg$O)YZ!b$qXr}KF^l7@3hb$d zLL565x)RNpOQ(DM7zZG|345jco-I%lSA!C$MbE^|puamG=sD@sPFtX{soBxfHS|)_ z=OSrt!4;5;%AXOJJhnX$&>ikI)25nnU5~~7VtW0sNo3-(+wD9GcBmCk+qHiUtkR?B zRVZGxvHDrO+#h2u)F3_Mf3BY;b`ywG09t&Yw|?r(*=vj{M#l<|3e^Y!=`sJy{$tk| z`Q%!A<5Cc_awa~@<8OC)EQm$yJLB=0%`c|3LF%-?XnYs2xu`tsC+(CKoeeFZ+I~IT zmg=>Engz{i#mcpt6HPBn{=Ik*1nCy}2RW(#6}32lX%D|GZ}@T+UIJm#~j8s3fX7R zIjptm!+eE($7&sHxLYoCNvf0SxcD;!OF!&&gNJoP)TN~jUFEBhuTIb%4hfCKC~j)* zWY%`+7vdClx_-P`f&T0CNna#Pmi+V+r|wjKfnzH4z`@mHk=1tQPItt?C_`*Bk=yoR zsJV9~myjdI4q|xOF#0xM*(3)!?MJB*#V4gPOkSl+P==_t3wmZ>!FC%U6P;(fAA#bN4T{C0l1Ix%ue zEq!!N!s^8agy^7dQ7^b;r1G3aHL1C{Hc#1LH7!#ulO^hUTpgFPY~wP6oK>Dfs;N_U z7e3~*Q;h25x1-fen0t!;5#n0jCff>}6py1eiL2`UwK2or@cmIN_#kRRyJG+9B_st| zK007tINY&O)P62uHOf)K8)bocae^SlyoncM^_7`~g$uZlizRP19O9oes}VU3W&?Za zSd0wdHp<*dpEBpRj(m)3Fhi46|ytGTt*(=GD;5M_PSl!|W zc)~842tma1dV*2J_OFk<)qn*APIws9%>9_jnXXMt_;o8Un;=N;>*6yV=Ezdo1cR)| zIuc*XaZ@5WujQPpHQsLjNqKkU=H!$ut}ZWIz&@|)*HT8z43FrL^E2?v)dM

31>i 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 literal 0 HcmV?d00001