From da107cf6e3d77c124cdf19d5568f548c7b695991 Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Thu, 15 Mar 2012 09:25:18 -0400 Subject: [PATCH] Initial Commit --- README.md | 3 + db_pdo.php | 293 +++++++++ drivers/firebird-ibase.php | 468 ++++++++++++++ drivers/firebird_sql.php | 130 ++++ drivers/mysql.php | 148 +++++ drivers/mysql_sql.php | 80 +++ drivers/odbc.php | 109 ++++ drivers/odbc_sql.php | 66 ++ drivers/pgsql.php | 209 ++++++ drivers/pgsql_sql.php | 67 ++ drivers/sqlite.php | 232 +++++++ drivers/sqlite_sql.php | 122 ++++ query_builder.php | 1049 +++++++++++++++++++++++++++++++ tests/databases/firebird-qb.php | 219 +++++++ tests/databases/firebird.php | 181 ++++++ tests/databases/mysql-qb.php | 26 + tests/databases/mysql.php | 32 + tests/databases/odbc-qb.php | 26 + tests/databases/odbc.php | 31 + tests/databases/pgsql-qb.php | 26 + tests/databases/pgsql.php | 37 ++ tests/databases/sqlite-qb.php | 203 ++++++ tests/databases/sqlite.php | 163 +++++ tests/index.php | 70 +++ tests/test_dbs/FB_TEST_DB.FDB | Bin 0 -> 802816 bytes tests/test_dbs/test_sqlite.db | Bin 0 -> 3072 bytes 26 files changed, 3990 insertions(+) create mode 100644 README.md create mode 100644 db_pdo.php create mode 100644 drivers/firebird-ibase.php create mode 100644 drivers/firebird_sql.php create mode 100644 drivers/mysql.php create mode 100644 drivers/mysql_sql.php create mode 100644 drivers/odbc.php create mode 100644 drivers/odbc_sql.php create mode 100644 drivers/pgsql.php create mode 100644 drivers/pgsql_sql.php create mode 100644 drivers/sqlite.php create mode 100644 drivers/sqlite_sql.php create mode 100644 query_builder.php create mode 100644 tests/databases/firebird-qb.php create mode 100644 tests/databases/firebird.php create mode 100644 tests/databases/mysql-qb.php create mode 100644 tests/databases/mysql.php create mode 100644 tests/databases/odbc-qb.php create mode 100644 tests/databases/odbc.php create mode 100644 tests/databases/pgsql-qb.php create mode 100644 tests/databases/pgsql.php create mode 100644 tests/databases/sqlite-qb.php create mode 100644 tests/databases/sqlite.php create mode 100644 tests/index.php create mode 100755 tests/test_dbs/FB_TEST_DB.FDB create mode 100644 tests/test_dbs/test_sqlite.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..048d5fa --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Query + +A query builder/abstraction layer. \ No newline at end of file diff --git a/db_pdo.php b/db_pdo.php new file mode 100644 index 0000000..04cc87a --- /dev/null +++ b/db_pdo.php @@ -0,0 +1,293 @@ +prepare($sql); + + if( ! (is_object($query) || is_resource($query))) + { + $this->get_last_error(); + return FALSE; + } + + // Set the statement in the class variable for easy later access + $this->statement =& $query; + + + if( ! (is_array($data) || is_object($data))) + { + trigger_error("Invalid data argument"); + return FALSE; + } + + // Bind the parameters + foreach($data as $k => $value) + { + if(is_numeric($k)) + { + $k++; + } + + $res = $query->bindValue($k, $value); + + if( ! $res) + { + trigger_error("Parameter not successfully bound"); + return FALSE; + } + } + + return $query; + + } + + // ------------------------------------------------------------------------- + + /** + * Create and execute a prepared statement with the provided parameters + * + * @param string $sql + * @param array $params + * @return PDOStatement + */ + public function prepare_execute($sql, $params) + { + $this->statement = $this->prepare_query($sql, $params); + $this->statement->execute(); + + return $this->statement; + } + + // ------------------------------------------------------------------------- + + /** + * Retreives the data from a select query + * + * @param PDOStatement $statement + * @return array + */ + public function get_query_data($statement) + { + $this->statement =& $statement; + + // Execute the query + $this->statement->execute(); + + // Return the data array fetched + return $this->statement->fetchAll(PDO::FETCH_ASSOC); + } + + // ------------------------------------------------------------------------- + + /** + * Returns number of rows affected by an INSERT, UPDATE, DELETE type query + * + * @param PDOStatement $statement + * @return int + */ + public function affected_rows($statement='') + { + if ( ! empty($statement)) + { + $this->statement = $statement; + } + + if (empty($this->statement)) + { + return FALSE; + } + + // Execute the query + $this->statement->execute(); + + // Return number of rows affected + return $this->statement->rowCount(); + } + + // -------------------------------------------------------------------------- + + /** + * Return the last error for the current database connection + * + * @return string + */ + public function get_last_error() + { + $info = $this->errorInfo(); + + echo "Error:
{$info[0]}:{$info[1]}\n{$info[2]}
"; + } + + // -------------------------------------------------------------------------- + + /** + * Surrounds the string with the databases identifier escape characters + * + * @param mixed $ident + * @return string + */ + public function quote_ident($ident) + { + if (is_array($ident)) + { + return array_map(array($this, 'quote_ident'), $ident); + } + + // Split each identifier by the period + $hiers = explode('.', $ident); + + return '"'.implode('"."', $hiers).'"'; + } + + // ------------------------------------------------------------------------- + + /** + * Deletes all the rows from a table. Does the same as the truncate + * method if the database does not support 'TRUNCATE'; + * + * @param string $table + * @return mixed + */ + public function empty_table($table) + { + $sql = 'DELETE FROM '.$this->quote_ident($table); + + return $this->query($sql); + } + + // ------------------------------------------------------------------------- + + /** + * Abstract public functions to override in child classes + */ + + /** + * Return list of tables for the current database + * + * @return array + */ + abstract public function get_tables(); + + /** + * Empty the passed table + * + * @param string $table + * + * @return void + */ + abstract public function truncate($table); + + /** + * Return the number of rows for the last SELECT query + * + * @return int + */ + abstract public function num_rows(); + + /** + * Retreives an array of non-user-created tables for + * the connection/database + * + * @return array + */ + abstract public function get_system_tables(); + + /** + * Return an SQL file with the database table structure + * + * @return string + */ + abstract public function backup_structure(); + + /** + * Return an SQL file with the database data as insert statements + * + * @return string + */ + abstract public function backup_data(); +} + +// ------------------------------------------------------------------------- + +/** + * Abstract parent for database manipulation subclasses + */ +abstract class DB_SQL { + + /** + * Get database-specific sql to create a new table + * + * @param string $name + * @param array $columns + * @param array $constraints + * @param array $indexes + * @return string + */ + abstract public function create_table($name, $columns, array $constraints=array(), array $indexes=array()); + + /** + * Get database-specific sql to drop a table + * + * @param string $name + * @return string + */ + abstract public function delete_table($name); + + /** + * Get database specific sql for limit clause + * + * @param string $sql + * @param int $limiit + * @param int $offset + * @return string + */ + abstract public function limit($sql, $limit, $offset=FALSE); + + /** + * Get the sql for random ordering + * + * @return string + */ + abstract public function random(); +} +// End of db_pdo.php \ No newline at end of file diff --git a/drivers/firebird-ibase.php b/drivers/firebird-ibase.php new file mode 100644 index 0000000..c0f93bb --- /dev/null +++ b/drivers/firebird-ibase.php @@ -0,0 +1,468 @@ +conn = @ibase_connect($dbpath, $user, $pass, 'utf-8'); + + // Throw an exception to make this match other pdo classes + if ( ! is_resource($this->conn)) + { + throw new PDOException(ibase_errmsg()); + die(); + } + + $class = __CLASS__."_sql"; + $this->sql = new $class; + } + + // -------------------------------------------------------------------------- + + /** + * Close the link to the database + */ + public function __destruct() + { + @ibase_close(); + @ibase_free_result($this->statement); + } + + // -------------------------------------------------------------------------- + + /** + * Empty a database table + * + * @param string $table + */ + public function truncate($table) + { + // Firebird lacka a truncate command + $sql = 'DELETE FROM "'.$table.'"'; + $this->statement = $this->query($sql); + } + + // -------------------------------------------------------------------------- + + /** + * Wrapper public function to better match PDO + * + * @param string $sql + * @param array $params + * @return $this + */ + public function query($sql) + { + $this->count = 0; + + if (isset($this->trans)) + { + $this->statement = @ibase_query($this->trans, $sql); + } + else + { + $this->statement = @ibase_query($this->conn, $sql); + } + + // Throw the error as a exception + if ($this->statement === FALSE) + { + throw new PDOException(ibase_errmsg()); + } + + return $this->statement; + } + + // -------------------------------------------------------------------------- + + /** + * Emulate PDO fetch public function + * + * @param int $fetch_style + * @return mixed + */ + public function fetch($fetch_style=PDO::FETCH_ASSOC) + { + switch($fetch_style) + { + case PDO::FETCH_OBJ: + return ibase_fetch_object($this->statement, IBASE_FETCH_BLOBS); + break; + + case PDO::FETCH_NUM: + return ibase_fetch_row($this->statement, IBASE_FETCH_BLOBS); + break; + + default: + return ibase_fetch_assoc($this->statement, IBASE_FETCH_BLOBS); + break; + } + } + + // -------------------------------------------------------------------------- + + /** + * Emulate PDO fetchAll public function + * + * @param int $fetch_style + * @return mixed + */ + public function fetchAll($fetch_style=PDO::FETCH_ASSOC) + { + $all = array(); + + while($row = $this->fetch($fetch_style)) + { + $all[] = $row; + } + + $this->result = $all; + + return $all; + } + + // -------------------------------------------------------------------------- + + /** + * Emulate PDO prepare + * + * @param string $query + * @return $this + */ + public function prepare($query, $options=NULL) + { + $this->statement = @ibase_prepare($this->conn, $query); + + // Throw the error as an exception + if ($this->statement === FALSE) + { + throw new PDOException(ibase_errmsg()); + } + + return $this->statement; + } + + // -------------------------------------------------------------------------- + + /** + * List tables for the current database + * + * @return array + */ + public function get_tables() + { + $sql = <<statement = $this->query($sql); + + $tables = array(); + + while($row = $this->fetch(PDO::FETCH_ASSOC)) + { + $tables[] = $row['RDB$RELATION_NAME']; + } + + return $tables; + } + + // -------------------------------------------------------------------------- + + /** + * List system tables for the current database + * + * @return array + */ + public function get_system_tables() + { + $sql = <<statement = $this->query($sql); + + $tables = array(); + + while($row = $this->fetch(PDO::FETCH_ASSOC)) + { + $tables[] = $row['RDB$RELATION_NAME']; + } + + return $tables; + } + + // -------------------------------------------------------------------------- + + /** + * Return the number of rows affected by the previous query + * + * @return int + */ + public function affected_rows($statement="") + { + return ibase_affected_rows(); + } + + // -------------------------------------------------------------------------- + + /** + * Return the number of rows returned for a SELECT query + * + * @return int + */ + public function num_rows() + { + // @todo: Redo this similar to the codeigniter driver + if(isset($this->result)) + { + return count($this->result); + } + + //Fetch all the rows for the result + $this->result = $this->fetchAll(); + + return count($this->result); + } + + // -------------------------------------------------------------------------- + + /** + * Start a database transaction + * + * @return bool + */ + public function beginTransaction() + { + if(($this->trans = ibase_trans($this->conn)) !== NULL) + { + return TRUE; + } + + return FALSE; + } + + // -------------------------------------------------------------------------- + + /** + * Commit a database transaction + * + * @return bool + */ + public function commit() + { + return ibase_commit($this->trans); + } + + // -------------------------------------------------------------------------- + + /** + * Rollback a transaction + * + * @return bool + */ + public function rollBack() + { + return ibase_rollback($this->trans); + } + + // -------------------------------------------------------------------------- + + /** + * Run a prepared statement query + * + * @param array $args + * @return bool + */ + public function execute($args) + { + //Add the prepared statement as the first parameter + array_unshift($args, $this->statement); + + // Let php do all the hard stuff in converting + // the array of arguments into a list of arguments + return call_user_func_array('ibase_execute', $args); + } + + // -------------------------------------------------------------------------- + + /** + * Prepare and execute a query + * + * @param string $sql + * @param array $args + * @return resource + */ + public function prepare_execute($sql, $args) + { + $query = $this->prepare($sql); + + // Set the statement in the class variable for easy later access + $this->statement =& $query; + + return $this->execute($args); + } + + // -------------------------------------------------------------------------- + + /** + * Method to emulate PDO->quote + * + * @param string $str + * @return string + */ + public function quote($str, $param_type=NULL) + { + if(is_numeric($str)) + { + return $str; + } + + return "'".str_replace("'", "''", $str)."'"; + } + + // -------------------------------------------------------------------------- + + /** + * Method to emulate PDO->errorInfo / PDOStatement->errorInfo + * + * @return array + */ + public function errorInfo() + { + $code = ibase_errcode(); + $msg = ibase_errmsg(); + + return array(0, $code, $msg); + } + + // -------------------------------------------------------------------------- + + /** + * Bind a prepared query with arguments for executing + * + * @param string $sql + * @param mixed $args + * @return FALSE + */ + public function prepare_query($sql, $args) + { + // You can't bind query statements before execution with + // the firebird database + return FALSE; + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's structure + * + * @return string + */ + public function backup_structure() + { + // @todo Implement Backup function + return ''; + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's data + * + * @param array $exclude + * @param bool $system_tables + * @return string + */ + public function backup_data($exclude=array(), $system_tables=FALSE) + { + // Determine which tables to use + if($system_tables == TRUE) + { + $tables = array_merge($this->get_system_tables(), $this->get_tables()); + } + else + { + $tables = $this->get_tables(); + } + + // Filter out the tables you don't want + if( ! empty($exclude)) + { + $tables = array_diff($tables, $exclude); + } + + $output_sql = ''; + + // Get the data for each object + foreach($tables as $t) + { + $sql = 'SELECT * FROM "'.trim($t).'"'; + $res = $this->query($sql); + $obj_res = $this->fetchAll(PDO::FETCH_ASSOC); + + unset($res); + + // Nab the column names by getting the keys of the first row + $columns = @array_keys($obj_res[0]); + + $insert_rows = array(); + + // Create the insert statements + foreach($obj_res as $row) + { + $row = array_values($row); + + // Quote values as needed by type + if(stripos($t, 'RDB$') === FALSE) + { + $row = array_map(array(&$this, 'quote'), $row); + $row = array_map('trim', $row); + } + + $row_string = 'INSERT INTO "'.trim($t).'" ("'.implode('","', $columns).'") VALUES ('.implode(',', $row).');'; + + unset($row); + + $insert_rows[] = $row_string; + } + + unset($obj_res); + + $output_sql .= "\n\nSET TRANSACTION;\n".implode("\n", $insert_rows)."\nCOMMIT;"; + } + + return $output_sql; + } +} +// End of firebird-ibase.php \ No newline at end of file diff --git a/drivers/firebird_sql.php b/drivers/firebird_sql.php new file mode 100644 index 0000000..1a13fda --- /dev/null +++ b/drivers/firebird_sql.php @@ -0,0 +1,130 @@ + ..., + // 'constraint' => ..., + // 'index' => ..., + // ) + foreach($fields as $colname => $type) + { + if(is_numeric($colname)) + { + $colname = $type; + } + + $column_array[$colname] = array(); + $column_array[$colname]['type'] = ($type !== $colname) ? $type : ''; + } + + if( ! empty($constraints)) + { + foreach($constraints as $col => $const) + { + $column_array[$col]['constraint'] = $const; + } + } + + // Join column definitons together + $columns = array(); + foreach($column_array as $n => $props) + { + $str = '"'.$n.'" '; + $str .= (isset($props['type'])) ? "{$props['type']} " : ""; + $str .= (isset($props['constraint'])) ? "{$props['constraint']} " : ""; + + $columns[] = $str; + } + + // Generate the sql for the creation of the table + $sql = 'CREATE TABLE "'.$name.'" ('; + $sql .= implode(',', $columns); + $sql .= ')'; + + return $sql; + } + + // -------------------------------------------------------------------------- + + /** + * Drop the selected table + * + * @param string $name + * @return string + */ + public function delete_table($name) + { + return 'DROP TABLE "'.$name.'"'; + } + + // -------------------------------------------------------------------------- + + /** + * Limit clause + * + * @param string $sql + * @param int $limit + * @param int $offset + * @return string + */ + public function limit($sql, $limit, $offset=FALSE) + { + // Keep the current sql string safe for a moment + $orig_sql = $sql; + + $sql = 'FIRST '. (int) $limit; + + if ($offset > 0) + { + $sql .= ' SKIP '. (int) $offset; + } + + $sql = preg_replace("`SELECT`i", "SELECT {$sql}", $orig_sql); + + return $sql; + } + + // -------------------------------------------------------------------------- + + /** + * Random ordering keyword + * + * @return string + */ + public function random() + { + return FALSE; + } +} +//End of firebird_sql.php \ No newline at end of file diff --git a/drivers/mysql.php b/drivers/mysql.php new file mode 100644 index 0000000..b35c5ef --- /dev/null +++ b/drivers/mysql.php @@ -0,0 +1,148 @@ +sql = new $class; + } + + // -------------------------------------------------------------------------- + + /** + * Empty a table + * + * @param string $table + */ + public function truncate($table) + { + $this->query("TRUNCATE `{$table}`"); + } + + // -------------------------------------------------------------------------- + + /** + * Get databases for the current connection + * + * @return array + */ + public function get_dbs() + { + $res = $this->query("SHOW DATABASES"); + return $this->fetchAll(PDO::FETCH_ASSOC); + } + + // -------------------------------------------------------------------------- + + /** + * Returns the tables available in the current database + * + * @return array + */ + public function get_tables() + { + $res = $this->query("SHOW TABLES"); + return $res->fetchAll(PDO::FETCH_ASSOC); + } + + // -------------------------------------------------------------------------- + + /** + * Returns system tables for the current database + * + * @return array + */ + public function get_system_tables() + { + //MySQL doesn't have system tables + return array(); + } + + // -------------------------------------------------------------------------- + + /** + * Return the number of rows returned for a SELECT query + * + * @return int + */ + public function num_rows() + { + return isset($this->statement) ? $this->statement->rowCount() : FALSE; + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's structure + * + * @return string + */ + public function backup_structure() + { + // @todo Implement Backup function + return ''; + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's data + * + * @return string + */ + public function backup_data() + { + // @todo Implement Backup function + return ''; + } + + // -------------------------------------------------------------------------- + + /** + * Surrounds the string with the databases identifier escape characters + * + * @param string $ident + * @return string + */ + public function quote_ident($ident) + { + if (is_array($ident)) + { + return array_map(array($this, 'quote_ident'), $ident); + } + + // Split each identifier by the period + $hiers = explode('.', $ident); + + return '`'.implode('`.`', $hiers).'`'; + } +} +//End of mysql.php \ No newline at end of file diff --git a/drivers/mysql_sql.php b/drivers/mysql_sql.php new file mode 100644 index 0000000..1eeec9c --- /dev/null +++ b/drivers/mysql_sql.php @@ -0,0 +1,80 @@ +sql = new $class; + } + + // -------------------------------------------------------------------------- + + /** + * List tables for the current database + * + * @return mixed + */ + public function get_tables() + { + //Not possible reliably with this driver + return FALSE; + } + + // -------------------------------------------------------------------------- + + /** + * List system tables for the current database/connection + * + * @return array + */ + public function get_system_tables() + { + //No way of determining for ODBC + return array(); + } + + // -------------------------------------------------------------------------- + + /** + * Empty the current database + * + * @return void + */ + public function truncate($table) + { + $sql = "DELETE FROM {$table}"; + $this->query($sql); + } + + // -------------------------------------------------------------------------- + + /** + * Return the number of rows returned for a SELECT query + * + * @return int + */ + public function num_rows() + { + // TODO: Implement + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's structure + * + * @return string + */ + public function backup_structure() + { + // Not applicable to ODBC + return ''; + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's data + * + * @return string + */ + public function backup_data() + { + // Not applicable to ODBC + return ''; + } +} +// End of odbc.php \ No newline at end of file diff --git a/drivers/odbc_sql.php b/drivers/odbc_sql.php new file mode 100644 index 0000000..88be0dd --- /dev/null +++ b/drivers/odbc_sql.php @@ -0,0 +1,66 @@ +sql = new $class; + } + + // -------------------------------------------------------------------------- + + /** + * Empty a table + * + * @param string $table + */ + public function truncate($table) + { + $sql = 'TRUNCATE "' . $table . '"'; + $this->query($sql); + } + + // -------------------------------------------------------------------------- + + /** + * Get list of databases for the current connection + * + * @return array + */ + public function get_dbs() + { + $sql = <<query($sql); + + $dbs = $res->fetchAll(PDO::FETCH_ASSOC); + + return $dbs; + } + + // -------------------------------------------------------------------------- + + /** + * Get the list of tables for the current db + * + * @return array + */ + public function get_tables() + { + $sql = <<query($sql); + + $tables = $res->fetchAll(PDO::FETCH_ASSOC); + + return $tables; + } + + // -------------------------------------------------------------------------- + + /** + * Get the list of system tables + * + * @return array + */ + public function get_system_tables() + { + $sql = <<query($sql); + + $tables = $res->fetchAll(PDO::FETCH_ASSOC); + + return $tables; + + } + + // -------------------------------------------------------------------------- + + /** + * Get a list of schemas, either for the current connection, or + * for the current datbase, if specified. + * + * @param string $database="" + * @return array + */ + public function get_schemas($database="") + { + if($database === "") + { + $sql = <<query($sql); + $schemas = $res->fetchAll(PDO::FETCH_ASSOC); + + return $schemas; + } + + // -------------------------------------------------------------------------- + + /** + * Get a list of views for the current db + * + * @return array + */ + public function get_views() + { + $sql = <<query($sql); + + $views = $res->fetchAll(PDO::FETCH_ASSOC); + + return $views; + } + + // -------------------------------------------------------------------------- + + /** + * Return the number of rows returned for a SELECT query + * + * @return int + */ + public function num_rows() + { + return (isset($this->statement)) ? $this->statement->rowCount : FALSE; + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's structure + * + * @return string + */ + public function backup_structure() + { + // @todo Implement Backup function + return ''; + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's data + * + * @return string + */ + public function backup_data() + { + // @todo Implement Backup function + return ''; + } +} +//End of pgsql.php \ No newline at end of file diff --git a/drivers/pgsql_sql.php b/drivers/pgsql_sql.php new file mode 100644 index 0000000..83859d4 --- /dev/null +++ b/drivers/pgsql_sql.php @@ -0,0 +1,67 @@ +sql = new $class; + } + + // -------------------------------------------------------------------------- + + /** + * Empty a table + * + * @param string $table + */ + public function truncate($table) + { + // SQLite has a TRUNCATE optimization, + // but no support for the actual command. + $sql = 'DELETE FROM "'.$table.'"'; + + $this->statement = $this->query($sql); + + return $this->statement; + } + + // -------------------------------------------------------------------------- + + /** + * List tables for the current database + * + * @return mixed + */ + public function get_tables() + { + $tables = array(); + $sql = <<query($sql); + $result = $res->fetchAll(PDO::FETCH_ASSOC); + + foreach($result as $r) + { + $tables[$r['name']] = $r['sql']; + } + + return $tables; + } + + // -------------------------------------------------------------------------- + + /** + * List system tables for the current database + * + * @return array + */ + public function get_system_tables() + { + //SQLite only has the sqlite_master table + // that is of any importance. + return array('sqlite_master'); + } + + // -------------------------------------------------------------------------- + + /** + * Load a database for the current connection + * + * @param string $db + * @param string $name + */ + public function load_database($db, $name) + { + $sql = 'ATTACH DATABASE "'.$db.'" AS "'.$name.'"'; + $this->query($sql); + } + + // -------------------------------------------------------------------------- + + /** + * Unload a database from the current connection + * + * @param string $name + */ + public function unload_database($name) + { + $sql = 'DETACH DATABASE ":name"'; + + $this->prepare_query($sql, array( + ':name' => $name, + )); + + $this->statement->execute(); + } + + // -------------------------------------------------------------------------- + + /** + * Return the number of rows returned for a SELECT query + * + * @return int + */ + public function num_rows() + { + return (isset($this->statement)) ? $this->statement->rowCount : FALSE; + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's structure + * + * @return string + */ + public function backup_structure() + { + // Fairly easy for SQLite...just query the master table + $sql = 'SELECT "sql" FROM "sqlite_master"'; + $res = $this->query($sql); + $result = $res->fetchAll(PDO::FETCH_ASSOC); + + $sql_array = array(); + + foreach($result as $r) + { + $sql_array[] = $r['sql']; + } + + $sql_structure = implode("\n\n", $sql_array); + + return $sql_structure; + } + + // -------------------------------------------------------------------------- + + /** + * Create an SQL backup file for the current database's data + * + * @param array $excluded + * @return string + */ + public function backup_data($excluded=array()) + { + // Get a list of all the objects + $sql = 'SELECT "name" FROM "sqlite_master"'; + + if( ! empty($excluded)) + { + $sql .= ' WHERE NOT IN("'.implode('","', $excluded).'")'; + } + + $res = $this->query($sql); + $result = $res->fetchAll(PDO::FETCH_ASSOC); + + unset($res); + + $output_sql = ''; + + // Get the data for each object + foreach($result as $r) + { + $sql = 'SELECT * FROM "'.$r['name'].'"'; + $res = $this->query($sql); + $obj_res = $res->fetchAll(PDO::FETCH_ASSOC); + + unset($res); + + // Nab the column names by getting the keys of the first row + $columns = array_keys($obj_res[0]); + + $insert_rows = array(); + + // Create the insert statements + foreach($obj_res as $row) + { + $row = array_values($row); + + // Quote values as needed by type + for($i=0, $icount=count($row); $i<$icount; $i++) + { + $row[$i] = (is_numeric($row[$i])) ? $row[$i] : $this->quote($row[$i]); + } + + $row_string = 'INSERT INTO "'.$r['name'].'" ("'.implode('","', $columns).'") VALUES ('.implode(',', $row).');'; + + unset($row); + + $insert_rows[] = $row_string; + } + + unset($obj_res); + + $output_sql .= "\n\n".implode("\n", $insert_rows); + } + + return $output_sql; + } +} +//End of sqlite.php \ No newline at end of file diff --git a/drivers/sqlite_sql.php b/drivers/sqlite_sql.php new file mode 100644 index 0000000..3bfe978 --- /dev/null +++ b/drivers/sqlite_sql.php @@ -0,0 +1,122 @@ + type pairs + * @param array $constraints // column => constraint pairs + * @param array $indexes // column => index pairs + * @return string + */ + public function create_table($name, $columns, array $constraints=array(), array $indexes=array()) + { + $column_array = array(); + + // Reorganize into an array indexed with column information + // Eg $column_array[$colname] = array( + // 'type' => ..., + // 'constraint' => ..., + // 'index' => ..., + // ) + foreach($columns as $colname => $type) + { + if(is_numeric($colname)) + { + $colname = $type; + } + + $column_array[$colname] = array(); + $column_array[$colname]['type'] = ($type !== $colname) ? $type : ''; + } + + if( ! empty($constraints)) + { + foreach($constraints as $col => $const) + { + $column_array[$col]['constraint'] = $const; + } + } + + // Join column definitons together + $columns = array(); + foreach($column_array as $n => $props) + { + $str = "{$n} "; + $str .= (isset($props['type'])) ? "{$props['type']} " : ""; + $str .= (isset($props['constraint'])) ? $props['constraint'] : ""; + + $columns[] = $str; + } + + // Generate the sql for the creation of the table + $sql = "CREATE TABLE IF NOT EXISTS \"{$name}\" ("; + $sql .= implode(", ", $columns); + $sql .= ")"; + + return $sql; + } + + // -------------------------------------------------------------------------- + + /** + * SQL to drop the specified table + * + * @param string $name + * @return string + */ + public function delete_table($name) + { + return 'DROP TABLE IF EXISTS "'.$name.'"'; + } + + // -------------------------------------------------------------------------- + + /** + * Limit clause + * + * @param string $sql + * @param int $limit + * @param int $offset + * @return string + */ + public function limit($sql, $limit, $offset=FALSE) + { + if ( ! is_numeric($offset)) + { + return $sql." LIMIT {$limit}"; + } + + return $sql." LIMIT {$offset}, {$limit}"; + } + + // -------------------------------------------------------------------------- + + /** + * Random ordering keyword + * + * @return string + */ + public function random() + { + return ' RANDOM()'; + } +} +//End of sqlite_sql.php \ No newline at end of file diff --git a/query_builder.php b/query_builder.php new file mode 100644 index 0000000..803b9fd --- /dev/null +++ b/query_builder.php @@ -0,0 +1,1049 @@ +db->sql + private $sql; + + // Query component order mapping + // for complex select queries + // + // Format: + // + // array( + // 'type' => 'where', + // 'conjunction' => ' AND ', + // 'string' => 'k=?' + // ) + private $query_map; + + /** + * Constructor + * + * @param object $conn_name - the name of the connection/parameters + */ + public function __construct($params) + { + $params->type = strtolower($params->type); + $dbtype = ($params->type !== 'postgresql') ? $params->type : 'pgsql'; + + // Initiate the constructor for the selected database + switch($dbtype) + { + default: + $this->db = new $dbtype("host={$params->host};port={$params->port};", $params->user, $params->pass); + break; + + case "sqlite": + if ( ! empty($params->user) && ! empty($params->pass)) + { + $this->db = new $dbtype($params->file, $params->user, $params->pass); + } + else + { + $this->db = new $dbtype($params->file); + } + break; + + case "firebird": + $this->db = new $dbtype("{$params->host}:{$params->file}", $params->user, $params->pass); + break; + } + + // Make things just slightly shorter + $this->sql =& $this->db->sql; + } + + // -------------------------------------------------------------------------- + // ! Select Queries + // -------------------------------------------------------------------------- + + /** + * Specifies rows to select in a query + * + * @param string $fields + * @return $this + */ + public function select($fields) + { + // Split fields by comma + $fields_array = explode(",", $fields); + $fields_array = array_map('trim', $fields_array); + + // Split on 'As' + foreach ($fields_array as $key => $field) + { + if (stripos($field, 'as') !== FALSE) + { + $fields_array[$key] = preg_split('`as`i', $field); + $fields_array[$key] = array_map('trim', $fields_array[$key]); + } + } + + // Quote the identifiers + $safe_array = array_map(array($this->db, 'quote_ident'), $fields_array); + + unset($fields_array); + + // Join the strings back together + for($i = 0, $c = count($safe_array); $i < $c; $i++) + { + if (is_array($safe_array[$i])) + { + $safe_array[$i] = implode(' AS ', $safe_array[$i]); + } + } + + $this->select_string = implode(', ', $safe_array); + + unset($safe_array); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Specify the database table to select from + * + * @param string $dbname + * @return $this + */ + public function from($dbname) + { + // Split identifiers on spaces + $ident_array = explode(' ', trim($dbname)); + $ident_array = array_map('trim', $ident_array); + + // Quote the identifiers + $ident_array = array_map(array($this->db, 'quote_ident'), $ident_array); + + // Paste it back together + $this->from_string = implode(' ', $ident_array); + + return $this; + } + + // -------------------------------------------------------------------------- + // ! 'Like' methods + // -------------------------------------------------------------------------- + + /** + * Creates a Like clause in the sql statement + * + * @param string $field + * @param mixed $val + * @param string $pos + * @return $this + */ + public function like($field, $val, $pos='both') + { + $field = $this->db->quote_ident($field); + + // Add the like string into the order map + $l = $field. ' LIKE ?'; + + if ($pos == 'before') + { + $val = "%{$val}"; + } + elseif ($pos == 'after') + { + $val = "{$val}%"; + } + else + { + $val = "%{$val}%"; + } + + $this->query_map[] = array( + 'type' => 'like', + 'conjunction' => (empty($this->query_map)) ? 'WHERE ' : ' AND ', + 'string' => $l + ); + + // Add to the values array + $this->values[] = $val; + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Generates an OR Like clause + * + * @param string $field + * @param mixed $val + * @param string $pos + * @return $this + */ + public function or_like($field, $val, $pos='both') + { + $field = $this->db->quote_ident($field); + + // Add the like string into the order map + $l = $field. ' LIKE ?'; + + if ($pos == 'before') + { + $val = "%{$val}"; + } + elseif ($pos == 'after') + { + $val = "{$val}%"; + } + else + { + $val = "%{$val}%"; + } + + $this->query_map[] = array( + 'type' => 'like', + 'conjunction' => (empty($this->query_map)) ? 'WHERE ' : ' OR ', + 'string' => $l + ); + + // Add to the values array + $this->values[] = $val; + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Generates a NOT LIKE clause + * + * @param string $field + * @param mixed $val + * @param string $pos + * @return $this + */ + public function not_like($field, $val, $pos='both') + { + $field = $this->db->quote_ident($field); + + // Add the like string into the order map + $l = $field. ' NOT LIKE ?'; + + if ($pos == 'before') + { + $val = "%{$val}"; + } + elseif ($pos == 'after') + { + $val = "{$val}%"; + } + else + { + $val = "%{$val}%"; + } + + $this->query_map[] = array( + 'type' => 'like', + 'conjunction' => (empty($this->query_map)) ? ' WHERE ' : ' AND ', + 'string' => $l + ); + + // Add to the values array + $this->values[] = $val; + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Generates a OR NOT LIKE clause + * + * @param string $field + * @param mixed $val + * @param string $pos + * @return $this; + */ + public function or_not_like($field, $val, $pos='both') + { + $field = $this->db->quote_ident($field); + + // Add the like string into the order map + $l = $field. ' NOT LIKE ?'; + + if ($pos == 'before') + { + $val = "%{$val}"; + } + elseif ($pos == 'after') + { + $val = "{$val}%"; + } + else + { + $val = "%{$val}%"; + } + + $this->query_map[] = array( + 'type' => 'like', + 'conjunction' => (empty($this->query_map)) ? ' WHERE ' : ' OR ', + 'string' => $l + ); + + // Add to the values array + $this->values[] = $val; + + return $this; + } + + // -------------------------------------------------------------------------- + // ! 'Where' methods + // -------------------------------------------------------------------------- + + /** + * Do all the repeditive stuff for where type methods + * + * @param mixed $key + * @param mixed $val + * @return array + */ + private function _where($key, $val=array()) + { + $where = array(); + + // Key and value passed? Add them to the where array + if (is_scalar($key) && is_scalar($val)) + { + $where[$key] = $val; + $this->values[] = $val; + } + // Array or object, loop through and add to the where array + elseif ( ! is_scalar($key)) + { + foreach($key as $k => $v) + { + $where[$k] = $v; + $this->values[] = $v; + } + } + + return $where; + } + + // -------------------------------------------------------------------------- + + /** + * Specify condition(s) in the where clause of a query + * Note: this function works with key / value, or a + * passed array with key / value pairs + * + * @param mixed $key + * @param mixed $val + * @return $this + */ + public function where($key, $val=array()) + { + $where = $this->_where($key, $val); + + // Create key/value placeholders + foreach($where as $f => $val) + { + // Split each key by spaces, in case there + // is an operator such as >, <, !=, etc. + $f_array = explode(' ', trim($f)); + + $item = $this->db->quote_ident($f_array[0]); + + // Simple key value, or an operator + $item .= (count($f_array === 1)) ? '= ?' : " {$f_array[1]} ?"; + + // Put in the query map for select statements + $this->query_map[] = array( + 'type' => 'where', + 'conjunction' => ( ! empty($this->query_map)) ? ' AND ' : ' WHERE ', + 'string' => $item + ); + } + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Where clause prefixed with "OR" + * + * @param string $field + * @param mixed $val + * @return $this + */ + public function or_where($field, $val=array()) + { + $where = $this->_where($field, $val); + + // Create key/value placeholders + foreach($where as $f => $val) + { + // Split each key by spaces, incase there + // is an operator such as >, <, !=, etc. + $f_array = explode(' ', trim($f)); + + // Simple key = val + if (count($f_array) === 1) + { + $item = $this->db->quote_ident($f_array[0]) . '= ?'; + } + else // Other operators + { + $item = $this->db->quote_ident($f_array[0]) . " {$f_array[1]} ?"; + } + + // Put in the query map for select statements + $this->query_map[] = array( + 'type' => 'where', + 'conjunction' => ( ! empty($this->query_map)) ? ' OR ' : ' WHERE ', + 'string' => $item + ); + } + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Where clause with 'IN' statement + * + * @param mixed $field + * @param mixed $val + * @return $this + */ + public function where_in($field, $val=array()) + { + $field = $this->db->quote_ident($field); + $params = array_fill(0, count($val), '?'); + + foreach($val as $v) + { + $this->values[] = $v; + } + + $string = $field . ' IN ('.implode(',', $params).') '; + + $this->query_map[] = array( + 'type' => 'where_in', + 'conjunction' => ( ! empty($this->query_map)) ? ' AND ' : ' WHERE ', + 'string' => $string + ); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Where in statement prefixed with "or" + * + * @param string $field + * @param mixed $val + * @return $this + */ + public function or_where_in($field, $val=array()) + { + $field = $this->db->quote_ident($field); + $params = array_fill(0, count($val), '?'); + + foreach($val as $v) + { + $this->values[] = $v; + } + + $string = $field . ' IN ('.implode(',', $params).') '; + + $this->query_map[] = array( + 'type' => 'where_in', + 'conjunction' => ( ! empty($this->query_map)) ? ' OR ' : ' WHERE ', + 'string' => $string + ); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * WHERE NOT IN (FOO) clause + * + * @param string $field + * @param mixed $val + * @return $this + */ + public function where_not_in($field, $val=array()) + { + $field = $this->db->quote_ident($field); + $params = array_fill(0, count($val), '?'); + + foreach($val as $v) + { + $this->values[] = $v; + } + + $string = $field.' NOT IN ('.implode(',', $params).') '; + + $this->query_map[] = array( + 'type' => 'where_in', + 'conjunction' => ( ! empty($this->query_map)) ? ' AND ' : ' WHERE ', + 'string' => $string + ); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * OR WHERE NOT IN (FOO) clause + * + * @param string $field + * @param mixed $val + * @return $this + */ + public function or_where_not_in($field, $val=array()) + { + $field = $this->db->quote_ident($field); + $params = array_fill(0, count($val), '?'); + + foreach($val as $v) + { + $this->values[] = $v; + } + + $string = $field.' NOT IN ('.implode(',', $params).') '; + + $this->query_map[] = array( + 'type' => 'where_in', + 'conjunction' => ( ! empty($this->query_map)) ? ' OR ' : ' WHERE ', + 'string' => $string + ); + + return $this; + } + + // -------------------------------------------------------------------------- + // ! Other Query Modifier methods + // -------------------------------------------------------------------------- + + /** + * Creates a join phrase in a compiled query + * + * @param string $table + * @param string $condition + * @param string $type + * @return $this + */ + public function join($table, $condition, $type='') + { + // Paste it back together + $table = implode(" ", array_map(array($this->db, 'quote_ident'), explode(' ', trim($table)))); + //$condition = preg_replace('`(\W)`', " $1 ", $condition); + $cond_array = explode(' ', trim($condition)); + $cond_array = array_map('trim', $cond_array); + + $condition = $table . ' ON ' . $this->db->quote_ident($cond_array[0]) . $cond_array[1] . + ' ' . $this->db->quote_ident($cond_array[2]); + + $this->query_map[] = array( + 'type' => 'join', + 'conjunction' => strtoupper($type).' JOIN ', + 'string' => $condition, + ); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Group the results by the selected field(s) + * + * @param mixed $field + * @return $this + */ + public function group_by($field) + { + if ( ! is_scalar($field)) + { + $this->group_array = array_map(array($this->db, 'quote_ident'), $field); + } + else + { + $this->group_array[] = $this->db->quote_ident($field); + } + + $this->group_string = ' GROUP BY ' . implode(', ', $this->group_array); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Order the results by the selected field(s) + * + * @param string $field + * @param string $type + * @return $this + */ + public function order_by($field, $type="") + { + // Random case + if (stripos($type, 'rand') !== FALSE) + { + $type = (($rand = $this->sql->random()) !== FALSE ) ? $rand : 'ASC'; + } + + // Set fields for later manipulation + $field = $this->db->quote_ident($field); + $this->order_array[$field] = $type; + + $order_clauses = array(); + + // Flatten key/val pairs into an array of space-separated pairs + foreach($this->order_array as $k => $v) + { + $order_clauses[] = $k . ' ' . strtoupper($v); + } + + // Set the final string + $this->order_string = (empty($rand)) + ? ' ORDER BY '.implode(',', $order_clauses) + : ' ORDER BY'.$rand; + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Set a limit on the current sql statement + * + * @param int $limit + * @param int $offset + * @return string + */ + public function limit($limit, $offset=FALSE) + { + $this->limit = $limit; + $this->offset = $offset; + + return $this; + } + + // -------------------------------------------------------------------------- + // ! Query execution methods + // -------------------------------------------------------------------------- + + /** + * Select and retrieve all records from the current table, and/or + * execute current compiled query + * + * @param $table + * @param int $limit + * @param int $offset + * @return object + */ + public function get($table='', $limit=FALSE, $offset=FALSE) + { + // Set the table + if ( ! empty($table)) + { + $this->from($table); + } + + // Set the limit, if it exists + if ($limit !== FALSE) + { + $this->limit($limit, $offset); + } + + $sql = $this->_compile(); + + // Do prepared statements for anything involving a "where" clause + if ( ! empty($this->query_map)) + { + $result = $this->db->prepare_execute($sql, $this->values); + } + else + { + // Otherwise, a simple query will do. + $result = $this->db->query($sql); + } + + // Reset for next query + $this->_reset(); + + return $result; + } + + // -------------------------------------------------------------------------- + + /** + * Creates an insert clause, and executes it + * + * @param string $table + * @param mixed $data + * @return mixed + */ + public function insert($table, $data=array()) + { + // No use duplicating logic! + if ( ! empty($data)) + { + $this->set($data); + } + + $sql = $this->_compile("insert", $table); + + $res = $this->db->prepare_execute($sql, $this->values); + + $this->_reset(); + + return $res; + } + + // -------------------------------------------------------------------------- + + /** + * Creates an update clause, and executes it + * + * @param string $table + * @param mixed $data + * @return mixed + */ + public function update($table, $data=array()) + { + // No use duplicating logic! + if ( ! empty($data)) + { + $this->set($data); + } + + $sql = $this->_compile('update', $table); + + $res = $this->db->prepare_execute($sql, $this->values); + + $this->_reset(); + + // Run the query + return $res; + } + + // -------------------------------------------------------------------------- + + /** + * Deletes data from a table + * + * @param string $table + * @param mixed $where + * @return mixed + */ + public function delete($table, $where='') + { + // Set the where clause + if ( ! empty($where)) + { + $this->where($where); + } + + // Create the SQL and parameters + $sql = $this->_compile("delete", $table); + + $res = $this->db->prepare_execute($sql, $this->values); + + $this->_reset(); + + // Delete the table rows, and return the result + return $res; + } + + // -------------------------------------------------------------------------- + // ! Query Grouping Methods + // -------------------------------------------------------------------------- + + /** + * Adds a paren to the current query for query grouping + * + * @return $this + */ + public function group_start() + { + $this->query_map[] = array( + 'type' => 'group_start', + 'conjunction' => '', + 'string' => ' (' + ); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Adds a paren to the current query for query grouping, + * prefixed with 'OR' + * + * @return $this + */ + public function or_group_start() + { + $this->query_map[] = array( + 'type' => 'group_start', + 'conjunction' => '', + 'string' => ' OR (' + ); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Adds a paren to the current query for query grouping, + * prefixed with 'OR NOT' + * + * @return $this + */ + public function or_not_group_start() + { + $this->query_map[] = array( + 'type' => 'group_start', + 'conjunction' => '', + 'string' => ' OR NOT (' + ); + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Ends a query group + * + * @return $this + */ + public function group_end() + { + $this->query_map[] = array( + 'type' => 'group_end', + 'conjunction' => '', + 'string' => ' ) ' + ); + + return $this; + } + + // -------------------------------------------------------------------------- + // ! Miscellaneous Methods + // -------------------------------------------------------------------------- + + /** + * Sets values for inserts / updates / deletes + * + * @param mixed $key + * @param mixed $val + * @return $this + */ + public function set($key, $val) + { + // Plain key, value pair + if (is_scalar($key) && is_scalar($val)) + { + $this->set_array[$key] = $val; + $this->values[] = $val; + } + // Object or array + elseif ( ! is_scalar($key)) + { + foreach($key as $k => $v) + { + $this->set_array[$k] = $v; + $this->values[] = $val; + } + } + + // Use the keys of the array to make the insert/update string + // Escape the field names + $this->set_array_keys = array_map(array($this->db, 'quote_ident'), array_keys($this->set_array)); + + // Generate the "set" string + $this->set_string = implode('=?, ', $this->set_array_keys); + $this->set_string .= '=?'; + + return $this; + } + + // -------------------------------------------------------------------------- + + /** + * Clear out the class variables, so the next query can be run + */ + private function _reset() + { + // Only unset class variables that + // are not callable. Otherwise, we'll + // delete class methods! + foreach($this as $name => $var) + { + // Skip properties that are needed for every query + $save_properties = array( + 'db', + 'sql' + ); + + if (in_array($name, $save_properties)) + { + continue; + } + + // Nothing query-generation related is safe! + if ( ! is_callable($this->$name)) + { + unset($this->$name); + } + + // Set values as an empty array + $this->values = array(); + } + } + + // -------------------------------------------------------------------------- + + /** + * String together the sql statements for sending to the db + * + * @param string $type + * @param string $table + * @return $string + */ + private function _compile($type='', $table="") + { + $sql = ''; + + switch($type) + { + default: + $sql = 'SELECT * FROM '.$this->from_string; + + // Set the select string + if ( ! empty($this->select_string)) + { + // Replace the star with the selected fields + $sql = str_replace('*', $this->select_string, $sql); + } + + // Set the where string + if ( ! empty($this->query_map)) + { + foreach($this->query_map as $q) + { + $sql .= $q['conjunction'] . $q['string']; + } + } + + // Set the group_by string + if ( ! empty($this->group_string)) + { + $sql .= $this->group_string; + } + + // Set the order_by string + if ( ! empty($this->order_string)) + { + $sql .= $this->order_string; + } + + // Set the limit via the class variables + if (isset($this->limit) && is_numeric($this->limit)) + { + $sql = $this->sql->limit($sql, $this->limit, $this->offset); + } + break; + + case "insert": + $param_count = count($this->set_array); + $params = array_fill(0, $param_count, '?'); + $sql = 'INSERT INTO '. $this->db->quote_ident($table) . + ' (' . implode(', ', $this->set_array_keys) . + ') VALUES ('.implode(', ', $params).')'; + break; + + case "update": + $sql = 'UPDATE '.$this->db->quote_ident($table). ' SET '. $this->set_string; + + // Set the where string + if ( ! empty($this->query_map)) + { + foreach($this->query_map as $q) + { + $sql .= $q['conjunction'] . $q['string']; + } + } + break; + + case "delete": + $sql = 'DELETE FROM '.$this->db->quote_ident($table); + + // Set the where string + if ( ! empty($this->query_map)) + { + foreach($this->query_map as $q) + { + $sql .= $q['conjunction'] . $q['string']; + } + } + + break; + } + + echo $sql.'
'; + + return $sql; + } +} +// End of query_builder.php \ No newline at end of file diff --git a/tests/databases/firebird-qb.php b/tests/databases/firebird-qb.php new file mode 100644 index 0000000..6e212b6 --- /dev/null +++ b/tests/databases/firebird-qb.php @@ -0,0 +1,219 @@ +type = 'firebird'; + $params->file = $dbpath; + $params->host = 'localhost'; + $params->user = 'sysdba'; + $params->pass = 'masterkey'; + $this->db = new Query_Builder($params); + + echo '
Firebird Queries
'; + } + + function TestGet() + { + $query = $this->db->get('create_test ct'); + + $this->assertTrue(is_resource($query)); + } + + function TestGetLimit() + { + $query = $this->db->get('create_test', 2); + + $this->assertTrue(is_resource($query)); + } + + function TestGetLimitSkip() + { + $query = $this->db->get('create_test', 2, 1); + + $this->assertTrue(is_resource($query)); + } + + function TestSelectWhereGet() + { + $query = $this->db->select('id, key as k, val') + ->where('id >', 1) + ->where('id <', 800) + ->get('create_test', 2, 1); + + $this->assertTrue(is_resource($query)); + } + + function TestSelectWhereGet2() + { + $query = $this->db->select('id, key as k, val') + ->where(' id ', 1) + + ->get('create_test', 2, 1); + + $this->assertTrue(is_resource($query)); + } + + function TestSelectGet() + { + $query = $this->db->select('id, key as k, val') + ->get('create_test', 2, 1); + + $this->assertTrue(is_resource($query)); + } + + function TestSelectFromGet() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test ct') + ->where('id >', 1) + ->get(); + + $this->assertTrue(is_resource($query)); + } + + function TestSelectFromLimitGet() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test ct') + ->where('id >', 1) + ->limit(3) + ->get(); + + $this->assertTrue(is_resource($query)); + } + + function TestOrderBy() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test') + ->where('id >', 0) + ->where('id <', 9000) + ->order_by('id', 'DESC') + ->order_by('k', 'ASC') + ->limit(5,2) + ->get(); + + $this->assertTrue(is_resource($query)); + } + + function TestOrderByRand() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test') + ->where('id >', 0) + ->where('id <', 9000) + ->order_by('id', 'rand') + ->limit(5,2) + ->get(); + + $this->assertTrue(is_resource($query)); + } + + function TestOrWhere() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test') + ->where(' id ', 1) + ->or_where('key >', 0) + ->limit(2, 1) + ->get(); + + $this->assertTrue(is_resource($query)); + } + + /*function TestGroupBy() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test') + ->where('id >', 0) + ->where('id <', 9000) + ->group_by('k') + ->group_by('val') + ->order_by('id', 'DESC') + ->order_by('k', 'ASC') + ->limit(5,2) + ->get(); + + $this->assertTrue(is_resource($query)); + }*/ + + function TestLike() + { + $query = $this->db->from('create_test') + ->like('key', 'og') + ->get(); + + $this->assertTrue(is_resource($query)); + } + + function TestWhereIn() + { + $query = $this->db->from('create_test') + ->where_in('key', array(12, 96, "works")) + ->get(); + + $this->assertTrue(is_resource($query)); + } + + function TestJoin() + { + $query = $this->db->from('create_test') + ->join('create_join cj', 'cj.id = create_test.id') + ->get(); + + $this->assertTrue(is_resource($query)); + } + + function TestInsert() + { + $query = $this->db->set('id', 4) + ->set('key', 4) + ->set('val', 5) + ->insert('create_test'); + + $this->assertTrue($query); + } + + function TestUpdate() + { + $query = $this->db->set('id', 4) + ->set('key', 'gogle') + ->set('val', 'non-word') + ->where('id', 4) + ->update('create_test'); + + $this->assertTrue($query); + } + + function TestDelete() + { + $query = $this->db->where('id', 4)->delete('create_test'); + + $this->assertTrue($query); + } + + +} \ No newline at end of file diff --git a/tests/databases/firebird.php b/tests/databases/firebird.php new file mode 100644 index 0000000..b1aa4af --- /dev/null +++ b/tests/databases/firebird.php @@ -0,0 +1,181 @@ +db = new Firebird('localhost:'.$dbpath); + $this->tables = $this->db->get_tables(); + } + + function tearDown() + { + unset($this->db); + unset($this->tables); + } + + function TestConnection() + { + $this->assertIsA($this->db, 'Firebird'); + } + + function TestGetTables() + { + $tables = $this->tables; + $this->assertTrue(is_array($tables)); + } + + function TestGetSystemTables() + { + $only_system = TRUE; + + $tables = $this->db->get_system_tables(); + + foreach($tables as $t) + { + if(stripos($t, 'rdb$') !== 0 && stripos($t, 'mon$') !== 0) + { + $only_system = FALSE; + break; + } + } + + $this->assertTrue($only_system); + } + + function TestCreateTransaction() + { + $res = $this->db->beginTransaction(); + $this->assertTrue($res); + } + + /*function TestCreateTable() + { + //Attempt to create the table + $sql = $this->db->sql->create_table('create_join', array( + 'id' => 'SMALLINT', + 'key' => 'VARCHAR(64)', + 'val' => 'BLOB SUB_TYPE TEXT' + )); + $this->db->query($sql); + + //This test fails for an unknown reason, when clearly the table exists + //Reset + $this->tearDown(); + $this->setUp(); + + //Check + $table_exists = (bool)in_array('create_test', $this->tables); + + echo "create_test exists :".(int)$table_exists.'
'; + + $this->assertTrue($table_exists); + }*/ + + + + function TestTruncate() + { + $this->db->truncate('create_test'); + + $this->assertTrue($this->db->affected_rows() > 0); + } + + function TestCommitTransaction() + { + $res = $this->db->beginTransaction(); + + $sql = 'INSERT INTO "create_test" ("id", "key", "val") VALUES (10, 12, 14)'; + $this->db->query($sql); + + $res = $this->db->commit(); + $this->assertTrue($res); + } + + function TestRollbackTransaction() + { + $res = $this->db->beginTransaction(); + + $sql = 'INSERT INTO "create_test" ("id", "key", "val") VALUES (182, 96, 43)'; + $this->db->query($sql); + + $res = $this->db->rollback(); + $this->assertTrue($res); + } + + + + function TestPreparedStatements() + { + $sql = <<db->prepare($sql); + $this->db->execute(array(1,"booger's", "Gross")); + + } + + function TestPrepareExecute() + { + $sql = <<db->prepare_execute($sql, array( + 2, "works", 'also?' + )); + + } + + function TestPrepareQuery() + { + $this->assertFalse($this->db->prepare_query('', array())); + } + + /*function TestDeleteTable() + { + //Attempt to delete the table + $sql = $this->db->sql->delete_table('create_test'); + $this->db->query($sql); + + //Reset + $this->tearDown(); + $this->setUp(); + + //Check + $table_exists = in_array('create_test', $this->tables); + $this->assertFalse($table_exists); + }*/ +} \ No newline at end of file diff --git a/tests/databases/mysql-qb.php b/tests/databases/mysql-qb.php new file mode 100644 index 0000000..2b6d235 --- /dev/null +++ b/tests/databases/mysql-qb.php @@ -0,0 +1,26 @@ +assertTrue(in_array('mysql', pdo_drivers())); + } +} \ No newline at end of file diff --git a/tests/databases/mysql.php b/tests/databases/mysql.php new file mode 100644 index 0000000..1c4a01a --- /dev/null +++ b/tests/databases/mysql.php @@ -0,0 +1,32 @@ +assertTrue(in_array('mysql', pdo_drivers())); + } +} + diff --git a/tests/databases/odbc-qb.php b/tests/databases/odbc-qb.php new file mode 100644 index 0000000..a794797 --- /dev/null +++ b/tests/databases/odbc-qb.php @@ -0,0 +1,26 @@ +assertTrue(in_array('odbc', pdo_drivers())); + } +} \ No newline at end of file diff --git a/tests/databases/odbc.php b/tests/databases/odbc.php new file mode 100644 index 0000000..aaf849e --- /dev/null +++ b/tests/databases/odbc.php @@ -0,0 +1,31 @@ +assertTrue(in_array('odbc', pdo_drivers())); + } +} \ No newline at end of file diff --git a/tests/databases/pgsql-qb.php b/tests/databases/pgsql-qb.php new file mode 100644 index 0000000..16039d5 --- /dev/null +++ b/tests/databases/pgsql-qb.php @@ -0,0 +1,26 @@ +assertTrue(in_array('pgsql', pdo_drivers())); + } +} \ No newline at end of file diff --git a/tests/databases/pgsql.php b/tests/databases/pgsql.php new file mode 100644 index 0000000..b8e5d2b --- /dev/null +++ b/tests/databases/pgsql.php @@ -0,0 +1,37 @@ +assertTrue(in_array('pgsql', pdo_drivers())); + } +} \ No newline at end of file diff --git a/tests/databases/sqlite-qb.php b/tests/databases/sqlite-qb.php new file mode 100644 index 0000000..ad06d7a --- /dev/null +++ b/tests/databases/sqlite-qb.php @@ -0,0 +1,203 @@ +type = 'sqlite'; + $params->file = $path; + $params->host = 'localhost'; + $this->db = new Query_Builder($params); + + echo '
SQLite Queries
'; + } + + function TestGet() + { + $query = $this->db->get('create_test ct'); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestGetLimit() + { + $query = $this->db->get('create_test', 2); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestGetLimitSkip() + { + $query = $this->db->get('create_test', 2, 1); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestSelectWhereGet() + { + $query = $this->db->select('id, key as k, val') + ->where('id >', 1) + ->where('id <', 900) + ->get('create_test', 2, 1); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestSelectWhereGet2() + { + $query = $this->db->select('id, key as k, val') + ->where('id !=', 1) + ->get('create_test', 2, 1); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestSelectGet() + { + $query = $this->db->select('id, key as k, val') + ->get('create_test', 2, 1); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestSelectFromGet() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test ct') + ->where('id >', 1) + ->get(); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestSelectFromLimitGet() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test ct') + ->where('id >', 1) + ->limit(3) + ->get(); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestOrderBy() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test') + ->where('id >', 0) + ->where('id <', 9000) + ->order_by('id', 'DESC') + ->order_by('k', 'ASC') + ->limit(5,2) + ->get(); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestOrderByRandom() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test') + ->where('id >', 0) + ->where('id <', 9000) + ->order_by('id', 'rand') + ->limit(5,2) + ->get(); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestGroupBy() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test') + ->where('id >', 0) + ->where('id <', 9000) + ->group_by('k') + ->group_by('val') + ->order_by('id', 'DESC') + ->order_by('k', 'ASC') + ->limit(5,2) + ->get(); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestOrWhere() + { + $query = $this->db->select('id, key as k, val') + ->from('create_test') + ->where(' id ', 1) + ->or_where('key >', 0) + ->limit(2, 1) + ->get(); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestLike() + { + $query = $this->db->from('create_test') + ->like('key', 'og') + ->get(); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestJoin() + { + $query = $this->db->from('create_test') + ->join('create_join cj', 'cj.id = create_test.id') + ->get(); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestInsert() + { + $query = $this->db->set('id', 4) + ->set('key', 4) + ->set('val', 5) + ->insert('create_test'); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestUpdate() + { + $query = $this->db->set('id', 4) + ->set('key', 'gogle') + ->set('val', 'non-word') + ->where('id', 4) + ->update('create_test'); + + $this->assertIsA($query, 'PDOStatement'); + } + + function TestDelete() + { + $query = $this->db->where('id', 4)->delete('create_test'); + + $this->assertIsA($query, 'PDOStatement'); + } +} \ No newline at end of file diff --git a/tests/databases/sqlite.php b/tests/databases/sqlite.php new file mode 100644 index 0000000..3bd5eb4 --- /dev/null +++ b/tests/databases/sqlite.php @@ -0,0 +1,163 @@ +db = new SQLite($path); + } + + function tearDown() + { + unset($this->db); + } + + function TestConnection() + { + $this->assertIsA($this->db, 'SQLite'); + } + + function TestGetTables() + { + $tables = $this->db->get_tables(); + $this->assertTrue(is_array($tables)); + } + + function TestGetSystemTables() + { + $tables = $this->db->get_system_tables(); + + $this->assertTrue(is_array($tables)); + } + + function TestCreateTransaction() + { + $res = $this->db->beginTransaction(); + $this->assertTrue($res); + } + + function TestCreateTable() + { + //Attempt to create the table + $sql = $this->db->sql->create_table('create_test', + array( + 'id' => 'INTEGER', + 'key' => 'TEXT', + 'val' => 'TEXT', + ), + array( + 'id' => 'PRIMARY KEY' + ) + ); + $this->db->query($sql); + + //Attempt to create the table + $sql = $this->db->sql->create_table('create_join', + array( + 'id' => 'INTEGER', + 'key' => 'TEXT', + 'val' => 'TEXT', + ), + array( + 'id' => 'PRIMARY KEY' + ) + ); + $this->db->query($sql); + + //Check + $dbs = $this->db->get_tables(); + $this->assertEqual($dbs['create_test'], 'CREATE TABLE "create_test" (id INTEGER PRIMARY KEY, key TEXT , val TEXT )'); + } + + function TestTruncate() + { + $this->db->truncate('create_test'); + $this->assertIsA($this->db->affected_rows(), 'int'); + } + + function TestPreparedStatements() + { + $sql = <<db->prepare_query($sql, array(1,"boogers", "Gross")); + + $statement->execute(); + + } + + function TestPrepareExecute() + { + $sql = <<db->prepare_execute($sql, array( + 2, "works", 'also?' + )); + + } + + function TestCommitTransaction() + { + $res = $this->db->beginTransaction(); + + $sql = 'INSERT INTO "create_test" ("id", "key", "val") VALUES (10, 12, 14)'; + $this->db->query($sql); + + $res = $this->db->commit(); + $this->assertTrue($res); + } + + function TestRollbackTransaction() + { + $res = $this->db->beginTransaction(); + + $sql = 'INSERT INTO "create_test" ("id", "key", "val") VALUES (182, 96, 43)'; + $this->db->query($sql); + + $res = $this->db->rollback(); + $this->assertTrue($res); + } + + // This is really time intensive ! Run only when needed + /*function TestDeleteTable() + { + //Make sure the table exists to delete + $dbs = $this->db->get_tables(); + $this->assertTrue(isset($dbs['create_test'])); + + //Attempt to delete the table + $sql = $this->db->sql->delete_table('create_test'); + $this->db->query($sql); + + //Check + $dbs = $this->db->get_tables(); + $this->assertFalse(in_array('create_test', $dbs)); + }*/ + +} \ No newline at end of file diff --git a/tests/index.php b/tests/index.php new file mode 100644 index 0000000..0b463cc --- /dev/null +++ b/tests/index.php @@ -0,0 +1,70 @@ +J?u2IO>g5iwgm_fHlBg@ z{_l+&Hy#<0Rr1hn$$SZ+tc>Y5g-CYfCvx)B0vO)z^O-| z2L1oXWQYGXM2{bSpX0xy4-p^&M1Tko0U|&IhyW2F0z`la5CI}^IueMX|Ig>{=|cpF z01+SpM1Tko0U|&IhyW2F0z`la{P7Y92E6yfj!zP;>`ff6;&=$hb{v=Cn8cC6(SqaCcq@Ag$7?ta;MjrVavW1Q zhH$jvU^w26wXz@MIEZ5xjw^6%#F53(jw6WUooFk29mmr+cH{USj?Fk$;y4pW1jn&R zEBgtK!;u!YAIH|nBDMv`N*wJt7>>8X_@)mLAOb{y2oQl&oj~CDXDsj^KVvl;1OJuP zXcf^F*Z+fgJ?MS?Rb1atMgA4@x{v&exW3+y58%uG5%)iAUJtSwmkM$Hfsa{@>7iH( zi}QEjeAKRuF>$_tG`Ih@BDvyPUS9LMPY%}>`^c@5*SzFLaIMWpZl%2DCAR|C7Wv4X zC$D+QEyuN1AGxf&<|Vfb*B1K7ohz?-$qnP$0w1{{dCg015ZC7W$PLJAUUL1o*5V_V zk=MNB`f#n;N3K_1^OEbqwI(0AZh6g1?i^g3=OcHvyyhj>g=>vIa!chkFS)PbT7!?= zSLHP?xv$__y^q`ydCg1iEL^Mek?WM#yyQA?E$t(Bro84Q_hnquedN9*uX)Lxfomxr zxpsNYOKvf)C4J=DhXnwMM{*8)Crn!M&E7eu}2s_I4ESBvvO=D+^m7!;Ut z)~NK02oM1xKm>>Y5g-CYfCvx)B0vO)01-G13Aoq)S1biM4bwzXCIUo&2oM1xKm>>Y z5g-CYfCvx)B0vP31o-@a(s@e!O0pW3z+V^ODB&pMco4_KI3B@q0LKq;9K`V?j;C-O z!topq`VavkKm>>Y5g-CYfCvx)B0vO)01-IV2~1B*|Lwc!YFL0>h&x=r zJ(B%I4~%@SHzQ?4fCvx)B0vO)01+SpM1Tko0U|&Ih`_%vfuQK)^ZEZ8k2}|}T8soR zwjRfI9@ps<5g-CYfCvx)B0vO)01+SpM1Tkofzz457j6H$d;XvI`Y%QXb`X6G?nHnH5CI}U1c(3;AOb{y2oM1xKm>?DQ1tQn{J(wuzsaSB1;zaTMx40bJW1a~ zfCvx)B0vO)01+SpM1Tko0U|&I{zwRX5$FHIw!8~s-Ty`S{Wq_V^7;Rfe~Z6&oPF-{ z<5&x@O@!OJ;vHHSU?WC<5&>Y5jb@SL@=Mv*wz&ReDnT1 z!#D5a^Z9?TF^?Dvu_z=~<5-KMh~s=5>u`+X7{f7+V*1Wg%_b7U{QuYSd*sO|_y50gGyd*L{KqVx|Nk!j z{#xCNWpC*>Y5g-CYfCvx)B0vO)z^PAw@BcSG#y6k; zf6~L{VSq(Mu$NE$BvA~B01+SpM1Tko0U|&IhyW2F0z`la5P?|;xcC1@Sb+VhlsT@~ ztei!hE)xMFKm>>Y5g-CYfCvx)B0vO)01+Spr!WC?%Ae2w>mKn;u_#O8{C*sd;`jlM z$8bE3;|Uy3<9G(gvp9Z)<9Qq};CKng5gha(0z`la5CI}U1c(3;AOb{y2oM1xa4Hj+ zo}M>Y5g-CYfCvx)B0vO)01@~D z5U}?Ddk=WT{s3{JTM_{xKm>>Y5g-CYfCvx)B0vO)01+Sp{~QGP{J*{bPrd$s4$7pH z2oM1xKm>>Y5g-CYfCvx)B0vO)z#oHT#GQ!q9vpjd+=b(A9P}XqM1Tko0U|&I zhyW2F0z`la5CI}^x)b;!&i{vOnU-KRY>WKO{r_Jn|2|G@0c@1$Pa;4BhyW2F0z`la z5CI}U1c(3;AOfd0fq>Y5g-CY zfCvzQ)0zO^|8I;07|BRt{{OFJiunHj|55%;^Z%!HHdEw@01+SpM1Tko0U|&IhyW2F z0z}{oM!;+S-^>4hfTaYPd%j?ND7i#{2oM1xKm>>Y5g-CYfCvx)B0vQG7zw!j|EJ*p z{~I!)eE@uA1wj*{J)O*|8L6F^8NpRv>Y5%@w7u;%~0{r{)G5ELkpM1Tko0U|&IhyW2F0z`la5CI}U1ilCa`22s! zme4f(|6eA5AJ=O}ZjgV!L%slPl;}?)Km>>Y5g-CYfCvx)B0vO)01+Sprz!zC-!G$Q z_W6T>;coB!|8*kdzEd?h6fYt`1c(3;AOb{y2oM1xKm>>Y5g-CYV9o^G`~T~(|NnoM z(zO47&KM~m5g-CYfCvx)B0vO)01+SpM1Tkofm4)#wg2CH{=Z(t`~Fij_7p85Km>>Y z5g-CYfCvx)B0vO)01+SpL}2a&`24>y5@5*GWB>p6q&T1d|2OjQ>Y5g-CYfCvx)BJhVK z;P3yxQAF$ZKV;15CI}U1c(3;AOb{y2oM1xKm>@u=R$zb|C{du>Y5g-CYfCvx)B0vO)01+Spr#b;^um5cO|L2|RNul@= z0U|&IhyW2F0z`la5CI}U1c(3;AOdqF;NJf~5BvZBTFP?&|8rDB!H576AOb{y2oM1x zKm>>Y5g-CYfCvzQQ<8u+{~z&;Uz6uqI!OeG01+SpM1Tko0U|&IhyW2F0z`laoPGrO z{J&<4b`z^*m&o6I|Nk}e?|1Z?mDkF@-=cK^He&QA5g-CYfCvx)B0vO)01+SpM1Tko zfj>Nf5HsfaYvgx8elz*a{r_7&0qpT@w*R2Ni2xBG0z`la5CI}U1c(3;AOb{y2oQnO zn1Fl#e>3*~e_N)9&;OssNu#I}0U|&IhyW2F0z`la5CI}U1c(3;AOg1e{~8Y(0oEeU zKICzYP7whjKm>>Y5g-CYfCvx)B0vO)01+SprxyV}{~xkNxrNoR@5tZW|NqtU@8h)R z-$sf4BmzW$2oM1xKm>>Y5g-CYfCvx)B5-OGnEU?!`KNY5D0W1E2oM1xKm>>Y5g-CY zfCvx)B0vO)z?=#2`G33rKkD_*865>A0z`la5CI}U1c(3;AOb{y2oM1xaM}>?_y51( zv`GU+ln4+3B0vO)01+SpM1Tko0U|&IhyW3&PJqw<8*d0S!Y#o5|I4Ka_y7N|>Y5g-CY;J=RmpZ~A5saVJY?0=BIkLxuw=Wk;}e-Z&AKm>>Y5g-CY zfCvx)B0vO)01-G{2zdGbukna{fVGOVw|HEmQ$&CW5CI}U1c(3;AOb{y2oM1xKm>@u z=|zCg|A%Z*ZpHrpKa;=t{{JiF-?abV28sS80z`la5CI}U1c(3;AOb{y2oM1xaEcSK zeEnJb|Gnn_YuO@<1Tc0hj@xkTKE=~T(IWyxfCvx)B0vO)01+SpM1Tko0U~fR1nhI+ zCnI;dhIP;X^Ird-%f#^c|G$)fzeBzNPS-4^coP94Km>>Y5g-CYfCvx)B0vO)z!!*s z>G$87>$m3r&1+5^ZO)(ciwF<_B0vO)01+SpM1Tko0U|&IhyW2dZ3*!Ce``+wV{MrK z{|lKKKL7u(<=?db|Fq3wiZ~G<0z`la5CI}U1c(3;AOb{y2z&tvSabc==l=t2v7j^k z1!P1?CIUo&2oM1xKm>>Y5g-CYfCvx)BJjsXfY1Ne+7h*x1=v;M_tq7B{@-?q{v-lK zfCvx)B0vO)01+SpM1Tko0U|&IPCEj8{(tL=U|_i0S`%PTM7#Y5{Y?aj01+SpM1Tko z0U|&IhyW2F0z`laoW=y)^Z)Ib|KBdtb6l^>Y5g-CYfCvx)B0vO) z01^1Y60rRL8wf%*mGl20b|yQd7ugp50v6Gy+2Pa}yEHY)8dFp3aB?%7N?yVu$x(JF zQDPet7c!QZW>3c_*yebdCE}CpaID0(#MZM!4B?|CwmEtsOGI%#Qeu}xrdS%^hr{dH z=J16q5}slQwM}eF+rT~zO|t_b=nPG=PlFe-hl1-_K6o*Er*`ufHoqw0MBsBL(0usK z-?37D)tRe{h0*-XWT}uVmWvbPC)#7`Z24F14eEzI$X50W3oQJI)dtwYU$H>oDQEOr zOx?M)Bc+ktRG~CeE=p|M$LZ1&JtZ|chz#|q;-=gRLp6{fgC`&FT_NoZ{H(8$<4Q)gi#@@#x$bY^PP zWZ^>(wMOFk)%T60&l76rd8%bi6?)6Lkbba$yZJZED7du8TuG z=im+@G}j5ubsm~+&%_1Oh0>;(wS|#UIIj!6X`wgmsaJ6*Zz@j}#)Rd1ad2D~ zTJ;ZDK(DSdUnu8F#Yz4Id+Bss7CKWRrYR4titSjjTt;5alqaT3Ik%08vdy^iE5p2! zLSxcHqwRSxb*>*7opwJDQJ!7m!Y>Vt3869Jp;5(ATKV)U@ql^dzdM9TeOzdcduV1% zl?V1m(@~U*^1yyo=!^-SF;AT;?`3TGL+h1Kj>|%8RA`NQXjNsmA!L@Hj+pvAuo0m( z;-QsUWlX-XdSrSOkL=v?KP+^HJ$0(`(9}7n{MUp=%|oLquW}P(lhad$yo{u)ys9+P zkWd-&P-(9VrOs8O?&rS3M1w+K&`aNZv)n^ki+Q7RHBkQAS^8DRvQ}uV_0np~ADdpp z)XVnWA%sQM2<0`N%9ZUKW!w137^)?QMcHiMxGHo8gw8;9orayxP3JRc3;m+9J+#1N z>TK(9oxf>Mh-!LdYGl<&x$tg#6aVQIPQ=(Bu_{~2h1_(hIJIdeH#$--f8^FLYZqsj zSqQ6gpj1%Jt>bRRqWX3OFc_m2@FJ{qItqCg458BzU@P-stlC$TBWntxSbeWu*ggMU z{$;ypUfHf0I1xD?dsWZAkSP~nTH`sC{dS#oJg;2W4V2L9ZK_tg!hy=J^&~-}>8)6b zxQtJattym`J7OwyI<9VIdrd>-{eSnEfX0@xrGW{yU0=?A62KAweH}ZJ%CpN;qijiP zHG4amV{foB+mbA>Gn41CcM|8a{fPqGoETxPiSyV`<7?QSc$Q7Z3oIBPWv|9Y*!N;< zSa+^O)yU7=6a2I+{QKT*|CEJHb>f@r{CnOB zg2s4IXylAxZ9-pfReil&pYtQ*$o(%^8cP$SN@Gt|jXhjrVdIQLX>Iub=IO36bS7G@ zJ|B8_Jh}f53uvFQz;8dclKo#;-b+WDp(7mRWg^Z8SiNyxJtgu&4V&i_FrQ2a)iRed-ao!bxHIo^gwB_Z^Ehn4~JqOHSRiH zF7Q+bSi<HNj8Sn6aHK71tL>3u|~%6=o4`Wg#i<7;^m{wpfY3YQz68e`da7*n7Rk#wNzk z94m}Xpkp;%#uLu_`&-%nW<4=~$7&FJ`$y_nV(4fN3LUwL@hS9GXV#CDiX*G=T>pA@ zMNleroh9QF6S^>9Gf_edata05+~d<}=tvo9Doz;k@6H|#8HWx-J{>jEGlH=U#2~lM zCIe%RJOKsX8}{ zS$5%BU$`RR7B%C)$hbf9Q?xtH@v_Ria4V4UZO$Xb~a7iwnRO*K&Ho~%UMVL#!*Kta8Q+Tns4aQjeHrMSTnO4 z84UvYVAzyFUa=tKG7|)|~RV-AN-M)E) zrV5WkDk0J$d6tcOOxo?&{4>R}+qj`8Zp4K*7}0v4rPwk#plQeB5I3G9Q9jIB7ELcM z+ho{-v(J7q_TbZ>sh$F94`Cw?jm9&wzF62WV-^N;DZ52kq8U2$I*|`b+$bkyM!a7w zBV_r;+oa$zufd@Qg)SzioX^^?{k2J5K|@zEDRdRb^Tk|2J|i+lAJ3|z*1-lk(65qX z@{)~Ib;6;_-9Hre?%(&awRPN5y!)mVw&9-t=QU7>eTf~^ zr`V)^Av>1(Cfk*|kgdcTfVY#I*zV*Q%O&8J&Gk6z&5wNU;6LQ;Kb>fesoDl~j{Dp|jIKJuiBaGr zMu9yi7S-6YYhKBlzZeL@aM5&O)(J+~g&GUCja^lZw@lPy@~teXSL}^i;Hu9 z-TMZDi!-2zf0fBdHNRsdd2*s$ZQ2k0GjZ{7Y=YgXPGK#l28Kb!Z~UTyl)T6d!gM_yol=z~Vg zI=Mm$_SL2{-RlGR4NijnGu{)Mc&-$8K8}ZRt6~m)^*A3)QVnlG#C}=C)_<7@vSIus}xt(Gvg@#ow4*OPkc)&Rfeyg zC>4rp#%I{*~=lt*HQO*fN)$FdaIN`wz_wEyol0W)-fJ>9kwF$Rw;cLQ+1+bj5^V& zRMdsmMWL|4wv~Xkl0~i4HrK3hQ~ID?AahR@iQx=qIFalskCfG?b`B5u>vx7T zuvTezsWMBYl_%gs?*#lFO_WxRPe?L%Yzs+ktdwK~-rVFZ*k z?8AF22TLkj;jidPms~CRGM6RR70a1`0(s!|=~Q|BW6fOH{l~RU#WJ zaSbL2Shw*fHZuY1shuzvLOJWVWyta8*Wm|p~Ms8#y z%(mia$KL-}Yquaq^dSO7 zU{(UE>@n&^bAP4SR%R~MvoHA*#g8*=)k;W}y@rh3sqL#?6z-0oLc4!uq}qLkkTFN$ ztKK$ksZ{k8u+M7zpJ zs2Q4fK+$TeR#jQ;Div>|&E$(oG_YKO{i`plA6ePXFqNPO;?vIXZDBE{_P>@2Q`4n! zWrin=Hi1pIKN$ZRBimdgb`z8~O-^8?wd^z4R0|fWA#H1H`VUkon;)CVPh(Vx3(59| zO|dW-ck6p@V^x*yBb#2I_EDA2wR(n(B?dgYl^kr3juBuM14qfD+Vf`Y#Qq%Fo;S~# zeN*$d3^SWmSL!yh#-M7gF+NFCX3D3BTxXo0!DKQoD?V;F+TV`l$AYzO?~!cdR>?)x zhceeO8=ICsItlGna?)ngMloSzl}F4^Hak7JaP6L4#0|x2{9;b7=9(o#J-Be~o?JY& zKjXdRK-spnSML3G(bKaQfy+TSb1$8AQ4&W-#oKp+su$Fzy!YHHeG0o6bb9+vaF4aP zqUfW~zdNnUAV--zH?38=(bn@}?ny9b@IRg-0{^7}Rz2b<-@M}N2p}x0S~;&+m~lpT zP3}jxj6e`}1Q2@tOL?1*0#nTFSGl5G*`e|;AXIzI)XB@958Q0U6W|OXV(VWtsOQ&| zGN%wK@=XkWSB4V${o6CDJXSq^qdaz@@Ey9T)_|#C!c|viVuU#9Hj912%jc2v&pIFb ztEK3oxOU^LT^>Ga*n)W$Vs-;iZA5#U*$0J3yVj*MS<)G?W6+7W4u|ly`QSiOCBFwCeRr}u3 zZM5$zw2QvHJCtutzF5^~)}HKQ^@VAYaZ0| z@8}hasf>yp(ZcW@;Y8G{^bl<9l^c-vjCJpG)n28e;8_lMM_pL0y|@rDuhLoYYae^7 zx1KO(|JlFsX+68#8;kU^BKI74qp2(z{7W1gceSP6kGn8?Tc}yh-kSfGo@9>s)j}VaC?z896$6RstOT%HOjOB_Lp2)9^Il(~I(*3s+;l6Vp@hc{o#+PA^Z$g-)hk z+la3jL=J`fKZJ^Raonz2?mbXZ_E+uy_*rYc>|@DJ6uu+L)t)<5o;t>oor;YyPriPU zXBpp1k6xR9Q_82G>eT7eDDgKmG^kV=r^a%5eB>Ssh4Szpqx#r?nnYN! zQ8=6PSptdwXqj!U!H~20xAvVS5k+@JaB(O95j2nVn7*!kZzLX8EB$78ai?mPrLuP; zlGbzLdnbR>w>_}#{y%qO;Ozf@2fqKeFotn}{h0e-nVey7CceeKpO|51V($NS%==#& z{|5a0eS;l}jj@ffufx&V2DU#s!}3w=4ZxiL?#M=##qmb?60H0GHfsrQWUr#%e~Gpk z^ZpmJL-6;1364Z)J$oAS{+okSEP~^q+Hcxh_VhOqAOfl!P#&^GMe$L)SheWwKueSa z?(n|7Fx$M7eV#?Ml9hfe))y!2Aw(JGxA9%|`EBzo6)Jri7zxa@RQ0K?q9tbcJ~E=F zP9m-b0p(qc4~WVZhTqJPs;eDM>$%3*e{QI9eQ1SFQF%KTi2B!ds-I(DD_fz`-P-oA z)0BO=*M(N-6inTrX069myFz6TcZB(2WtNGig)5NI?%(NC^|2*p%;J1pslIX^$G@$q zY{51balBHcPczHEp{dr5Sn)Ml%J30afxF7{EwEHsi|N5dw3@MVbViCHiU~-xlY37@K40v9zaCZ-%wCJ zDf<>;lQR>mSEG$5*Gbq^8!czv&bPt{?f&Lst)Ve=V=I#I0|6gdqSs*4E}OGlyDgMq zCt~A-=c-+0=R&{H+?H!yY`TS+J3O{20; z`4aootVJHb=un^@}aHi=q-g(e8H}k)IWzeT^G;Q+b&$jKYuR zObLsZlrI>U2T4&o@BnfvMHLgY^h#eUC!@Do-&k?SUaH($=a^T8%S%Ds8F0=vC@2ct zGt(C+KN~J0e!Q#atbe`=eHF>@yNjQ7sxr&In_JWiYNFn<5jaj2#g{#-faG6%sdp6= z$5F7rzwagtrmrrPN`?GyjP_-f`xqg)XJfhM#@~umI_xsDrNbUpq{H8uRC%MKYm8k~ zxrHc>UBPEFDXToTsEY>ZiE6Oina^8}9zUhN2_H&R~1= zOW9KWGWMF7|NkcYH2F>J{pbDv&Frnj#pwTkleJ_1|8V?LwlThi9gls7?TB51eg7A+ zBhf!&Q_)M&_x}#t6}gaQBAeMy!&|WC|Jzvek9mFVLcH(qo9uY#n`{sI{3|i<|F&53 z|1H)MycBExu_u7~{*Eexew^k6R2y8qJ3-D6$Tqk-jozvlBUC3;xLL~x)mFC2T5V-@ z((Dhb6LJK>UM@IjFEr<6%aeFh;A%``!C`u-xC*Z^Dt}_sGC7;~@Mb=j=^0d{z`yUQ z?jwAJDU~969`_L)P-iIFsjo7Ss_F*UxVES(l`=u(>=Lv2Fag`5GXI*GS!+xy$Gj`RR2v(w z(!H?uT&l=gYaP@nGydwtNv?IU+8uJ)tY$ znI_R5_6{%TReR=CzH{#q{BD?=Kkj`SN3p>l9-p%{c7X&qF}U#6}jQ>c0kpeDyB1P^V%oX zZX^aqR8TqD?C*>~jkVbP23KJvwA+G;RQQdItNy=Lg7WdhD(Zz_uo3! ziah{7PL|mf$r5XY4}c>W2e>RTj&Xod_T%_Q_PzKRTLK>dKaE|$w&G}yUCds?K7dQH z4j>WT%nn7?vn>(K?PD##LF@zA7%t(hU+dWc>;;(8a9%r~9SMzMEx>w~4wc~paGYHc zT!%4%3)$c$>dvq!g4Y3pLm1<>) zE!VI0%CTVo$w{$SlIs==o`30~QB;xgs!>p)0`~N7E5?SlDSag zub)D{#NY9lVwfuxj5kVi7yKW(V<*~*?)CM4&c~EaI0)xCJyV_>;qT{l#nYz+ltQY# z-Xbsi%A6tkG~UcZ$ua=kUz3j}-*`sgO?F?MFSPh9<0zR3NmBEL5j z3X^zmD3<7=9f@~7I~9wzP1zMj0_V*~ABj?Xqr{6*sl;3$aeym3&d2*)9uf^IPMh$}_V95VdPXo_SQQ zUNwwue}`>NgvfCBJ4Qt;ZGl8A{d;iAXKOh({*EySG3+oG`a?6uDlJ?qFh7%_PM5WQeO+0pLt_%bHKJff)UFmhu@z(1IGK@R#TTNt=!%JkH9#&|NldJkxjw>|B2K^@c%!K_y2u^9Yf!LNAhCUjrRb)1>gVM zvHpK4)&RT}A7wl64#1`H8TRAYH`%3F0}w&q|6mmJ{P6w%Df;^lq3=HxnPfp62ZRrR z&FK4|&kkxO`2XYn|Hs*Dq1Eg%;Rj$6egHPW|NnaQ{YzL6u-@^=&<`R&1Uv{>?Q1cz zIx|`v$DB-xw|;WAx3wGPml32mzIsABy7FyOTJ0TMwN|0kvj^wjm{f6f>=~}mD&p!0 zBFZrTHl$lGA1L);AesQ?RJLtS%6FhbwfQ(nj4kkbx+1nl&zPg;9dM(xMIXRxutN1) z!~g()2Qy@_UfP{mBBIVPLa$$2Qq&1b36DmFN_Q+h8<9$n8Zr=iR7_A03O(wwh?Tz$ z37^e8US)LcrqHK)*NB*1VdY$lzx}BEaNDF^p{Gsy+m0Id5ITNY8NrI7C|`3i=9+kV zZJi|1GpxYq-xc%gf~fw2Ys9HSwfC?TUug)d^d9r7W|wxfo_6VBNtJe#c2PlZ-NTan zMm|)&k4#TZ%)rsf1pGBrYV>KN`Hy+1azp6NO^l5dF*Y%G&30FCv_)aEf`nA&8&0s;jbePgNOEJlp=op0SpOD>IqTX^v+OWu4SFlF#$kj(X*x zFg16uS&BMq?#zsDX<}*udFeHGCU=RLwj{$cd55QLK zBDSS=fVI~yXK&RkXTh2Q_F7;$+lJ#vaDaV3xSVBi{FuL-(EMnJ-~0r>XLy9ok9PR) ze#gFh$v?Emzx5a2{>ByGp1I=N|N4q!?P5%_f2fDg_ibhWE4vUflaOg+JmCTLdy(sR ztNeX|x(99;KK$l?V}JFH%ii|BNw=I-l%rmI8LQb_DgUS6{x>%Hz4Ocj;JZ@)pZxiE z|4b!f*sULFtdw`?cgPQ!lb&K1U|=r;9EUu(IvF~|2e~*OJ-d^+65SCQ9_+lhGcypC08d)asY>`xWj0XJLxu9SDg)gj;SW(!sh`3klU z@r=c`;=XRS4nY;~>#^*{nO?}vA--VikgH(pxSfb6XX|+OD(%+s@byVH_UEEf-Vs-a ze6O1=OxGb_!PX%zo_)h^0|AFzrQJFNRlKif9&&StuUoEyt>bp5Y#C)MNC(YlS&qh| zj0-r+xT<+EIAqy*z*h61>=SF4dEGuXvJop-$0kafW-ypCk;96C{BMnhwLCptMw>`> z9Gy!Mob{WnWR-q_T>7y*CNJCUv#R!MtgE?Vcbwr*UEIz{ihrE_8tjO@>X)k674ouQ zs?PYK@N2tU>ty(HkTs===}BzvsEny-wDL~B8Adck{KpP_`uZO66iSUTGLM$JYFDox zDHZvS(iyQr_M`TBJdW6G$;Xdy&bSC?3C6Z*>%0;E*C>C{;PHp6#!*aFa^BVNHpU(R z+im#3;m5{Z{8+2Bxcf4GH}UDvR2#HW>988u7;5^^7ngU3%p)UUf2yBbD&!_g`I*AT z$?5Sm1^65g1|!e%{4~dLoR6blQ(nbcYM7iZLFV!jApRJZIj0PGz1`se4et<55%b6N^U1MoQ~2!Y&q-6eniH zqYN58C$K0;@-cEQ)Q>&-G~njO^=PkhmxosXP@b_m2vL* zf8JiIVZH2iJ;QeC=dwwCIcwGX*s)YMJCqt?TT^{(5`VYg@3)f~b}%`>zL)G{`D8C^ zOrDK(0XUcFV^<{l*~&x@V~O)HHqgg*#h1ZPKtF4b53qM)L+t4o@^Kxw0`U# z7-H>Oh8+uK*x^tg+ZO6!qoE<59QqIeB0vQGWeL>()8(K2jypmy7j*IW#XpWx{WDe@ z3TQt!=3pncvR^slRrgQd`<;-pZOepM&1YCj{4)q`Wq<1wz^?{V#<5VMx>TbnWBR!= z_VZU2QZnYki8;u$xbI`S_WGF9@K2U*@>7lw5!`?m@`VQ$_2&K5OC!*Zs(L4^?fj?9 zfA?GAfFHhiiftBFdDkndc`V+hFF0DJ$N)h1(i=QZuJK5lfAwuRVzR=f2IRg^cNo4njdZ z2@!9yD({61T2m#w1G2gZ<9R5aH$$d?*Cvkhy(F_l4|XPPM#yoH*y{wL-@#})su&G_ zzJ<*wx%Z@4pu;;=jKxj%Be~IccxZ#UuuLrEs4PTar%h)3+#Wh1KAT}D&HKrX$?A?( zavAqC$~(vuv5p<{l-#lBM^<$Gh0{v5SlGa&-kpmSPov7a!f0N(mlyY}W?L!T@4pEr zHDVFmudKTTEHi$;@_|?PD;L&?68hL*56l8kcyk#U&hJA z&5)Z@+>E+DU&M|r)Pmz{L>~P$>LZ_$o7b75oH5&gC=`W^QGQo1Y*2=)`q~`wR`oTn zWvVt!tbAV2%T%dAJj)LL?*2cYkm3FRAM5?-{|{gfz%Xmqd)OPP0y~g82kQXPXHWIA zNUDdumRyDX0R3z-nT0<9^t}^Z>@fQL+Y-HaH((D-Czi7};)Cqz_!6uQSjN`Izse$U zoQs{!_Q(3MPhb%K0++E*q8a!M%(AUG@;DOc|Nk^HfcFNhWSb-C*GIDKSUAI;4rkdF zII`g(7DWI5b@c!DXc#j=UDf|&AMA`=B%E|1j35C z(b%hrz@4ZRy}Q4v{UNt9%KevBZ=l+hw(XBq1(3hZX8dLQ2oo~JGV0$Gv$TO#K8yaD z<(qxB`>AmbwNp^}?W`)Q`e`;fbS8y;%+^n9HPh(r!>#IlRX#X9XhFu&K5#rKDz26c zZ;{L?u8L3PlFzF5TwwNNopSuXzu{Rq8za?Dw!ALQ>G@Ofwd73yPF3Tp7}@0J%E*#+ z`E8m*I+ZP0<&25}{MJ>Cv$6$CmhVNXDuceM(ciaZ#Ctp-H0y|v?b&p_p`hw5S>mWH zXAEEUF9}<%(E`6_fw)hBlV)jQ!n|F}|Ej^7&NK_n(`#6SVWBVD@wur{hn1tFMryqDd-xx7;izY|6sJ&Mwp?LIMipO|r< z8a6)RybI?o%I69{<>2i{4oaKVvo|pgQX_COB1T6=89Lj82YFR)+nfMa&tcp6EuRc6 zmZyq2WBe9I69=X3piM zs)x#JN8o}*RwlF8|E=b8sm7#jolj8?7>{Sf8tHP)Kp6QZ?8WxxPb5_Hv_#}H|0GOX zG5k#-`0NIv%4YX_Fso9y7OX1_PLZM+9~mSkGvn|4|5T?T^Hj8)l=nqTm3JM#YjDkBHQzkG>( z4ZBFp|L@b!VVB`pseg^7^)B`%`uzvc@4q6I!5Bb4#sK=*PcZ+#KY0$@l3dQt#5lmQ z!~n(sG8hBMU<{y_wIqhvo9O!=z#4$d<3lVHA7-D%k}MJLz&?Q_dgA$%Xg_htD0 zKE5;f&g1*jTBIM}ui^WrwViASzW3pqK3{ACyOI1dj?H4<31hb^9C#{YC|`fW=Pde$ zf36A+zP^>Qol@>XDR-l{+>l%DS}C_t%5C$O8+6P4wUk>g<^G+w+<;r|ds1#n%KeqM zT)$iHUrD)=l>2jUxr|%xPo&(Wl)KbhuFoy^Eh#rH<-Xx9*Xx#pM-!2Mqf+idZ@C_~ z+_aQCU&?VCFg}ia?{>>gAP^6`R?3Y_DPDqG_R?EnFYBb-YAJWVw_Jt2tdVkgDYx2N zuEJh&Qf`%$TjecRVJ|DB+)638+*_`~Ue1+r=Sewh6_|rdg}w06V$L%w<@&tuS79&R zQtn(Sceb}&g}r=D$_+`muXxK<*bA>IcsvKBT!*(@g}r=9%4MY78J==I75365<$9&u zB5%10ds!gmx~1HFZ@CJ4X_9hhOF3&xlRf`>D(t0R$}N?0{I~IO$W_=&9RhKlUzKwF zxAAevRoDyfPja~>QqJgUa!H3=g}vxfu2ahK-^RxwS79$H1mgEQQ_At*#>XL7VJ}Gp z;&NY-a{RaPamZEJ3-7;jxppbXe;XgjMa}wX_#a)>5sM=bmoxTPV)=rU5?qwrP=y@t z$8tGi-zADLPq{&-+?jp<$bD9Gxdk$w{I~J3$1~%ULx3O-F4rRE_;2H5m+PsJGee1Q z-o7{c*5Aypt$){I>8+*y}9->$qHR&sA&6C=l_*Cx{EHwKeYE;Pnj)3Yt z5!+g}Rnu$WYjpL5nA0}8Oi|x1lbQ<`3!CLWqw2S^<}Kw|ujnexG8U!!=1wmcRqv$w zo^Ib2Ri$j}YTO&aA&1Pa?OQc7#2x1+F^50rDhHAV=$Qb@NXSn&MO+xN?z&dZrHCb}F?L3#>xiF1ZV& zoORxmyTBp$8F#!XK1M!FN;&I1E{c4gbjZE$lADln)_GHI!XfwfF1c|jXPr0Y#vOA1 zmrHI;%30@4xiN>_|KgGxm2%d3Q*P8D_q&X;o5c~kCuhul3b zxuTS_&YN;Yhukig+*&DToj2vy+U1U4?UGv~dXW^QK(RlEbN0F1b}w z&N^?(t+L6Xo^i>INIC1gDK}!tp&VM`l3OX{KK{TuZ_2H-g&eA0b>5U)Zpq=)3Dqew%9pH=v!6HRvJSbQ zyX2NhIqSSBx6G15IrX1ia_0)U8fKk0<<7Oqeel-bxa5X~-0^^Q-jo}*j*)yFTQS8x(T(^QPROC5M@&11`A%c|YsCDK}ur;nY5tT)&jF&YN=mHn|$M z%O#hQa@KiME@R2TuD7}5`h?uaA6Vy2xjst{VZP^*>lJdpc*{C(%Jo`uu$B ze8M_!%Jo=s;JL*m*Dd6J)^43Q<+?37oSJgUog?Jz=S{hD9CB-2a%W39>%1vQ>i{=WnTyJ07skLs-Zywd7#eXS(FRCgW+HH|4%& z$-%B$TykF(a`yA4+*d6*%1xV6-y4Mm`iSnl(Wv8a!YJ-HSC0|9e$RS zv(B4xXIXNv>vvpookH&853KX1T&E?6FmJl#I)vQczGa;^4!Z_2e=aya#;F1dwL&N^?(Ewstiuy48K7DzekyeYT9lH>g$ zm)v|I_wfhTc~fq_C5JEtmt2dG`B0vO) z01+SpM1Tko0U|&IhyW2dO$Y=|e8$8}Jx20)qv|i)HTYJ@uNwJz`-iLs^9g+Yf0rvM zwQ~ReZutG%qW7|;@CA4R>-}HF`u`m`rr-mh9eV)Y!QTI;h5x@nwlX=yA~=46_5S*KFdfqkOV+*%Nh2om=88}2b?v`@Pbidm! zUM6O`faACNz%7LMy5#zX-)$E!60*IT*Nvn?X6W7a-zQytgYsT{$D4HJH|xCyWS-ew z2)TvaY3lzyS!90Cbiu9!L*dScgMY6QygQDtD^G9ov!xBT@q4j{ec0YMzw3zV0TTtjP`OV;U??K%O2RC5m%VO zSb^JtanDLCsIYfS*5%$|wtK>6Mg4&H7)(yX`PlR-;aBd2hv~WPy~nio;<#OOR<=E` zckwz_)9u|{(gzvw9y(PQahHRx#JPFG#=Xjbiraa}iF)=V%IqFI6YhGJXWog~;)v(r z_mcCd*XMLU^LcQ)4RE2X$ZI?gf~DH_&F|*Z77%XhjW)~hvAKzf``P4%j?JMt!1K)} zGkDB+eB541-AQxbYXI*to~>Esmg)aJWHOS4GGQNt?tAq~7KS+*e)%hkco&Oa8NX^K z?7mOWvDqInw@i1HG=(o8?*8B13jolBo&f+Wa4su{c2# zz;b`kTKEXTd!kxdFy$!D%PmaP3wSlI)u=8nVxcJBqA8rymIKU+U6O_3Mq6S$S1gp< zSTLsQzH$vV{fjIr&tuW}jPfy3jxnuvv`|j7a1ixCIl@Ak@DW;0u~1}sT)eBZtg}RH zW~8)cns31`*AwDAZ^$FSufbQN=j@_aZq7@b%%!<|rJi&`+mce<1pApU06 zQC`TB@fo$5tK7_jda1D5o?MM-VVT$_mPptyg;-LiX)y~0@#e^Klm*kyhq6#^U{3T2 zrfJ1BYmnAD7LQ{8Sed_ts~m?4w6@FjhywTif%l*m*CXP%6h;a~i`9Id%55oTzMYZ3 zv9C284)V9lEx>Y5g-CYfCvx)BJlYW@S6V*vZd@*y!UUL z4xjya2VhVi!QB4{+m>3!@~M@KrSkCopJm(N^Dmn$uul^!*wcw3yDX7sOK`j$&%@u} zx$Lrd7VrBjvNvNn`1f1EHsff;@n&=d+lOOgG{;)-{YGR3=K5E#smLmp#_?0U<8N1Z zgpJ~e;CM~Tv8_1P;vIhpZ5jIs=Jt1l@@x{{k zzw3Y73R%oSiTm<7dEA%J?aV2@=A5fBKyD4Z8gs6mDk|=6&Y_xX7R*7*lhJ{BBQfOc z9LfBs3V}*~=A3GU4nG*MNp~KY^Q-pZ@DHkIV}*`N8}zfD%KO6x#XOj6kQeiibJoG< z{C#xzgK%-+^Z#O=*_^}YWA-0ZiG*7o_UI+=8v(aG>{3idsd;C(D6fTQc|1&WDF=FC zlg7NYeJufA`7U*MJ<9HU?1fE=I9T%(Dh_j!hdoxxS1AzO3}A~wUd`EC4CZuy*kPso zN!%Z{DC8Y^Fn4*r2EaQHPC^IlP}B>Gfo(qKx8`%{X%Q9A7N4gVbpvL6_+-+tYT~ZE z;B)nctTFwBdWD;^_l@1Muto7asyfn`>N?S0TkC7kZXK{i-@3`~p>pehJ<51E=CBYC z{|s^Ks9sN3QB+yh_L%l*&DP=h@{fx9`RsvxiZv!`E?&(6{>HxoL_Bn7y^FbdvFyuK zQ+}^%EGpzNNAIe(t0cm8Z_L$;B}DFN!H=s&!KDN9_Tn*fPIIE_^s@_>Jm&D7O{OZ* zceBG>zPD*tsjl2QFt6{U!!H4qd5C#_V<`=P<6`+&)#z2^ALjdwrIQ6Yllxwkdd|fT z^Zstx3VMF?|2;^C#@@nY`&n!y+(0n46{`elUOD~%W3N64H$)G?>*T{M(D(=ov^>fJ ztp`}3{Rb@YrN>y{OgI_rJjen|o?wBmKFI=0pJIWtpJsvXXIP-;Sr+L1Aq(^!Vu8$$ zSfKwo78rP*1qKhZz|adUF#IA5oQv0=EIYyi*_T;h`B4@)?-dqUu|E)4d4Hg0>jQzn z$bO&{{g-igab8yx#dW>=JKPdnyu|y+mR6Z5CI}U1c(3; zAOb{y2oM1xKm>>Y5%_`=;QjyKcraLum*CW}U*Q!qo3Q>LE6ESm@R_vUzF^IN4fYKL z+4Vf!1?mk=Fb2BCT(Ko0hr1 zP3wQ4O&fTmO&ff)O&dDUrVZbp4)q*t)4HE%(|Vq4(|Vt6)B1jhu!j)#Fv7lourDI) zQG|Uk9qN4rVfQcAdLCM=^*+2<>w9FemU(Qk*8lioZQ$TyZSd*E+R!tLwc&@;p}uDq zYu(Qw?DGhF7-5ef?8^vy6k#7|*9ISK*M=Ty*M=WWhcXA+weBCZYdw#(YrRjlYkg0( zYni9pwf;lx+Q5$x_Bn)o5n+Fj4)wo;utyMf{~22E{by)>51gT89yvqnfAkD(;J_K$ z;K4Jrp(oDJh96Id2A(`a>wfkOt>^I$t@mJu*7tOWmU*T_>wmUG8+fim8+^V)8#>&f z4L^|%4Ib&xx?k?ldX6IO1D#smgPmIDp-!#;K&LkFgHCPmu}*F1$xdzfsdQ-QsZOo? z=}xWZP^Z@WBZPepVP8bpmk{;{!tOsy8@m52ZTOjVX!wD%wC+dF(s~{}OY1#wmezOh zEG_fISz7;-XK4e^o}~@`5Md7?Y(qL&)7yO*pLcfC0`Lb!6Le};TMo|2fJVqU9gBQSVR{r0$~p!>|um` z0byT6*rN#h5_0Ypgx!BOEaGff#M!Wjvtbcu!y?XxMVt+bI2#smwl@4Sa_-r)VG#)X zJi;DE*dqx0GQu83*ayymMVteRI7b_P1vz)%99YCTu!wVD5$C`n&Vfao1B*BZ7J;zO zA?%9?dw*SM@Fj#jg0TC$VG-T1h;CR!H!PwX7SRoh=!Qjf!-DHVLr->V-H&Bp5gAxS z1{RTlMPy(R8CXOH7LkEPWMIK{q2VJLSOmfzMc4=WVG;eXh<;c^KP;jj7SRuj=+}nB zb-|jx?x*@;6aBD>e%J&;KZnpSBJ@iLeFUNR55Oh{V8eBxo(BeC69ce`0ocROG9GFCgrT2zwM^UqRUYgRqD}Si~SKVh|QF2pg^o^*ufa zix`AO48kG?VG)C{2!wqeVGkqh5rlmiVV|lCWezWcMJ$6wEQ3WXgGFRv5m{J778a3( zMPy+SS=ex0sQ+LV7LkQTWML6mSOmfzLfFFy`vStgh_FWy_J?($fmaZA|8iKwa#+N2 zSj2Kz#Bx}~a#+N2Sj2Kz#Bx|La_-sXun2^G9$^n7>=A^08DWni>;vb)BF=+FoCga= z&K)=p7I7Xd;yhTyd9aA{U=ioRBF=+FAnbDp`y#@=fSh{?VUHl}{uQu@6|jgEu!t3~ zh!wDi6|jgEu!t4Vs|PbrG=;h!&A}>iu!U}B?o5;f^@~{bneGXw?MA(-Q_6Wl6 zFTf@W;MNrCd!PWDD8MEPu!#a}q5zvHz$OZ?i2`f_VGkkfkD8Eo2>SxUzKF0#5%v{? z-M<<(u^Kk98aA;SHnAG%YYO#0z8W^M8aA;SHnAEuu^Kjku+JmxVT3(`u+NdD#l4W@K2m~3lwc7h zSVRdHQG!L3U=bx)L(uRJt2^QQO8hUOMEMgNZViPQ4lh*h0CM|Pxlh*&r zCT(E0wq4gu9(}k$6g{H?bW1YfY!Tc(C5t$h+jIWtm zo7O|og88<%+K3ieT_}}CR*e>Fm=-PPO2x^k;>37eAW<_>S`-dNib!u6XC4bA`M$__ zAz#E!ceEzQ*UXEAk{fdcAs}|!H%0W2aAwdF*6MNZ@sZIP+v~g|fuu-Q5Ia1Lr9;Jh zl*Ke7Vj&h2S7R&|nVcvWc{Exg@d$RH7I_5XEEXyQWub+|!*Zv60y3OOZ9t2R7G_qD zj;txy1fo)78;c5!udrlqBod8bQ|6RVTW*fT!ZQ=ASHo2V3q&L3!UfZX@mwJgfI0-i zw$qT9vJVGB3AtfZBv210#r+nBg9$YOjSO25HZn{LM8n0L2$~EejG&QF?Q}7Z4VMvP zuC%pcB-P2>wnJ(#e+DlZhlhTM^<+}fE50XZEAMGDwWJ6@z+Tw+UI>6=V>vXNo^tdo)MlLB0IAt1yjuXOg^-xVQ-weAl zx1vTPT6`i`$WNCFGpj~Ru}BI55>h@MN%36669Q{0FP2KMpEZFDD{Y!tTNud~O4tq? zSIZ=d`9@}#SzE-&thlZv#wMl;Gb8zY30q@hdOU)aAyX6RB!zkIj27YnEi}$w9^V=X z)|PYQtt^x*j1U_`|kcX=C%lp~Tve{KN*yP}8;e^b~%N ziyUp{mNZ?;73%d^NW7qNQ6yA936ow8I~ke6bF^xD3j1;OP|SL#UyUAu9ZnV6Qqiz1 zZrZ{T4Lhofxw5WDv>EZvMy5x>3W$>%3WZ5%2P8cF8VkiXm8S}0!VKzR6Fg-omBuC; z!l9@nwefrf=FTr*vm&qCp1VFN#b_mqaQ0i2g%?H;{cI>r0{pL$@p z3NNz_vCHvRp)vITYgy_w(f>D!Q=Un|Kuc&c82@${{sc`Mh~` zeHbmwN6^B26s=LTOot!L2M6ZsqQ;B%J8QhFvUN9S>-OgA(ivOTSD%qpU!>ox`r3@D z@1C~kysWDI?##w(0u>eDJ&PJ|>}tHVt8w45#xOG~!|S@50s%*TcwZ`gRag3!u5_%+ zC}($d)!*4wAMbEgkGFI*+}hD_M@K`V!>A`4bz42TFlW`1tvOjwYW=dFyfGKwk&Exl z#c#^RZ^^}P&Bbra#dqi8x98$_o-R9t91P?UB9_Wzq3idrdhwTP2bz5U)8DKyHwxVrQhDA z-;mL7%IM8HuZXd&ARJ)mf96gZH=U^ilnZNq_#y;H%C%erc?JdrM5Mv zZf;B6)t0(vY3iD;R9)Dr&##TtZ*Q)@vAMoBU{&n*E^4SV>-K$2zo}Vouxj{yi|V(v z)nC(AUoWfrdsu2`b84|}RrvRGq}p_|&cCZekLgCWf482zwmErKTXIiF^7c&fxlFQm zM}O+RWvPW7_O?K4hiVJ7>Q*(kx1(;6ZZropT{Z{q&|@{t&KAK9&2?Mb>Tc?)yQ8bF z7C3n&*WBi89W=EWR=cQ4wGQrWO5fC$-rkkolT9a^jF!S}UFn95Xe`VNTa5)hY&45* z3#WEQDbXOG-pI>qE@yh5}jJYY)$ae zY~fOAz^zM-21Kl%Hz4j_7Ea)4FxwEpPDdMJaR+ZhwBe!SD(+fZ&pPeRh$}l=qMe4e z7FSE+x=0%CN9>J>YnXnuuHUZfjcEFDb$5sBu8h=e*XwT4>u$yWO?5jv>aOjoOY27F zFIeVmZOmV0wKf_%L~G;rjtJT$MuX$J&Uj-+v^h|1JKG#LMe48D>u=KQ_vrQ4bk?Iy zZEu3iS4|L>b~ZuqPFH&q1YSUlCdf_}yP3sqXR&+3v3paoEA`mzO|h#xV^?;?Zp+3N zW@Q8Ax}4t9<<&-6oUyl2;{8S&DxQ?8#?v-mg+mY^xa+hjT!ysjDA(W zes#aTtzW-)nSMi7zcH)t$m)%4wsz7jndCM7$$OS1w`G&JW|Mbilk?jAT2A$CyybLP zrvC0s{k}~7J(>C|v-Q_x>#xn$Uze@lm94)kSKrVk+f_|Xwsut=9(-<6*R`eZ>_|5> zS*@!^StO*ItkxAe2cmU#LtEs=w#c=eksX=HPW;~=*|RJXt7vj1M3d_#7TwLF_k^Q6 zBGIi~QKqXFSS+GiV7KXMYJ_>TwdfQPAOb{y2oQmPX##%p|F6{Z{{N&F5dHre7JJa@ z|CfV}zYeN_Fq?lp`0jUm3A~|Szp-E6(Xa39*Y_;b_b$`#TBhH2JgzvyE`{;Uv6Gg#`5QP zXC|>Hlh~U{+?7e(ok{G=B<{&1?#(3b%Otk;C$8*IT-Berx<9e4KXFZe;@bYib^VF$ z{fTJK@=T1ODxJcT^8P(OW&7EZ_TH#%%`u)r*&MmdobLUOW%@9 z-rEkxr@5rU^%%%6_(kZ0Wa&CBYHnl68x+R;sHJiFEo7$aC1<=v5@9(@S zo4z`m-j+>YlTBZnO<$KyZ_lQ$&!%t4rfTk`|-?xRxcZV~Isg4)2QSlit^q-rAhLvN=7! z%kF9MuCCPenbZxL)QuUu2ix?zcs+~Vz+yMD*bWv$-{L0Gz1YQKx3Ji)EOr}88WSJM{RS zdK`Ur<@WLF=H#~Kl@%dk?$$r9gg1~j^7cE z-x-eY3CH(_<9CJQcZcKq!tr~;@q5GZ`@->-4x4kyy&cKBI+AyHB=>bB@99V?Uz1m| z=v6FwHH)@&*qlz@*OA)VnYywwbya8T>dw@*&eS!XscSn^*L9}0cc!lIOx@6#!k|>5 z)8U))uB9z^FKyYkwB??qE%z>Mxo>I9)~=Q-yIQX5YPq_rWm{LvHC-*&cD2-XRJgFj zFxzb%4ZAxUZtrMdx^icErJlS>PhPDjx9Q1iF#Mt?uhWy;_2l(>@&-M5qn_NMCwJ<} zoAl((dUBVZjCVNQU+(B=+1b%@Q%B3q9WA>$TJYu)=?(MBNW)c;hN~kD+ae9uL>jJ* zG+Y;H*dA%PKGJYQq~XR$!;VP9&Pc;ek%pTi4Z9)@_of=|OEn}ih7Zl1ndnWK=*^jE zi(ctWbFbcbm)=;9F&q2ieOp_4cU$`Qwsc*S>09&Gro?SciQP?!+nW-1G$rnAO6+M$ z>}^Wi)s(oqDY3695pJ_N-`w8Tczs)AAZ@td+}RYpsVRDMQ*>8TG}cz(j`PO0h8=AU zJKGv=YHPT;tzlPN!!2zMb-LR}=dF7EZF>D~yZoONE8k8jV%Yx9Q7&TaYlHTifH zzqsSh8}jiR^YQk7>H3TAM+Arf5g-CYfCvx)B5-m9c>ka8^>=?hz;6E_dmpET|9^a& zZWsz1XS(lav7X0Ry!&w$?>Wfgy-%=s-;*q!d5XpRpJwraXIOmjSr#AqA&U<`z+%0J z5cWq1`y9ePkFbXk_63A}5n*3K*dqx0GQvK{Vtq#u_7#NPABgwfABguo5Qt|U48;2% z3d9E<4#Wo^3B-pU4aA2ZVzJDDK)m}0fq2hj2>Up~J{68)(T}e=oZJedzl0Zht?z{R8Os z52D*YbWjclJb|!JBJ5KLyDN}*Mve&V3MVii(EqH@puqpn-n)Rgab0JErwXVCfGVKb zFEpDZ8kX6lER#}q1L%iH$wC9wO|a1bgaY`Gv>&8M8d3cEQi zZByjw)FTKwcYK?gK_I$W1fiQl0J`~?5TGu&T|pqZV+bOL5OXsKFPB14xio^mWnS8$ z&K^Yow_^z4hTv`sLpv1&E<;$i=@$^{?LmZOdlVtr5VkFYkZrRF)i(D$!nYkkm^I}Z zMNUs47#k0qhM;Mwrw};pIRs9734zlP6m1Rx(ReVlAspn*Or1v{v=a|jmpEP{n0IM@sVgQXAcwZ>I4E*Ay`!kfvVC7Qk8iCA*UWe$f-vW8VVt! z79Nq5X@rcLLb#~u=Mfs}2*N!*jBrl~2bDn>s9A)7ntK+Zp8zF=f!aPkQ z#M2DIJEahYDg7)$Eg{^}>_Z5(^cX@doj|CiQxbwJ-lR@Fa}xqQ-h@zAHzT0d&FBj^ zqc7ZyzHl@8!p-OlHzQb>q@<7DqE0=08$x8=rp}za4S~9DQ`67fre>a#l*~(#GJ8~3 z=8nk-jVCJ$$0(ROttnGaYY3O6DKkfOCH1hbq#xIn%t>9DJ*O*k&+5wji@LH92nEq6 z9t$CaQ%IRP6GEt^kdnR-QZg?_5$-9b%sm!U=1;_wg~1pL)%8pa!P8>O41!Lk9zp0p z1Zd2hLa0B4GMsx3K@}19Zy|_Kdb3k3MBbAKeTQ&ssdET-hR|f07ZIKeLA>T3Lr^LN zyjmDUu&Fs7Z0Z7nO(DQk>JbExLO`j^DFl#u3IU{^Ljb7@Ta*PU5tQer9!VfzVnUfY zl~7VoC6x4Y3DiD;+E1YN6R7=!vM@9elozJXPbkwbOeixCCY98qNhSS6Qpuc{RA$di zDhL<}o|C9MJ9puZ%M+3@bzz4&^}tSZ`jMUH%<-LO>eNm%{nSn~^W08z_NATX+|jGe z`D0g`3uh!_`pK)!sk2v`)90@?XI{A4Og(swnSS&dGjl9$&Ynn{b7#`#{4;5D;Tg%8 zxsV3m4ESciHv_&I@Xdg427EK%I}5(E=E4QZNIf|VzO&#vYtB5k%S^qr%S<2L4Zge0 z*(Z0Kb7yy(^XGS)3lGRf`i0%*)PvWV(~n+f&OC9QnL2%)nSS~@GxPj)=IoK{&AErK zH|HPCnG27|M&@|VoH~^=r=QA!Zw`EO;F|~EJox6pHxIsfbK$sb%)XEZUkiLK@U_6# z0$t@SzR8?<{3bJX@+LEV?j|$y z>`mtEi#M5b58Z6eKX$XZ@Stu?pSanaI&-r*{mjkg%!QlH)C0Ge>4$DJGmqY8&OULQ zId}RtbN;E@z*{$Fp1sYSdO|l+r*$L!v~Fac*NxdDA!F|0kTL&w$XI|gSL$TQm^v3S zrk@QNGcSgW)RCBxemG`i9*-HbCu7FkxtKBkY|L1IqgVRHm@)OxfHD2pfH8Aoz(}1L zFw)Nq7?}$L#_R(djJZcP81u(B7z=Rz%A6QArcRF<(@&2YGtZBLZybE%;2Q_uIQYiF zH*PGzFg*KW9DK*XcMN>Tz;_IM$G~^Yn0;}JG51iyK(JV20S4o_6A5GLOv0FcCV{@0 zFj5ap80kkQ&_^bW*;5n7+*1?A{Bsk=0<6gMFHIOzN0Y|%v7|9`e9}mroHWwsCXLLq zli)iEzT3ceo3Q}v^1_L2;JXccw}J0A@ZAo++rf7`_-+T^?clrJSV;OTo#K{2Kp-Fx z5C{ka1OftqpK1h{{*SHxp1-qD?hX1A+~@oM_{Co(*o^KEWz3v8_7&T{J1nrTynZB^a%)Ge8oIR}>X$aq$r!-^sAu{G4CLw_J zG@Q605D*9m1Ox&C0fB%(Kp-Fx5C{ka1Ofs99|Zd8|3T13X)pe5`v1pRWdCKW)^Oja zzyes~y(Z0{Ngw9omOwxtAP^7;2m}NI0s(=5KtLcM5D*9m1Ox&<6$tdx|H~*{j1J=8 ze)|8fQ0$WwW3OuUSOGpwF}%s@*C>|!si>VOgg`(bAP^7;2m}NI0s(=5KtLcM5D*9m z1Ox&Mfqwe`W;`3F=kPDr{}Bzq=M8fj1!L{k|25b=J(h0fB%(Kp-Fx5C{ka1Ofs9fq+0jARrJB2)rW@VER9H)_wot9s0j7iMS~c z5C{ka1Ofs9fq+0jARrJB2nYlO0s;Ynz&ikee*6E-Ct!@`@Ne_{zq|iG{UE7xV*CFc z(6pk60s(=5KtLcM5D*9m1Ox&C0fB%(Kp-FxXd%G#|4Qo@(fi%+|5_tb=>Lw%^a(mF zt2Fd|Szaxdw#uv70}XlAS}n`fgAG;wMWt$~3h6kR-vHTNVQJ&{4ECk~Iae+g|)W9?AY!J!>(oVVAU!~HCx_25v^6TCCSd_YEq$GvW?8p z#*&OE0)BiWyR~=KYh@X)GFX*Y3pJOfi^FbXbG?+8Sztt`Y@N^bjJjnpn_H3Vm4+>& zFqT7=&!Ps4S=);5R+G6bFtMr~Rb=<128u7`Rz`M5Mgf+af%nPgUE1WHY*j{uEVk*K@3c8yInqmEV3Fut8D9T{sZ)|ccZt7>eeY@1r=-vI{r)0bwB3{%S$4 zAyP>fLm2}f4D)iMTq?>eVro}8!@I}3YCM|LCv#|(d;_BodMf--4`QQrk5F`lZ8z6B)A(tGMPIbYt7S$egMM(*u6T29+qu2W3{jK|CBGSB0y6|#n)8J$R-RnXh#kU;Q^P#vRK&Z8Ml#waj3rOOIw0P#d!%E{ z4eTE2?&Z6UajeIlh00s#=#gvz8eNwWgy`ZY0#oXG?CRE1wwdbmlYMRd2JW-V)k&zh%jlMgf&)Jt13c zR;iWmmR;(*u&S~77mF{GzrQ!P zJg4pS5;Z(y$ydl4=n}G|p0&ZusPQ_wzE4dy<SQ zfiKy{m~;gfH?uj6WiDj0Fj5kWBPFpwQj%?3b|kK)s>^lAttG6;m9i}txM0d)=V_Me zrJTe>CaICFE=zSsjvPp)%QzEi)F zNm`h3K+8($1h|wkG?TIbP5nmu8 zI|ma@k<%$}H~Ybz$%v;8vNWNwz+Ygxr5{smXFO5LqD`9B>?yNGTiYXbXs7lbk4k}= z3DLn8v2ahRF$(eyJg0#6TWGGNwI6Cf_yM>PC1X zAwgC`0^67z+UiP}-G%O;8=N#Ip(~)p8g==`*4XLIYB(0U0t*MUG!GQ}3f6w90aTm) zT#2a@`Nb7WKG^8!jBViU$Tn@lTCB^gC|#X*PTF=y-^QiIbMmZqb;2MN_+E zLF62fva3>dc;|+#PEW!D-)^g(?iZthQ8Wl!fSWnHEh6ol=-YP^QHFjCX?MUz!3)z_ z-C0h(txIe0GIT#)rOD8HFzTz>MRd1j$vjzhG+L`59e2^{_#aK!?f?G)+`9|cH4&!& ze-rxuhq(T){iO_f6>7bu>i>HZ{j;T6 z@)}1cX~y9KBfursU}RF?iVgyaunN0Db8PhXy2PkHiP;bV2DvQmZQ8t5fdwHC<3O%} z=^{QdR)a$2bf(T{TSlW3Ol0X8=ba=JAKX+sC_^E0yJP2LbEp1pI|p^S;x4|t>OGxn zWalRDgc+pq_EZ;6j~mv3oF!G96=9TGvzC-=;SyOwP5(W8*_UD{I_4 z{9>b<6o#uEj|~i#p^v)L7`g}sy}c=+ZDS%&$!12O*8Jg&sRqi5RoCX`ngneaU}C>_ zoQ#vY$;ub7eq-W-w7cB-Ske8a>_9@>R08?IN}0`_ZI%v(S|^4(wXHy1W~jqx)zJmK z%`v`nXbiH%A|xGNK<~3Hnm$p1o@Lv7jmHRW!O(1ugf_I%mNBomiUsGd7diPqaGA0GY!bQ@P6_7T@GAS4@nWn>t|b97=k--;XDf zW=9tp(8f2gV4~6iCf_VO0-LhNnohnQL>d@Z`CMSf661qhdTmr-Slm}Yr-wqlK{MZ7 zDa$tGMaQDmY^~0WRo{s^AVtT}7jgy2n@kMnX1a21}6j0Q12Y&z5onS6rFjo(J`aBS#IuE#zB5@vGwr5HBT<2QsOGdP zWMkeko}0oba>84qEirQlw$`e5`awlw zF>PZpyX-HcjE|1A#hOi;wy6wLNMX6ukXNjOZh4z?#%L@wyb60~Yj$(zTQxS~o!0f8 zkz}L1)R583T@F2~QWQcq78T#syN7tRQ|70{gpT_2#Yv7QJ&RK~{vN}tR|Vo5Hg-)l z?UgK^P?KyWgBcMf8raI+y#w#+Hk#;?#JvwA2`$Mq+Zt9Nr(t`CwV5!0g?D1@r5Q?V zW ztUc4R7cIAC_HoU`Z0!+3TKp&VG!{vidti*j3c@U>n1lRu$qt6W1QxD3Bp=&nHgFns zb3)x-Ebm9lG!`-8lwd*k%xE36^U$QaZ8clV$$L6c?v>h%xb_~4>8~ZslB=zu=C6>4 z()vc*W;+dT*50Kzlo%OmY61<&61FXY(AE>tt&j!ltBbJ6HHU}~-3VzD1u%zX0kfa4 z*gxD?h?gN}l=F40svYWCb2z9Sa>fKCnNl6?<4lL`Iy(w;V2wp=z;_N0?bLVj>ai)y zYYb>=#@*WbhJ?DUx$ofB{CWq0g27a;!SaiaA=j}?I6Dz;yF2|tg_gFT4cJbR$+HgG zW~YQ{Hl<1;b)4J&8)zUI5|!Jo?4D%H&i&v{J&i4wRf}(wLEOZ= zRgw$#YLj&zo!{6tIN2eKc*@_gj&9m9wACyA^`qOoV{o!#i1s5i653=H8!%Y@mObKE zZ({_v>F>tQ7Bg7L7VHPkmJe?W4{v*7vTgkE-luItV+ZDchAvH2)C* zfuy7HTdwV-wyb+MIk(v~!-9^sAV`irM@IM|;15%RD=56T-~x^vT-W5>-Y+oH+D7b9 zdz{L+^`^>qt+j2i;lt+(+s?h_KReR7K{A|HH`%%7K3$K+UnT8ba4lnw@CV_^1%nZH zP3^Q5DcdGG_S!?82A-(?p4M*ZSZu;?M|})C$jiL0%$pu=z3u&{rbTHZ?9kXETb8l8 z+XTe19n*l8*aKhW@_yK%Ve*Di7LD07)3a@~G3@YEtejj}D&*t_93@d7c-U~qW?mJq zyVRhjZDl^7aC0ljxz@g2&z8kRbIZcO7P?Od>Df9O?(}|$bU8f4+Z(@X%i$r^Vaprq za(LM6ad^+*-&C+3v<}@n2b9KMo0Bcg=M&fR~s^%&D?I# z-1@M`MhC_UC4MkrpSyM3dKRA?V7sgCZFt!Gc$s+Sf;${td^aqN-67@|%d6FE)*ncOufRExj=hXt zsxYZZc#y#l)%73s`j)un?g$Ny^|+R}&%)b`@&0=To4l<_*qMg6p2tP84UZj$dn$Gg z0U*4_d5zi+o7fG-#(VA18)Dr%_C3~LiS4R)xIngf+iG4Ky!#H!X19Oz&fBCzcQ!sC zedO*B2l7$n9_j86e=vkEhon0{c1Q!=2k-plF1lcoF5INkLHA3y-P1*`U?;4S^`UKtLcM5D*9m1Ox&C0fB%(Kp-Fx5C{kae&!MA=l{c8qu$KP6$<_aH{y+IM41uVcKtLcM z5D*9m1Ox&C0fB%(Kp-Fx5C{kae&!Ki`oHW`%Rj}v&Gbv~``=C#nLwgs?OdS{kw{rA zEH^TP5~;b$LOPY(NK)WnwTKr6s`;{=PR)%|ATXDj+eATmE~Deed}it#DKk%zGIxQL z`B6NWoqC3p=@)SKJSnLcNl70gCG!#~vqvOl0jCc_Rz0`3F*P$aKO~X1yf{5Sm6|#& z$t#8=D~*}d^h_8;c$3`xd}g6BeU?J0FFm)&zvNE_7v%Jr%9hbL;8Y= zvagg2^VKJ%U_C5Jnw@K8W~ZiAiGu81Z8%yQC?kYEDpJOqQ);$?9PGUXn-^mM6|NQR z*_o*TQ3OYfbLBkF5ZiVT$!bgU5{kLMP@0~}Og$*6(+^4N%uz{AJuIo|Me7syAHac#FiI=57k>+(4j1{my6cO8QNj zq%1aa_CZAosQGMZel`=Mpkmdz?LcMbf&mJsmR-S(O~h|(z>TFs2{#5oTFv6iX6MO9 zQi3@o3F(?Vn;HULp~fC>aUKujMyZ&`jf8VUm4ZsSiaa-Kfk36WG68!Axx6qw48)Nx>P_=E(2tE zOKdhG1>~jbrWSEL)-tV(#k5w#osG#W!?8f?cO>STTr|mJayhSsQWqqB=Cr1#&S-l2 ztfptqY5MF_nm+fmrq4g4=?gI}5NzzJmj<*T!#XO-fkwfOp*Xchp=O{Taf6H*Qv!`@ z{mEFc9%EnYSv}S=&xB&ZaK3=IDV0mv>cNc~DaBf&Sc`-@tTqFTb*Y?)N-Ewvnz!mw zEEqr+nurDiOVupl7y5+}3gY!wZ3~Y<6t3jU2O8YCmC%#`x}~A3%%DZO53^xE_Us8J^gWauYRQq}!d9(^>im7XT3cELa^0C zL1npWSt}d$AmcxZew)upp&-vi4(Ys?g@Vm&;vhpdV>&umvAi!BLay185>njk!bn-G zS64z_W7QDKu&-h7+kk=T7_A091C!ac>Nz%W?LtmP$99Za8fgJ^BAvnkbff_3fwpyP zSZ`UkUeML)2_2;M?2(W<^Jqv-9Sf=H2V-jHp_n>*G^WleX!nL?sWDwH)*g+8_MM8U z^P4aPFdJ;vRky3ED0!u}SWW&D&ToRXKtLcM5D*9m1Ox&C0fB%(Kp@aYU@iauao7JJ z`~S&y3h_-KAP^7;2m}NI0s(=5KtLcM5D*9m1Ox&Cfp;1L7t{YGC;or(omMYVUV(r> zKp-Fx5C{ka1Ofs9fq+0jARrJB2nYn)2&|?5C&d1L8z}KjARrJB2nYlO0s;YnfIvVX zAP^7;2m}NI?_dNjrvHom|95cXilPey1Ofs9fq+0jARrJB2nYlO0s;YnfIz@Sfa(8! z@&9kY^Ud@la`pcsM28VIO@8T(2Z>&O2r=f4k~HuzNyCqjH1-@xS3FA6*2hTNc8sJe zpC;+5^Cazfo}{ZUkTmrINz*TqH1migr5-0KbDX5vCrFw*K~nZfk`JFGY4H?3Pm`28 zgU_=hGSle%{GNnM*drEAltb#3N&NJ~8`X_*U>HhWsr z=FVtZ7Qy2WpVPEO1dl)bw5H_{JpS-`P0K$P)0UpY_Xl-t`4L?|d`8#yoWh)c z&lhxU?~#yx_|cHIatxo3$F$<{80cf#>O=TE8q-QfOk-w%m{vIz({DJ9yJuti%TL9$ z8=t{%g(U5zH&C;e2cAITpGD!1qVNx+@W)X2Cs5eOP}mnx*k@6oV<^z`D9}SF(4#28 zaTMe!e7=Z+Jb(f`hJrkf&xet*$umBQjL#s`bI9~*WO^Q%BIvsIGK2HwffLB|Ib`}U zGJP?srH&!9lbSZWS<~dD!%u+y(_sG+*gpuikASU4va0j6Pa^H}Nc#xVzJ#=oBkd`q zjU%Rf;cy_N$$Jk!iWE9h4Db{uk>UbU9EoYEi?H3-P!oSNACXcA5!FxDDuY?IV?p6NekX{_lH0DI~4gmMShnezekZc z?j5@GV~4&#kuOr@_bKuR6iND&S=D|gpCt8Dq<)&zUnBL`NxhHM z-yrojN&PIT|B}>yMe65By`R+ICiQnn{XD6^OX}~D`ahBS1ya9A>hF{K2c&+9)ITKk zk4XJvQV&b)T)6lX2nYlO0s;YnfIvVXAP^7;2m}NI0s(=5K;S2Y0Mq~DKD_=d_Wy(Q zkCN;EAJb@%?f)-kb1U^qV^6`Zm8%DbC>YJ{$yT$unpJJsR?Yq*4Ms_gS-F))u3WNf z)oh_uv+t8ehDiyPi^XiMfZM-M(M=RI@>azv<*ib#VA)@yfj9-j)`6T=;WS^Ms7yh1 zsbCfJc2EjM8u`MiRkB(74WueXWNF{8MFvPw9jAm4DSEw>z9t1@RcpyxM;Jsjw|c)qra^|`efpcy*k`1%0GV!uK21ZPqajUFg3LWaqu_vT zAYGHX9ng5z(1f)o)v1-{ibGlH#|tHQ`U;GX}&hfq+0jARrJB z2nYlO0s;YnfIvVX@Uw&f)BmCB`~D5N@&9g+Dd|ffZVChh0s;YnfIvVXAP^7;2m}NI z0s(=5KtLeyjzC~7{a+LD|KAa9C<-YM5C{ka1Ofs9fq+0jARrJB2nYlO0s?`HA#gGM zU&Q~v7=q%tKtLcM5D*9m1Ox&C0fB%(Kp-Fx5C{kae)NCC0yebc|0Cd^ z$$ece-=E9?7%sdIo&r$S# zik={I`UxaB4(i82{YfeMJQ7?WbM_A@`bQLfjEvMvAUYzMGY?2+>LJOTJw`_62pJ1F zRR2jbKSk!J$^2C^lRjk@w*&$L0fB%(Kp-Fx5C{ka1Ofs9fq+0jARrL%M_{e}{}JK; z?+==|D-aL}2m}NI0s(=5KtLcM5D*9m1Ox&Cfp-`J{r3Nl2Z$oHLnE4@iY$}#@8u>2+{YgDqbC6O|VG+`wO)_jZ(c>Oi&36+w){1NiB|^hTr6vAa1H}Hed z{Fu*7or@_mPsNnE3o&JW6c1*no{1^bFW~O^n38%irlgO>l*~&pW%kH`vaoqH5Nv=` zC=q0w!U+YOwu}ZDr=tmV=HY}ocRZoat9URw^=Lw!egbzNOQ@+62{rxVsG4~)q0XL6 zs0%a^3@oH($3d80*n)pEQwmxdFO0M{jZ)C6=k}_xpwgI{nVKH}q|LWHM08oldk;|k zq7~ERXa#RuU~hOp?eTI3MVD1{i&C~YO8N%YNvu|@foM=8g~K1jyAq1!Y;BkTO?4Gt zbjd(xXxMw|wS0MhX=s$R<;Cgwsnpa&LJrQPre{u%$}7fztTfnvD1X)o{k^{BbbRZ#x?efm~`OL!3 z>C_|f(9GlUQ0mEeD1AB}%AAXbW}k_N=AMg(=3m+pTA+A9VWiU!B=pqLxHj`xTuVI> z*V3orTIOtAn|(U2%{?2}=3m&NErfwjWBQLrLuX^5)Zs+S7r)L!ew*-lUVVYE_r5L0uZG7i)zEe-lNs0n`D)m^e@)boz41GB9_bor+)7RVhYl z8Ltni*&D#RR4kONfKK5G-gCjnPEA4QFWa>SUfy9_0ZCC;%lX36K}k{+_6`y&C`o$N zavsZ4(0z;^;XEFYWX*k#$Am_K8;gYiN?gz82l2uVyj}w@$r#h6F)N$L>pI-g-6)kC z`&a?WrMMo`+^>=@5A!*vRzT-yzuhDv$sDk9X+%Tqiq>W=pcgSUuy>_|Q5PG%C&#{ag z)mXCakx_K5EL##bXtHco|4<7KYeBWKTrJlt8%L#JZE5HH(O6(U&SoJbh)ER2{9M3n zh`h%rAlEI8WTko#P#F@cV(tI59+Y)3$+2l#)$y=a&laTt()Odn&Sa(@98jkp8c=7B z4ydVz2h{YV18U~60d@A+fI4?_RGlA4pqn$o(+O?*OhTJEo6u6{5?cDHgqC?aq0K&% z(B{r3w0X&y(KhiJEx~8B7sV9_2m}NI0s(=5KtLeii$Fj9|1aSGZ_t&^_y0jA zA7TFgT=4P;zxJ={BYzwn`O=n=u~F~a`x0B4ukIVsyzkzVV%;y^`&4XDi8=4y`}J64 z7+gFr-W!chXgzP*`xSj?gVuW4-tTB;TjSl_M=upxZJaJPXAP^7;2m}NI z0s(=5KtLcM5D*9m1Ox)_5Cqop|DT`?YOx*r|Ew}0rvGR2tA$dM{S!m5NgR7q*YI+(=&z8u9(fq+0jARrJB2nYlO0s;YnfIvVXAP^7;2n7CX zAaF7L-`)RDG7EsgA^;rgTq15%!+~q|;Yo8Tx!IO}E5mX)C_J9yRGl(d~2&pI^qO_!9^S1Ox&C0fB%( zKp-Fx5C{ka1Ofs9fxvnZ@aUmfCVtP7)F5zM1?!5sO#%>$o&{r?bCy{5k}x z?@pI)r(-egSUSZ`$9_A1KIx|0>6%phv&EmkqP&rw{Z4X#?TarrUm)Hn6LzOVeN2{)y_C6LL1y1gO41KHGxJHq%jl*lTKlY` zTSo2U3sVk?oh;a|B>ex~`2R`vTl@(G1Ofs9fq+0jARrJB2nYlO0s;YnfIvVX@Xkbl z?f-{7MQ65vAUpuh$Cl|6vE8&g_CC*3@l_xo5D*9m1Ox&C0fB%(Kp-Fx5C{ka1Ok_i zK*v7+z$Lb`}g5?mJ)UI!9ICE zK5XBg@d@_$58y^OANT?E(TZLh<5k|*xySAWTz^xqB?)wk-9fM`~xD~ zKcG;oSydZ2^iMo6(3v;tKIrig*i*1;J4iXsRE1Oo2}1f*}jMKR_yRbJd{ zecMdE<|T{2F=WUZPl*;yy*O|@JsvamFN@5hDZ?Zab)0v$X6*lMBn13wN< zoqc!=lib0>u4SvW@B6UmQwEcyWWHeM%B2!|i(dn~c>ClHzs01R(7&Pj=BZMpY}XoA zHEUa!m#5Q_Ehnj8NA8nn6djr8xmwN_mJYt|gHT_LO?2i;)oM6NFR#2NIy29gxh`II zT_v6`Puj1pJc!b&mD`go73|gTCO2ZFFCJ`EttG2!m2y^Nf1$Rg!Cr6tkA3Uh&0`C| z0uMWvEoQ5YY_*y_*vMOUu3D&|7XRsz`3wU%@Im+TB=_r_ZXfF|2Rb)Pzi-uEU-}qk z1TPlf@hLg$FWvcKR$$p?i*?I)R$ys5gI=*&_WQ{&%dcE6TG^8CfasU*EmC-9f7Pda z+zFP?q7A#g$Y-D*B!?+Pd26x0+$fZm%GFgqiTQ)e^7ZAh84wO@#QrRzh>`@*z7Ri^g|y`eajSrU$j%7zxGMS=VRae!|3>SeU-h`%MbiD z@(U6s@@31uy!=#j?rg63EuTX6%}Zq+x>&ueG;CR`p^^NTwf=a#Me1Uq1T=iB#nW-e zZl_}vJ6~EcN58ff#MEP=kv1s`= zEbDtcIOr6nrD8c-yR3E%(T(+N{&H~8FrO65rRB>>qhsE%_mr!bSGH)^nCRC3nRpwa zDSA0}J>3_ZqZ(`g$yf^U|5J$lKTn5Y2iOyxqhaX(UpE)%h&e}h;B%{a9evAqADuF$ z=@ZcV_ux}EuBR_Y^7L8g|CPuBZH}brTj6N zb@a0SUbAXH_z&%GD^M+r`UeRFfu^|Mz(Y`(zbkn~@!9u?1FD*0L zIJ5UvvQv6qlF?y{ctWlSr7R4=E%h}u-IuF;|n>IgQ-0vsHp*k)@atuGexUT;yj!saK~zN@czG9 zy#H?+@Bf>J{=bX96}^^@MsJ|^M;Ea7e?5KMyq=ygchSAL(zxD)u73{KAzW!(Z$|JN z*CAYKTyKW)JN!QScz74>#znZk41NDTT*c4=4dC+?eHVQJ*Dbh`xV{Bl|1n(mXuD|& z*PGD$&*S<8t{c<^3gYuC*zf-=uA*`s^!{tP^=1Ofs9Ai(C*QMTS=r>_d- zfvHDUjlXT>A0g&1(DGtqCN(|dCoHa+R;PJdEG#!NzSI8N`9^35tzdUAGdnfyCv0B4 z+`DOip)@^{nY-+AhiR9ac6J?UuXWRAE<3G8LsVLOKW2?~nsf6_Uv|D>nsw8r){!>j zroH_B5vH`8c6uH8rrfksmz_2Q@4bB4o}XRt6PefUmmygGdD_|aq#bn9rq`2pgOhe< zJ!uDc+RXfV(#BZYxw*`G(qbPOX;X8TpH`z8${nm0*U(Y=j)f4!Xx>Jt%g;9i$&RO; zUQb%nNjtTkv{&=AnRQ?l!u};Ib7mcV!^A!&?2hyEnT5u5%1;egdpsC8{n|{w;45-p zEd91t`uTOGpKPU{TUYw6t@N|&O8@Rw`pml0U(rgRURU~fD}8ER=|@}XXV#T|q?LYp z9qB_*L-}}H*ZdN3`u)1bTbQLyuV-#Dn7UZc?QKlWOs%ia!k=Vs!Cr5A4W_DkZE1cz zb0ah0)JugNHW(U9HiG8$I&5A+cWKcTR`Ns#GrdS~ysF zqfMK6a}SDP1Shk#){mYv2C(6FF1G#D>dW2wKcX!{|G%2P9J?0#|JPCx?*JHp{{MCC z{eLOCi*AkXrmgt=wz=EY|Dl^>@BdBXS~_nmVDEo{ZZLLJ5Z6~CyXijY{(BPyIe_ekc^#AMW0DJ&8L;rtGdmnvCyPj^r zzW-)?e@(rfzNB79x2W%<&G`Nrbp9_X@1t9k>uEE-zZMklDiAuKKtLeigTR{ezDZn8 z+sOpQo%5G3Cq|s~EcyI;^m(J5zOkM)*MuJ(HnLoSj);`CI85>ygt<;&Qr!fBN#vA3}Pz z|5RFs?jLdTW@9J4zPkT-I)+JH$8hSt4jnOs^t|rt(8nWA-mLDa_0|2y(~nHz`jJ!j znf0{$s8jcK=;je8Z&vr2_0|2y)9o}GA)G@htVhoXAuVt7b=XlNNbA}Z)@A!O+UXnX z(MiLOPTH8Cy?h&P7`g%U%Eo-=^6ipgY^}1iP)aY`rm49)>3Z4keF3;(8sr|0!IDaLwcTA$0!pxIT_+9@h_{^Pk7{ zaa{Abeh8iag1(y$<1R}DS@WB7ax`~BiiARrJB2nYlO0+)rrnrk`p z1A#w9V$0sy9Df~_eqFY$2yves zmVRBfr3i7~8O1 z1ATiveV+T{AnkhQCGL-dwCnMi;{G^DyB@zM?vDc-!R6~VVYrpEF%E~E%O0m;7T1BL zU59-k?D*86zU%Q3<32S=d--!q7_RNC%=5FCuk(hnOUu))%LkDA)S!(nzkQ>QPmN0L0jfUPHTa8Mt0CaSzuWxOU)r z6S4m;;QBPKBCcUvuOar|6On7^6S#`FHsg9dd=0%E#yNj{-im7q7vcI^=o&hL>t0;D zLx>@O>lJ;Pj^Mfn*A0jbVBquX+B6--b+4A98?>Ed;QMRpPI>~@$JJ@tqaq*mJ@mSA z4V_n}FH2Mu!~y|eOq?d5Pm^QFW*&S;yqaqw<_MRYWt!fSA5rQ+px&NDz}MEP6a zgHXO$X}VJog;*Z09sE^N+;2e`$1g$;+qX-PZ$TKu{pnpy-LoMaQ}=riO1mFIZkgOO zA;`o17KFm=PjB!#w9bLNN_zWHND%6-Un&n)*hgz&h3`Qqnrqa>!4!H#D-FH}p#!2&Q5Jx!t zQncxDKBGyGuR$E!SeqVx8BKb84btF6=wXj)(&KB8Hmpt0&R)3m>}wDwi`Oa-KX2jC z^RGdueQVM)KW3MneGNj@^P}he&pB6t{^xuN;;4lmZ5vDYZ8YnOuR*ASYtv)Tze$g; zK^&=An;!dvO?rF{LgibNo}H0!>DkvH)V#In+ik^;NCa_Q!jC@6#%A*@13L@xDuum2 zgHWyf(wW|LI0Nx2g__CmD+r~;FBMToI{37C(&1MSTa1^G4!&fbbodp7quV8hhITBc3na`oNe%=!>=Hmnl2$7bXZS1{0hP$=n~T5EQ2Qs&nS zzr00KlXdvJ+vow1>yi)Nk5FjHvl$(`*0rM*64Pc z$2ABd(2M&wF5kL!M1 z@5glmt}$G1gs-Hp;5riC4u65|R10sVD`6`j-1~ayN;(zVPM-~JMa;mhl*g6C^&@>N z&JS#*bGYuubq}tsxXXp#+)i_UpcLu22X+YiD z**4;rKtLcM5cqjTAoPv5VI!~`xtsn90R<%{V~}*b6AhEmG$levB8bWDM@F$*u|_Uc z8~8t0Xk-hogX18i55&)Ug%tkauaa0Ew_tI)_N`VP15hQ{A2R^3ZxR;L0kSJr4q>e9 zolAwPUAwN^yc_!l^0Mp({%_^A!O80^Ni2u?b`As)ww=dzH;?Uv=>9B_+D`U51~+z& zeckk~i)2k4vr4V82S8rC_pj7C|LW{I~gm!19FZK;5$@QhEOKc9TPQ7SSTc@3Iu zBhSkt`OU|Y{|zq}r1+k)H|g@(l1nEuX}^bVfQ|V|zI=dj z1abO1U@7^MQkcTG6wr^K!D$xJ*Wp-oJ5n{<`hqD!Z(NmnZ7T{?A5xlPp1rBZIhh3{J*eP??W(HH-da6Jzx)ql$;!oGCE7}Z%tj*YgfnJ79 zI<$pL7h01JZQ;@}j{W(eEnGTnZ8{usa>}Q!NryIb>6A6;&}J?j*L-^bY>zd2zm;Dq zl-O7c__8F94Ih^X>{w(N%D#lr*7)V>=HG%)@+HJ#*z5}^B<~A-G4yET=rT~pd#d7; z&!c^V4(fL=-G*B+XEgO^zJh-jMP%AHmlibl(YKTTfC75{qMhkjy#h!C)%z9dn+WM6 z438F5h8owVyH;)35;z?MA_QYD!+&fa~5UJUO8IUx|yNlXxG%B%Q)_pNTZM=5Y<+dL6p{myJoB57ow^8=W%@=*S)y*geNH(-iEUSSHe$V8$AX)z+rse0{uT7 znuM>wd+8hcB%Rk`AJE@Jci{VOd>_!SLM*|n=z_K#=LxQY4PZNb1}1T)0Ja8o5_W*? zbX2_paRnzmok@HZ2nYlO0s?{mA_(yHt8}0SCE&05!isI-twTxBHpNPwx zl>_@5wR&}h$)*z9Qtsu(*L`PS9Q)2-=S$_T`;6am)v{Kcbszi9Jq$hkuqO?EenhAP zFCX%4K=G)u@~PO0hG^3*aBvxc3(ESx#n~@4Yp0@nm&p zTfR=-zLcK4v6qj$eW_q*a`T4a%P}A^nOny;zE2zRe#_c2cN6Qk8mJ(}b+@D|!!0t= zk?H&@^9kr7QF-cX7t8ycw)Q1#@v9V97{5RtevaPYaiKMiwY01dnt`qFU61<43i5^D4t5rwlWPx(| zk+tiHfyTy}&YUZHxwYvqa2cI;F**!LMyFnk4g-zRDgJcLzJb!Qx^mZ#-qJPey4XOj zj869F)}}+Q4tBn^ZPK|hIf0!Y8P{2hwMLQKZYAbj(o-}($2a#5m`@6L`O)$I(WHad z3COnluygeDCLO#?KnFLIo@_hmU~2*$+)6G&2QL!PZM`TR?5m)A_eJU8B?3CQiCiRK zj8o9TEu@!@&&Q}QYglJ~_uYDN=3_?Zt}AkTC#T#0pE*!$!J87>`u{fQ|J$JdPeT8n zg#JHC7ozW>`*GcYYd8D>jOb46|Gyji|C2Zi@NPN;oxg(5EAdIV_Zsy6CybqRpYblb z#kh**@mt65Z$Rfi6}cKYia&vXKtLcM5D*9m1Ox&C0fB%(;Gzf&F*}ue)~n&}U%mka zPhzLOdesF-4``hYgI3{BMuUv6b0DnAmc8R%Vn@S#*gCqjL(g?-KQeAh#JRC1LuO0F z%T|3EYEHdYzQr^je-fvR&cU)KOQ(#BzAT+GHV>kKtsfcJryRQ#&Y^)Q>(7hp`~1Wi zER#$>m)NnhUSzlq?4C>mH(#0-uPRJD4lf)@TZ0#TkHE{9hL^wfJ|j@!pjt1drY+KW zcM+&?Jgt|C_W^bsEnmPJSIZ^5+S9>zhaa_TlWZSf16zMWk6m}JQQYV3(c|D+ZwWeV zz@WvOGMVk-QfMu_JC(IcA6id4;3P%lVbl!w=`YuMfkzlgwe_DOrFE9TmNTs&M@2-zlL-Fk3sMM z1mXY`5eFcN&o^-1|9o^7aRAd)iOx_Gzh5_}>4JF;_WVwjJ5N*{Uaa#*rAUit3&|Ic$a*bJ%Cu1l!q=;mOBv^D)T! z`0aPzd+6?ud^lOiHxs7M7GmUh$E5XSlQ@U8f#{!4glE(v7D z;vOVO(XWY9j9G~6gLchYZ7dbD%OlLrHArt4JIb`-Yd<3L+TLcqn>0MX?e8QJaVqfl z+z2PBKR{T-A9xu2NX64m1<#Mp=d2z_M5C2l3R>mcQJ5gqHRmN1%?)e z!!3p)0|eI@47B7ar0Q)+~=_6b!f6hq$8`Cw}Z>qUK0=Ya`NL2!Y;l&8NEFyy^TK|O#SMp zpq9LboKM@trl5)nH+i-0tzE|j@aHJ;Ch;KX`+$Y-^nDQQ_kh{Kd{b!s9lbg!`T`nU<&9e2UvjPGckS@G*;%IEM4l=wTyxisk*G?TFY=4oYwmh-BpTw=LN>oz zD9LZ@Q$AU`Bgk*KYOJekmsTZ4*=LNhN4)E`CIYuBwth1TPaAn#rIpy*Z&}y5b}Fv% z{RZBAjWRcDyR*W=b#bk0;30>uJ3d2E#z6F$3}h`A*y}eJ=zIAP+*h#nH=5Ig9qO8m z!VE?IVwp3VOGFwabl4#}kLAotNFCx3^P;ew7e8){ibKY!a^(_xWpiBbxdk$xqJ(?z zp&z3e!d>0Cr;}Yf&ZN`HE|M`opResag0Bef@pSJMrKm%QM z@0~woz%bxbf$+cvS=!DPBUXJrU$2G11|Jq213%s!TnB?}?DSc-nJC4O;MHhJP4*y` z?LnSn>t26JiqCh0U4sM5_L>u1|M_i*HNeoNH2|aZ84Rpe?9oLB!%%xLbcj8z#6BZo zsNYDy0z1^z9Z-g@JG~ubkS(y+xo~&x)rJR$FLS62_v;R{6O-C{3`RZIV`7rRL<`4tp{-~18=3*uGyHTI>g_Ec>_)yj8_eo+@qiCt z9m5BUjL{J;*ck1Bz8CxHHL&NZ?Dx9V zSWK^N+?k@n^hdn-V343KckBObwl(k)iVLwty!&qtO~rC}@82yr@1LU`(c5v(KTl)E z*QBpR%5)Sl`fB*RQr{8yx>lw)gWr^o2FrABAT4bT?1g5Hc>MBSx?kQ(H^{5>Rq12& z327BMh`$~L2D@z))0yujM_8xJ?Xhwz4Tz<#JB5 zNQPOUGt=HYQ{QupELCgCTR(1O;W_>YfOUiRW8^opMt)7B&VTNn3`ae4;ix+oj!I}F zZq-<$k=Xp_+e(t(T_0j@ut6&UaY;RznAQb*Z;?cit4UNKk90PKb70_;-A8A ziS@BRmC*($eovoo7T*KgT4Wuy?8B^COTNBv?t%L;LYi(_8@N?@aeRZW&$!-s2VVO8PgVz$yUGjexPoJLlNPr1BV z{41?v-S0{4m>0TPvb!jb&YG&j_8ejmId%tc(OtWP_m;oi!3P`U6Jk~C8Ztif^3=t~ zOjC;ck6mLX8fcH1D8?2WGf~JiYmS-A8Vr1u;wW}a;BE7D$6Vvz5RU!0e;aqqf4rsr zvSjj|e>?X~Z*DuYA=N+Eju92Syb-n6J40xG!(Lyt=jcNP;-?AvSA z^=>(@$Nn*F^Nw`Y-ePiXGVL3^BzNt&`2KR*w|q?)-{>meRi5%)<(BU%l&`t%+Aa;> zgX1{1?{zKmJ#Cuu+v{#F>w2>&-TFUs-!~8xbU6CA(wEIuI$+*ShPfB<`u`H~`u5VH z$lWv*$;LH3f-&bDIMG`zY*v@1OaB^Paq%=_~}HTS2lqyqeDsYI>^X> z(`7YDc=YFlt3M~8KlfJ_1O3fUeCZ4HXmau1!*h@_e5G%HyNC~Qhn5E!a@V!)-_<_3 zxl5W?9|PH?ExsV+X5(gJ+k#D_l(XWJ@9pmFSvkA1XSYDcMW-g$#UlJSy6<#nT_|M{ z#%Eu5*3H{Zo*tJfm#60rh(gSI$Q-WhxYYiR2YhkLTZ{GOMxnG+cGuJP4Q9COkwbd> zA8a|j{jX{2{B~FWd)l*pK8+E^bkV0FhrGl3A4BFMHm(`y>t6Nmb&uF1|5X<(l=tZ9 zc~?izGaY?RWr?fTycydHmfNoF8+^1tC+?5=9BbondvHP_5;Ve>0;$a=GrCb zJN?Ppx^-VZ9xV<14v77P!C(%Qmy-FQ>?l!Pqm+E?Y70K&v^jl!t9zWTc;>Gacm7(r zw0)Y3HILu!j9+=@znAp%lBWIm2j1!B|LHC^{fVdCf8v(=Pkut>#$t?a-CaGu>4npH ztiau_s?E{ikW|WcfjPmteW>#$pA~>yK5liNDc<)lWei!}jBE#pe5n?&gU6 zfAtid{>)?h`7_t{^JlRAu=UlomO7rrt_$#5lWMZxHmWM4ySswJ@QUe)}Unrc@C2 zRLw`M#a4mJ#Ex$LAL0V^{}I|_JQes+WQ7ige_2Y0S7<6EN#D{}=z#tiyiae5&S|&M zJ=zjnhdxN>)g}72^5^s=Wre;P{Bt@ST%l8eKc_naD|Day7nGK(bgT4l!1?F*@2qFe zJPR0@ta{d!RXcjs#Rkz&WN38ke!ysZi9H6U#I9M=XH7|A&%dv5<=u~!|5ARFy@gV4 z#+G2c<9QEG&$~E1&(;=9=iTGU8Ri{z;X670vmKLfdsy%x>ARnf0m8ReI~s@k`}Bg< ziyu<+&DBf6tLd-M<>c!<6e9nhfU4tg$|(;{r(B#)F`TYtj@lib;!~Z!FXooVA?&oW zws+o|hF8APKN2;#8+|@o%QBy92x)MDbEew1opP$@4!5NCYJ+}#sp(tchfQMEGIT^sM=dpHpn0FLU|O8`!74p&|bt-LTNAXFM?vH8wx5vhjq`#EmsAPK{E1 zbp=c57e5m%L}@?6rMBci?4i`P(|1FwLg?_dR9)C*R-bEhF>=zcRWz9P+>G zDdUGcxPHjR^+PVMoKt(E@ZIES?J2cTY40huZnd%Jb93uHNG}0*$n}2GET4ay((%9P zuGlv{_4=k;uWzDW>$g2QN{>gO>0MR)WZnKN7^wYhFg_BukHFn6LbC-;xuo@f2J3%c zJe!^QUw7kqvq#ss+0`{}cJ`SA9VVEypg#4qL*3?psZ3q;SnlB^}R_sCvHfTy|2`wrsV4}SzW1Ex{Plhn33YD=w@czWKc88&Se5JEFT2n2y++^Td#Ug7z36+? zU2bL%<}c1;)SYPweJJ^^!e}~7fs(>24~;V5m6R71p)Fak%d#S6CQ;>U@=w2a>58Sx zdol2^Z0Sl&i9rcY@Eeof6*^^dqg&k4Xqg<3MdaT`Re5u8uF>{=P~}GFUomNsbJ#wf0kr<@S|3C}a6pIR7UU_XETOEs^s(!|vDf0^$rzLPr zn8F7FZc3Z}G>;zr9oEqcS^U4J(dUJS1?Z!9u&g3_2e|wH>-7OOsze>Czf+y8JzKP^ z_BM?m`(zf#ERb0svp{Bn%mSGOG7DrD$SjaqAhW=~l?A-M*LfJnpO&9~m)~#eyXG8? z&y_gCL+VMRKkZ5CPkS=Iz7)=6)K3&Si2X~K-#*Z1btgqpI-TK1Yj@Pzxj;w6x}K-c z399n`?UKI5cViwR_H|mh6sOyvdnz#+{I zwFzMc!b!|27)8iFnFTTnWES{^w}3vgC134wm%6U^8!;OFTnSFG+4$hv{P8?%x8d8H zw`>?`GXLAtWShs+rN`zP(c}KJ&@~U^Lzh*w`^Yf=tCRdYO8wJ^V?pp zM}A3O*Vc`LF{(#~Hmn~G#+B3i3gpX|X|K&&hBrSlIA|k#-mh@Di51B-v4lnEJ@q*azuHcvSsPou+e#fQz%NwTi+_h1fl zZKASezEeF+&s(90~wA$L< zYimn*uWEfhPtRV@&r{DeGs>4fhHkBAuHSloJoqh|Wh<{IsgO>gZ0Y$45jTF5K7hBX z2kQB`;kOh04oy86W){-ni9P@`Pg~ zPY2d*jJ=-;}B<$^WkdmGSz1wFx2nWERLQkXazJKxTo=0+|Ie3uG3^ERb0svp{Bn%mPu#1u1~-nVCsg&ejl*lv%Rjtv`|!CY zOwY$;4)YW1x_t_|9`tl2`TzB*LY=7ZP?X#G5iT^Rg;>?#HV5IVb0|dy46v z6}V@`6z?etzUL>Zs1m?Rf8^w#RE*2=*UK^F9&ss%%eiMtxU73FE9~K7noRHiH>S5> zppWidC|C7rQQeb8hiZ4JT{X=``>MaEDy!eE2B-b5x>&ibXtKPnc%W=g(V^1)IMeUx z;!Q>CRcp~AwM(s6=hdRTrV+E7qNHMg_283I~Ydb!^rChxz`V&2@wa~k2DRfmZ1pY&OOC{%Gu3*9)Rp6b^ zg%;8*S=_G`O+#@uqV>8Z#8$IB=Q{FUy~~4o&qOZET{B=;@3_0fj&JiTt-d@kb9 z6`k!;jhL+naT%v_8}G};CAx^SH~x~}$PL*SE}}# zPt*n3&LH`B_%cK+!f)NhDEpM@1zyOsh)pMdSr%>JPUbe^m*e-PpHKXIl)v4%YJmP2 z!?jtIu`T4DxG2ZJNBLXy!;gQD_NldiP{{>H_HsjP6&v+#?AMMGg{Wx>)&x77g@fKM0hhlzE z^zz7`Uj-&549$9(mU67#Bs1D88EMPd81`?voPW6VQ=omTaYF@X>by28S<$K$cvB+- z9M6E{RjM|!U8E>qkfyPU{IOd!@_@9}timoQ@tcre!TfHn<@CnS0BY(OyH`Z&Ib&Th z^eh#M4**Z#|w{`pmiH}{`@fq$TKbvq?&g5$6qSASVe!KpG;gDj6)BQ zIIbJ*CpL|r*wWQ_>7u%e>-lP~=Z(L_&!L91UO9la!ODSUOIA8I&MkBzI^x$NvW(u{ z(_r&AA;j-R`VBfZPx@uX?Gx`;68{9#p)GKr<+=faJ`IGe67quxF}An?!^bdl5sqKl zTubS9gqwp~1an<(iuq2&ye@`>x-qg>Jz_pJMNCwhtdlZP4B{8p9g7E+^sT)!-%_bn z;@rO!FQ1f@4!K;18)1$r zVtsm^`-}1Kx2MS0e?0ViC{Fv$#svj$nWcB%z4*5NrPV~S7zf`*l9ZE07d}R|gB8reSs?e7$y?1T=J}KsCBR3O7 z{JzaYj?0|=E-T~8q4zc%{US}_a#U~mJn=?Ev4`G@gsQPJt(;WxE7GgfrtvLtOZwHz z@*YRE-}I?bSf8bfQk>SN8aHaJHdGzwz%}8L{>3YGtFInysfd$4iwP0aIc01rUH+XX z*6GN2#c2QkdOSDtQ>E@w$Lr^-1NARgL%8Ogf)?Wmrk&Qv$6 zZhYTAtyZ0#mQ&-?=BNh54dMHls#?{E>$xiAH?0}_2jo-(;#T0=jJVONW_7kQr}`^v z)lOX3RL)gLDw|a!zK>Sas-qP-wWngPx=_)qR^a=Q@>+GVJf}{S&sD96+llWN%4$_R zuBC|EP~NPLmgQ6{;x^&BsBA9IG-y^AOLJ;NX{{Q=b+~k{I#t@N+VOo~Nv%3vl2Zpu z=BgURZNhhjxaqj|mgrwY_W60Yz?}d%hOh#SJM+WuR~@VCA$0BN_drnz^)RX8@}iQm z()=yU;~UZ0QgrDudZBdh)WT<)&<$9Hj;T9-7^eu)t9QK^L+B@L*d0SYI-gpjlPiZa zTq(QPNOo~OF%=%7-{R37^XL=BAv|se>M?}di3d&%k&nj%Yv_o_Qpqlehv>I>EQon5 z5yc@qmH_n_!fnLkHenGxck6lJELh^vBiRM<5d9X9`7w`MMR5p^UnU-}A|9_Q$OG#U ziN`IHT@Vk^Z}I4gc`OvgAv}JGcytpFqZyy@Jg_E`c+8jVf_R92i$`b7qf-=z@aQ5Q z^N5Gp7S6v2 z!lMPC#}Jx{$IO@nzPLQPV;(n1Zect`zr~|H=FudYLwMW(xMK(}CmyfB_xR)TSP=8L zUUCcLA^J@oIjPUD6U`w!bU*MI!b^#V)$VqA%#V3AN^W61M8CyD>ho(va|n-@5)X7r zhr5aa|n-X0DBApE@_^?)%kzvAD2gG%%e_n3*#aB zEgn*z*NEm29wuz>cRer0Jx#Rh5uB)xMKfBPbtwCr)n5ex)9Y7dFXh%3-^D1=+VF;le z;e0h@g!3i1A`Bt4Bb+bBwG7u%#3KwLwCnGPAJTtAI9~)AVJe??bbx&b3H7cb`ol86 zn^@L6g_wVbFK5Z571M|e_7i7;5(K3R~ES6 znqx8*`$h9jBKslzRPMLzcLn!b_Pc`nE&G}G!~RpjZ`see-ug-RaXpXq0Sec{xE`-*Q6sqS#v60pI9&<;xQ^kfa6OMT1!oa9L0(ijM@_G6#kvCIkI;^A zxS~bvs+gnJRkW%xtTot;FoIBt_%r2m)S>cLbslRA&LW&dSY6(tS`n`h-vhZFp$XwY zS&Q0HHb<=}YgN0k?qCFA2;pLBi#ky{2kQ`8)mf}FIEio+p%3vh5P!a;6>}`017SMC z-jWtISTaZTAbv-2tr|pFgD_d#qK*{LQM-!UR5Rqx;#o!s@W0k!D#Y(2`qyZVL>1)d zgD=%T9eVCZy00_Wo||m?m?|p9g!hUO^%qzhM)y5N_p!X)k>5)4bI}wGsE+hr{z;^_ zJ+Bx0X_S-|6@5(=tr$_?)%jcLrvb;m>hudSPY*D90AuEe`U1ZFI4Nx&^FqvpvmmQ~ z1qRJ^8)0I3t0TXKU3zMtCu2+QxgH@V=SwusrC< z50m`eWW1Qe?BjLQ5?;&`_VIdYBfOZ4YvcVV!uufMVfg_^-bnJdlJR10tdG}COL#H= z)W_?k&ExHk%8nFab%yY+Cp;{#bL6!oe=QMjcT~=t^5CY8@ODRK$0@&F+6Zq{b_~2< zA-r!OJS@N7kzYshmy_{E<;=nBrX{>l*>UiCX(PN**)j0yxo7(Kwu+yq_k#cM=|!mpk$uB!4gw?}DhDIe6W)5#9w+*>UiC zY4dm&L}kao`#!?EjPS7B=g7P^=Xa9vM&-=G>!u~VQQ2|udTArPQQ0x@zMb$cA$%ha zo~W)_;FY6aqx%-qecO}o^WI0>=)Pa3`-YP5i^`0F^I^J=*U)TAzRxQ^_tSm1P=4zY z@0;(vhu%QEgw`?{0w^U6z(?t3NO*PeWzS6*(S``YNf*~#~L<>eK0-(0${DfvFHyj)B7@tTsx z}))3B}kyz=ra-FH3Z_kGGO-*%Mzyz=rLy6-x=Z!-Bl zue>}>_hA)x{*G@a-{+N=|3&vi0UeLNrPWa53D zUU~Tv-G}|V@^^eO`980_JVp0mlaBly$CB^!$_w0s{By?^0r@*VpM0NJUjB^ktET%7 zC*S9lmrv1sRdnAclkfA&%OBHy6?EUh}^?;oN+e~|xI|6`WxPOrRt2r2aM zL9fTqpRxSE`XB2)ue`h;DfE3sbl>~*E%}dipI2V={Qq)JO4mkgI&%_!{@MB_`2O&> zaUH~UNBt|Dl$n@WAhSSbfy@G#1u_d{7RW4+Ss=4OW`WECnFTTnxE9b$(e@*7pFq4c zP5btGPld{5I12FgwYl7Pnu?e8KHY@>E9f5X572V-p?fN#dlpPax9H6pbROIfpylXe zr^i@rJ_|kmkJ0V;qth3wx2p4b|G!p^*Uwd>^=)bx@&<$fgw}eD7bE^;T^q*!(LaDN zhOoVE7WM_4t7ag+2J$70@t;FDRyzyh|8vx)+BUTsa$kPzA7j*rpQxFu#%tQJH(;&W zgD{G)re+rQ5Wp@xh(C|<{j&%s5e`((!k&S1ReyDxS_HWjVFtp5X|vSvX>&2&-S*f2M&b22tyUKR4?Ki5MK)U0>=6$5RR13!r1>@wXVEP zt$^Hv(2h`v_%qm3@L*XR_7wyUgdGS2WwX?5#9u6JQvS6P7Q)E#W=H1n z`@<=ianeO1W;Z=!#_17AZ`;{ROPD(x%sQRM{19Ped6Oga82%?xFh}!2oIykAb<;EE zs4Ur-y|fYL&Zr#8fZS&Y^ZkU83J4nfLr;naA|snT&Z}ROWolZhFEzFDgqmW-o1o zd0teG1oIPw`L%?Z<<~eekLy34f;lR4K4v#PV~)y_joC{}n4@wem`4foQo_h`uOlxZ z`H>XNQJM2GyXhHoRF-VaURuKJ&QbeS!p!6UEZ<5PS>|#6`%^Hd)q`$&!rYNm4|-`M z%pGn$I7FCvE&$8C4<^ez=Ks26%pGav%sh91(h_F39-L2@c^(1FytgCEJYQf|3g)zW&`Mv)b6YPbRfEb(EAw%4Zavsan0ZbC z%eJj9&!&~yDa zpbwxM@s)`0h1`kIjBp5j0Hf#w7(gGuZus~k2tx>$;On1+uRk`eEq`vn3Hb9z5c&{5 z1M%nJ>(7SVh){{J2YmpW&qrQH1G;pMbAFUWUF6 z`0{5FP9m&9A3!_e72+2`Zbg`ZZ~%P(J4(^#fj)qp@bR}JY(lsIU;j9K{k`Y|I0Ya7 z7{WM058@jTKLKCA3G#FVg|G{K0PD~P(2jWX(^(Sv`;SuDWSM|?XcKK9IH%v5Lj?Myd4V@_)S zcxef<+x}@H%x@yhEVGZzGW*!qrC?5L|G4QHb5i@qOG}vD_Rlqhc>`f)nSE@Q*~h*r z1#?>a$4$?eliELC+6Xh%MN&3u2=je}nPv8|S!N%*A{jH)S$^4Y(-UT@w`?E8OG}vD z_D?ZkUQL);W*?hn_OUM~)^Q!}Y3(03J!4L4|9EK$v)lf;2n@QP@NUA)GW*ypvyc6M zQZT2rf86wpIjI5Ur6tU6`{(|?XcKDL$|<*=33FOK z=%#1PN%f$YmN2{Z;9nBv*@T&8_OV%JA6v^wnA7S(H$7ucst3Ka5#|L^Ig;{xlrY~w zm|12Yn`QQ~wVZ%?L0Uh6o1QQ)Na_dh(h_F39{dZ!JcBT^%sw{D>|<*=33FOK=%#1P zN%f$YmN2{Z;O7Z5?~lnc``9eAkFDh-%xU$Yo1QTz)q`GI!tBn1lHkG-bVk8XPAl2kux|NjPtfvdJ|62>?Na9ua86>Iuu z!iUG1e(>9m;_SaYRjukm)y(`kfQR9;U%+{P$FUZFHsVI{{T%%B*|=W9I{Y=2GcjhL zQ%#6lgKInDc2~@V?+*O%(|6;#0qgINmCsa7_`VZ9`Z1jSw-;;gFP6>3*gVb+gdeWY z06c+p_pONAiSOs(gSXtbJL3F z8CiyK5&aeytoUn#&^MGKTN*$K}$okk5fDzRAckB)_p(xy1!*iUYaqAuj9>z6)~v@whCAxxC!SGK7ohH@VEi+Ee0k zy`dODLUjKo((C8Ke&cV%MDWGq5}%D){8A&!ko-iy#RcnOiA$rQ7!X2q|2ooZF6>{9 z;(Pq@xWwn97GG^-8Iqsqx42-9E^&FWp%_3ybU%*tn#+y!+y7575q$Bu#Al=yPdBm* z$xrlKT(EAKxYQYn0U<>9cBI!_UPfFV#rOE*aglnz+Q>2_KhbY-k$S(%Pz)d;y5EBI znhTFdZ;6TEi^oOk{cS24#-`CPa13D_VHd{w*P(x)9q~<&rz0qY(-`YNh_U`1HJEpg zK7%y~{Rqv7zlgE^3gL{RpQK^uB*%80#O#Sbs0#J0UkC zG$5SASpQLs_3y60ynFN~3?ZyT=tg`E;?I<0-aYyhjvyRF7{yrsYRmzcjrc~$l?WHn zzi<*`{rfT2KU~(PHb5Rg=tY=;`12U+KU&&`vk~?DfAmivY$~0FIRSH34dO4MkKr7` z8H90+_3y-3|LT%9)d#s7VK#z7{3(p}?=NmsdocDtiqMF8|7$P@pdIn%=Mu&q4ayS=chwILVWZ=nbmWR@_X)eQ2(<%{^_O7_mg!{pR)8-bDk~+o`di` zC(o$=tAqNQ{V`fMJ!4E7xA4*uMt9ue2V|$WkUd!DIS4HC9E7J+FsF@Mxak>l(zu0} zmN2{H7L$beVZzKZ&p}|B=OBDN1#{ZCg`1u+CyiTpX(PY&EcXxxmU#}s zyHhYnWzH`fZhFE@>yYfS;iZi*)7l~_8+!@!ErgL}o`b+L&p~)+GG`ziEb|TMv#B=2sGCmU#{W%iNdr zNDAh(deBYJn3L*3FKvXG))qUY)9OJtJ!4L)2feg}*{uir2y-=IW|{jrSmrqh3sW$s z)q_@gS>usZ4Js>byvC!W!>tEzCCuE1!7|T5V43G2xa0ru>M;Jl5_|pEV$Od~ZLe>| zIRG;;-#@4J!ME4r{|D<@F)v^yeEnR0fBza>&(^lW@1LpmVcfqIK79|a4Tu}AovBV@ zyuTa%{4lP4mIvTwi$ zLi*9HJ%M$9YSc!FKrVU@Xw8Lv9@zsriHnSpUT0((!bS93 zTx5*&T0?Ol7rigF=EA;_>`R@*MaD?4F|rKdBKj>ZGDg~9C=TSJ_sZ5>*oTt6vXi*T z80l3;mLXh3zr{tyNb3#7fn4+pP;+Ug-@c4kByo{3(i$Vn5H6zM;v!?D(+tIdT=bse zn#)|`B72G_agj073M0!9E~4M!B4eawhT=dj-ytqMCw&rf{PEt&GDcc#WEsLm^jlnL zjPw$Y1(K>VMDNo8t)B_|jNgt);ETs29wRNje1&7ZqTk{nb^cEb#efjPMehZ#xv+03 zd%-7gnJ0DrMI+0Q{6xRSMe6*2F%$=K(fiYDF6@K;9lpmO_qoiII{*I|S%z>C{T3Ig z^Z&P@7(haF>pk-|mlx6RUiQpKq{l_-{O=oChU6#uO)h#*eya1&8HxkBe2KW!5*OLW zKZ%Rf`QI_J4B;aBEiO{$KVv8kkZUEQWh}#Lje^9lL==yTrw2+(=Xzns5@NcZni#`KRbKx<@FGG$$9v69!USnh#lAq|exX637!B7k!A-azf z7aoiJtAbqQJ$jXqWk`Oa-{KC{T3I)cQOI~iGqa1s3$ z7xA60P_I4{Pd^i$@4vku55sr5LSIevTRaTk$xs~lO!PT@nhTH7$~k?Abbl*dhVNu# z8S+d-zs1Gyoeaf+T=dz2nhTHRZYuO#x(wgR$TEbB=(o5SzLTLikjwqVg~xo?735<0 zPDYj?TtvUgMW2_*zLTLikc&QJQ9l=+@4vbr7pe2VV`Le^Mf6)-q|SfFP#nnRF5<%T z{pH+8DDr+UQs++?S%z>C{T3Ig^S^B<4&h>8HWUYP(Pwt*=fdOR-37Two&QxM%MdQ2 z-{K;5{@)sk1G(gg3y+((7vv&!{z)Ut5H6zM;u5bZDgJ9iaUhr31+hy#|4L-x`v0ZK z>*sC!U+>*N9sd6W{Qh3_1&qP(*L(Q&;@XI~?eP0g!QWqmzJTqx_M?1uN%;MJ=nL2nzrO}?+W(({xE=8O zPs88uL0^FO|5u?1uQTY86@cVo4UGK@K{r|>n{2wLi-}mzbeo`Mp zmM>)N|DDhgeazqILV0gimx`m0gdxBEQNqV#|BuIcL6KWGcguUT(#SF-KhbY-k@se~ zp*WC>-XB}rn#cZS-|ZwW^4=^lvJBxO`YkT<-YhZ{2XfK-W@|1y_J2R*_~Shnd5`|| z3jH(DZ*dWS>5`!sKtk+vA93Naf7z!Sid-%W#9#Wck!47JqTk{o{?d;O#erP(KHK`a z@YuiX-<`xo{G|&1|Y`3fFaKrTJRg~$GHfgFFlGA91gKO0$wa1s3$7x9-S4aI?6^nTs?x$xM3t{@ljm;TYn zGK7ohx44ME^t7Qkkc-~OTXW&Df7u@#k=}C=f9b4|We6A1Z*dWS>6?b)KrS~B7asq= z9&-HgxQM^>_ePc>TtvUcMe6;p8;S$D=zYKSbK&uS*-spi9v7+i|IWxVgp266xJbQ! z%1|80rH;7p_C{T3Ig_y5LF9LS}VxbXP@bA|k;1yb*yFtQBc zBKj>ZQtyA!P#nnRUx8hhF&_W_VIeN_rQZL7k!1)M(Qk2)djBayaUhrT#D&NIzgv)t z)ca2wS%z>C{T3Ig_m3Ki1G)SYapCd*Zx!Ss_5SCKEJL`6ev``rsrO$NDc1h~9B|=J zCBE?b|D*L-hmP_8F@)U+o3IbSBJ2ZLgZN9B18@%E48l0p`|reh|J8L^hYqJLU>(LRgK^j`&iWAI1;?@8 ze;?NS4_39QHIVxe79lhseiG~b4_CJ3=M(HjKf+Fgb=U`>6Y-^pzkq&(34~Mm{)AcR zPne7O1Z`MP02~NVp}%1r!U}}hh`)sO{wGS>u-_p1H4ye83}d~2 zAL1JkUkUkQe(eEliuL|`u-<<|ahn=ITm$w1Xhod)c@P^u=zYz2?fw0bb==1hSA_rM z)ATX@x9Ed16|X^=NBdjLS|3;ED(Ajv{r)`K>)Ky`{AR+)@}MI>OmbHW=CpN- zZhFR?v~JN$8)2sXtL53>N|+xcOe{a($h^LPW-?~l>)J0HZhFE@dsy3R)4a5V*J-dr>lWSgj5%rDqL-F1yXzKTMws6~7+HS3BlG(HvJ}i|>lWSg zj5%rDqL-F1yXzL~2=gk!$ntceRApXhxEE{}dSXd*e>R$TIicv3v*p{U1{>r>$Fb(=+Czb&FnF!tAbF{61mkJ~@{A2qVk9 zzW=K!nA6rRy6G8n(!xbAEn#-oEq;?QFCmO9-{#0XSLUe{%u$*1%Z8htFw_1ccG>XK zMwn?&5h)vgO_=#?K$dw949m9=hfgPCroBge%x-$doKz2bX$iAi4}P97^V}Ae=MzSj zxqs>-DVWphK{q{PPO1mJw1nBM2R}}jbA*v)?ssCD*Z03C1#?TMxdCFu$BIvdnWhSmyQp8&fc+)q`$&#++0SdT9x>TMup~ z%se-P7RN3-;>6T7LNEBe)J=9>92c3(n@lT7LN7;~4Ybi824lvKH*ihy4!V zdrxAV{|LtXXCQ7nzMqAkJpy$Lh;;k57fK)MkBy<{ko-hH_0?6ZysN)C*MavLO=|p+p*d_ESnv0t zxxAL%AF_`~5*O)1yh8I_s~`!jNmWD*x+?9|9Hgp266xuE_bE|Z4lFfL8Rh5a5mUowe{ zF?MR?7{W#L+gwoR5tpY8&0$>hnU?ywuzz$FX7&|p`4B;aBZ7yhw6PLd+G>37~XJ%?H z>=VklmPuTUu~Q?*5H6zM<^n%~xO~yj9L8mWxUlai=Upan>6Uu`3r3D1TtvUkMe6;h z49#I&^ckD_xv-BZ=U^srk$V40BgYUfqTl8s_5M*qa~PM;5Eq{R{|U(P$Njx@OTGU& zBgYUfqMx|r_kCKpFkWNwSwk^^gy=piv%!S%z>C{T3IQGj-5V z9LVLJ1+hy#UyCf<@&791rN{sGV7z}n#`;gzx8Z;ajGtpX|182F<^c2{emdg&A?v*W zS`iLotbZ5g0Ib0rfV~*w--)mtp%n3_G1h;ewoOf7EdLb3F@zPE1JI243mEHfhunlP z9bq5l01ROcKo8<~V2oey5x53n5@Y>GFxJ1Tx=kI&c>ZC80|=dnuSEQ5jP*A_E=9P2 zG5rzj`9FX;0L_SB2YCfT55ftI^^ai=z!2sD9K;y^K7?Hejfg*ovHl~KZR#TW3MLUw zBW%DNfNsQBBEA=LCqgsAA?*1-ia7uS6>T^}Q1>I?JOG4C80$ZYvHr30w)~!gC(3Xh z074()XCVGuS(};-xe=ifVGrg2Y$}_hx)DE&eufPQ0|;j^)<2Fp0HdXC>L~gZ4k3&o zv?5+1{v^iwYan03xc)hW?U)15k2wG{5WgC7A3`_6F^u=`#Te`xo+dT9x>JND5^m^Tq- zmU(=gWgcH|PQjcu_Ti>y%t>P(UfKvV^+QY9Xd=vSB+M-H_&UoxzTTLOnfj}JJGE^9$#0EUiuAorj32L>DkUnV;^2xva>t(aS3w(^xPuuTVt8W*IDNA^$Usf z7CLFaY`<){=?OFKgKZnyOB-RPJ+Q^j=Lz#`$j&VD_&UoxzCM|PIc@C2P0yH<#y-5X zggGimf_Z{4^I8^`d3>E^9$!C`f;lR4zMb9lj5%rS!%It;-La3;g!xvoGs`@_&NBBi zolL=;HumABC(N`Dwp}*7v=L_716%BTf-rXzW|q1Ch-DsMKbDM{_RIF|?51bTN%f$Y zmN2{Z;8DWN{X#7B_&UoxzJ53bb6P#n!v5`oR>; zY4xC+o-rrYgI?MQv+lF|7&bb*tcof|6gK=iK$u$yGs`@_&N7d$k0oQyMdzmbWy4KR zm~+wD>2`Vc(h_F39^6Nmn+P+@Jig8{_mS*L!JJkPy6G8nQbp*cCCqL;_*;berG%Mf z?)zYw$JgJPf;p`obkj5Dqto-nsZ zWyvlZUfKwAdsL33JpUSDt|L3M%;W1U^Z5EBDVWphK{q{PPO1mJw1nBM2j4=ND+x2p zJig8{kFP(Rf;p`obkj5DqnEw%C5)9OUm z|DT4o8PESe3%`FHzWykD{b79HfG~j2iar3P`v&UZo5J@$gfND%9lm})`T%Aiz6SCo z`2Xh+j@7~kg|EM<7QQLuK7?+B>4=|zuRmVXmftII5B&d8gf-{`Xh*z4{CW8PXAw>! z9DuLC1HOKLHO?V~+=?&*;R1a91dbt$BkZc0 zg+2kCLx}h$$kP!N!fE*W2jT1QsKhyh=o46j(2vlJ_>1uMkHgnL4B!6%!d`?S^a1oD zz5(&2kT1ahpFlVQUw=1z{dMJSY6avTgm#2V#Gk=@|AX-L_rdqyg`oEv94Ny%gowWg zUw;z5|7nEd2xIW|hvDn@BEA!HGeQHxIr#cV;p^`%X;UNU0~kVBhtQ4q8pNN0uYUr* z{t<+O2&3@%SECPLHsYsa4S@N%O)vQ15B&YtLLP_AWsm>V6{m~(1)_4*MRkq;{&CYs zzl*M1QoF@VoBv&O<=l45t-$an0Q3m`E?DM12$s1IVs;AVw04V|o-rr2TfDS{*=@Jv z2=f-g%rf^uu*`iB*QH=iYqz-R8FNy*#Y-DuraDC4^EVRahX^yv+y}uj_d(PpW2U;s zFB@)p!c6sz{XX;35@xsEat&d=pD?q`eGn{jA4E|K=CpQ;o1QTzwOhQjgxPJkR1xN1 zAw8dkHhk+y}uj_dz_9h`E#M zF28KJ=_AaYw8qFT8(!Kx=1y8yB<1;8!h9EDW|{jSSmr*6Z=_&OYqz-R8FNy*#Y;<= z-FC~j2{X^zVVV0NSmr*6FQ;HmYqz-R8FNy*#Y;<=-FC}Y3G-62Gt1lu!7}$j98JNT z)>d)T6J}b2WS0#uZG@TD6-n7RPMCS043@bMf@SW57*EDb>yP~M?51bTN%f$YmN2{Z z;1R;SknGGd_d&4CeGmsyFsIdnZhFR?R1bP-3A0-d9wN;12s6vv2f;G;LF`MxoK_FI z=^1lUJ?N#4Fw?psDI5CFOG}vDdT=jc zo<(+MnfoAE=01qg6wGP$pqrjCC)I;qTEgtsgS!axD+n{o+y}uj_d#q=!JJkPy6G8n zQa$LUCCqL;I6|1OBg`yw9|X(X2Qiq8nbsfqWy4KRm}w1?T{gV55oTIfB;|RSFkelW zS>`?nmbnjNT?*#3deBYJn3L*3FD+qq>%mQgndeKe%zY3nb05S&3g)zW&`r;nlj=b) zEn#-+!8L@ritNlX_d&4CeGq*qnA7S(H$7pdHAr^ZSV34y2n);X-?{Vub@zwf|4;k+ z`uxA)daPNmZ&B^|z6U=U%*~}$KbEeM%)O#PiVg!*9-8~S68**yg$spN8A9e&4}AsiTU@K@2|(~M{(8T z^oKF;e>%Qzhc7;kx&Avb?|%~W{(JHL0Q~St%=JH9)`oF? zFyFrtaecTpB5nuf{hz{o|8Dr++i~rKKYkGN{!8(FFZ}KUnE!vKxP@Kl>>6N!A7H-c zA*8>X$}Qg-GRNk7@qAaBV-wA*@5Jww<_dnN%-qB;ME4}&<8jKTsik7pR6o{=8j1oHXpnvh|h$jJcQ3Ti4Xe|UoXt3Gv@OxE6X50l841d zkL?nlf3TE?@Hs_%*!TFG!hGh%e9l-|2Jw+RY(6so`5Tt<5I$cdKJ2GFRhUml%;#%X zmO*?Z51Wt7f&QwcJcQ3t;`2-N`~IxUr`q`aT+YRGzGCDUlAq|exu7jWW$mP)Ic!en zXNXG|aruPH#eNsG$7H^2Wf}BrBoCVp+FHctOP2BwTOA}mJpTL9f_&N+#(a)jSqAZu zJZwH_4-%jMZYdAp^I_t{e%c2L^XZQH{FRku5Fg3I=7V-9@%c+jc?h4q#D{&lcNgZf zAm;OVE6X50l84O)?PTI}#8Mu@=LzD&{@*(b^O+y>`7e`+ZY;WJ8n zX3+cK@xpwhZ7^zr9 z^O3f}?_0`4_^cs5>@VM2n2$6L#;h!Z_(&c$A88+a&{7`4=WgP|KKLDl`AGZV{Z^Ji zd?XK>kF*aiu(Ch#8y#Iah`SslY9$fW&|6%z2C*bS1qaR=h*B;Ktpe?ixf-`d~f0Eu6Wf_`g~hxJ?2Ya6Jp`l_Wo zgwL0V503*rS(uN!3%_Dz8N^5Ou=&Wl@T8?YgwJP*&#S2|@yWt`E6X50l84Pl z-i2SXl!x&781Y$5d_G)|Plw?vSy=}0kvwcZhOcBP58?BE;=^;S-&L58;VW5L2Jw+R zY(9psWGN5fvy=GnSm@)0`53;Em1Phg$;0Mj_)3=Y5I)<9509H}Da^<4m8>j-_(&c$ zAH!F&l!x%yM0|LRb!}ljhOcC08N^5Ou=yCilBGO^&uZc`o8AY@3-d92B`eDyK9YyU zN3Y9bU&&G)!smA4b0hJ&wJ;yUSF*AU;v;$3d<+Oa4Nc-R;R+d40 zBoCX9v=4sYQXazRCgQ{6%r_S1BkhARE6X50l84Pl+6N!Bl!x${L40^j`Xz<=Nc-UZ zR+d40BoCX9v=82ADG%W@o%rzhb#*~LIcXpKj+JE)AIZb!BkhAdmhuoj<-~{A{1+AG zBkhCVwz3T3BYD_-q$E-T9*K9YycN7@Jf%~Br1 zN5(us%JX+}(jNG3)VJXJ|9V>NFZ%fZFpd9@!tdAP|2^mf*bl#d0>1t@{QgG74dMGK z`27vIPQdT)Ltnrc{QgqJ_2Sxqxb5)!Pr~2tL|?!#uD$3BH~_z2;rkx+1?-3ae>xlg zXYYH;*Y9H!%1%Qdo9mQ! z;4v%9pm&DkVZAdJ$UE>!OL+*Nza~CB_Wx92KJpIy3oFYYK9YycN8W*-vy_MM`8@IA zvHwpO<|FUKKew_B;v;$3e8fjOY$*@n^Ks(CWB(s1%tw5rPg_|A@sT`iKH?*N%2FP} z=YzzD$Nt|_n2-2Khpa4v_(&cWA3gt)e58+C%0u`(L40`p{~d+-h>!F!E6X50l84Pl ze560Ll!x$n8}Z@s|3?b*5g+LftSp20NFFvH@sajh%0u{UCO$m=zp*eM@sU1cWf{ar z^04`ckMw(%@(@1v5g#7^Usafo_(=P#EQ9z+9yTBGk$%@w9>Qli@!|3Rr3LxS6Cdfl zR+d40BoCX9v=826DG%XuEAip+|E|J(q;Lum|00b2 zw<63yIDj?(JFw=zAMrae{=Xez6T$_o`9F>||9deP;1uQn==A{O2t9~zK>P&e12jRN zj-U{B)ne^E_5x@}{2=BBtU>5UI9-Fa_gM44qXzTzF-PD4!d`@C#9zdk|Kpe&Pzw11 z<^@b33}G*TUc@&beg)(ngm#1@So6OdYyQ_w!~6xz7ubcc1ECV}XRzk~Am$ECV$Q&6 zgyRSURakqE_=}i7&;=$^_z}z}7(!Twa1Lw!k7CXLZpfqsM| z2nP|m5nqG&Gw4%jgj|Vm5q%1ySo6OcdjZTw{07Ja2)zg=%Vwd^VJ_Agv|(<6Uh9v! z4G1$3e;#Z8kCx($Mf5A2LpX!533~x7LVOM4`ylHz2eT2zvF3kg$sDyBeGj_NVH9B) zK_UJW*8J};)@%MDBlKe*fF{J5AN_9s6e4*4Kb9Y%{(kE{toH+q_MV{qx9qhhUfTTn zn0d7KmaIe4?_ZtgR?36rw>a_;$=16Q7^1xg=FvV~{@N)wJ!3}O>tm|O#_Xjf%xKpj ztzg#gSdE$Y24wkR!pQQQNVeXO24=J`px4K2rI-ETPm6udMje%i*-g)OMjhaR_R>a}Y40tuvwl}<%3H9Co1QS!{#!O?FKvXG_CpcOA0y0tgo)+b9hv9Dd^iPjROWolZhFQX zl_eXqmzFR`7+K~yE-d%Z-`|yjIVy8LW;Z=!j>?ja*-J~9qjDse^*j&# z-r#vFEZ;&HS>`?`YmSEe26IuF^D(>W2{Y}TVq^BwMwn?o6v3?La%jxFHz3R0kHm5( zaj@oL2xi(h#mDTXXUs|UpqG{~yY--+uc0w>-w?~MB#bQIOtLlC!oZwX54!0Yb5cd< zr6tU6J*ekoXw1AfAj>xqMwb0K8`k@|J*^(J=3js#V@|3Ey|j7E9kd^clnp)aLSt?w zj4bm!43=-8zgu%AVmo)xzA1j$aMMSaJ816|yF7bo3A0-d>bVsfGtaGH`Fg_0GWUyE z^CAq)Y4xC+o-rrYgI-#~?AC*N{)EQNdjqn}eH|=cLmaHR4+iG6deBYJn3L*3FD+qq z>p?w7LSyFs3zn}Uj4V$l*_z)Vm}%b>zihba2{Y}TVwVjsZG@TjLy_{V=Rs)9yf+}r z)r65{o>x$f^wG!s4R&;=)q`$&#++0SdT9x>TMz2F4jOYgVPu)-3b0&Cf4Al-7(h_F39<=5b7#Py(HP`>I24C&}55nhPgRldB|0($W`{DN=hrfRq;Q&G> z;wuq<8h(ER5C47=;WWYq`2F38uS9$=0W1`y7|?;nTXKU#^s4dL(W zwFF}bt%z5MKUsmj4Iy8Ge}4{PJN*9siaBZq;#WiNL+D002ETtV{QgblSR(;{e-FYa z!gRz>!0*@d49>&fKZ|e@VGaELcEl^hFM`~PFazNL{Qe!K=y!qNzZ3rcc7#m`7vSF? z$6kMXOVIBE-~JfFI6@EN8xTJMzrP7G`~JJ&`>%uV-;Q|m(~kom^nQQr|Fg{gKg+{Z z#+T>=BJv;eciBaI*7)xdH+@vbyA~w1LADf<;^5_rC?5L zgShD#b5a|`OG}ue->|&@+6goF53u|YVPts|$+J>0r?o-c^o%*F4dSJZFw4r_-%l7><~{bVO~y=n*7#+^O;4C<-x|A~@zN4zw++%jn7My|W!__tVd z1P1-y;Qj%Yd5=AoR}kiBlQGktH9lrHJz=JENbIuVrHwGt*&<@+e(h_F39{dzx=KcYed5=AoJIKy|l!7^}9(2<) z=BO;$cJ|T|X15;v1H#Px11$6U50+aA^9NEer`3aQdd8eo4|-`M%rv$kW#fH>nfnJ= z<~{aUZXwLOlQGjch+j6`^n{tlH*7n5X$iAi5AGt&+&{oF&#_>+i7>xC1#?C(RxDl%+g`k3QE(^I;!F&K^qSBXgk+ zSy=}0kvuFuy1$tCeB4qV!bhKZr1`KvBj*w&@{zevAG5Ly;v;$3d}J=vA6m*o_~`SH zG#?%tl{1SH`51Ghtt^B1NFFvHW3IHNJcN%vD@pU=aa1|aD3OmbSK7)lh>zr9^FjN9 z%G>W*%0u|PgZRYj{~^a8|9xQ0mA0}B;$xksB=3W6w10@t?^?=3_~C> zq{pYrm@92%8N^5Ou%8dwXvF6|mhuoj`uru$hyA~Y3qGGNW3IH7We^|9!{&pwA@SL5 zDG%ZEAn{>eQO;vZd_KlpX)DVhK9Yyc2W?m4^Bb1(5I*-2A0BsJRrvWBbET~;gZM}u zHXpRFiO;WF%0u`pCq6tzy|geNX&=1Z$})(LnoKL-_n-LHwQ49{3;8+JZ8*u-&|+2IPxX zeLVi(k2wIRG1h+&WBohov7a8kU&Q$Sag6VeVXS``-+S?W2EL!i`2JCh?`NOP0+|Ie z3uG3^ERb0svp{Bn%mSGOG7DrD_}N?FeODVgI zU`U%E;HGB`N%I4|w1mN(AFwu_#|e8Fc1RoVchfV5r15?)En#rS`(FGc_QOCRWm ztt^B33?&b<&#3#j5Eaf45nurh2H*YM=N5)8h)ygu6kK|$V zkulQkmhuoj4aA4%rq>kaBV(kGSXl<~kvwcZ(mvQ`DG%XONql%7yDH2_+6O~cmO*?Z z51Ws)4>nuML-<_67_WX`^P2bz1;>0BNc-SnE6X50l84Pl+6NC=%0u{^Cq6Ht_rYXA zKJ%r0@PL(N5Fg3I<|FNc4VLl{J`==;_y0Rnn2)p%)>~Ny@sT`iKGHr|Ybg)mbDH?@ zT>g`V`AGXzq^Z&BX3%5W>Uz^?s-ZMn=;r0Krw?`r$=?i_r$})(L z^|L=J@N-gCfd~PH@y#D{X!hDReQY*_KK9YyUNAFj|W2Khz5I)xs zA71}oSD246R%&G##7FY5`50rRmhuojRm6wa{}&bJV~mwrSqAZuJZwJ3SgEBvgwG|6 zjq3Lsum8VLaIAEpF;;428N^5Ou=yBcrIzv#KF<;#UjP40VLrxKsg-3AAIZb!V~mwr z%0u{koA~hh|8ErJ(`}5ET3H71kvwcZ##pJPJcQ3zi4U*;|8ikI(mq&kWf{ar^04_x z`(Ul5JcQ42;=}9zj~3=5?SnN|mO*?Z51WrP5MFO758-o!`0)Dw@xpwheQ>XpWe^|9 z!{#IHgH@LD5I%>953m0}P?(Rj5AL?I4B{hs*nFgYu)A6;r0Lf3iFZn!5vnX zL3|_+i;q5gh1v(NwUmeO*-LzQ&i?Mge58HQXJr}0NAj@wNc-T`mhuojqlNKHd*Gwd z+=6nf|IZKfqYl=eXw*OYp8cCyAhSSbfy@G#1u_d{7RW4+Ss=4OW`WECnFTTn{9;(Z z9sk$+|7Gj{v-SV~FH85WSdj8r0C*wm|GVNvEmj{}r}Tloz1ke`59wpm3uZJvUjHxW z=^@gcW6+V4KF|>>%b-3($;0BK&nlum(6?F2L-^=({4^h4|1W3lCGwFz&|kB%4B{hs z*nFf9^f60$2%iSx!|VU$9KJ+8(kJ?;m1Phg$;0L&W2D2D@(@1y{6GDCc>TYe(U-_a z#z@~{Wf{ar^04{H80i*Ec?h427_-)Vc>Vvgg~qTuaxz9bXk{71NAj@w$QbFHEaf45 z^tpkW53m21v;C5uPrHnfK4@hb#7FY5`N$aQMoW1JAAP2v=ELj%<=nqSJ~Br7Mk~u8 zK9YycN5)9kS;|BB=<^0OA71}2X96bjkulO=v9b)}BYD_-WQ_C;mhuoj`Yb}thu8nh zd4Y+1WQ=sRm1Phg$;0L&W2E<3%0u|*a|$&dUjHv=2`2K9G18S*mO*?Z51Ws)5AL#* zhw#y77-~Me{$I`+Oync&gXLD1L3|_+i;q56j@k$Pmhuoj`g}vphu8nh8H9;^q_g3m*Z<4;go%8leb8%V8N^5Ou=z;);5JKn2p@eeqUOWv z|K;q$L_X3!_+=~0AU=|Z%}3e?ud+e0cr8oOzhQ2V2BE%RkvJE6X50l84Pl+6TXrpJSl+{|CJIQ;Tm^_@$r3 zUjO^*F@GKV|M%cJ9rAE}i#k!4Q|)!NY6#aJ?EAmJu0>s{&8bthEouzcA?*LZ2-gP4 zXKQlmP)(cKf$Km`i)zC4e06p{Ky2*nOJ;%00+|Ie3uG3^ERb0svp{Bne`5<+d%AmL z6&>PBb=R1~BSF50mq&$OKiJVJzSMk6d5AA%?FaAS&x!BUX=M?F-^+vWi|^E7DG$NF z7Jd2g$Hy;ysqI!4LHNBq2*32D-fSrk!LRqQKLP?g|NpMSe55aRu9amFAIZb=Wjm!W z^(ISs2%mo9!}I@_6y_s+sV!EPL3|_+n~(IRHe1R=_{g{?9+F?ar0?_{QQw00|4ZTm z8GWf#ALHkl$n@WAhSSbfy@G#1u_d{7RW4+Ss=4O zW`WECnFW51EKqduvMO6$RHP~|tD@4&CbDKVJum7t@XZM2*!jL0GY{aI<5P-IqHfE7 z*PHfVMROAR@-fyw`8@8WJaE3A@%h>2agWa9$A)^~y>brP|Njef0{|$#r>I`ll+4U$ zaS40cbz(0&^RuS3Np+Xr5JxqX-l)!%%vQ%sZd7A*U;IaWDKqeuLDt!osr7@~w*0dC zka2cok+xwC*r_*e+4A7Vt+TcrS7mxbf@(2mq-#3qU|qLw-Lh@lybrp%%BSHu<~p8? zv{XRL7RFVqF(JLiqMtRcmyxz}AL8_x2gQgf0!Eg#q|>sj?{RM1s&u&*&k}b+aT_IcirmM@gMafib@f(gUSdMH_tXO7Syxq;y zvqi0de;!->7gtx3Ev8s59(Q$-E!wZ)^DUxhtL5m!&$1NH>%lD#4sN7psm-QKLG-E2 zm+RjBrBZF)vbhbPWJw zv1vzgnaKavwzj#tUVu&KxVr4}rGIPjC;wYRddpSsV8*Ga6 zp_i9cz!c&TFxmf038vn^ia=J^83#Z|7ZBwI}J zw^rxsvTZS?a#7>hf@QV^%WR8MH&4$Ncn43hT$H%Fl58=>a-m#Zwk`Z}f%laA8~G{X z%-;yhYzvmz7XRYD>pWZF9Xv%_{Ls~vWQ!@5iyyeUY+Lx{0_j~_oJX8#3zpdyEVC^p z-8?;8Oubw@a&g?%W!qv(<>J3PwqTiU!7|(8 z^KPD=Ev8;BKIiI6vc(k3#h<&nY+FpJTpV_6!E$7a^@A+4Ee^SPdbXH)x%d-TSCTEJ zSS}8_x@=oasa*V#V+)qKT(Hcx_^_L&XN#nAq3_l{4qsg#kJ@cXwwPkMVE+r6@Zb7D zZdeuat3K#*vF)eJ(va zEtv%}3uG3^ERb0svp{Bn%mSGOG7DrD$Sm+5#RAs;jU^QgKYdzVy&6uU-jBaT-M@9? z`r(ah-?ZhS&F7kAXU7pWqAseU^2@6DABz4(m9Ko=%HG=+KhspF_kQeM`s&52`Ug7n z{*p!cg|AK?11~6#Z((_@vBH=ci4dcp$+&}goXV@4{v-7UyJqE zN7oP9c)qX(c(7vtIR8Xd7hpUoL?aNko@dwm^y@F^{`HWovWpXQiu3IS7+Dyk#gp>$(^HFz-HY;z Ui$VDxSm}>~(GVC7fe{!202T^g=Kufz literal 0 HcmV?d00001