pax_global_header00006660000000000000000000000064134313545340014517gustar00rootroot0000000000000052 comment=882ddf832665a72b403075de94e1ba55de88339f rows-0.4.1/000077500000000000000000000000001343135453400125135ustar00rootroot00000000000000rows-0.4.1/.env000066400000000000000000000003231343135453400133020ustar00rootroot00000000000000# Docker settings POSTGRES_HOST=localhost POSTGRES_PORT=42001 POSTGRES_USER=postgres POSTGRES_PASSWORD=postgres POSTGRES_DB=rows # Test settings POSTGRESQL_URI=postgres://postgres:postgres@127.0.0.1:42001/rows rows-0.4.1/.gitignore000066400000000000000000000003501343135453400145010ustar00rootroot00000000000000*.csv *.db *.egg-info/ *.pyc *.sqlite *.sw? *~ .*.sw? .DS_Store .activate .coverage .directory .env .idea/* .ipynb_checkpoints/ .pytest_cache/ .tox MANIFEST build/* data/ dist/* docs-build/ docs/man/ docs/reference/ reg_settings.py rows-0.4.1/AUTHORS.md000066400000000000000000000026131343135453400141640ustar00rootroot00000000000000# Authors and Contributors of `rows` Library Project page: . ## Author/Maintainer Created and maintained by Álvaro Justen aka turicas: - - - ## Contributors - Érico Andrei - Bernardo Fontes - Alexandre Brandão (slex) - Paulo Roberto Alves de Oliveira (aka kretcheu) - Jean Ferri - Evaldo Junior - Rhenan Bartels - Mauro Baraldi - Henrique Bastos - Rômulo Collopy - Davi Oliveira - Ellison Leão - Ramiro Luz - Humberto Rocha - Sebastian Oliva - Guillermo Colmenero - Izabela Borges - João S. O. Bueno - Lucas Rangel Cezimbra - Marcelo Jorge Vieira - Marcos Vinícius - Daniel Drumond rows-0.4.1/Dockerfile000066400000000000000000000014261343135453400145100ustar00rootroot00000000000000FROM python:3.7 MAINTAINER Álvaro Justen # Install system dependencies RUN apt-get update RUN apt-get install --no-install-recommends -y \ build-essential git locales python3-dev libsnappy-dev \ libxml2-dev libxslt-dev libz-dev && \ apt-get clean && \ pip install --no-cache-dir -U pip # Configure locale (needed to run tests) RUN echo 'en_US.UTF-8 UTF-8' > /etc/locale.gen RUN echo 'pt_BR.UTF-8 UTF-8' >> /etc/locale.gen RUN /usr/sbin/locale-gen # Clone the repository and install Python dependencies RUN git clone https://github.com/turicas/rows.git /rows RUN cd /rows && \ git checkout master && \ pip install --no-cache-dir -r requirements-development.txt && \ pip install --no-cache-dir -e . rows-0.4.1/LICENSE000066400000000000000000000167441343135453400135340ustar00rootroot00000000000000 GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. rows-0.4.1/Makefile000066400000000000000000000024611343135453400141560ustar00rootroot00000000000000envtest: clean nosetests tests/ test: tox clean: find -regex '.*\.pyc' -exec rm {} \; find -regex '.*~' -exec rm {} \; rm -rf reg-settings.py MANIFEST dist build *.egg-info rows.1 .tox rm -rf docs-build docs/reference docs/man coverage erase fix-imports: autoflake --in-place --recursive --remove-unused-variables --remove-all-unused-imports . isort -rc . black . install: make clean make uninstall python setup.py install uninstall: pip uninstall -y rows lint: pylint rows/*.py lint-tests: pylint tests/*.py docs: make clean install click-man --target=docs/man/ rows pycco --directory=docs/reference --generate_index --skip-bad-files rows/*.py pycco --directory=docs/reference/plugins --generate_index --skip-bad-files rows/plugins/*.py mkdocs build --strict --site-dir=docs-build rm -rf docs/man docs/reference docs-serve: docs cd docs-build && python3 -m http.server docs-upload: docs -git branch --delete --force --quiet gh-pages -git push turicas :gh-pages ghp-import --no-jekyll --message="Docs automatically built from $(shell git rev-parse HEAD)" --branch=gh-pages --push --force --remote=turicas docs-build/ release: python setup.py bdist bdist_wheel --universal bdist_egg upload .PHONY: test clean docs docs-serve docs-upload fix-imports lint lint-tests install uninstall release rows-0.4.1/README.md000066400000000000000000000021441343135453400137730ustar00rootroot00000000000000# rows [![Join the chat at https://gitter.im/turicas/rows](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/turicas/rows?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![Current version at PyPI](https://img.shields.io/pypi/v/rows.svg)](https://pypi.python.org/pypi/rows) [![Downloads per month on PyPI](https://img.shields.io/pypi/dm/rows.svg)](https://pypi.python.org/pypi/rows) ![Supported Python Versions](https://img.shields.io/pypi/pyversions/rows.svg) ![Software status](https://img.shields.io/pypi/status/rows.svg) [![License: LGPLv3](https://img.shields.io/pypi/l/rows.svg)](https://github.com/turicas/rows/blob/develop/LICENSE) No matter in which format your tabular data is: `rows` will import it, automatically detect types and give you high-level Python objects so you can start **working with the data** instead of **trying to parse it**. It is also locale-and-unicode aware. :) Want to learn more? [Read the documentation](http://turicas.info/rows) (or build and browse the docs locally by running `make docs-serve` after installing `requirements-development.txt`). rows-0.4.1/docker-compose.yml000066400000000000000000000002441343135453400161500ustar00rootroot00000000000000version: '3' services: db: image: postgres:10 container_name: rows_postgresql env_file: .env ports: - "42001:5432" rows-0.4.1/docs/000077500000000000000000000000001343135453400134435ustar00rootroot00000000000000rows-0.4.1/docs/changelog.md000066400000000000000000000323231343135453400157170ustar00rootroot00000000000000# Log of Changes ## Version `0.4.2dev0` **Released on: (in development)** ### General Changes and Enhancements ### Plugins ### Command-Line Interface ### Utils ### Bug Fixes ## Version `0.4.1` (bugfix release) **Released on: 2019-02-14** ### General Changes and Enhancements - Add new way to make docs (remove sphinx and uses mkdocs + click-man + pycco) - Update Dockerfile ### Bug Fixes - [#305](https://github.com/turicas/rows/issues/305) "0" was not being deserialized by `IntegerField` ## Version `0.4.0` **Released on: 2019-02-09** ### General Changes and Enhancements - [#243](https://github.com/turicas/rows/issues/243) Change license to LGPL3.0. - Added official Python 3.6 support. - `Table.__add__` does not depend on table sizes anymore. - Implemented `Table.__iadd__` (`table += other` will work). - [#234](https://github.com/turicas/rows/issues/234) Remove `BinaryField` from the default list of detection types. ### Plugins - [#224](https://github.com/turicas/rows/issues/224) Add `|` as possible delimiter (CSV dialect detection). - Export CSV in batches. - Change CSV dialect detection sample size to 256KiB. - [#225](https://github.com/turicas/rows/issues/225) Create export callbacks (CSV and SQLite plugins). - [#270](https://github.com/turicas/rows/pull/270) Added options to export pretty text table frames (TXT plugin). - [#274](https://github.com/turicas/rows/issues/274) `start_row` and `start_column` now behave the same way in XLS and XLSX (starting from 0). - [#261](https://github.com/turicas/rows/issues/261) Add support to `end_row` and `end_column` on XLS and XLSX (thanks [@Lrcezimbra](https://github.com/Lrcezimbra) for the suggestion). - [#4](https://github.com/turicas/rows/issues/4) Add PostgreSQL plugin (thanks to [@juliano777](https://github.com/juliano777)). - [#290](https://github.com/turicas/rows/pull/290) Fix percent formatting reading on XLSX and ODS file formats (thanks to [@jsbueno](https://github.com/jsbueno)). - [#220](https://github.com/turicas/rows/issues/220) Do not use non-import_fields and force_types columns on type detection algorithm. - [#50](https://github.com/turicas/rows/issues/50) Create PDF extraction plugin with two backend libraries (`pymupdf` and `pdfminer.six`) and 3 table extraction algorithms. - [#294](https://github.com/isses/294) Decrease XLSX reading time (thanks to [@israelst](https://github.com/israelst)). - Change to pure Python version of Apache Thrift library (parquet plugin) - [@299](https://github.com/turicas/rows/issues/299) Change CSV field limit ### Command-Line Interface - [#242](https://github.com/turicas/rows/issues/242) Add `--fields`/`--fields-exclude` to `convert`, `join` and `sum` (and rename `--fields-exclude` on `print`), also remove `--fields` from `query` (is not needed). - [#235](https://github.com/turicas/rows/issues/235) Implement `--http-cache` and `--http-cache-path`. - [#237](https://github.com/turicas/rows/issues/237) Implement `rows schema` (generates schema in text, SQL and Django models). - Enable progress bar when downloading files. - Create `pgimport` and `pgexport` commands. - Create `csv-to-sqlite` and `sqlite-to-csv` commands. - Create `pdf-to-text` command. - Add shortcut for all command names: `2` can be used instead of `-to-` (so `rows pdf2text` is a shortcut to `rows pdf-to-text`). ### Utils - Create `utils.open_compressed` helper function: can read/write files, automatically dealing with on-the-fly compression. - Add progress bar support to `utils.download_file` (thanks to `tqdm` library). - Add helper class `utils.CsvLazyDictWriter` (write as `dict`s without needing to pass the keys in advance). - Add `utils.pgimport` and `utils.pgexport` functions. - Add `utils.csv2sqlite` and `utils.sqlite2csv` functions. ### Bug Fixes - [#223](https://github.com/turicas/rows/issues/223) `UnicodeDecodeError` on dialect detection. - [#214](https://github.com/turicas/rows/issues/214) Problem detecting dialect. - [#181](https://github.com/turicas/rows/issues/181) Create slugs inside `Table.__init__`. - [#221](https://github.com/turicas/rows/issues/221) Error on `pip install rows`. - [#238](https://github.com/turicas/rows/issues/238) `import_from_dicts` supports generator as input - [#239](https://github.com/turicas/rows/issues/239) Use correct field ordering - [#299](https://github.com/turicas/rows/issues/302) Integer field detected for numbers started with zero ## Version `0.3.1` **Released on: 2017-05-08** ### Enhancements - Move information on README to a site, organize and add more examples. Documentation is available at [turicas.info/rows](http://turicas.info/rows). Thanks to [@ellisonleao](https://github.com/ellisonleao) for Sphinx implementation and [@ramiroluz](https://github.com/ramiroluz) for new examples. - Little code refactorings. ### Bug Fixes - [#200](https://github.com/turicas/rows/pull/200) Escape output when exporting to HTML (thanks to [@arloc](https://github.com/arloc)) - Fix some tests - [#215](https://github.com/turicas/rows/issues/215) DecimalField does not handle negative values correctly if using locale (thanks to [@draug3n](https://github.com/draug3n) for reporting) ## Version `0.3.0` **Released on: 2016-09-02** ### Backwards Incompatible Changes ### Bug Fixes - Return `None` on XLS blank cells; - [#188](https://github.com/turicas/rows/issues/188) Change `sample_size` on encoding detection. ### Enhancements and Refactorings - `rows.fields.detect_fields` will consider `BinaryField` if all the values are `str` (Python 2)/`bytes` (Python 3) and all other fields will work only with `unicode` (Python 2)/`str` (Python 3); - Plugins HTML and XPath now uses a better way to return inner HTML (when `preserve_html=True`); - [#189](https://github.com/turicas/rows/issues/189) Optimize `Table.__add__`. ### New Features - Support for Python 3 (finally!); - `rows.fields.BinaryField` now automatically uses base64 to encode/decode; - Added `encoding` information to `rows.Table` metadata in text plugins; - Added `sheet_name` information to `rows.Table` metadata in XLS and XLSX plugins; - [#190](https://github.com/turicas/rows/issues/190) Add `query_args` to `import_from_sqlite`; - [#177](https://github.com/turicas/rows/issues/177) Add `dialect` to `export_to_csv`. ## Version `0.2.1` **Released on: 2016-08-10** ### Backwards Incompatible Changes - `rows.utils.export_to_uri` signature is now like `rows.export_to_*` (first the `rows.Table` object, then the URI) - Changed default table name in `import_from_sqlite` and `export_to_sqlite` (from `rows` and `rows_{number}` to `table{number}`) ### Bug Fixes - [#170](https://github.com/turicas/rows/issues/170) (SQLite plugin) Error converting `int` and `float` when value is `None`. - [#168](https://github.com/turicas/rows/issues/168) Use `Field.serialize` if does not know the field type (affecting: XLS, XLSX and SQLite plugins). - [#167](https://github.com/turicas/rows/issues/167) Use more data to detect dialect, delimit the possible delimiters and fallback to excel if can't detect. - [#176](https://github.com/turicas/rows/issues/176) Problem using quotes on CSV plugin. - [#179](https://github.com/turicas/rows/issues/179) Fix double underscore problem on `rows.utils.slug` - [#175](https://github.com/turicas/rows/issues/175) Fix `None` serialization/deserialization in all plugins (and also field types) - [#172](https://github.com/turicas/rows/issues/172) Expose all tables in `rows query` for SQLite databases - Fix `examples/cli/convert.sh` (missing `-`) - Avoids SQL injection in table name ### Enhancements and Refactorings - Refactor `rows.utils.import_from_uri` - Encoding and file type are better detected on `rows.utils.import_from_uri` - Added helper functions to `rows.utils` regarding encoding and file type/plugin detection - There's a better description of plugin metadata (MIME types accepted) on `rows.utils` (should be refactored to be inside each plugin) - Moved `slug` and `ipartition` functions to `rows.plugins.utils` - Optimize `rows query` when using only one SQLite source ## Version `0.2.0` **Released on: 2016-07-15** ### Backwards Incompatible Changes - `rows.fields.UnicodeField` was renamed to `rows.fields.TextField` - `rows.fields.BytesField` was renamed to `rows.fields.BinaryField` ### Bug Fixes - Fix import errors on older versions of urllib3 and Python (thanks to [@jeanferri](https://github.com/jeanferri)) - [#156](https://github.com/turicas/rows/issues/156) `BoolField` should not accept "0" and "1" as possible values - [#86](https://github.com/turicas/rows/issues/86) Fix `Content-Type` parsing - Fix locale-related tests - [#85](https://github.com/turicas/rows/issues/85) Fix `preserve_html` if `fields` is not provided - Fix problem with big integers - [#131](https://github.com/turicas/rows/issues/131) Fix problem when empty sample data - Fix problem with `unicode` and `DateField` - Fix `PercentField.serialize(None)` - Fix bug with `Decimal` receiving `''` - Fix bug in `PercentField.serialize(Decimal('0'))` - Fix nested table behaviour on HTML plugin ### General Changes - (EXPERIMENTAL) Add `rows.FlexibleTable` class (with help on tests from [@maurobaraildi](https://github.com/maurobaraldi)) - Lots of refactorings - Add `rows.operations.transpose` - Add `Table.__repr__` - Renamte `rows.fields.UnicodeField` to `rows.fields.TextField` and `rows.fields.ByteField` to `rows.fields.BinaryField` - Add a man page (thanks to [@kretcheu](https://github.com/kretcheu)) - [#40](https://github.com/turicas/rows/issues/40) The package is available on Debian! - [#120](https://github.com/turicas/rows/issues/120) The package is available on Fedora! - Add some examples - [#138](https://github.com/turicas/rows/issues/138) Add `rows.fields.JSONField` - [#146](https://github.com/turicas/rows/issues/146) Add `rows.fields.EmailField` - Enhance encoding detection using [file-magic](https://pypi.python.org/pypi/file-magic) library - [#160](https://github.com/turicas/rows/issues/160) Add support for column get/set/del in `rows.Table` ### Tests - Fix "\r\n" on tests to work on Windows - Enhance tests with `mock` to assure some functions are being called - Improve some tests ### Plugins - Add plugin JSON (thanks [@sxslex](https://github.com/sxslex)) - [#107](https://github.com/turicas/rows/issues/107) Add `import_from_txt` - [#149](https://github.com/turicas/rows/issues/149) Add `import_from_xpath` - (EXPERIMENTAL) Add `import_from_ods` - (EXPERIMENTAL) Add `import_from_parquet` - Add `import_from_sqlite` and `export_to_sqlite` (implemented by [@turicas](https://github.com/turicas) with help from [@infog](https://github.com/infog)) - Add `import_from_xlsx` and `export_to_xlsx` (thanks to [@RhenanBartels](https://github.com/turicas/RhenanBartels)) - Autodetect delimiter in CSV files - Export to TXT, JSON and XLS also support an already opened file and CSV can export to memory (thanks to [@jeanferri](https://github.com/jeanferri)) - [#93](https://github.com/turicas/rows/issues/93) Add HTML helpers inside `rows.plugins.html`: `count_tables`, `extract_text`, `extract_links` and `tag_to_dict` - [#162](https://github.com/turicas/rows/issues/162) Add `import_from_dicts` and `export_to_dicts` - Refactor `export_to_txt` ### Utils - Create `rows.plugins.utils` - [#119](https://github.com/turicas/rows/issues/119) Rename field name if name is duplicated (to "field_2", "field_3", ..., "field_N") or if starts with a number. - Add option to import only some fields (`import_fields` parameter inside `create_table`) - Add option to export only some fields (`export_fields` parameter inside `prepare_to_export`) - Add option `force_types` to force field types in some columns (instead of detecting) on `create_table`. - Support lazy objects on `create_table` - Add `samples` parameter to `create_table` ### CLI - Add option to disable SSL verification (`--verify-ssl=no`) - Add `print` command - Add `--version` - CLI is not installed by default (should be installed as `pip install rows[cli]`) - Automatically detect default encoding (if not specified) - Add `--order-by` to some commands and remove `sort` command. #111 - Do not use locale by default - Add `query` command: converts (from many sources) internally to SQLite, execute the query and then export ## Version `0.1.1` **Released on: 2015-09-03** - Fix code to run on Windows (thanks [@sxslex](https://github.com/sxslex)) - Fix locale (name, default name etc.) - Remove `filemagic` dependency (waiting for `python-magic` to be available on PyPI) - Write log of changes for `0.1.0` and `0.1.1` ## Version `0.1.0` **Released on: 2015-08-29** - Implement `Table` and its basic methods - Implement basic plugin support with many utilities and the following formats: - `csv` (input/output) - `html` (input/output) - `txt` (output) - `xls` (input/output) - Implement the following field types - many of them with locale support: - `ByteField` - `BoolField` - `IntegerField` - `FloatField` - `DecimalField` - `PercentField` - `DateField` - `DatetimeField` - `UnicodeField` - Implement basic `Table` operations: - `sum` - `join` - `transform` - `serialize` - Implement a command-line interface with the following commands: - `convert` - `join` - `sort` - `sum` - Add examples to the repository rows-0.4.1/docs/cli.md000066400000000000000000000377141343135453400145500ustar00rootroot00000000000000# Command-Line Interface `rows` exposes a command-line interface with common operations such as converting and querying data. > Note: we still need to improve this documentation. Please run `rows --help` > to see all the available commands, [see the code reference][cli-reference] or > take a look at [rows/cli.py][rows-cli]. [Man pages are also > available][cli-manpage]. ## Commands All the commands accepts any of the formats supported by the library (unless in some specific/optimized cases, like `csv2sqlite`, `sqlite2csv`, `pgimport` and `pgexport`) and for all input data you can specify an URL instead of a local filename (example: `rows convert https://website/file.html file.csv`). > Note: you must install the specific dependencies for each format you want > support (example: to extract tables from HTML the Python library `lxml` is > required). - [`rows convert`][cli-convert]: convert a table from one format to another. - [`rows csv2sqlite`][cli-csv2sqlite]: convert one or more CSV files (compressed or not) to SQLite in an optimized way (if source is CSV and destination is SQLite, use this rather than `rows convert`). - [`rows join`][cli-join]: equivalent to SQL's `JOIN` - get rows from each table and join them. - [`rows pdf-to-text`][cli-pdf-to-text]: extract text from a PDF file and save into a file or print to standard output; - [`rows pgexport`][cli-pgexport]: export a PostgreSQL table into a CSV file (compressed or not) in the most optimized way: using `psql`'s `COPY` command. - [`rows pgimport`][cli-pgimport]: import a CSV file (compressed or not) into a PostgreSQL table in the most optimized way: using `psql`'s `COPY` command. - [`rows print`][cli-print]: print a table to the standard output (you can choose between some frame styles). - [`rows query`][cli-query]: query a table using SQL (converts the table to an in-memory SQLite database) and output to the standard output or a file. - [`rows schema`][cli-schema]: inspects a table and defines its schema. Can output in many formats, like text, SQL or even Django models. - [`rows sqlite2csv`][cli-sqlite2csv]: convert a SQLite table into a CSV file (compressed or not). - [`rows sum`][cli-sum]: aggreate the rows of two equivalent tables (must have same field names and types), equivalent to SQL's `UNION`. > Note: everytime we specify "compressed or not" means you can use the file as > is or a compressed version of it. The supported compression formats are: > gzip (`.gz`), lzma (`.xz`) and bzip2 (`.bz2`). [Support for archive > formats such as zip, tar and rar will be implemented in the > future][issue-archives]. ## Global and Common Parameters Some parameters are global to the command-line interface and the sub-commands also have specific options. The global options are: - `--http-cache=BOOLEAN`: Enable/disable HTTP cache (default: `true`) - `--http-cache-path=TEXT`: Set HTTP cache path (default: `USER_HOME_PATH/.cache/rows/http` ## `rows convert` Convert a table from a `source` URI to `destination`. Useful to convert files between formats, like extracting data from a HTML table and converting to CSV. > Note: if you'd like to convert from/to CSV, SQLite or PostgreSQL, see the > more optimized commands [`csv2sqlite`][cli-csv2sqlite], > [`sqlite2csv`][cli-sqlite2csv], [`pgimport`][cli-pgimport] and > [`pgexport`][cli-pgexport]. Usage: `rows convert [OPTIONS] SOURCE DESTINATION` Options: - `--input-encoding=TEXT`: Encoding of input tables (default: `utf-8`) - `--output-encoding=TEXT`: Encoding of output tables (default: `utf-8`) - `--input-locale=TEXT`: Locale of input tables. Used to parse integers, floats etc. (default: `C`) - `--output-locale=TEXT`: Locale of output tables. Used to parse integers, floats etc. (default: `C`) - `--verify-ssl=BOOLEAN`: Verify SSL certificate, if source is downloaded via HTTPS (default: `true`) - `--order-by=TEXT`: Order result by this field (default: same order as input data) - `--fields=TEXT`: A comma-separated list of fields to import (default: all fields) - `--fields-exclude=TEXT`: A comma-separated list of fields to exclude when exporting (default: none) Examples: ```bash # needs: pip install rows[html] rows convert \ http://www.sports-reference.com/olympics/countries/BRA/summer/2016/ \ brazil-2016.csv rows convert \ http://www.worldometers.info/world-population/population-by-country/ \ population.csv ``` ## `rows csv2sqlite` Convert one or more CSV files (compressed or not) to SQLite in an optimized way (if source is CSV and destination is SQLite, use this rather than `rows convert`). The supported compression formats are: gzip (`.gz`), lzma (`.xz`) and bzip2 (`.bz2`). Usage: `rows csv2sqlite [OPTIONS] SOURCES... OUTPUT` Options: - `--batch-size=INTEGER`: number of rows to batch insert into SQLite (default: `10000`) - `--samples=INTEGER`: number of sample rows to detect schema (default: `5000`) - `--input-encoding=TEXT`: input encoding (default: `utf-8`) - `--dialect=TEXT`: CSV dialect to be used (default: will detect automatically) - `--schemas=TEXT`: comma-separated list of schema files (default: will detect automatically) - these files must have the columns `field_name` and `field_type` (you can see and example by running [`rows schema`][cli-schema]) Example: ```bash rows csv2sqlite \ --dialect=excel \ --input-encoding=latin1 \ file1.csv file2.csv \ result.sqlite ``` ## `rows join` Join tables from `source` URIs using `key(s)` to group rows and save into `destination`. **This command is not optimized and its use is discouraged** ([rows query][cli-query] may be more effective). Usage: `rows join [OPTIONS] KEYS SOURCES... DESTINATION` Options: - `--input-encoding=TEXT`: Encoding of input tables (default: `utf-8`) - `--output-encoding=TEXT`: Encoding of output tables (default: `utf-8`) - `--input-locale=TEXT`: Locale of input tables. Used to parse integers, floats etc. (default: `C`) - `--output-locale=TEXT`: Locale of output tables. Used to parse integers, floats etc. (default: `C`) - `--verify-ssl=BOOLEAN`: Verify SSL certificate, if source is downloaded via HTTPS (default: `true`) - `--order-by=TEXT`: Order result by this field (default: same order as input data) - `--fields=TEXT`: A comma-separated list of fields to import (default: all fields) - `--fields-exclude=TEXT`: A comma-separated list of fields to exclude when exporting (default: none) Example: join `a.csv` and `b.csv` into a new file called `c.csv` using the field `id` as a key (both `a.csv` and `b.csv` must have the field `id`): ```bash rows join id a.csv b.csv c.csv ``` ## `rows pdf-to-text` Extract text from a PDF file and save into a file or print to standard output. Usage: `rows pdf-to-text [OPTIONS] SOURCE [OUTPUT]` Options: - `--output-encoding=TEXT`: encoding to be used on output file (default: `utf-8`) - valid only when `output` is specified (if not, uses the default standard output's encoding) - `--quiet`: do not show progress bars when downloading and extracting. This option is automatically disabled if `output` is empty (default: show progress bars) - `--backend=TEXT`: PDF library to use as backend (default: `pymupdf`) - `--pages=TEXT`: page ranges Example: ```bash # needs: pip install rows[pdf] URL="http://www.imprensaoficial.rr.gov.br/app/_edicoes/2018/01/doe-20180131.pdf" rows pdf-to-text $URL result.txt # Save to file, show progress bars rows pdf-to-text --quiet $URL result.txt # Save to file, no progress bars rows pdf-to-text --pages=1,2,3 $URL # Print first 3 pages to stdout rows pdf-to-text --pages=1-3 $URL # Print first 3 pages to stdout (using ranges) ``` ## `rows pgexport` Export a PostgreSQL table into a CSV file (compressed or not) in the most optimized way: using `psql`'s `COPY` command. The supported compression formats are: gzip (`.gz`), lzma (`.xz`) and bzip2 (`.bz2`). Usage: `rows pgexport [OPTIONS] DATABASE_URI TABLE_NAME DESTINATION` Options: - `--output-encoding=TEXT`: encoding to be used on output file (default: `utf-8`) - `--dialect=TEXT`: CSV dialect to be used on output file (default: `excel`) Example: ```bash # needs: pip install rows[postgresql] rows pgexport \ postgres://postgres:postgres@127.0.0.1:42001/rows \ my_table \ my_table.csv.gz ``` ## `rows pgimport` Import a CSV file (compressed or not) into a PostgreSQL table in the most optimized way: using `psql`'s `COPY` command. The supported compression formats are: gzip (`.gz`), lzma (`.xz`) and bzip2 (`.bz2`). Usage: `rows pgimport [OPTIONS] SOURCE DATABASE_URI TABLE_NAME` Options: - `--input-encoding=TEXT`: Encoding of input CSV file (default: `utf-8`) - `--no-create-table=BOOLEAN`: should rows create the table or leave it to PostgreSQL? (default: false, ie: create the table) - `--dialect=TEXT`: CSV dialect to be used (default: will detect automatically) - `--schemas=TEXT`: schema filename to be used (default: will detect schema automatically) - this file must have the columns `field_name` and `field_type` (you can see and example by running [`rows schema`][cli-schema]) Example: ```bash # needs: pip install rows[postgresql] rows pgimport \ my_data.csv.xz \ postgres://postgres:postgres@127.0.0.1:42001/rows \ my_table ``` ## `rows print` Print the selected `source` table Usage: `rows print [OPTIONS] SOURCE` Options: - `--input-encoding=TEXT`: Encoding of input tables (default: `utf-8`) - `--output-encoding=TEXT`: Encoding of output tables (default: `utf-8`) - `--input-locale=TEXT`: Locale of input tables. Used to parse integers, floats etc. (default: `C`) - `--output-locale=TEXT`: Locale of output tables. Used to parse integers, floats etc. (default: `C`) - `--verify-ssl=BOOLEAN`: Verify SSL certificate, if source is downloaded via HTTPS (default: `true`) - `--order-by=TEXT`: Order result by this field (default: same order as input data) - `--fields=TEXT`: A comma-separated list of fields to import (default: all fields) - `--fields-exclude=TEXT`: A comma-separated list of fields to exclude when exporting (default: none) - `--frame-style=TEXT`: frame style to "draw" the table; options: `ascii`, `single`, `double`, `none` (default: `ascii`) - `--table-index=INTEGER`: if source is HTML, specify the table index to extract (default: `0`, ie: first `` inside the HTML file) Examples: ```bash rows print \ --fields=state,city \ --order-by=city \ data/brazilian-cities.csv ``` > Note: download [brazilian-cities.csv][br-cities]. ```bash # needs: pip install rows[html] rows print \ --table-index=1 \ # extracts second table some-html-file.html ``` ## `rows query` Yep, you can SQL-query any supported file format! Each of the source files will be a table inside an in-memory SQLite database, called `table1`, ..., `tableN`. If the `--output` is not specified, `rows` will print a table to the standard output. Usage: `rows query [OPTIONS] QUERY SOURCES...` Options: - `--input-encoding=TEXT`: Encoding of input tables (default: `utf-8`) - `--output-encoding=TEXT`: Encoding of output tables (default: `utf-8`) - `--input-locale=TEXT`: Locale of input tables. Used to parse integers, floats etc. (default: `C`) - `--output-locale=TEXT`: Locale of output tables. Used to parse integers, floats etc. (default: `C`) - `--verify-ssl=BOOLEAN`: Verify SSL certificate, if source is downloaded via HTTPS (default: `true`) - `--samples=INTEGER`: number of sample rows to detect schema (default: `5000`) - `--output=TEXT`: filename to outputs - will use file extension to define which plugin to use (default: standard output, plugin text) - `--frame-style=TEXT`: frame style to "draw" the table; options: `ascii`, `single`, `double`, `none` (default: `ascii`) Examples: ```bash # needs: pip install rows[html] rows query \ "SELECT * FROM table1 WHERE inhabitants > 1000000" \ data/brazilian-cities.csv \ --output=data/result.html ``` > Note: download [brazilian-cities.csv][br-cities]. ```bash # needs: pip install rows[pdf] rows query \ 'SELECT * FROM table1 WHERE categoria = "Imprópria"' \ http://balneabilidade.inema.ba.gov.br/index.php/relatoriodebalneabilidade/geraBoletim?idcampanha=36381 \ --output=bathing-conditions.xls ``` In the last example `rows` will: - Download a file using HTTP - Identify its format (PDF) - Automatically extract a table based on objects' positions - Create an in-memory database with extracted data - Run the SQL query - Export the result to XLS In just one command, automatically. How crazy is that? ## `rows schema` Identifies the table schema by inspecting data. The files generated by this command (`txt` format) can be used in `--schema` and `--schemas` options (a CSV version of these files can also be used). Usage: `rows schema [OPTIONS] SOURCE [OUTPUT]` Options: - `--input-encoding=TEXT`: Encoding of input tables (default: `utf-8`) - `--input-locale=TEXT`: Locale of input tables. Used to parse integers, floats etc. (default: `C`) - `--verify-ssl=BOOLEAN`: Verify SSL certificate, if source is downloaded via HTTPS (default: `true`) - `-f TEXT`, `--format=TEXT`: output format; options: `txt`, `sql`, `django` (default: `txt`) - `--fields=TEXT`: A comma-separated list of fields to import (default: all fields) - `--fields-exclude=TEXT`: A comma-separated list of fields to exclude when exporting (default: none) - `--samples=INTEGER`: number of sample rows to detect schema (default: `5000`) Example: ```bash rows schema --samples=100 data/brazilian-cities.csv ``` > Note: download [brazilian-cities.csv][br-cities]. Output: ``` +-------------+------------+ | field_name | field_type | +-------------+------------+ | state | text | | city | text | | inhabitants | integer | | area | float | +-------------+------------+ ``` ## `rows sqlite2csv` Convert a SQLite table into a CSV file (compressed or not). The supported compression formats are: gzip (`.gz`), lzma (`.xz`) and bzip2 (`.bz2`). Usage: `rows sqlite2csv [OPTIONS] SOURCE TABLE_NAME OUTPUT` Options: - `--batch-size=INTEGER`: number of rows to batch insert into SQLite (default: `10000`) - `--dialect=TEXT`: CSV dialect to be used on output file (default: `excel`) Example: ```bash rows sqlite2csv my_db.sqlite my_table my_table.csv.bz2 ``` ## `rows sum` Sum tables (append rows from one to the other) from `source` URIs and save into `destination`. The tables must have the same fields. **This command is not optimized and its use is discouraged** ([rows query][cli-query] may be more effective). Usage: `rows sum [OPTIONS] SOURCES... DESTINATION` Options: - `--input-encoding=TEXT`: Encoding of input tables (default: `utf-8`) - `--output-encoding=TEXT`: Encoding of output tables (default: `utf-8`) - `--input-locale=TEXT`: Locale of input tables. Used to parse integers, floats etc. (default: `C`) - `--output-locale=TEXT`: Locale of output tables. Used to parse integers, floats etc. (default: `C`) - `--verify-ssl=BOOLEAN`: Verify SSL certificate, if source is downloaded via HTTPS (default: `true`) - `--order-by=TEXT`: Order result by this field (default: same order as input data) - `--fields=TEXT`: A comma-separated list of fields to import (default: all fields) - `--fields-exclude=TEXT`: A comma-separated list of fields to exclude when exporting (default: none) Example: ```bash rows sum \ --fields=id,name,phone \ people.csv \ # This file has `id`, `name` and other fields phones.csv \ # This file has `id`, `phone` and other fields contacts.csv # Will have `id`, `name` and `phone` fields ``` [br-cities]: https://gist.github.com/turicas/ec0abcfe0d7abf7a97ef7a0c1d72c7f7 [cli-convert]: #rows-convert [cli-csv2sqlite]: #rows-csv2sqlite [cli-join]: #rows-join [cli-manpage]: man/rows.1 [cli-pdf-to-text]: #rows-pdf-to-text [cli-pgexport]: #rows-pgexport [cli-pgimport]: #rows-pgimport [cli-print]: #rows-print [cli-query]: #rows-query [cli-reference]: reference/cli.html [cli-schema]: #rows-schema [cli-sqlite2csv]: #rows-sqlite2csv [cli-sum]: #rows-sum [issue-archives]: https://github.com/turicas/rows/issues/236 [rows-cli]: https://github.com/turicas/rows/blob/master/rows/cli.py rows-0.4.1/docs/contributing.md000066400000000000000000000050251343135453400164760ustar00rootroot00000000000000# Contributing ## Creating your development environment The preferred way is to create a virtualenv (you can do by using virtualenv, virtualenvwrapper, pyenv or whatever tool you'd like). Create the virtualenv: ```bash mkvirtualenv rows ``` Install all plugins' dependencies: ```bash pip install --editable .[all] ``` Install development dependencies: ```bash pip install -r requirements-development.txt ``` ## Running the tests There are two possible ways of running the tests: on your own virtualenv or for each Python version. For the PostgreSQL plugin you're going to need a PostgreSQL server running and must set the `POSTGRESQL_URI` environment variable. If you have docker installed you can easily create a container running PostgreSQL with the provided `docker-compose.yml` by running: ```bash docker-compose -p rows -f docker-compose.yml up -d ``` ### Running on your virtualenv ```bash nosetests -dsv --with-yanc --with-coverage --cover-package rows tests/*.py ``` ### Running for all Python versions Run tests: ```bash make test ``` or (if you don't have `make`): ```bash tox ``` you can also run tox against an specific python version: ```bash tox -e py27 tox -e py35 ``` *tox known issues* : running tox with py27 environ may raise InvocationError in non Linux environments. To avoid it you may rebuild tox environment in every run with `tox -e py27 -r` or if you want to run nosetests directly (see last section). ## Running PostgreSQL tests A PostgreSQL server is needed to run the PostgreSQL plugin tests. You can use [Docker](https://docker.io/) to easily run a PostgreSQL server, but can also use your own method to run it. The `POSTGRESQL_URI` environment variable need to be se so you can run the tests. Running the PostgreSQL container using docker-compose, set the environment variable and run the PostgreSQL-specific tests: ```bash docker-compose -p rows -f docker-compose.yml up -d export POSTGRESQL_URI=postgres://postgres:postgres@127.0.0.1:42001/rows nosetests -dsv --with-yanc --with-coverage --cover-package rows tests/tests_plugin_postgresql.py ``` ## Generating the documentation Just run: ```bash make docs ``` And check the `docs-build/` directory. You can also serve it via HTTP: ```bash make docs-serve ``` ## Releasing new versions ``` # X = next version number git checkout -b release/X # update docs/changelog.md & commit # change version number in `setup` and `rows/__init__.py` & commit git checkout master && git merge --no-ff release/X git tag -a X git br -d release/X make release make docs-upload ``` rows-0.4.1/docs/index.md000066400000000000000000000103601343135453400150740ustar00rootroot00000000000000# Welcome to rows documentation! No matter in which format your tabular data is: `rows` will import it, automatically detect types and give you high-level Python objects so you can start **working with the data** instead of **trying to parse it**. It is also locale-and-unicode aware. :) Have you ever lost your precious time reading a CSV that had a different dialect? Or trying to learn a whole new library API to read a new tabular data format your customer just sent? You've got gray hair when trying to access some data and the only answer was `UnicodeDecodeError`? So, [rows][rows] was custom made for you - run `pip install rows` and be happy! :-) The library is officialy supported on Python versions 2.7, 3.5 and 3.6 (but may work on other versions too). > Note: if you're using [rows][rows] in some project please [tell > us][rows-issue-103]! :-) ## Contents - [Installation][doc-installation] - [Quick-start guide][doc-quick-start] - [Command-line interface][doc-cli] - [Supported plugins][doc-plugins] - [Using locale when importing data][doc-locale] - [Table operations][doc-operations] - [Contributing][doc-contributing] - [Useful links][doc-links] - [Log of changes][doc-changelog] - [Code reference][reference] ## Basic Usage `rows` will import tabular data in any of the supported formats, automatically detect/convert encoding and column types for you, so you can focus on work on the data. Given a CSV file like this: ``` state,city,inhabitants,area AC,Acrelândia,12538,1807.92 AC,Assis Brasil,6072,4974.18 AC,Brasiléia,21398,3916.5 AC,Bujari,8471,3034.87 AC,Capixaba,8798,1702.58 [...] RJ,Angra dos Reis,169511,825.09 RJ,Aperibé,10213,94.64 RJ,Araruama,112008,638.02 RJ,Areal,11423,110.92 RJ,Armação dos Búzios,27560,70.28 [...] ``` You can use `rows` to do some math with it without the need to convert anything: ```python import rows cities = rows.import_from_csv("data/brazilian-cities.csv") rio_biggest_cities = [ city for city in cities if city.state == "RJ" and city.inhabitants > 500000 ] for city in rio_biggest_cities: density = city.inhabitants / city.area print(f"{city.city} ({density:5.2f} ppl/km²)") ``` > Note: download [brazilian-cities.csv][br-cities]. The result: ```text Duque de Caxias (1828.51 ppl/km²) Nova Iguaçu (1527.59 ppl/km²) Rio de Janeiro (5265.81 ppl/km²) São Gonçalo (4035.88 ppl/km²) ``` The library can also export data in any of the available plugins and have a command-line interface for more common tasks. For more examples, please refer to our [quick-start guide][doc-quick-start]. > Note: `rows` is still not lazy by default, except for some operations like > `csv2sqlite`, `sqlite2csv`, `pgimport` and `pgexport` (so using > `rows.import_from_X` will put everything in memory), [we're working on > this][rows-lazyness]. ## Architecture The library is composed by: - A common interface to tabular data (the `Table` class) - A set of plugins to populate `Table` objects from formats like CSV, XLS, XLSX, HTML and XPath, Parquet, PDF, TXT, JSON, SQLite; - A set of common fields (such as `BoolField`, `IntegerField`) which know exactly how to serialize and deserialize data for each object type you'll get - A set of utilities (such as field type recognition) to help working with tabular data - A command-line interface so you can have easy access to the most used features: convert between formats, sum, join and sort tables. ## Semantic Versioning `rows` uses [semantic versioning][semver]. Note that it means we do not guarantee API backwards compatibility on `0.x.y` versions (but we try the best to). ## License This library is released under the [GNU Lesser General Public License version 3][lgpl3]. [br-cities]: https://gist.github.com/turicas/ec0abcfe0d7abf7a97ef7a0c1d72c7f7 [doc-changelog]: changelog.md [doc-cli]: cli.md [doc-contributing]: contributing.md [doc-installation]: installation.md [doc-links]: links.md [doc-locale]: locale.md [doc-operations]: operations.md [doc-plugins]: plugins.md [doc-quick-start]: quick-start.md [lgpl3]: http://www.gnu.org/licenses/lgpl-3.0.html [reference]: reference/ [rows-issue-103]: https://github.com/turicas/rows/issues/103 [rows-lazyness]: https://github.com/turicas/rows/issues/45 [rows]: https://github.com/turicas/rows/ [semver]: http://semver.org/ rows-0.4.1/docs/installation.md000066400000000000000000000042161343135453400164710ustar00rootroot00000000000000# Installation ## [PyPI][pypi-rows] ```bash pip install rows ``` ## GitHub ```bash pip install "https://github.com/turicas/rows/archive/develop.zip#egg=rows" # or (needs git) pip install "git+https://github.com/turicas/rows.git@develop#egg=rows" ``` or: ```bash git clone https://github.com/turicas/rows.git cd rows python setup.py install ``` The use of virtualenv is recommended. You can create a development image using Docker: ```bash cat Dockerfile | docker build -t turicas/rows:latest - ``` ## Debian If you use Debian [sid][debian-sid] or [testing][debian-testing] you can install it directly from the main repository by running: ```bash apt install python-rows # Python library only apt install rows # Python library + CLI ``` You may need to install SQLite too (on Ubuntu, for example). ## Fedora ```bash dnf install python-row # Python library + CLI ``` ## Docker If you don't want to install on your machine but you'd like to try the library, there's a docker image available: ```bash mkdir -p data # Put your files here echo -e "a,b\n1,2\n3,4" > data/test.csv # To access the IPython shell: docker run --rm -it -v $(pwd)/data:/data turicas/rows:0.4.0 ipython # To access the command-line interface docker run --rm -it -v $(pwd)/data:/data turicas/rows:0.4.0 rows print /data/test.csv ``` ## Installing plugins The plugins `csv`, `dicts`, `json`, `sqlite` and `txt` are built-in by default but if you want to use another one you need to explicitly install its dependencies, for example: ```bash pip install rows[html] pip install rows[xls] ``` > Note: if you're running another command line interpreter (like zsh) you may > need to escape the characters `[` and `]`. You also need to install some dependencies to use the [command-line interface][rows-cli]. You can do it installing the `cli` extra requirement: ```bash pip install rows[cli] ``` And - easily - you can install all the dependencies by using the `all` extra requirement: ```bash pip install rows[all] ``` [debian-sid]: https://www.debian.org/releases/sid/ [debian-testing]: https://www.debian.org/releases/testing/ [pypi-rows]: https://pypi.org/project/rows/ [rows-cli]: cli.md rows-0.4.1/docs/links.md000066400000000000000000000064571343135453400151210ustar00rootroot00000000000000# Useful Links ## Showcase - (Portuguese) [How rows is helping make Brazilian data more accessible][brasilio-talk-pt] - (Portuguese) [Talk (videos + slides) on rows by Álvaro Justen][rows-talk-pt] ## Related and Similar Projects - (Article) [Data science at the command-line](https://github.com/jeroenjanssens/data-science-at-the-command-line) - [Ghost.py](https://github.com/jeanphix/Ghost.py) - [OKFN's goodtables](https://github.com/okfn/goodtables) - [OKFN's messytables](https://github.com/okfn/messytables) - [Pipe](https://github.com/JulienPalard/Pipe) - [Recorde](https://github.com/pinard/Recode) - [TableFactory](https://pypi.python.org/pypi/TableFactory) - [Tabula](http://tabula.technology/) - [continuous-docs](https://github.com/icgood/continuous-docs) - [csvcat](https://pypi.python.org/pypi/csvcat) - [csvstudio](https://github.com/mdipierro/csvstudio) - [dataconverters](https://github.com/okfn/dataconverters) - [dateparser](https://github.com/scrapinghub/dateparser) - [django-import-export](https://github.com/django-import-export/django-import-export) - [extruct](https://github.com/scrapinghub/extruct) - [grablib](https://github.com/lorien/grab) - [import.io](http://import.io/) - [libextract](https://github.com/datalib/libextract) - [libextract](https://github.com/datalib/libextract) - [multicorn](https://github.com/Kozea/Multicorn) - [odo](https://github.com/blaze/odo) - [pandashells](https://github.com/robdmc/pandashells) (and pandas DataFrame) - [parse](https://github.com/r1chardj0n3s/parse) - [proof](https://github.com/wireservice/proof) - [records](https://github.com/kennethreitz/records) - [schema](https://pypi.python.org/pypi/schema) - [scrapelib](https://github.com/jamesturk/scrapelib) - [scrapy](http://scrapy.org/) - [screed](https://github.com/ctb/screed) - [selection](https://github.com/lorien/selection) - [streamtools](http://blog.nytlabs.com/streamtools/) - [table-extractor](https://pypi.python.org/pypi/table-extractor) - [tablib](https://tablib.readthedocs.org/en/latest/) - [telega-mega-import](https://github.com/django-stars/telega-mega-import) - [textql](https://github.com/dinedal/textql) - [texttables](https://github.com/Taywee/texttables) - [validictory](https://github.com/jamesturk/validictory) - [validr](https://pypi.python.org/pypi/validr) - [visidata](https://github.com/saulpw/visidata/) - [webscraper.io](http://webscraper.io/) ## Known Issues - [Create a better plugin interface so anyone can benefit of it][rows-issue-27] - [Create an object to represent a set of `rows.Table`s, like `TableSet`][rows-issue-47] - Performance: the automatic type detection algorithm can cost time: it iterates over all rows to determine the type of each column. You can disable it by passing `samples=0` to any `import_from_*` function or either changing the number of sample rows (any positive number is accepted). - [Code design issues][rows-issue-31] [rows-issue-27]: https://github.com/turicas/rows/issues/27 [rows-issue-31]: https://github.com/turicas/rows/issues/31 [rows-issue-47]: https://github.com/turicas/rows/issues/47 [rows-showcase-source]: https://github.com/leonardocsantoss/django-rows [rows-showcase]: http://rows.irdx.com.br/ [rows-talk-pt]: http://blog.justen.eng.br/2016/05/dados-tabulares-a-maneira-pythonica.html [brasilio-talk-pt]: https://www.youtube.com/watch?v=MZZFmucRxoY rows-0.4.1/docs/locale.md000066400000000000000000000027141343135453400152300ustar00rootroot00000000000000# Locale Many fields inside `rows.fields` are locale-aware. If you have some data using Brazilian Portuguese number formatting, for example (`,` as decimal separators and `.` as thousands separator) you can configure this into the library and `rows` will automatically understand these numbers! Let's see it working by extracting the population of cities in Rio de Janeiro state: ```python import locale import requests import rows from io import BytesIO url = "http://cidades.ibge.gov.br/comparamun/compara.php?idtema=1&codv=v01&coduf=33" html = requests.get(url).content with rows.locale_context(name="pt_BR.UTF-8", category=locale.LC_NUMERIC): rio = rows.import_from_html(BytesIO(html)) total_population = sum(city.pessoas for city in rio) # 'pessoas' is the fieldname related to the number of people in each city print(f"Rio de Janeiro has {total_population} inhabitants") ``` The column `pessoas` will be imported as an `IntegerField` and the result is: ```text Rio de Janeiro has 15989929 inhabitants ``` ## Locale dependency `rows.locale_context` depends on your operational system locales to work. In order to successfully use this context manager make sure the desired locale is available in a system level. For example, for Debian based systems: 1. Make sure the desired locale is present and uncommented `/etc/locale.gen` 2. Run `locale-gen` For more information [see the code reference][locale-reference]. [locale-reference]: reference/localization.html rows-0.4.1/docs/operations.md000066400000000000000000000015671343135453400161610ustar00rootroot00000000000000# Table operations The module `rows.operations` contains some operations you can do on your `Table` objects: - `rows.operations.join`: return a new `Table` based on the joining of a list of `Table`s and a field to act as `key` between them. Note: for performance reasons you may not use this function, since the join operation is done in Python - you can also convert everything to SQLite, query data there and then have your results in a `Table`, like the [`rows query`][rows-cli-query] command. - `rows.operations.transform`: return a new `Table` based on other tables and a transformation function. - `rows.operations.transpose`: transpose the `Table` based on a specific field. For more details [see the reference][operations-reference]. [rows-cli-query]: https://github.com/turicas/rows/blob/master/rows/cli.py#L291 [operations-reference]: reference/operations.html rows-0.4.1/docs/plugins.md000066400000000000000000000313011343135453400154440ustar00rootroot00000000000000# Supported Plugins The idea behing plugins is very simple: it's a piece of code which extracts data from/exports to some specific format and interfaces with the core library functions, which will know how to detect and convert data types, export to other formats etc. If you don't find the plugin for the format you need, feel free [to contribute][doc-contributing]. :-) Each `import_from_X` function receive specific parameters (depending on the format you're working) but also general parameters such as `skip_header` and `fields` (they are passed to the [rows.plugins.utils.create_table function][create-table-function]). Some plugins also provide helper functions to work with the specific format, which can help a lot extracting non-tabular data (like `rows.plugins.html.extract_links` and `rows.plugins.pdf.pdf_to_text`). This documentation is still in progress - please look into [the plugins' source code][plugins-source] to see all available parameters. Contributions on the documentation are very welcome. Look into the [examples folder][examples] to see the plugins in action. :) Current implemented plugins: - [CSV][section-csv] - [List of dicts][section-dicts] - [HTML][section-html] - [JSON][section-json] - [ODS][section-ods] - [Parquet][section-parquet] - [PDF][section-pdf] - [PostgreSQL][section-postgresql] - [SQLite][section-sqlite] - [TXT][section-txt] - [XLS][section-xls] - [XLSX][section-xlsx] - [XPath][section-xpath] > Note: `rows` is still not lazy by default, except for some operations like > `csv2sqlite`, `sqlite2csv`, `pgimport` and `pgexport` (so using > `rows.import_from_X` will put everything in memory), [we're working on > this][rows-lazyness]. ## CSV [See code reference][reference-csv] Use `rows.import_from_csv` and `rows.export_to_csv` (dependencies are installed by default). The CSV dialect is **detected automatically** but you can specify it by passing the `dialect` parameter. Helper functions: - `rows.plugins.csv.discover_dialect`: tries to figure out the CSV dialect based on a sample (in bytes). - `rows.utils.csv2sqlite`: lazily convert a CSV into a SQLite table (the command-line version of this function is pretty useful -- see more by running `rows csv2sqlite --help`). The CSV can be optionally compressed (`.csv`, `.csv.gz` and `.csv.xz`). Learn by example: - [`examples/library/usa_legislators.py`][example-legislators] ## List of dicts [See code reference][reference-dicts] Use `rows.import_from_dicts` and `rows.export_to_dicts` (no dependencies). Useful when you have the data in memory and would like to detect/convert data types and/or export to a supported format. Learn by example: - [`examples/library/organizaciones.py`][example-organizaciones] ## HTML [See code reference][reference-html] Use `rows.import_from_html` and `rows.export_to_html` (dependencies must be installed with `pip install rows[html]`). You can specify the table index in case there's more than one `
` inside the HTML, decide whether to keep the HTML code inside the ` element will return only one string, even # if there is a
` tags (useful to extract links and "hidden" data) and other options. Very useful in Web scraping. Learn by example: - [`examples/library/airports.py`][example-airports] - [`examples/library/extract_links.py`][example-extract-links] - [`examples/library/slip_opinions.py`][example-slip-opinions] Helper functions: - `rows.plugins.html.count_tables`: return the number of tables for a given HTML; - `rows.plugins.html.tag_to_dict`: extract tag's attributes into a `dict`; - `rows.plugins.html.extract_text`: extract the text content from a given HTML; - `rows.plugins.html.extract_links`: extract the `href` attributes from a given HTML (returns a list of strings). ## JSON [See code reference][reference-json] Use `rows.import_from_json` and `rows.export_to_json` (no dependencies). Each table is converted to an array of objects (where each row is represented by an object). ## ODS [See code reference][reference-ods] Use `rows.import_from_ods` (dependencies must be installed with `pip install rows[ods]`). ## Parquet [See code reference][reference-parquet] Use `rows.import_from_parquet` passing the filename (dependencies must be installed with `pip install rows[parquet]` and if the data is compressed using snappy you'll also need to `pip install rows[parquet-snappy]` and the `libsnappy-dev` system library) -- read [this blog post][blog-rows-parquet] for more details and one example. ## PDF [See code reference][reference-pdf] Use `rows.import_from_pdf` (dependencies must be installed with `pip install rows[pdf]`). ### PDF Parser Backend There are two available backends (under-the-hood libraries to parse the PDF), which you can select by passing the `backend` parameter (results may differ depending on the backend): - `'pymupdf'`: use if possible, is much faster than the other option; - `'pdfminer'`: 100% Python implementation, very slow. Get this list programatically with `rows.plugins.pdf.backends()`. You can also subclass `rows.plugins.pdf.PDFBackend` and implement your own PDF parser, if needed. ### Specify Table Boundaries You can specify some parameters to delimit where the table is located in the PDF, like: - `starts_after` and `ends_before`: delimits the objects before/after the table. Can be: regular strings (exact match); regular expressions objects; or functions (receives the object and must return `True` for the object which define if the table starts/ends there). - `page_numbers`: sequence with desired page numbers (starts from `1`). ### Specify Detection Algorithms There are 3 available algorithms to identify text objects and define where the table is located inside each page - you can subclass them and overwrite some methods to have custom behaviour (like the `get_lines`, where you can access objects' positions, for example). The algorithms available are (get the list programatically with `rows.plugins.pdf.algorithms()`): - `rows.plugins.pdf.YGroupsAlgorithm`: default, group text objects by y position and identify table lines based on these groups. - `rows.plugins.pdf.HeaderPositionAlgorithm`: use the table header to identify cell positions and then fill the table with found objects (useful in sparse tables). - `rows.plugins.pdf.RectsBoundariesAlgorithm`: detect the table boundaries by the rectangles on the page (currently only available using the `'pdfminer'` backend, which is very slow). ### Helper Functions - `rows.plugins.pdf.number_of_pages`: returns an integer representing the number of pages of a specific PDF file/stream; - `rows.plugins.pdf.pdf_to_text`: generator: each iteration will return the text for a specific page (can specify `page_numbers` to delimit which pages will be returned); - `rows.plugins.pdf.pdf_table_lines`: almost the same as `rows.import_from_pdf`, but returns a list of strings instead of a `rows.Table` object. Useful if the PDF is not well structured and needs some tweaking before importing as a `rows.Table` (so you can export to another format). ### Examples - [`balneabilidade-brasil`][example-balneabilidade]: downloads thousands of PDFs from Brazilian organizations which monitors water quality, then extract the tables in each PDF and put all rows together in one CSV; - [`examples/cli/extract-pdf.sh`][example-pdf-cli]: PDF extraction using the command-line interface (the parameters cannot be customized using this method by now -- more improvements in next versions). ## PostgreSQL [See code reference][reference-postgresql] Use `rows.import_from_postgresql` and `rows.export_to_postgresql` (dependencies must be installed with `pip install rows[postgresql]`). ### Parameters On both `rows.import_from_postgresql` and `rows.export_to_postgresql` you can pass either a connection string or a `psycopg2` connection object. On `rows.import_from_postgresql` you can pass a `query` parameter instead of a `table_name`. ### Helper Functions - `rows.utils.pgimport`: import data from CSV into PostgreSQL using the fastest possible method - requires the `psql` command available on your system (the command-line version of this function is pretty useful -- see more by running `rows pgimport --help`). The CSV can be optionally compressed (`.csv`, `.csv.gz` and `.csv.xz`); - `rows.utils.pgexport`: export data from PostgreSQL into a CSV file using the fastest possible method - requires the `psql` command available on your system (the command-line version of this function is pretty useful -- see more by running `rows pgexport --help`). The CSV can be optionally compressed (`.csv`, `.csv.gz` and `.csv.xz`). ## SQLite [See code reference][reference-sqlite] Use `rows.import_from_sqlite` and `rows.export_to_sqlite` (no dependencies). Helper functions: - `rows.utils.sqlite2csv`: lazily SQLite tables into CSV files (the command-line version of this function is pretty useful -- see more by running `rows sqlite2csv --help`). The CSV can be optionally compressed (`.csv`, `.csv.gz` and `.csv.xz`). ## TXT [See code reference][reference-txt] Use `rows.import_from_txt` and `rows.export_to_txt` (no dependencies). You can customize the border style. ## XLS [See code reference][reference-xls] Use `rows.import_from_xls` and `rows.export_to_xls` (dependencies must be installed with `pip install rows[xls]`). You can customize things like `sheet_name`, `sheet_index`, `start_row`, `end_row`, `start_column` and `end_column` (the last 5 options are indexes and starts from 0). On `rows.export_to_xls` you can define the `sheet_name`. ## XLSX [See code reference][reference-xlsx] use `rows.import_from_xlsx` and `rows.export_to_xlsx` (dependencies must be installed with `pip install rows[xlsx]`). You can customize things like `sheet_name`, `sheet_index`, `start_row`, `end_row`, `start_column` and `end_column` (the last 5 options are indexes and starts from 0). On `rows.export_to_xlsx` you can define the `sheet_name`. ## XPath [See code reference][reference-xpath] Dependencies must be installed with `pip install rows[xpath]`). Very useful in Web scraping. Use `rows.import_from_xpath` passing the following arguments: - `filename_or_fobj`: source XML/HTML; - `rows_xpath`: XPath to find the elements which will be transformed into rows; - `fields_xpath`: `collections.OrderedDict` containing XPaths for each of the fields (key: field name, value: XPath string) - you'll probrably want to use `./` so it'll search inside the row found by `rows_xpath`). Learn by example: - [`examples/library/ecuador_radiodifusoras.py`][example-radiodifusoras] - [`examples/library/brazilian_cities_wikipedia.py`][example-br-cities] [blog-rows-parquet]: http://blog.justen.eng.br/2016/03/reading-parquet-files-in-python-with-rows.html [create-table-function]: https://github.com/turicas/rows/blob/master/rows/utils.py [doc-contributing]: contributing.md [example-airports]: https://github.com/turicas/rows/blob/master/examples/library/airports.py [example-balneabilidade]: https://github.com/Correio24horas/balneabilidade-bahia [example-br-cities]: https://github.com/turicas/rows/blob/master/examples/library/brazilian_cities_wikipedia.py [example-extract-links]: https://github.com/turicas/rows/blob/master/examples/library/extract_links.py [example-legislators]: https://github.com/turicas/rows/blob/master/examples/library/usa_legislators.py [example-organizaciones]: https://github.com/turicas/rows/blob/master/examples/library/organizaciones.py [example-pdf-cli]: https://github.com/turicas/rows/blob/master/examples/cli/extract-pdf.sh [example-radiodifusoras]: https://github.com/turicas/rows/blob/master/examples/library/ecuador_radiodifusoras.py [example-slip-opinions]: https://github.com/turicas/rows/blob/master/examples/library/slip_opinions.py [examples]: https://github.com/turicas/rows/tree/master/examples/library [plugins-source]: https://github.com/turicas/rows/tree/master/rows/plugins [reference-csv]: reference/plugins/plugin_csv.html [reference-dicts]: reference/plugins/dicts.html [reference-html]: reference/plugins/plugin_html.html [reference-json]: reference/plugins/plugin_json.html [reference-ods]: reference/plugins/ods.html [reference-parquet]: reference/plugins/plugin_parquet.html [reference-pdf]: reference/plugins/plugin_pdf.html [reference-postgresql]: reference/plugins/postgresql.html [reference-sqlite]: reference/plugins/sqlite.html [reference-txt]: reference/plugins/txt.html [reference-xls]: reference/plugins/xls.html [reference-xlsx]: reference/plugins/xlsx.html [reference-xpath]: reference/plugins/xpath.html [rows-lazyness]: https://github.com/turicas/rows/issues/45 [section-csv]: #csv [section-dicts]: #list-of-dicts [section-html]: #html [section-json]: #json [section-ods]: #ods [section-parquet]: #parquet [section-pdf]: #pdf [section-postgresql]: #postgresql [section-sqlite]: #sqlite [section-txt]: #txt [section-xls]: #xls [section-xlsx]: #xlsx [section-xpath]: #xpath rows-0.4.1/docs/quick-start.md000066400000000000000000000250531343135453400162410ustar00rootroot00000000000000# Quick Start Guide ## Programatically creating a `Table` object `rows` can import data from any of the supported formats (using `rows.import_from_X` functions) and will return a `Table` object for you, but you can also create a `Table` object by hand. ### Using `Table.append` ```python from collections import OrderedDict from rows import fields, Table # Create a schema for the new table (check also all the available field types # inside `rows.fields`). country_fields = OrderedDict([ ("name", fields.TextField), ("population", fields.IntegerField), ]) # Data from: countries = Table(fields=country_fields) countries.append({"name": "Argentina", "population": "45101781"}) countries.append({"name": "Brazil", "population": "212392717"}) countries.append({"name": "Colombia", "population": "49849818"}) countries.append({"name": "Ecuador", "population": "17100444"}) countries.append({"name": "Peru", "population": "32933835"}) ``` Then you can iterate over it: ```python for country in countries: print(country) # Result: # Row(name='Argentina', population=45101781) # Row(name='Brazil', population=212392717) # Row(name='Colombia', population=49849818) # Row(name='Ecuador', population=17100444) # Row(name='Peru', population=32933835) # "Row" is a namedtuple created from `country_fields` # We've added population as a string, the library automatically converted to # integer so we can also sum: countries_population = sum(country.population for country in countries) print(countries_population) # prints 357378595 ``` You could also export this table to CSV or any other supported format: ```python import rows rows.export_to_csv(countries, "some-LA-countries.csv") ``` If you had this file before, you could: ```python import rows countries = rows.import_from_csv("some-LA-countries.csv") for country in countries: print(country) # And the result will be the same. # Since the library has an automatic type detector, the "population" column # will be detected and converted to integer. Let's see the detected types: print(countries.fields) # Result: # OrderedDict([ # ('name', ), # ('population', ) # ]) ``` ### From a `list` of `dict`s If you have the data in a list of dictionaries already you can simply use `rows.import_from_dicts`: ```python import rows data = [ {"name": "Argentina", "population": "45101781"}, {"name": "Brazil", "population": "212392717"}, {"name": "Colombia", "population": "49849818"}, {"name": "Ecuador", "population": "17100444"}, {"name": "Peru", "population": "32933835"}, {"name": "Guyana", }, # Missing "population", will fill with `None` ] table = rows.import_from_dicts(data) print(table[-1]) # Can use indexes # Result: # Row(name='Guyana', population=None) ``` ## Importing from other formats `rows`' ability to import data is amazing: its plugins will do the hard job of parsing the file format so you don't need to. They can help you exporting data also. For example, let's download a CSV from the Web and import it: ```python import requests import rows from io import BytesIO url = "http://unitedstates.sunlightfoundation.com/legislators/legislators.csv" csv = requests.get(url).content # Download CSV data legislators = rows.import_from_csv(BytesIO(csv)) # already imported! print("rows automatically identified the types:") for field_name, field_type in legislators.fields.items(): print(f"{field_name} is {field_type}") ``` And you'll see something like this: ```text [...] gender is [...] govtrack_id is [...] birthdate is [...] ``` > Note that **native Python objects** are returned for each row inside a > `namedtuple`! The library recognizes each field type and converts it > *automagically* no matter which plugin you're using to import the data. We can then work on this data: ```python women = sum(1 for row in legislators if row.in_office and row.gender == 'F') men = sum(1 for row in legislators if row.in_office and row.gender == 'M') print(f"Women vs Men (in office): {women} vs {men}.") # Result: # Women vs Men: 108 vs 432. ``` Since `birthdate` is automatically detected and converted to a `rows.fields.DateField` we can do some quick analysis: ```python legislators.order_by("birthdate") older, younger = legislators[-1], legislators[0] print(f"{older.lastname}, {older.firstname} is older than {younger.lastname}, {younger.firstname}.") # Result: # Stefanik, Elise is older than Byrd, Robert. ``` You can also get a whole column, like this: ```python print(legislators["gender"]) # Result (a list of strings): # ['M', 'M', 'M', 'M', 'M', 'M', ..., 'M', 'M', 'F'] ``` And change the whole column (or add a new one): ```python legislators["gender"] = [ "male" if gender == "M" else "female" for gender in legislators["gender"] ] print(legislators["gender"]) # Result: # ['male', 'male', 'male', ..., 'male', 'female'] ``` Or delete it: ```python print("gender" in legislators.field_names) # Result: True del legislators["gender"] print("gender" in legislators.field_names) # Result: False print(legislators[0].gender) # Raises the exception: # AttributeError: 'Row' object has no attribute 'gender' ``` Exercise: use `rows.import_from_html` to import [population data from worldometers.com][worldometers-population-table] (tip: you must run `pip install rows[html]` first to install the needed dependencies). ### Common Parameters Each plugin has its own parameters (like `index` in `import_from_html` and `sheet_name` in `import_from_xls`) but all plugins create a `rows.Table` object so they also have some common parameters you can pass to `import_from_X`. They are: - `fields`: an `OrderedDict` with field names and types (disable automatic detection of types). - `force_types`: a `dict` mapping field names to field types you'd like to force, so `rows` won't try to detect it. Example: `{"population": rows.fields.IntegerField}`. - `skip_header`: Ignore header row. Only used if `fields` is not `None`. Default: `True`. - `import_fields`: a `list` with field names to import (other fields will be ignored) -- fields will be imported in this order. - `export_fields`: a `list` with field names to export (other fields will be ignored) -- fields will be exported in this order. - `samples`: number of sample rows to use on field type autodetect algorithm. Default: `None` (use all rows). ## Exporting Data If you have a `Table` object you can export it to all available plugins which have the "export" feature. Let's use the HTML plugin: ```python rows.export_to_html(legislators, "legislators.html") ``` And you'll get a file with the following contents: ```html [...]
title firstname middlename lastname name_suffix nickname
``` ### Exporting to memory Some plugins don't require a filename to export to, so you can get the result as a string, for example: ```python fields_to_export = ("title", "firstname", "lastname", "party") content = rows.export_to_txt(legislators, export_fields=fields_to_export) print(content) ``` The result will be: ```text +-------+-------------+--------------------+-------+ | title | firstname | lastname | party | +-------+-------------+--------------------+-------+ | Sen | Robert | Byrd | D | | Rep | Ralph | Hall | R | | Sen | Ted | Stevens | R | | Sen | Frank | Lautenberg | D | [...] | Rep | Aaron | Schock | R | | Rep | Matt | Gaetz | R | | Rep | Trey | Hollingsworth | R | | Rep | Mike | Gallagher | R | | Rep | Elise | Stefanik | R | +-------+-------------+--------------------+-------+ ``` The plugins `csv`, `json` and `html` have this behaviour. It makes sense on file-oriented formats to returned the data as output, but some plugins return different objects; on `sqlite` the returned object is a `sqlite3.Connection`, see: ```python connection = rows.export_to_sqlite(legislators, ":memory:") query = "SELECT firstname, lastname FROM table1 WHERE birthdate > 1980-01-01" connection = rows.export_to_sqlite(legislators, ":memory:") print(list(connection.execute(query).fetchall())) ``` You'll get the following output: ```text [('Darren', 'Soto'), ('Adam', 'Kinzinger'), ('Ron', 'DeSantis'), (...)] ``` #### Using file and connection objects The majority of plugins also accept file-objects instead of filenames (for importing and also for exporting), for example: ```python from io import BytesIO fobj = BytesIO() rows.export_to_csv(legislators, fobj) fobj.seek(0) # You need to point the file cursor to the first position. print(fobj.read()) ``` The following text will be printed: ```text b"title,firstname,lastname,party\r\nSen,Robert,Byrd,D\r\nRep,Ralph,Hall,R[...]" ``` The same happens for `sqlite3.Connection` objects when importing: ```python # Reuses the `connection` and `query` variables from the last sections' example table = rows.import_from_sqlite(connection, query=query) print(rows.export_to_txt(table)) ``` The following output will be printed: ```text +-----------+-----------------+ | firstname | lastname | +-----------+-----------------+ | Darren | Soto | | Adam | Kinzinger | | Ron | DeSantis | | Stephanie | Murphy | | Seth | Moulton | | Jaime | Herrera Beutler | | Pete | Aguilar | | Scott | Taylor | | Jim | Banks | | Ruben | Gallego | | Lee | Zeldin | | Carlos | Curbelo | | Justin | Amash | | Ruben | Kihuen | | Jason | Smith | | Brian | Mast | | Joseph | Kennedy | | Eric | Swalwell | | Tulsi | Gabbard | | Aaron | Schock | | Matt | Gaetz | | Trey | Hollingsworth | | Mike | Gallagher | | Elise | Stefanik | +-----------+-----------------+ ``` ## Learn more Now you have finished the quickstart guide. See the [examples][rows-examples] folder for more examples. [rows-examples]: https://github.com/turicas/rows/tree/master/examples [worldometers-population-table]: http://www.worldometers.info/world-population/population-by-country/ rows-0.4.1/examples/000077500000000000000000000000001343135453400143315ustar00rootroot00000000000000rows-0.4.1/examples/cli/000077500000000000000000000000001343135453400151005ustar00rootroot00000000000000rows-0.4.1/examples/cli/convert.sh000077500000000000000000000013271343135453400171220ustar00rootroot00000000000000#!/bin/bash URL="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=1&codv=v01&coduf=43" LOCALE="pt_BR.UTF-8" FILENAME="populacao-rs" rows convert --input-locale=$LOCALE --input-encoding=utf-8 $URL $FILENAME.csv rows convert $FILENAME.csv $FILENAME.html rows convert $FILENAME.html $FILENAME.xls rows convert $FILENAME.xls $FILENAME.txt rows convert $FILENAME.txt $FILENAME.xlsx rows convert $FILENAME.xlsx $FILENAME.sqlite rows convert $FILENAME.sqlite $FILENAME.json # When converting to JSON we cannot guarantee field order! # `convert` can also sort the data before saving it into the CSV file rows convert --input-locale=$LOCALE --input-encoding=utf-8 \ --order-by=^pessoas $URL $FILENAME-sorted.csv rows-0.4.1/examples/cli/extract-pdf.sh000077500000000000000000000004261343135453400176620ustar00rootroot00000000000000#!/bin/bash URL='http://balneabilidade.inema.ba.gov.br/index.php/relatoriodebalneabilidade/geraBoletim?idcampanha=27641' QUERY='SELECT * FROM table1 WHERE categoria = "Imprópria"' rows convert "$URL" water-quality.csv rows query "$QUERY" "$URL" --output=bad-water-quality.csv rows-0.4.1/examples/cli/join.sh000077500000000000000000000002461343135453400164000ustar00rootroot00000000000000#!/bin/bash KEYS="uf,municipio" SOURCE1="populacao-sudeste.csv" SOURCE2="area-sudeste.csv" DESTINATION="sudeste.csv" rows join $KEYS $SOURCE1 $SOURCE2 $DESTINATION rows-0.4.1/examples/cli/query.sh000077500000000000000000000023021343135453400166010ustar00rootroot00000000000000#!/bin/bash # This script will run rows' "query" subcommand passing the URL of two HTML # sources, a SQL query and a output CSV filename. rows CLI will: # - Download each file, identify its format (HTML) and import as a table # - Convert the two tables into one SQLite in-memory database # - Run the query into the database # - Export the results to a CSV file # Rio de Janeiro: inhabitants (per city) SOURCE1="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=1&codv=v01&coduf=33" # Rio de Janeiro: area in km² (per city) SOURCE2="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=16&codv=v01&coduf=33" LOCALE="pt_BR.UTF-8" SOURCES="$SOURCE1 $SOURCE2" # $SOURCE1 (inhabitants) will be "table1" # $SOURCE2 (area) will be "table2" QUERY="SELECT table1.uf AS state, table1.municipio AS city, table1.pessoas AS inhabitants, table2.km2 as area, (table1.pessoas / table2.km2) AS demographic_density FROM table1, table2 WHERE table1.uf = table2.uf AND table1.municipio = table2.municipio" OUTPUT="rj-density.csv" rows query --input-locale=$LOCALE --input-encoding=utf-8 "$QUERY" $SOURCES \ --output=$OUTPUT rows-0.4.1/examples/cli/sum.sh000077500000000000000000000020661343135453400162470ustar00rootroot00000000000000#!/bin/bash # population SOURCE1="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=1&codv=v01&coduf=33" SOURCE2="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=1&codv=v01&coduf=35" SOURCE3="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=1&codv=v01&coduf=31" SOURCE4="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=1&codv=v01&coduf=32" DESTINATION="populacao-sudeste.csv" LOCALE="pt_BR.UTF-8" rows sum --input-locale=$LOCALE --input-encoding=utf-8 \ $SOURCE1 $SOURCE2 $SOURCE3 $SOURCE4 \ $DESTINATION # area SOURCE5="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=16&codv=v01&coduf=33" SOURCE6="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=16&codv=v01&coduf=35" SOURCE7="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=16&codv=v01&coduf=31" SOURCE8="http://cidades.ibge.gov.br/comparamun/compara.php?idtema=16&codv=v01&coduf=32" DESTINATION="area-sudeste.csv" rows sum --input-locale=$LOCALE --input-encoding=utf-8 \ $SOURCE5 $SOURCE6 $SOURCE7 $SOURCE8 \ $DESTINATION rows-0.4.1/examples/data/000077500000000000000000000000001343135453400152425ustar00rootroot00000000000000rows-0.4.1/examples/data/brazilian-cities.csv000066400000000000000000004712611343135453400212230ustar00rootroot00000000000000state,city,inhabitants,area AC,Acrelândia,12538,1807.92 AC,Assis Brasil,6072,4974.18 AC,Brasiléia,21398,3916.5 AC,Bujari,8471,3034.87 AC,Capixaba,8798,1702.58 AC,Cruzeiro do Sul,78507,8779.39 AC,Epitaciolândia,15100,1654.77 AC,Feijó,32412,27974.89 AC,Jordão,6577,5357.28 AC,Mâncio Lima,15206,5453.07 AC,Manoel Urbano,7981,10634.46 AC,Marechal Thaumaturgo,14227,8191.69 AC,Plácido de Castro,17209,1943.25 AC,Porto Acre,14880,2604.86 AC,Porto Walter,9176,6443.83 AC,Rio Branco,336038,8835.54 AC,Rodrigues Alves,14389,3076.95 AC,Santa Rosa do Purus,4691,6145.61 AC,Sena Madureira,38029,23751.47 AC,Senador Guiomard,20179,2321.45 AC,Tarauacá,35590,20171.05 AC,Xapuri,16091,5347.45 AL,Água Branca,19377,454.63 AL,Anadia,17424,189.47 AL,Arapiraca,214006,356.18 AL,Atalaia,44322,528.77 AL,Barra de Santo Antônio,14230,138.43 AL,Barra de São Miguel,7574,76.62 AL,Batalha,17076,320.92 AL,Belém,4551,48.63 AL,Belo Monte,7030,334.15 AL,Boca da Mata,25776,186.53 AL,Branquinha,10583,166.32 AL,Cacimbinhas,10195,272.98 AL,Cajueiro,20409,124.26 AL,Campestre,6598,66.39 AL,Campo Alegre,50816,295.1 AL,Campo Grande,9032,167.32 AL,Canapi,17250,574.57 AL,Capela,17077,242.62 AL,Carneiros,8290,113.06 AL,Chã Preta,7146,172.85 AL,Coité do Nóia,10926,88.51 AL,Colônia Leopoldina,20019,207.89 AL,Coqueiro Seco,5526,39.73 AL,Coruripe,52130,918.21 AL,Craíbas,22641,271.33 AL,Delmiro Gouveia,48096,607.81 AL,Dois Riachos,10880,140.47 AL,Estrela de Alagoas,17251,259.77 AL,Feira Grande,21321,172.75 AL,Feliz Deserto,4345,91.84 AL,Flexeiras,12325,333.22 AL,Girau do Ponciano,36600,500.62 AL,Ibateguara,15149,265.31 AL,Igaci,25188,334.45 AL,Igreja Nova,23292,427.42 AL,Inhapi,17898,376.86 AL,Jacaré dos Homens,5413,142.34 AL,Jacuípe,6997,210.38 AL,Japaratinga,7754,85.95 AL,Jaramataia,5558,103.71 AL,Jequiá da Praia,12029,351.61 AL,Joaquim Gomes,22575,298.29 AL,Jundiá,4202,92.22 AL,Junqueiro,23836,241.59 AL,Lagoa da Canoa,18250,88.45 AL,Limoeiro de Anadia,26992,315.78 AL,Maceió,932748,503.07 AL,Major Isidoro,18897,453.9 AL,Mar Vermelho,3652,93.1 AL,Maragogi,28749,334.04 AL,Maravilha,10284,302.06 AL,Marechal Deodoro,45977,331.68 AL,Maribondo,13619,174.28 AL,Mata Grande,24698,907.98 AL,Matriz de Camaragibe,23785,219.99 AL,Messias,15682,113.83 AL,Minador do Negrão,5275,167.61 AL,Monteirópolis,6935,86.1 AL,Murici,26710,426.82 AL,Novo Lino,12060,233.41 AL,Olho d`Água das Flores,20364,183.44 AL,Olho d`Água do Casado,8491,322.95 AL,Olho d`Água Grande,4957,118.51 AL,Olivença,11047,172.96 AL,Ouro Branco,10912,204.77 AL,Palestina,5112,48.9 AL,Palmeira dos Índios,70368,452.71 AL,Pão de Açúcar,23811,682.99 AL,Pariconha,10264,258.53 AL,Paripueira,11347,92.97 AL,Passo de Camaragibe,14763,244.47 AL,Paulo Jacinto,7426,118.46 AL,Penedo,60378,689.16 AL,Piaçabuçu,17203,240.01 AL,Pilar,33305,249.71 AL,Pindoba,2866,117.6 AL,Piranhas,23045,408.11 AL,Poço das Trincheiras,13872,291.94 AL,Porto Calvo,25708,307.92 AL,Porto de Pedras,8429,257.66 AL,Porto Real do Colégio,19334,240.52 AL,Quebrangulo,11480,319.83 AL,Rio Largo,68481,306.33 AL,Roteiro,6656,129.29 AL,Santa Luzia do Norte,6891,29.6 AL,Santana do Ipanema,44932,437.88 AL,Santana do Mundaú,10961,224.81 AL,São Brás,6718,139.95 AL,São José da Laje,22686,256.64 AL,São José da Tapera,30088,495.11 AL,São Luís do Quitunde,32412,397.18 AL,São Miguel dos Campos,54577,360.79 AL,São Miguel dos Milagres,7163,76.74 AL,São Sebastião,32010,315.11 AL,Satuba,14603,42.63 AL,Senador Rui Palmeira,13047,342.72 AL,Tanque d`Arca,6122,129.51 AL,Taquarana,19020,166.05 AL,Teotônio Vilela,41152,297.88 AL,Traipu,25702,697.97 AL,União dos Palmares,62358,420.66 AL,Viçosa,25407,343.36 AP,Amapá,8069,9175.99 AP,Calçoene,9000,14269.37 AP,Cutias,4696,2114.25 AP,Ferreira Gomes,5802,5046.26 AP,Itaubal,4265,1703.97 AP,Laranjal do Jari,39942,30971.9 AP,Macapá,398204,6408.55 AP,Mazagão,17032,13130.98 AP,Oiapoque,20509,22625.18 AP,Pedra Branca do Amaparí,10772,9495.52 AP,Porto Grande,16809,4401.79 AP,Pracuúba,3793,4956.48 AP,Santana,101262,1579.61 AP,Serra do Navio,4380,7756.14 AP,Tartarugalzinho,12563,6709.66 AP,Vitória do Jari,12428,2482.89 AM,Alvarães,14088,5911.77 AM,Amaturá,9467,4758.74 AM,Anamã,10214,2453.94 AM,Anori,16317,5795.31 AM,Apuí,18007,54240.02 AM,Atalaia do Norte,15153,76351.67 AM,Autazes,32135,7599.36 AM,Barcelos,25718,122476.12 AM,Barreirinha,27355,5750.57 AM,Benjamin Constant,33411,8793.42 AM,Beruri,15486,17250.72 AM,Boa Vista do Ramos,14979,2586.85 AM,Boca do Acre,30632,21951.26 AM,Borba,34961,44251.75 AM,Caapiranga,10975,9456.62 AM,Canutama,12738,29819.71 AM,Carauari,25774,25767.68 AM,Careiro,32734,6091.55 AM,Careiro da Várzea,23930,2631.14 AM,Coari,75965,57921.91 AM,Codajás,23206,18711.55 AM,Eirunepé,30665,15011.77 AM,Envira,16338,7499.33 AM,Fonte Boa,22817,12110.93 AM,Guajará,13974,7578.88 AM,Humaitá,44227,33071.79 AM,Ipixuna,22254,12044.7 AM,Iranduba,40781,2214.25 AM,Itacoatiara,86839,8892.04 AM,Itamarati,8038,25275.93 AM,Itapiranga,8211,4231.15 AM,Japurá,7326,55791.84 AM,Juruá,10802,19400.7 AM,Jutaí,17992,69551.83 AM,Lábrea,37701,68233.82 AM,Manacapuru,85141,7330.08 AM,Manaquiri,22801,3975.77 AM,Manaus,1802014,11401.09 AM,Manicoré,47017,48282.66 AM,Maraã,17528,16910.37 AM,Maués,52236,39989.89 AM,Nhamundá,18278,14105.59 AM,Nova Olinda do Norte,30696,5608.57 AM,Novo Airão,14723,37771.38 AM,Novo Aripuanã,21451,41187.89 AM,Parintins,102033,5952.39 AM,Pauini,18166,41610.06 AM,Presidente Figueiredo,27175,25422.33 AM,Rio Preto da Eva,25719,5813.23 AM,Santa Isabel do Rio Negro,18146,62846.41 AM,Santo Antônio do Içá,24481,12307.19 AM,São Gabriel da Cachoeira,37896,109183.43 AM,São Paulo de Olivença,31422,19745.9 AM,São Sebastião do Uatumã,10705,10741.08 AM,Silves,8444,3748.83 AM,Tabatinga,52272,3224.88 AM,Tapauá,19077,89325.19 AM,Tefé,61453,23704.48 AM,Tonantins,17079,6432.68 AM,Uarini,11891,10246.24 AM,Urucará,17094,27904.26 AM,Urucurituba,17837,2906.7 BA,Abaíra,8316,530.26 BA,Abaré,17064,1484.87 BA,Acajutiba,14653,180.15 BA,Adustina,15702,632.14 BA,Água Fria,15731,661.86 BA,Aiquara,4602,159.69 BA,Alagoinhas,141949,752.38 BA,Alcobaça,21271,1481.25 BA,Almadina,6357,251.11 BA,Amargosa,34351,463.19 BA,Amélia Rodrigues,25190,173.48 BA,América Dourada,15961,837.72 BA,Anagé,25516,1947.54 BA,Andaraí,13960,1861.72 BA,Andorinha,14414,1247.61 BA,Angical,14073,1528.28 BA,Anguera,10242,177.04 BA,Antas,17072,321.61 BA,Antônio Cardoso,11554,294.45 BA,Antônio Gonçalves,11015,313.95 BA,Aporá,17731,561.83 BA,Apuarema,7459,154.86 BA,Araças,11561,487.12 BA,Aracatu,13743,1489.8 BA,Araci,51651,1556.14 BA,Aramari,10036,329.65 BA,Arataca,10392,375.21 BA,Aratuípe,8599,181.14 BA,Aurelino Leal,13595,457.74 BA,Baianópolis,13850,3342.56 BA,Baixa Grande,20060,946.65 BA,Banzaê,11814,227.54 BA,Barra,49325,11414.41 BA,Barra da Estiva,21187,1346.79 BA,Barra do Choça,34788,783.14 BA,Barra do Mendes,13987,1540.8 BA,Barra do Rocha,6313,208.35 BA,Barreiras,137427,7859.23 BA,Barro Alto,13612,416.5 BA,Barro Preto,6453,128.38 BA,Barrocas,14191,200.97 BA,Belmonte,21798,1970.14 BA,Belo Campo,16021,629.07 BA,Biritinga,14836,550.08 BA,Boa Nova,15411,868.79 BA,Boa Vista do Tupim,17991,2811.23 BA,Bom Jesus da Lapa,63480,4200.13 BA,Bom Jesus da Serra,10113,421.54 BA,Boninal,13695,934.01 BA,Bonito,14834,726.62 BA,Boquira,22037,1482.65 BA,Botuporã,11154,645.53 BA,Brejões,14282,480.83 BA,Brejolândia,11077,2744.72 BA,Brotas de Macaúbas,10717,2240.11 BA,Brumado,64602,2226.8 BA,Buerarema,18605,230.46 BA,Buritirama,19600,3942.08 BA,Caatiba,11420,515.86 BA,Cabaceiras do Paraguaçu,17327,226.02 BA,Cachoeira,32026,395.22 BA,Caculé,22236,668.36 BA,Caém,10368,548.38 BA,Caetanos,13639,774.59 BA,Caetité,47515,2442.9 BA,Cafarnaum,17209,675.25 BA,Cairu,15374,460.98 BA,Caldeirão Grande,12491,454.94 BA,Camacan,31472,626.65 BA,Camaçari,242970,784.66 BA,Camamu,35180,920.37 BA,Campo Alegre de Lourdes,28090,2781.17 BA,Campo Formoso,66616,7258.68 BA,Canápolis,9410,437.22 BA,Canarana,24067,576.37 BA,Canavieiras,32336,1326.93 BA,Candeal,8895,445.1 BA,Candeias,83158,258.36 BA,Candiba,13210,417.98 BA,Cândido Sales,27918,1617.67 BA,Cansanção,32908,1336.75 BA,Canudos,15732,3214.22 BA,Capela do Alto Alegre,11527,649.43 BA,Capim Grosso,26577,334.42 BA,Caraíbas,10222,805.63 BA,Caravelas,21414,2393.5 BA,Cardeal da Silva,8899,256.91 BA,Carinhanha,28380,2737.18 BA,Casa Nova,64940,9647.07 BA,Castro Alves,25408,711.74 BA,Catolândia,2612,642.57 BA,Catu,51077,416.22 BA,Caturama,8843,664.55 BA,Central,17013,602.41 BA,Chorrochó,10734,3005.32 BA,Cícero Dantas,32300,884.97 BA,Cipó,15755,128.31 BA,Coaraci,20964,282.66 BA,Cocos,18153,10227.37 BA,Conceição da Feira,20391,162.88 BA,Conceição do Almeida,17889,289.94 BA,Conceição do Coité,62040,1016.01 BA,Conceição do Jacuípe,30123,117.53 BA,Conde,23620,964.64 BA,Condeúba,16898,1285.93 BA,Contendas do Sincorá,4663,1044.69 BA,Coração de Maria,22401,348.16 BA,Cordeiros,8168,535.49 BA,Coribe,14307,2478.51 BA,Coronel João Sá,17066,883.52 BA,Correntina,31249,11921.68 BA,Cotegipe,13636,4195.83 BA,Cravolândia,5041,162.17 BA,Crisópolis,20046,607.66 BA,Cristópolis,13280,1043.11 BA,Cruz das Almas,58606,145.74 BA,Curaçá,32168,6079.02 BA,Dário Meira,12836,445.42 BA,Dias d`Ávila,66440,184.23 BA,Dom Basílio,11355,676.9 BA,Dom Macedo Costa,3874,84.76 BA,Elísio Medrado,7947,193.53 BA,Encruzilhada,23766,1982.47 BA,Entre Rios,39872,1215.3 BA,Érico Cardoso,10859,701.42 BA,Esplanada,32802,1297.98 BA,Euclides da Cunha,56289,2028.42 BA,Eunápolis,100196,1179.13 BA,Fátima,17652,359.39 BA,Feira da Mata,6184,1633.88 BA,Feira de Santana,556642,1337.99 BA,Filadélfia,16740,570.07 BA,Firmino Alves,5384,162.42 BA,Floresta Azul,10660,293.46 BA,Formosa do Rio Preto,22528,16303.86 BA,Gandu,30336,243.15 BA,Gavião,4561,369.88 BA,Gentio do Ouro,10622,3699.87 BA,Glória,15076,1255.56 BA,Gongogi,8357,197.67 BA,Governador Mangabeira,19818,106.32 BA,Guajeru,10412,936.09 BA,Guanambi,78833,1296.65 BA,Guaratinga,22165,2325.39 BA,Heliópolis,13192,338.8 BA,Iaçu,25736,2451.42 BA,Ibiassucê,10062,426.67 BA,Ibicaraí,24272,231.94 BA,Ibicoara,17282,849.84 BA,Ibicuí,15785,1176.84 BA,Ibipeba,17008,1383.53 BA,Ibipitanga,14171,954.37 BA,Ibiquera,4866,945.3 BA,Ibirapitanga,22598,447.26 BA,Ibirapuã,7956,787.74 BA,Ibirataia,18943,294.87 BA,Ibitiara,15508,1847.57 BA,Ibititá,17840,623.08 BA,Ibotirama,25424,1722.47 BA,Ichu,5255,127.67 BA,Igaporã,15205,832.52 BA,Igrapiúna,13343,527.21 BA,Iguaí,25705,827.84 BA,Ilhéus,184236,1760.11 BA,Inhambupe,36306,1222.58 BA,Ipecaetá,15331,369.89 BA,Ipiaú,44390,267.33 BA,Ipirá,59343,3060.26 BA,Ipupiara,9285,1061.16 BA,Irajuba,7002,413.52 BA,Iramaia,11990,1947.24 BA,Iraquara,22601,1029.41 BA,Irará,27466,277.79 BA,Irecê,66181,319.03 BA,Itabela,28390,850.84 BA,Itaberaba,61631,2343.51 BA,Itabuna,204667,432.24 BA,Itacaré,24318,737.87 BA,Itaeté,14924,1208.93 BA,Itagi,13051,259.19 BA,Itagibá,15193,788.83 BA,Itagimirim,7110,839.02 BA,Itaguaçu da Bahia,13209,4451.27 BA,Itaju do Colônia,7309,1222.71 BA,Itajuípe,21081,284.5 BA,Itamaraju,63069,2215.14 BA,Itamari,7903,111.09 BA,Itambé,23089,1407.31 BA,Itanagra,7598,490.53 BA,Itanhém,20216,1463.82 BA,Itaparica,20725,118.04 BA,Itapé,10995,459.36 BA,Itapebi,10495,1005.37 BA,Itapetinga,68273,1627.52 BA,Itapicuru,32261,1585.59 BA,Itapitanga,10207,408.38 BA,Itaquara,7678,322.98 BA,Itarantim,18539,1805.13 BA,Itatim,14522,583.45 BA,Itiruçu,12693,313.71 BA,Itiúba,36113,1722.75 BA,Itororó,19914,313.59 BA,Ituaçu,18127,1216.28 BA,Ituberá,26591,417.27 BA,Iuiú,10900,1485.73 BA,Jaborandi,8973,9545.13 BA,Jacaraci,13651,1235.6 BA,Jacobina,79247,2358.69 BA,Jaguaquara,51011,928.24 BA,Jaguarari,30343,2456.61 BA,Jaguaripe,16467,898.67 BA,Jandaíra,10331,641.21 BA,Jequié,151895,3227.34 BA,Jeremoabo,37680,4656.27 BA,Jiquiriçá,14118,239.4 BA,Jitaúna,14115,218.92 BA,João Dourado,22549,914.86 BA,Juazeiro,197965,6500.52 BA,Jucuruçu,10290,1457.86 BA,Jussara,15052,948.58 BA,Jussari,6474,356.85 BA,Jussiape,8031,585.19 BA,Lafaiete Coutinho,3901,405.39 BA,Lagoa Real,13934,877.43 BA,Laje,22201,457.74 BA,Lajedão,3733,615.47 BA,Lajedinho,3936,776.06 BA,Lajedo do Tabocal,8305,431.9 BA,Lamarão,9560,209.01 BA,Lapão,25646,605.08 BA,Lauro de Freitas,163449,57.69 BA,Lençóis,10368,1277.08 BA,Licínio de Almeida,12311,843.39 BA,Livramento de Nossa Senhora,42693,2135.59 BA,Luís Eduardo Magalhães,60105,3941.07 BA,Macajuba,11229,650.3 BA,Macarani,17093,1287.52 BA,Macaúbas,47051,2994.15 BA,Macururé,8073,2294.25 BA,Madre de Deus,17376,32.2 BA,Maetinga,7038,681.66 BA,Maiquinique,8782,491.98 BA,Mairi,19326,952.6 BA,Malhada,16014,2008.35 BA,Malhada de Pedras,8468,529.06 BA,Manoel Vitorino,14387,2231.63 BA,Mansidão,12592,3177.43 BA,Maracás,24613,2253.09 BA,Maragogipe,42815,440.16 BA,Maraú,19101,823.36 BA,Marcionílio Souza,10500,1277.2 BA,Mascote,14640,772.46 BA,Mata de São João,40183,633.2 BA,Matina,11145,775.74 BA,Medeiros Neto,21560,1238.75 BA,Miguel Calmon,26475,1568.22 BA,Milagres,10306,284.38 BA,Mirangaba,16279,1697.95 BA,Mirante,10507,1083.67 BA,Monte Santo,52338,3186.38 BA,Morpará,8280,1697.01 BA,Morro do Chapéu,35164,5741.65 BA,Mortugaba,12477,612.22 BA,Mucugê,10545,2455.04 BA,Mucuri,36026,1781.14 BA,Mulungu do Morro,12249,565.98 BA,Mundo Novo,24395,1493.34 BA,Muniz Ferreira,7317,110.12 BA,Muquém de São Francisco,10272,3637.58 BA,Muritiba,28899,89.31 BA,Mutuípe,21449,283.21 BA,Nazaré,27274,253.78 BA,Nilo Peçanha,12530,399.33 BA,Nordestina,12371,468.89 BA,Nova Canaã,16713,853.7 BA,Nova Fátima,7602,349.9 BA,Nova Ibiá,6648,178.75 BA,Nova Itarana,7435,470.44 BA,Nova Redenção,8034,430.96 BA,Nova Soure,24136,950.4 BA,Nova Viçosa,38556,1322.85 BA,Novo Horizonte,10673,609.18 BA,Novo Triunfo,15051,251.32 BA,Olindina,24943,542.18 BA,Oliveira dos Brejinhos,21831,3512.69 BA,Ouriçangas,8298,155.09 BA,Ourolândia,16425,1489.24 BA,Palmas de Monte Alto,20775,2524.85 BA,Palmeiras,8410,657.68 BA,Paramirim,21001,1170.13 BA,Paratinga,29504,2614.78 BA,Paripiranga,27778,435.7 BA,Pau Brasil,10852,606.52 BA,Paulo Afonso,108396,1579.72 BA,Pé de Serra,13752,616.21 BA,Pedrão,6876,159.8 BA,Pedro Alexandre,16995,896.07 BA,Piatã,17982,1713.76 BA,Pilão Arcado,32860,11731.5 BA,Pindaí,15628,614.09 BA,Pindobaçu,20121,496.28 BA,Pintadas,10342,545.59 BA,Piraí do Norte,9799,187.28 BA,Piripá,12783,439.63 BA,Piritiba,22399,975.57 BA,Planaltino,8822,927.02 BA,Planalto,24481,883.77 BA,Poções,44701,826.5 BA,Pojuca,33066,290.12 BA,Ponto Novo,15742,497.4 BA,Porto Seguro,126929,2408.33 BA,Potiraguá,9810,985.49 BA,Prado,27627,1740.3 BA,Presidente Dutra,13750,163.55 BA,Presidente Jânio Quadros,13652,1185.15 BA,Presidente Tancredo Neves,23846,417.2 BA,Queimadas,24602,2027.88 BA,Quijingue,27228,1342.67 BA,Quixabeira,9554,387.68 BA,Rafael Jambeiro,22874,1207.22 BA,Remanso,38957,4683.41 BA,Retirolândia,12055,181.46 BA,Riachão das Neves,21937,5673.02 BA,Riachão do Jacuípe,33172,1190.2 BA,Riacho de Santana,30646,2582.4 BA,Ribeira do Amparo,14276,642.59 BA,Ribeira do Pombal,47518,762.21 BA,Ribeirão do Largo,8602,1271.35 BA,Rio de Contas,13007,1063.77 BA,Rio do Antônio,14815,814.37 BA,Rio do Pires,11918,819.79 BA,Rio Real,37164,716.89 BA,Rodelas,7775,2723.53 BA,Ruy Barbosa,29887,2171.51 BA,Salinas da Margarida,13456,149.82 BA,Salvador,2675656,693.28 BA,Santa Bárbara,19064,345.67 BA,Santa Brígida,15060,882.81 BA,Santa Cruz Cabrália,26264,1551.98 BA,Santa Cruz da Vitória,6673,298.21 BA,Santa Inês,10363,315.66 BA,Santa Luzia,13344,774.92 BA,Santa Maria da Vitória,40309,1966.84 BA,Santa Rita de Cássia,26250,5977.77 BA,Santa Teresinha,9648,707.24 BA,Santaluz,33838,1563.29 BA,Santana,24750,1820.17 BA,Santanópolis,8776,230.83 BA,Santo Amaro,57800,492.92 BA,Santo Antônio de Jesus,90985,261.35 BA,Santo Estêvão,47880,362.96 BA,São Desidério,27659,15157.01 BA,São Domingos,9226,326.95 BA,São Felipe,20305,205.99 BA,São Félix,14098,99.2 BA,São Félix do Coribe,13048,949.34 BA,São Francisco do Conde,33183,262.86 BA,São Gabriel,18427,1199.52 BA,São Gonçalo dos Campos,33283,300.73 BA,São José da Vitória,5715,72.49 BA,São José do Jacuípe,10180,402.43 BA,São Miguel das Matas,10414,214.41 BA,São Sebastião do Passé,42153,538.32 BA,Sapeaçu,16585,117.21 BA,Sátiro Dias,18964,1010.05 BA,Saubara,11201,163.5 BA,Saúde,11845,504.31 BA,Seabra,41798,2517.29 BA,Sebastião Laranjeiras,10371,1948.61 BA,Senhor do Bonfim,74419,827.49 BA,Sento Sé,37425,12698.71 BA,Serra do Ramalho,31638,2593.23 BA,Serra Dourada,18112,1346.63 BA,Serra Preta,15401,536.49 BA,Serrinha,76762,624.23 BA,Serrolândia,12344,295.85 BA,Simões Filho,118047,201.22 BA,Sítio do Mato,12050,1751.22 BA,Sítio do Quinto,12592,700.17 BA,Sobradinho,22000,1238.92 BA,Souto Soares,15899,993.51 BA,Tabocas do Brejo Velho,11431,1375.74 BA,Tanhaçu,20013,1234.44 BA,Tanque Novo,16128,722.9 BA,Tanquinho,8008,219.85 BA,Taperoá,18748,410.79 BA,Tapiramutá,16516,663.88 BA,Teixeira de Freitas,138341,1163.83 BA,Teodoro Sampaio,7895,231.54 BA,Teofilândia,21482,335.54 BA,Teolândia,14836,317.83 BA,Terra Nova,12803,198.93 BA,Tremedal,17029,1679.46 BA,Tucano,52418,2799.15 BA,Uauá,24294,3035.24 BA,Ubaíra,19750,726.26 BA,Ubaitaba,20691,178.81 BA,Ubatã,25004,268.24 BA,Uibaí,13625,550.99 BA,Umburanas,17000,1670.42 BA,Una,24110,1177.44 BA,Urandi,16466,969.45 BA,Uruçuca,19837,391.98 BA,Utinga,18173,638.23 BA,Valença,88673,1192.61 BA,Valente,24560,384.34 BA,Várzea da Roça,13786,513.92 BA,Várzea do Poço,8661,204.91 BA,Várzea Nova,13073,1192.93 BA,Varzedo,9109,226.8 BA,Vera Cruz,37567,299.73 BA,Vereda,6800,874.33 BA,Vitória da Conquista,306866,3356.89 BA,Wagner,8983,421.0 BA,Wanderley,12485,2959.51 BA,Wenceslau Guimarães,22189,674.03 BA,Xique-Xique,45536,5502.33 CE,Abaiara,10496,178.83 CE,Acarape,15338,155.68 CE,Acaraú,57551,842.56 CE,Acopiara,51160,2265.35 CE,Aiuaba,16203,2434.42 CE,Alcântaras,10771,138.61 CE,Altaneira,6856,73.3 CE,Alto Santo,16359,1338.21 CE,Amontada,39232,1179.04 CE,Antonina do Norte,6984,260.1 CE,Apuiarés,13925,545.16 CE,Aquiraz,72628,482.57 CE,Aracati,69159,1228.06 CE,Aracoiaba,25391,656.6 CE,Ararendá,10491,344.13 CE,Araripe,20685,1099.93 CE,Aratuba,11529,114.79 CE,Arneiroz,7650,1066.36 CE,Assaré,22445,1116.33 CE,Aurora,24566,885.84 CE,Baixio,6026,146.43 CE,Banabuiú,17315,1080.33 CE,Barbalha,55323,569.51 CE,Barreira,19573,245.81 CE,Barro,21514,711.89 CE,Barroquinha,14476,383.41 CE,Baturité,33321,308.58 CE,Beberibe,49311,1623.89 CE,Bela Cruz,30878,843.02 CE,Boa Viagem,52498,2836.78 CE,Brejo Santo,45193,663.43 CE,Camocim,60158,1124.78 CE,Campos Sales,26506,1082.77 CE,Canindé,74473,3218.48 CE,Capistrano,17062,222.55 CE,Caridade,20020,846.51 CE,Cariré,18347,756.88 CE,Caririaçu,26393,623.56 CE,Cariús,18567,1061.8 CE,Carnaubal,16746,364.81 CE,Cascavel,66142,837.33 CE,Catarina,18745,486.86 CE,Catunda,9952,790.71 CE,Caucaia,325441,1228.51 CE,Cedro,24527,725.8 CE,Chaval,12615,238.23 CE,Choró,12853,815.77 CE,Chorozinho,18915,278.41 CE,Coreaú,21954,775.8 CE,Crateús,72812,2985.14 CE,Crato,121428,1176.47 CE,Croatá,17069,696.98 CE,Cruz,22479,329.95 CE,Deputado Irapuan Pinheiro,9095,470.43 CE,Ererê,6840,382.71 CE,Eusébio,46033,79.01 CE,Farias Brito,19007,503.62 CE,Forquilha,21786,516.99 CE,Fortaleza,2452185,314.93 CE,Fortim,14817,278.77 CE,Frecheirinha,12991,181.24 CE,General Sampaio,6218,205.81 CE,Graça,15049,281.87 CE,Granja,52645,2697.22 CE,Granjeiro,4629,100.13 CE,Groaíras,10228,155.95 CE,Guaiúba,24091,267.13 CE,Guaraciaba do Norte,37775,611.46 CE,Guaramiranga,4164,59.44 CE,Hidrolândia,19325,966.85 CE,Horizonte,55187,159.98 CE,Ibaretama,12922,877.26 CE,Ibiapina,23808,414.94 CE,Ibicuitinga,11335,424.25 CE,Icapuí,18392,423.45 CE,Icó,65456,1872.0 CE,Iguatu,96495,1029.21 CE,Independência,25573,3218.68 CE,Ipaporanga,11343,702.14 CE,Ipaumirim,12009,273.83 CE,Ipu,40296,629.32 CE,Ipueiras,37862,1477.41 CE,Iracema,13722,821.25 CE,Irauçuba,22324,1461.25 CE,Itaiçaba,7316,212.11 CE,Itaitinga,35817,151.44 CE,Itapagé,48350,439.51 CE,Itapipoca,116065,1614.16 CE,Itapiúna,18626,588.7 CE,Itarema,37471,720.66 CE,Itatira,18894,783.44 CE,Jaguaretama,17863,1759.4 CE,Jaguaribara,10399,668.74 CE,Jaguaribe,34409,1876.81 CE,Jaguaruana,32236,867.56 CE,Jardim,26688,552.42 CE,Jati,7660,361.07 CE,Jijoca de Jericoacoara,17002,204.79 CE,Juazeiro do Norte,249939,248.83 CE,Jucás,23807,937.19 CE,Lavras da Mangabeira,31090,947.97 CE,Limoeiro do Norte,56264,751.07 CE,Madalena,18088,1034.72 CE,Maracanaú,209057,106.65 CE,Maranguape,113561,590.87 CE,Marco,24703,574.14 CE,Martinópole,10214,298.96 CE,Massapê,35191,566.58 CE,Mauriti,44240,1049.49 CE,Meruoca,13693,149.85 CE,Milagres,28316,606.44 CE,Milhã,13086,502.34 CE,Miraíma,12800,699.96 CE,Missão Velha,34274,645.7 CE,Mombaça,42690,2119.48 CE,Monsenhor Tabosa,16705,886.14 CE,Morada Nova,62065,2779.25 CE,Moraújo,8070,415.63 CE,Morrinhos,20700,415.56 CE,Mucambo,14102,190.6 CE,Mulungu,11485,134.57 CE,Nova Olinda,14256,284.4 CE,Nova Russas,30965,742.77 CE,Novo Oriente,27453,949.39 CE,Ocara,24007,765.41 CE,Orós,21389,576.27 CE,Pacajus,61838,254.48 CE,Pacatuba,72299,131.99 CE,Pacoti,11607,112.02 CE,Pacujá,5986,76.13 CE,Palhano,8866,440.38 CE,Palmácia,12005,117.81 CE,Paracuru,31636,300.29 CE,Paraipaba,30041,300.92 CE,Parambu,31309,2303.54 CE,Paramoti,11308,482.59 CE,Pedra Branca,41890,1303.29 CE,Penaforte,8226,141.93 CE,Pentecoste,35400,1378.31 CE,Pereiro,15757,433.51 CE,Pindoretama,18683,72.96 CE,Piquet Carneiro,15467,587.88 CE,Pires Ferreira,10216,243.1 CE,Poranga,12001,1309.26 CE,Porteiras,15061,217.58 CE,Potengi,10276,338.73 CE,Potiretama,6126,410.34 CE,Quiterianópolis,19921,1040.99 CE,Quixadá,80604,2019.83 CE,Quixelô,15000,559.56 CE,Quixeramobim,71887,3275.63 CE,Quixeré,19412,612.62 CE,Redenção,26415,225.31 CE,Reriutaba,19455,383.32 CE,Russas,69833,1590.21 CE,Saboeiro,15752,1383.48 CE,Salitre,15453,804.36 CE,Santa Quitéria,42763,4260.48 CE,Santana do Acaraú,29946,969.33 CE,Santana do Cariri,17170,855.56 CE,São Benedito,44178,338.25 CE,São Gonçalo do Amarante,43890,834.45 CE,São João do Jaguaribe,7900,280.46 CE,São Luís do Curu,12332,122.42 CE,Senador Pompeu,26469,1002.13 CE,Senador Sá,6852,423.92 CE,Sobral,188233,2122.9 CE,Solonópole,17665,1536.17 CE,Tabuleiro do Norte,29204,861.83 CE,Tamboril,25451,1961.31 CE,Tarrafas,8910,454.39 CE,Tauá,55716,4018.16 CE,Tejuçuoca,16827,750.63 CE,Tianguá,68892,908.89 CE,Trairi,51422,925.72 CE,Tururu,14408,202.28 CE,Ubajara,31787,421.03 CE,Umari,7545,263.93 CE,Umirim,18802,316.82 CE,Uruburetama,19765,97.07 CE,Uruoca,12883,696.75 CE,Varjota,17593,179.4 CE,Várzea Alegre,38434,835.71 CE,Viçosa do Ceará,54955,1311.63 DF,Brasília,2570160,5780.0 ES,Afonso Cláudio,31091,951.42 ES,Água Doce do Norte,11771,473.73 ES,Águia Branca,9519,454.45 ES,Alegre,30768,772.0 ES,Alfredo Chaves,13955,615.79 ES,Alto Rio Novo,7317,227.63 ES,Anchieta,23902,409.23 ES,Apiacá,7512,193.99 ES,Aracruz,81832,1423.87 ES,Atilio Vivacqua,9850,223.45 ES,Baixo Guandu,29081,917.07 ES,Barra de São Francisco,40649,941.8 ES,Boa Esperança,14199,428.5 ES,Bom Jesus do Norte,9476,89.08 ES,Brejetuba,11915,344.17 ES,Cachoeiro de Itapemirim,189889,878.18 ES,Cariacica,348738,279.86 ES,Castelo,34747,664.06 ES,Colatina,111788,1416.8 ES,Conceição da Barra,28449,1184.91 ES,Conceição do Castelo,11681,369.23 ES,Divino de São Lourenço,4516,173.88 ES,Domingos Martins,31847,1228.35 ES,Dores do Rio Preto,6397,159.3 ES,Ecoporanga,23212,2285.37 ES,Fundão,17025,288.72 ES,Governador Lindenberg,10869,359.98 ES,Guaçuí,27851,468.34 ES,Guarapari,105286,594.49 ES,Ibatiba,22366,240.54 ES,Ibiraçu,11178,201.25 ES,Ibitirama,8957,329.87 ES,Iconha,12523,203.53 ES,Irupi,11723,184.55 ES,Itaguaçu,14134,531.5 ES,Itapemirim,30988,561.87 ES,Itarana,10881,298.76 ES,Iúna,27328,461.08 ES,Jaguaré,24678,659.75 ES,Jerônimo Monteiro,10879,161.98 ES,João Neiva,15809,284.73 ES,Laranja da Terra,10826,458.37 ES,Linhares,141306,3504.14 ES,Mantenópolis,13612,321.42 ES,Marataízes,34140,133.08 ES,Marechal Floriano,14262,285.38 ES,Marilândia,11107,309.02 ES,Mimoso do Sul,25902,869.43 ES,Montanha,17849,1098.92 ES,Mucurici,5655,540.19 ES,Muniz Freire,18397,679.32 ES,Muqui,14396,327.49 ES,Nova Venécia,46031,1442.16 ES,Pancas,21548,829.94 ES,Pedro Canário,23794,433.88 ES,Pinheiros,23895,973.14 ES,Piúma,18123,74.83 ES,Ponto Belo,6979,360.66 ES,Presidente Kennedy,10314,583.93 ES,Rio Bananal,17530,642.23 ES,Rio Novo do Sul,11325,204.36 ES,Santa Leopoldina,12240,718.1 ES,Santa Maria de Jetibá,34176,735.58 ES,Santa Teresa,21823,683.16 ES,São Domingos do Norte,8001,298.71 ES,São Gabriel da Palha,31859,434.89 ES,São José do Calçado,10408,273.49 ES,São Mateus,109028,2338.73 ES,São Roque do Canaã,11273,342.01 ES,Serra,409267,551.69 ES,Sooretama,23843,586.42 ES,Vargem Alta,19130,413.63 ES,Venda Nova do Imigrante,20447,185.91 ES,Viana,65001,312.75 ES,Vila Pavão,8672,433.26 ES,Vila Valério,13830,470.1 ES,Vila Velha,414586,210.07 ES,Vitória,327801,98.19 GO,Abadia de Goiás,6876,146.78 GO,Abadiânia,15757,1045.13 GO,Acreúna,20279,1566.0 GO,Adelândia,2477,115.35 GO,Água Fria de Goiás,5090,2029.42 GO,Água Limpa,2013,452.86 GO,Águas Lindas de Goiás,159378,188.39 GO,Alexânia,23814,847.89 GO,Aloândia,2051,102.16 GO,Alto Horizonte,4505,503.76 GO,Alto Paraíso de Goiás,6885,2593.91 GO,Alvorada do Norte,8084,1259.37 GO,Amaralina,3434,1343.17 GO,Americano do Brasil,5508,133.56 GO,Amorinópolis,3609,408.53 GO,Anápolis,334613,933.16 GO,Anhanguera,1020,56.95 GO,Anicuns,20239,979.23 GO,Aparecida de Goiânia,455657,288.34 GO,Aparecida do Rio Doce,2427,602.13 GO,Aporé,3803,2900.16 GO,Araçu,3802,148.94 GO,Aragarças,18305,662.9 GO,Aragoiânia,8365,219.55 GO,Araguapaz,7510,2193.7 GO,Arenópolis,3277,1074.6 GO,Aruanã,7496,3050.31 GO,Aurilândia,3650,565.34 GO,Avelinópolis,2450,173.64 GO,Baliza,3714,1782.6 GO,Barro Alto,8716,1093.25 GO,Bela Vista de Goiás,24554,1255.42 GO,Bom Jardim de Goiás,8423,1899.49 GO,Bom Jesus de Goiás,20727,1405.12 GO,Bonfinópolis,7536,122.29 GO,Bonópolis,3503,1628.49 GO,Brazabrantes,3232,123.07 GO,Britânia,5509,1461.19 GO,Buriti Alegre,9054,895.46 GO,Buriti de Goiás,2560,199.29 GO,Buritinópolis,3321,247.05 GO,Cabeceiras,7354,1127.61 GO,Cachoeira Alta,10553,1654.55 GO,Cachoeira de Goiás,1417,422.75 GO,Cachoeira Dourada,8254,521.13 GO,Caçu,13283,2251.01 GO,Caiapônia,16757,8637.87 GO,Caldas Novas,70473,1595.97 GO,Caldazinha,3325,250.89 GO,Campestre de Goiás,3387,273.82 GO,Campinaçu,3656,1974.38 GO,Campinorte,11111,1067.2 GO,Campo Alegre de Goiás,6060,2462.99 GO,Campo Limpo de Goiás,6241,159.56 GO,Campos Belos,18410,724.07 GO,Campos Verdes,5020,441.65 GO,Carmo do Rio Verde,8928,418.54 GO,Castelândia,3638,297.43 GO,Catalão,86647,3821.46 GO,Caturaí,4686,207.26 GO,Cavalcante,9392,6953.67 GO,Ceres,20722,214.32 GO,Cezarina,7545,415.81 GO,Chapadão do Céu,7001,2185.12 GO,Cidade Ocidental,55915,389.99 GO,Cocalzinho de Goiás,17407,1789.04 GO,Colinas do Sul,3523,1708.19 GO,Córrego do Ouro,2632,462.3 GO,Corumbá de Goiás,10361,1061.96 GO,Corumbaíba,8181,1883.67 GO,Cristalina,46580,6162.09 GO,Cristianópolis,2932,225.36 GO,Crixás,15760,4661.16 GO,Cromínia,3555,364.11 GO,Cumari,2964,570.54 GO,Damianópolis,3292,415.35 GO,Damolândia,2747,84.5 GO,Davinópolis,2056,481.3 GO,Diorama,2479,687.35 GO,Divinópolis de Goiás,4962,830.97 GO,Doverlândia,7892,3222.94 GO,Edealina,3733,603.65 GO,Edéia,11266,1461.5 GO,Estrela do Norte,3320,301.64 GO,Faina,6983,1945.66 GO,Fazenda Nova,6322,1281.42 GO,Firminópolis,11580,423.65 GO,Flores de Goiás,12066,3709.43 GO,Formosa,100085,5811.79 GO,Formoso,4883,844.29 GO,Gameleira de Goiás,3275,592.0 GO,Goianápolis,10695,162.44 GO,Goiandira,5265,564.69 GO,Goianésia,59549,1547.27 GO,Goiânia,1302001,732.8 GO,Goianira,34060,209.04 GO,Goiás,24727,3108.02 GO,Goiatuba,32492,2475.11 GO,Gouvelândia,4949,824.26 GO,Guapó,13976,516.84 GO,Guaraíta,2376,205.31 GO,Guarani de Goiás,4258,1229.15 GO,Guarinos,2299,595.87 GO,Heitoraí,3571,229.64 GO,Hidrolândia,17398,943.9 GO,Hidrolina,4029,580.39 GO,Iaciara,12427,1550.38 GO,Inaciolândia,5699,688.4 GO,Indiara,13687,956.48 GO,Inhumas,48246,613.23 GO,Ipameri,24735,4368.99 GO,Ipiranga de Goiás,2844,241.29 GO,Iporá,31274,1026.38 GO,Israelândia,2887,577.48 GO,Itaberaí,35371,1457.28 GO,Itaguari,4513,146.64 GO,Itaguaru,5437,239.68 GO,Itajá,5062,2091.4 GO,Itapaci,18458,956.13 GO,Itapirapuã,7835,2043.72 GO,Itapuranga,26125,1276.48 GO,Itarumã,6300,3433.63 GO,Itauçu,8575,383.84 GO,Itumbiara,92883,2462.93 GO,Ivolândia,2663,1257.66 GO,Jandaia,6164,864.11 GO,Jaraguá,41870,1849.55 GO,Jataí,88006,7174.23 GO,Jaupaci,3000,527.1 GO,Jesúpolis,2300,122.48 GO,Joviânia,7118,445.49 GO,Jussara,19153,4084.11 GO,Lagoa Santa,1254,458.87 GO,Leopoldo de Bulhões,7882,480.89 GO,Luziânia,174531,3961.12 GO,Mairipotaba,2374,467.43 GO,Mambaí,6871,880.62 GO,Mara Rosa,10649,1687.91 GO,Marzagão,2072,222.43 GO,Matrinchã,4414,1150.89 GO,Maurilândia,11521,389.76 GO,Mimoso de Goiás,2685,1386.92 GO,Minaçu,31154,2860.74 GO,Mineiros,52935,9060.09 GO,Moiporá,1763,460.62 GO,Monte Alegre de Goiás,7730,3119.81 GO,Montes Claros de Goiás,7987,2899.18 GO,Montividiu,10572,1874.15 GO,Montividiu do Norte,4122,1333.0 GO,Morrinhos,41460,2846.2 GO,Morro Agudo de Goiás,2356,282.62 GO,Mossâmedes,5007,684.45 GO,Mozarlândia,13404,1734.36 GO,Mundo Novo,6438,2146.65 GO,Mutunópolis,3849,955.88 GO,Nazário,7874,269.1 GO,Nerópolis,24210,204.22 GO,Niquelândia,42361,9843.25 GO,Nova América,2259,212.03 GO,Nova Aurora,2062,302.66 GO,Nova Crixás,11927,7298.78 GO,Nova Glória,8508,412.95 GO,Nova Iguaçu de Goiás,2826,628.44 GO,Nova Roma,3471,2135.96 GO,Nova Veneza,8129,123.38 GO,Novo Brasil,3519,649.95 GO,Novo Gama,95018,194.99 GO,Novo Planalto,3956,1242.96 GO,Orizona,14300,1972.88 GO,Ouro Verde de Goiás,4034,208.77 GO,Ouvidor,5467,413.78 GO,Padre Bernardo,27671,3139.18 GO,Palestina de Goiás,3371,1320.69 GO,Palmeiras de Goiás,23338,1539.69 GO,Palmelo,2335,58.96 GO,Palminópolis,3557,387.69 GO,Panamá,2682,433.76 GO,Paranaiguara,9100,1153.83 GO,Paraúna,10863,3779.39 GO,Perolândia,2950,1029.62 GO,Petrolina de Goiás,10283,531.3 GO,Pilar de Goiás,2773,906.65 GO,Piracanjuba,24026,2405.12 GO,Piranhas,11266,2047.77 GO,Pirenópolis,23006,2205.01 GO,Pires do Rio,28762,1073.36 GO,Planaltina,81649,2543.87 GO,Pontalina,17121,1436.95 GO,Porangatu,42355,4820.52 GO,Porteirão,3347,603.94 GO,Portelândia,3839,556.58 GO,Posse,31419,2024.54 GO,Professor Jamil,3239,347.47 GO,Quirinópolis,43220,3786.69 GO,Rialma,10523,268.47 GO,Rianápolis,4566,159.26 GO,Rio Quente,3312,255.96 GO,Rio Verde,176424,8379.66 GO,Rubiataba,18915,748.26 GO,Sanclerlândia,7550,496.83 GO,Santa Bárbara de Goiás,5751,139.6 GO,Santa Cruz de Goiás,3142,1108.96 GO,Santa Fé de Goiás,4762,1169.17 GO,Santa Helena de Goiás,36469,1141.33 GO,Santa Isabel,3686,807.2 GO,Santa Rita do Araguaia,6924,1361.77 GO,Santa Rita do Novo Destino,3173,956.04 GO,Santa Rosa de Goiás,2909,164.1 GO,Santa Tereza de Goiás,3995,794.56 GO,Santa Terezinha de Goiás,10302,1202.24 GO,Santo Antônio da Barra,4423,451.6 GO,Santo Antônio de Goiás,4703,132.81 GO,Santo Antônio do Descoberto,63248,944.14 GO,São Domingos,11272,3295.74 GO,São Francisco de Goiás,6120,415.79 GO,São João da Paraúna,1689,287.83 GO,São João d`Aliança,10257,3327.38 GO,São Luís de Montes Belos,30034,826.0 GO,São Luíz do Norte,4617,586.06 GO,São Miguel do Araguaia,22283,6144.41 GO,São Miguel do Passa Quatro,3757,537.79 GO,São Patrício,1991,171.96 GO,São Simão,17088,414.01 GO,Senador Canedo,84443,245.28 GO,Serranópolis,7481,5526.72 GO,Silvânia,19089,2345.94 GO,Simolândia,6514,347.98 GO,Sítio d`Abadia,2825,1598.35 GO,Taquaral de Goiás,3541,204.22 GO,Teresina de Goiás,3016,774.64 GO,Terezópolis de Goiás,6561,106.91 GO,Três Ranchos,2819,282.07 GO,Trindade,104488,710.71 GO,Trombas,3452,799.13 GO,Turvânia,4839,480.78 GO,Turvelândia,4399,933.96 GO,Uirapuru,2933,1153.48 GO,Uruaçu,36929,2141.82 GO,Uruana,13826,522.51 GO,Urutaí,3074,626.72 GO,Valparaíso de Goiás,132982,61.41 GO,Varjão,3659,519.19 GO,Vianópolis,12548,954.28 GO,Vicentinópolis,7371,737.26 GO,Vila Boa,4735,1060.17 GO,Vila Propício,5145,2181.58 MA,Açailândia,104047,5806.44 MA,Afonso Cunha,5905,371.34 MA,Água Doce do Maranhão,11581,443.27 MA,Alcântara,21851,1486.68 MA,Aldeias Altas,23952,1942.11 MA,Altamira do Maranhão,11063,721.31 MA,Alto Alegre do Maranhão,24599,383.31 MA,Alto Alegre do Pindaré,31057,1932.29 MA,Alto Parnaíba,10766,11132.18 MA,Amapá do Maranhão,6431,502.4 MA,Amarante do Maranhão,37932,7438.15 MA,Anajatuba,25291,1011.13 MA,Anapurus,13939,608.3 MA,Apicum-Açu,14959,353.17 MA,Araguanã,13973,805.2 MA,Araioses,42505,1782.6 MA,Arame,31702,3008.69 MA,Arari,28488,1100.28 MA,Axixá,11407,203.15 MA,Bacabal,100014,1682.96 MA,Bacabeira,14925,615.59 MA,Bacuri,16604,787.86 MA,Bacurituba,5293,674.51 MA,Balsas,83528,13141.73 MA,Barão de Grajaú,17841,2247.24 MA,Barra do Corda,82830,5202.7 MA,Barreirinhas,54930,3111.99 MA,Bela Vista do Maranhão,12049,255.55 MA,Belágua,6524,499.43 MA,Benedito Leite,5469,1781.73 MA,Bequimão,20344,768.95 MA,Bernardo do Mearim,5996,261.45 MA,Boa Vista do Gurupi,7949,403.46 MA,Bom Jardim,39049,6590.53 MA,Bom Jesus das Selvas,28459,2679.1 MA,Bom Lugar,14818,445.98 MA,Brejo,33359,1074.63 MA,Brejo de Areia,5577,362.46 MA,Buriti,27013,1473.96 MA,Buriti Bravo,22899,1582.55 MA,Buriticupu,65237,2545.44 MA,Buritirana,14784,818.42 MA,Cachoeira Grande,8446,705.65 MA,Cajapió,10593,908.73 MA,Cajari,18338,662.07 MA,Campestre do Maranhão,13369,615.38 MA,Cândido Mendes,18505,1632.91 MA,Cantanhede,20448,773.01 MA,Capinzal do Norte,10698,590.53 MA,Carolina,23959,6441.6 MA,Carutapera,22006,1232.08 MA,Caxias,155129,5150.67 MA,Cedral,10297,283.19 MA,Central do Maranhão,7887,319.34 MA,Centro do Guilherme,12565,1074.07 MA,Centro Novo do Maranhão,17622,8258.42 MA,Chapadinha,73350,3247.38 MA,Cidelândia,13681,1464.03 MA,Codó,118038,4361.34 MA,Coelho Neto,46750,975.55 MA,Colinas,39132,1980.55 MA,Conceição do Lago-Açu,14436,733.23 MA,Coroatá,61725,2263.78 MA,Cururupu,32652,1223.37 MA,Davinópolis,12579,335.78 MA,Dom Pedro,22681,358.49 MA,Duque Bacelar,10649,317.92 MA,Esperantinópolis,18452,480.92 MA,Estreito,35835,2718.98 MA,Feira Nova do Maranhão,8126,1473.42 MA,Fernando Falcão,9241,5086.58 MA,Formosa da Serra Negra,17757,3950.53 MA,Fortaleza dos Nogueiras,11646,1664.33 MA,Fortuna,15098,695.0 MA,Godofredo Viana,10635,675.17 MA,Gonçalves Dias,17482,883.59 MA,Governador Archer,10205,445.86 MA,Governador Edison Lobão,15895,615.85 MA,Governador Eugênio Barros,15991,816.99 MA,Governador Luiz Rocha,7337,373.16 MA,Governador Newton Bello,11921,1160.49 MA,Governador Nunes Freire,25401,1037.13 MA,Graça Aranha,6140,271.44 MA,Grajaú,62093,8830.96 MA,Guimarães,12081,595.38 MA,Humberto de Campos,26189,2131.25 MA,Icatu,25145,1448.78 MA,Igarapé do Meio,12550,368.69 MA,Igarapé Grande,11041,374.25 MA,Imperatriz,247505,1368.99 MA,Itaipava do Grajaú,14297,1238.82 MA,Itapecuru Mirim,62110,1471.44 MA,Itinga do Maranhão,24863,3581.72 MA,Jatobá,8526,591.38 MA,Jenipapo dos Vieiras,15440,1962.9 MA,João Lisboa,20381,636.89 MA,Joselândia,15433,681.69 MA,Junco do Maranhão,4020,555.09 MA,Lago da Pedra,46083,1240.45 MA,Lago do Junco,10729,309.02 MA,Lago dos Rodrigues,7794,180.37 MA,Lago Verde,15412,623.24 MA,Lagoa do Mato,10934,1688.05 MA,Lagoa Grande do Maranhão,10517,744.3 MA,Lajeado Novo,6923,1047.73 MA,Lima Campos,11423,321.93 MA,Loreto,11390,3596.84 MA,Luís Domingues,6510,464.06 MA,Magalhães de Almeida,17587,433.15 MA,Maracaçumé,19155,629.31 MA,Marajá do Sena,8051,1447.68 MA,Maranhãozinho,14065,972.62 MA,Mata Roma,15150,548.41 MA,Matinha,21885,408.73 MA,Matões,31015,1976.14 MA,Matões do Norte,13794,794.65 MA,Milagres do Maranhão,8118,634.74 MA,Mirador,20452,8450.85 MA,Miranda do Norte,24427,341.11 MA,Mirinzal,14218,687.75 MA,Monção,31738,1301.97 MA,Montes Altos,9413,1488.34 MA,Morros,17783,1715.18 MA,Nina Rodrigues,12464,572.51 MA,Nova Colinas,4885,743.11 MA,Nova Iorque,4590,976.85 MA,Nova Olinda do Maranhão,19134,2452.62 MA,Olho d`Água das Cunhãs,18601,695.33 MA,Olinda Nova do Maranhão,13181,197.64 MA,Paço do Lumiar,105121,122.83 MA,Palmeirândia,18764,525.58 MA,Paraibano,20103,530.52 MA,Parnarama,34586,3439.23 MA,Passagem Franca,17562,1358.33 MA,Pastos Bons,18067,1635.31 MA,Paulino Neves,14519,979.18 MA,Paulo Ramos,20079,1053.41 MA,Pedreiras,39448,288.43 MA,Pedro do Rosário,22732,1749.89 MA,Penalva,34267,738.25 MA,Peri Mirim,13803,405.3 MA,Peritoró,21201,824.72 MA,Pindaré-Mirim,31152,273.53 MA,Pinheiro,78162,1512.68 MA,Pio XII,22016,545.14 MA,Pirapemas,17381,688.76 MA,Poção de Pedras,19708,961.94 MA,Porto Franco,21530,1417.49 MA,Porto Rico do Maranhão,6030,218.83 MA,Presidente Dutra,44731,771.57 MA,Presidente Juscelino,11541,354.7 MA,Presidente Médici,6374,437.69 MA,Presidente Sarney,17165,724.15 MA,Presidente Vargas,10717,459.36 MA,Primeira Cruz,13954,1367.68 MA,Raposa,26327,66.28 MA,Riachão,20209,6373.02 MA,Ribamar Fiquene,7318,750.55 MA,Rosário,39576,685.04 MA,Sambaíba,5487,2478.7 MA,Santa Filomena do Maranhão,7061,602.34 MA,Santa Helena,39110,2308.19 MA,Santa Inês,77282,381.16 MA,Santa Luzia,74043,5462.96 MA,Santa Luzia do Paruá,22644,897.15 MA,Santa Quitéria do Maranhão,29191,1917.59 MA,Santa Rita,32366,706.39 MA,Santana do Maranhão,11661,932.02 MA,Santo Amaro do Maranhão,13820,1601.18 MA,Santo Antônio dos Lopes,14288,771.42 MA,São Benedito do Rio Preto,17799,931.48 MA,São Bento,40736,459.07 MA,São Bernardo,26476,1006.92 MA,São Domingos do Azeitão,6983,960.93 MA,São Domingos do Maranhão,33607,1151.98 MA,São Félix de Balsas,4702,2032.36 MA,São Francisco do Brejão,10261,745.61 MA,São Francisco do Maranhão,12146,2347.2 MA,São João Batista,19920,690.68 MA,São João do Carú,12309,615.7 MA,São João do Paraíso,10814,2053.84 MA,São João do Soter,17238,1438.07 MA,São João dos Patos,24928,1500.63 MA,São José de Ribamar,163045,388.37 MA,São José dos Basílios,7496,362.69 MA,São Luís,1014837,834.79 MA,São Luís Gonzaga do Maranhão,20153,968.57 MA,São Mateus do Maranhão,39093,783.34 MA,São Pedro da Água Branca,12028,720.45 MA,São Pedro dos Crentes,4425,979.63 MA,São Raimundo das Mangabeiras,17474,3521.53 MA,São Raimundo do Doca Bezerra,6090,419.35 MA,São Roberto,5957,227.46 MA,São Vicente Ferrer,20863,390.85 MA,Satubinha,11990,441.81 MA,Senador Alexandre Costa,10256,426.44 MA,Senador La Rocque,17998,1236.87 MA,Serrano do Maranhão,10940,1207.06 MA,Sítio Novo,17002,3114.87 MA,Sucupira do Norte,10444,1074.47 MA,Sucupira do Riachão,4613,564.97 MA,Tasso Fragoso,7796,4382.98 MA,Timbiras,27997,1486.59 MA,Timon,155460,1743.25 MA,Trizidela do Vale,18953,222.95 MA,Tufilândia,5596,271.01 MA,Tuntum,39183,3390.0 MA,Turiaçu,33933,2578.5 MA,Turilândia,22846,1511.86 MA,Tutóia,52788,1651.66 MA,Urbano Santos,24573,1207.63 MA,Vargem Grande,49412,1957.75 MA,Viana,49496,1168.44 MA,Vila Nova dos Martírios,11258,1188.78 MA,Vitória do Mearim,31217,716.72 MA,Vitorino Freire,31658,1305.31 MA,Zé Doca,50173,2416.06 MT,Acorizal,5516,840.59 MT,Água Boa,20856,7481.12 MT,Alta Floresta,49164,8976.18 MT,Alto Araguaia,15644,5514.51 MT,Alto Boa Vista,5247,2240.45 MT,Alto Garças,10350,3748.05 MT,Alto Paraguai,10066,1846.3 MT,Alto Taquari,8072,1416.52 MT,Apiacás,8567,20377.51 MT,Araguaiana,3197,6429.38 MT,Araguainha,1096,687.97 MT,Araputanga,15342,1600.24 MT,Arenápolis,10316,416.79 MT,Aripuanã,18656,25056.78 MT,Barão de Melgaço,7591,11174.5 MT,Barra do Bugres,31793,6060.2 MT,Barra do Garças,56560,9078.98 MT,Bom Jesus do Araguaia,5314,4274.21 MT,Brasnorte,15357,15959.14 MT,Cáceres,87942,24351.41 MT,Campinápolis,14305,5967.35 MT,Campo Novo do Parecis,27577,9434.42 MT,Campo Verde,31589,4782.12 MT,Campos de Júlio,5154,6801.86 MT,Canabrava do Norte,4786,3452.68 MT,Canarana,18754,10882.4 MT,Carlinda,10990,2393.02 MT,Castanheira,8231,3909.54 MT,Chapada dos Guimarães,17821,6256.99 MT,Cláudia,11028,3849.99 MT,Cocalinho,5490,16530.66 MT,Colíder,30766,3093.17 MT,Colniza,26381,27946.83 MT,Comodoro,18178,21769.72 MT,Confresa,25124,5801.39 MT,Conquista d`Oeste,3385,2672.21 MT,Cotriguaçu,14983,9460.47 MT,Cuiabá,551098,3495.42 MT,Curvelândia,4866,359.76 MT,Denise,8523,1307.19 MT,Diamantino,20341,8230.1 MT,Dom Aquino,8171,2204.16 MT,Feliz Natal,10933,11462.46 MT,Figueirópolis d`Oeste,3796,899.25 MT,Gaúcha do Norte,6293,16930.67 MT,General Carneiro,5027,3794.94 MT,Glória d`Oeste,3135,853.84 MT,Guarantã do Norte,32216,4734.59 MT,Guiratinga,13934,5061.69 MT,Indiavaí,2397,603.29 MT,Ipiranga do Norte,5123,3467.05 MT,Itanhangá,5276,2898.08 MT,Itaúba,4575,4529.58 MT,Itiquira,11478,8722.48 MT,Jaciara,25647,1653.54 MT,Jangada,7696,1018.49 MT,Jauru,10455,1301.89 MT,Juara,32791,22641.19 MT,Juína,39255,26189.96 MT,Juruena,11201,2778.96 MT,Juscimeira,11430,2206.13 MT,Lambari d`Oeste,5431,1763.89 MT,Lucas do Rio Verde,45556,3663.99 MT,Luciára,2224,4243.04 MT,Marcelândia,12006,12281.25 MT,Matupá,14174,5239.67 MT,Mirassol d`Oeste,25299,1076.36 MT,Nobres,15002,3892.06 MT,Nortelândia,6436,1348.88 MT,Nossa Senhora do Livramento,11609,5076.78 MT,Nova Bandeirantes,11643,9606.26 MT,Nova Brasilândia,4587,3281.88 MT,Nova Canaã do Norte,12127,5966.2 MT,Nova Guarita,4932,1114.13 MT,Nova Lacerda,5436,4735.09 MT,Nova Marilândia,2951,1939.8 MT,Nova Maringá,6590,11557.3 MT,Nova Monte verde,8093,5248.54 MT,Nova Mutum,31649,9562.66 MT,Nova Nazaré,3029,4038.06 MT,Nova Olímpia,17515,1549.82 MT,Nova Santa Helena,3468,2359.82 MT,Nova Ubiratã,9218,12706.74 MT,Nova Xavantina,19643,5573.68 MT,Novo Horizonte do Norte,3749,879.66 MT,Novo Mundo,7332,5790.8 MT,Novo Santo Antônio,2005,4393.8 MT,Novo São Joaquim,6042,5035.15 MT,Paranaíta,10684,4796.01 MT,Paranatinga,19290,24166.08 MT,Pedra Preta,15755,4108.59 MT,Peixoto de Azevedo,30812,14257.44 MT,Planalto da Serra,2726,2455.43 MT,Poconé,31779,17270.99 MT,Pontal do Araguaia,5395,2738.78 MT,Ponte Branca,1768,685.99 MT,Pontes e Lacerda,41408,8558.93 MT,Porto Alegre do Norte,10748,3972.25 MT,Porto dos Gaúchos,5449,6992.7 MT,Porto Esperidião,11031,5809.02 MT,Porto Estrela,3649,2062.76 MT,Poxoréo,17599,6909.69 MT,Primavera do Leste,52066,5471.64 MT,Querência,13033,17786.2 MT,Reserva do Cabaçal,2572,1337.04 MT,Ribeirão Cascalheira,8881,11354.81 MT,Ribeirãozinho,2199,625.58 MT,Rio Branco,5070,562.84 MT,Rondolândia,3604,12670.49 MT,Rondonópolis,195476,4159.12 MT,Rosário Oeste,17679,7475.56 MT,Salto do Céu,3908,1752.31 MT,Santa Carmem,4085,3855.36 MT,Santa Cruz do Xingu,1900,5651.75 MT,Santa Rita do Trivelato,2491,4728.2 MT,Santa Terezinha,7397,6467.37 MT,Santo Afonso,2991,1174.19 MT,Santo Antônio do Leste,3754,3600.71 MT,Santo Antônio do Leverger,18463,12261.29 MT,São Félix do Araguaia,10625,16713.46 MT,São José do Povo,3592,443.88 MT,São José do Rio Claro,17124,4536.2 MT,São José do Xingu,5240,7459.65 MT,São José dos Quatro Marcos,18998,1287.88 MT,São Pedro da Cipa,4158,342.95 MT,Sapezal,18094,13624.37 MT,Serra Nova Dourada,1365,1500.39 MT,Sinop,113099,3942.23 MT,Sorriso,66521,9329.6 MT,Tabaporã,9932,8317.43 MT,Tangará da Serra,83431,11323.64 MT,Tapurah,10392,4510.65 MT,Terra Nova do Norte,11291,2562.23 MT,Tesouro,3418,4169.56 MT,Torixoréu,4071,2399.46 MT,União do Sul,3760,4581.91 MT,Vale de São Domingos,3052,1933.05 MT,Várzea Grande,252596,1048.21 MT,Vera,10235,2962.69 MT,Vila Bela da Santíssima Trindade,14493,13420.98 MT,Vila Rica,21382,7431.08 MS,Água Clara,14424,11031.12 MS,Alcinópolis,4569,4399.68 MS,Amambai,34730,4202.32 MS,Anastácio,23835,2949.13 MS,Anaurilândia,8493,3395.44 MS,Angélica,9185,1273.27 MS,Antônio João,8208,1145.18 MS,Aparecida do Taboado,22320,2750.15 MS,Aquidauana,45614,16957.75 MS,Aral Moreira,10251,1655.66 MS,Bandeirantes,6609,3115.68 MS,Bataguassu,19839,2415.3 MS,Batayporã,10936,1828.02 MS,Bela Vista,23181,4892.6 MS,Bodoquena,7985,2507.32 MS,Bonito,19587,4934.41 MS,Brasilândia,11826,5806.9 MS,Caarapó,25767,2089.6 MS,Camapuã,13625,6229.62 MS,Campo Grande,786797,8092.95 MS,Caracol,5398,2940.25 MS,Cassilândia,20966,3649.72 MS,Chapadão do Sul,19648,3851.0 MS,Corguinho,4862,2639.85 MS,Coronel Sapucaia,14064,1025.05 MS,Corumbá,103703,64962.72 MS,Costa Rica,19695,5371.8 MS,Coxim,32159,6409.22 MS,Deodápolis,12139,831.21 MS,Dois Irmãos do Buriti,10363,2344.59 MS,Douradina,5364,280.79 MS,Dourados,196035,4086.24 MS,Eldorado,11694,1017.79 MS,Fátima do Sul,19035,315.16 MS,Figueirão,2928,4882.87 MS,Glória de Dourados,9927,491.75 MS,Guia Lopes da Laguna,10366,1210.61 MS,Iguatemi,14875,2946.52 MS,Inocência,7669,5776.03 MS,Itaporã,20865,1321.81 MS,Itaquiraí,18614,2064.04 MS,Ivinhema,22341,2010.17 MS,Japorã,7731,419.4 MS,Jaraguari,6341,2912.82 MS,Jardim,24346,2201.51 MS,Jateí,4011,1927.95 MS,Juti,5900,1584.54 MS,Ladário,19617,340.77 MS,Laguna Carapã,6491,1734.07 MS,Maracaju,37405,5299.18 MS,Miranda,25595,5478.83 MS,Mundo Novo,17043,477.78 MS,Naviraí,46424,3193.54 MS,Nioaque,14391,3923.79 MS,Nova Alvorada do Sul,16432,4019.32 MS,Nova Andradina,45585,4776.0 MS,Novo Horizonte do Sul,4940,849.09 MS,Paranaíba,40192,5402.65 MS,Paranhos,12350,1309.16 MS,Pedro Gomes,7967,3651.18 MS,Ponta Porã,77872,5330.45 MS,Porto Murtinho,15372,17744.41 MS,Ribas do Rio Pardo,20946,17308.08 MS,Rio Brilhante,30663,3987.4 MS,Rio Negro,5036,1807.67 MS,Rio Verde de Mato Grosso,18890,8153.9 MS,Rochedo,4928,1561.06 MS,Santa Rita do Pardo,7259,6143.07 MS,São Gabriel do Oeste,22203,3864.69 MS,Selvíria,6287,3258.33 MS,Sete Quedas,10780,833.73 MS,Sidrolândia,42132,5286.41 MS,Sonora,14833,4075.42 MS,Tacuru,10215,1785.32 MS,Taquarussu,3518,1041.12 MS,Terenos,17146,2844.51 MS,Três Lagoas,101791,10206.95 MS,Vicentina,5901,310.16 MG,Abadia dos Dourados,6704,881.06 MG,Abaeté,22690,1817.07 MG,Abre Campo,13311,470.55 MG,Acaiaca,3920,101.89 MG,Açucena,10276,815.42 MG,Água Boa,15195,1320.27 MG,Água Comprida,2025,492.21 MG,Aguanil,4054,232.09 MG,Águas Formosas,18479,820.08 MG,Águas Vermelhas,12722,1259.28 MG,Aimorés,24959,1348.78 MG,Aiuruoca,6162,649.68 MG,Alagoa,2709,161.36 MG,Albertina,2913,58.01 MG,Além Paraíba,34349,510.35 MG,Alfenas,73774,850.45 MG,Alfredo Vasconcelos,6075,130.82 MG,Almenara,38775,2294.43 MG,Alpercata,7172,166.97 MG,Alpinópolis,18488,454.75 MG,Alterosa,13717,362.01 MG,Alto Caparaó,5297,103.69 MG,Alto Jequitibá,8318,152.27 MG,Alto Rio Doce,12159,518.05 MG,Alvarenga,4444,278.17 MG,Alvinópolis,15261,599.44 MG,Alvorada de Minas,3546,374.01 MG,Amparo do Serra,5053,145.91 MG,Andradas,37270,469.37 MG,Andrelândia,12173,1005.29 MG,Angelândia,8003,185.21 MG,Antônio Carlos,11114,529.92 MG,Antônio Dias,9565,787.06 MG,Antônio Prado de Minas,1671,83.8 MG,Araçaí,2243,186.54 MG,Aracitaba,2058,106.61 MG,Araçuaí,36013,2236.28 MG,Araguari,109801,2729.51 MG,Arantina,2823,89.42 MG,Araponga,8152,303.79 MG,Araporã,6144,295.84 MG,Arapuá,2775,173.89 MG,Araújos,7883,245.52 MG,Araxá,93672,1164.36 MG,Arceburgo,9509,162.88 MG,Arcos,36597,509.87 MG,Areado,13731,283.12 MG,Argirita,2901,159.38 MG,Aricanduva,4770,243.33 MG,Arinos,17674,5279.42 MG,Astolfo Dutra,13049,158.89 MG,Ataléia,14455,1836.98 MG,Augusto de Lima,4960,1254.83 MG,Baependi,18307,750.55 MG,Baldim,7913,556.27 MG,Bambuí,22734,1455.82 MG,Bandeira,4987,483.79 MG,Bandeira do Sul,5338,47.07 MG,Barão de Cocais,28442,340.6 MG,Barão de Monte Alto,5720,198.31 MG,Barbacena,126284,759.19 MG,Barra Longa,6143,383.63 MG,Barroso,19599,82.07 MG,Bela Vista de Minas,10004,109.14 MG,Belmiro Braga,3403,393.13 MG,Belo Horizonte,2375151,331.4 MG,Belo Oriente,23397,334.91 MG,Belo Vale,7536,365.92 MG,Berilo,12300,587.11 MG,Berizal,4370,488.76 MG,Bertópolis,4498,427.8 MG,Betim,378089,342.85 MG,Bias Fortes,3793,283.54 MG,Bicas,13653,140.08 MG,Biquinhas,2630,458.95 MG,Boa Esperança,38516,860.67 MG,Bocaina de Minas,5007,503.79 MG,Bocaiúva,46654,3227.63 MG,Bom Despacho,45624,1223.87 MG,Bom Jardim de Minas,6501,412.02 MG,Bom Jesus da Penha,3887,208.35 MG,Bom Jesus do Amparo,5491,195.61 MG,Bom Jesus do Galho,15364,592.29 MG,Bom Repouso,10457,229.85 MG,Bom Sucesso,17243,705.05 MG,Bonfim,6818,301.87 MG,Bonfinópolis de Minas,5865,1850.49 MG,Bonito de Minas,9673,3904.91 MG,Borda da Mata,17118,301.11 MG,Botelhos,14920,334.09 MG,Botumirim,6497,1568.88 MG,Brás Pires,4637,223.35 MG,Brasilândia de Minas,14226,2509.69 MG,Brasília de Minas,31213,1399.48 MG,Braúnas,5030,378.32 MG,Brazópolis,14661,367.69 MG,Brumadinho,33973,639.43 MG,Bueno Brandão,10892,356.15 MG,Buenópolis,10292,1599.88 MG,Bugre,3992,161.91 MG,Buritis,22737,5225.19 MG,Buritizeiro,26922,7218.4 MG,Cabeceira Grande,6453,1031.41 MG,Cabo Verde,13823,368.21 MG,Cachoeira da Prata,3654,61.38 MG,Cachoeira de Minas,11034,304.24 MG,Cachoeira de Pajeú,8959,695.67 MG,Cachoeira Dourada,2505,200.93 MG,Caetanópolis,10218,156.04 MG,Caeté,40750,542.57 MG,Caiana,4968,106.47 MG,Cajuri,4047,83.04 MG,Caldas,13633,711.41 MG,Camacho,3154,223.0 MG,Camanducaia,21080,528.48 MG,Cambuí,26488,244.57 MG,Cambuquira,12602,246.38 MG,Campanário,3564,442.4 MG,Campanha,15433,335.59 MG,Campestre,20686,577.84 MG,Campina Verde,19324,3650.75 MG,Campo Azul,3684,505.91 MG,Campo Belo,51544,528.23 MG,Campo do Meio,11476,275.43 MG,Campo Florido,6870,1264.25 MG,Campos Altos,14206,710.65 MG,Campos Gerais,27600,769.5 MG,Cana Verde,5589,212.72 MG,Canaã,4628,174.9 MG,Canápolis,11365,839.74 MG,Candeias,14595,720.51 MG,Cantagalo,4195,141.86 MG,Caparaó,5209,130.69 MG,Capela Nova,4755,111.07 MG,Capelinha,34803,965.37 MG,Capetinga,7089,297.94 MG,Capim Branco,8881,95.33 MG,Capinópolis,15290,620.72 MG,Capitão Andrade,4925,279.09 MG,Capitão Enéas,14206,971.58 MG,Capitólio,8183,521.8 MG,Caputira,9030,187.7 MG,Caraí,22343,1242.2 MG,Caranaíba,3288,159.95 MG,Carandaí,23346,485.73 MG,Carangola,32296,353.4 MG,Caratinga,85239,1258.78 MG,Carbonita,9148,1456.1 MG,Careaçu,6298,181.01 MG,Carlos Chagas,20069,3202.98 MG,Carmésia,2446,259.1 MG,Carmo da Cachoeira,11836,506.33 MG,Carmo da Mata,10927,357.18 MG,Carmo de Minas,13750,322.29 MG,Carmo do Cajuru,20012,455.81 MG,Carmo do Paranaíba,29735,1307.86 MG,Carmo do Rio Claro,20426,1065.69 MG,Carmópolis de Minas,17048,400.01 MG,Carneirinho,9471,2063.32 MG,Carrancas,3948,727.89 MG,Carvalhópolis,3341,81.1 MG,Carvalhos,4556,282.25 MG,Casa Grande,2244,157.73 MG,Cascalho Rico,2857,367.31 MG,Cássia,17412,665.8 MG,Cataguases,69757,491.77 MG,Catas Altas,4846,240.04 MG,Catas Altas da Noruega,3462,141.62 MG,Catuji,6708,419.53 MG,Catuti,5102,287.81 MG,Caxambu,21705,100.48 MG,Cedro do Abaeté,1210,283.21 MG,Central de Minas,6772,204.33 MG,Centralina,10266,327.19 MG,Chácara,2792,152.81 MG,Chalé,5645,212.67 MG,Chapada do Norte,15189,830.97 MG,Chapada Gaúcha,10805,3255.19 MG,Chiador,2785,252.94 MG,Cipotânea,6547,153.48 MG,Claraval,4542,227.63 MG,Claro dos Poções,7775,720.42 MG,Cláudio,25771,630.71 MG,Coimbra,7054,106.88 MG,Coluna,9024,348.49 MG,Comendador Gomes,2972,1041.05 MG,Comercinho,8298,654.96 MG,Conceição da Aparecida,9820,352.52 MG,Conceição da Barra de Minas,3954,273.01 MG,Conceição das Alagoas,23043,1340.25 MG,Conceição das Pedras,2749,102.21 MG,Conceição de Ipanema,4456,253.94 MG,Conceição do Mato Dentro,17908,1726.83 MG,Conceição do Pará,5158,250.33 MG,Conceição do Rio Verde,12949,369.68 MG,Conceição dos Ouros,10388,182.97 MG,Cônego Marinho,7101,1642.0 MG,Confins,5936,42.36 MG,Congonhal,10468,205.13 MG,Congonhas,48519,304.07 MG,Congonhas do Norte,4943,398.85 MG,Conquista,6526,618.36 MG,Conselheiro Lafaiete,116512,370.25 MG,Conselheiro Pena,22242,1483.88 MG,Consolação,1727,86.39 MG,Contagem,603442,195.27 MG,Coqueiral,9289,296.16 MG,Coração de Jesus,26033,2225.22 MG,Cordisburgo,8667,823.65 MG,Cordislândia,3435,179.54 MG,Corinto,23914,2525.4 MG,Coroaci,10270,576.27 MG,Coromandel,27547,3313.12 MG,Coronel Fabriciano,103694,221.25 MG,Coronel Murta,9117,815.41 MG,Coronel Pacheco,2983,131.51 MG,Coronel Xavier Chaves,3301,140.95 MG,Córrego Danta,3391,657.43 MG,Córrego do Bom Jesus,3730,123.65 MG,Córrego Fundo,5790,101.11 MG,Córrego Novo,3127,205.39 MG,Couto de Magalhães de Minas,4204,485.65 MG,Crisólita,6047,966.2 MG,Cristais,11286,628.43 MG,Cristália,5760,840.7 MG,Cristiano Otoni,5007,132.87 MG,Cristina,10210,311.33 MG,Crucilândia,4757,167.16 MG,Cruzeiro da Fortaleza,3934,188.13 MG,Cruzília,14591,522.42 MG,Cuparaque,4680,226.75 MG,Curral de Dentro,6913,568.26 MG,Curvelo,74219,3298.79 MG,Datas,5211,310.1 MG,Delfim Moreira,7971,408.47 MG,Delfinópolis,6830,1378.42 MG,Delta,8089,102.84 MG,Descoberto,4768,213.17 MG,Desterro de Entre Rios,7002,377.17 MG,Desterro do Melo,3015,142.28 MG,Diamantina,45880,3891.66 MG,Diogo de Vasconcelos,3848,165.09 MG,Dionísio,8739,344.44 MG,Divinésia,3293,116.97 MG,Divino,19133,337.78 MG,Divino das Laranjeiras,4937,342.25 MG,Divinolândia de Minas,7024,133.12 MG,Divinópolis,213016,708.12 MG,Divisa Alegre,5884,117.8 MG,Divisa Nova,5763,216.96 MG,Divisópolis,8974,572.93 MG,Dom Bosco,3814,817.38 MG,Dom Cavati,5209,59.52 MG,Dom Joaquim,4535,398.82 MG,Dom Silvério,5196,194.97 MG,Dom Viçoso,2994,113.92 MG,Dona Eusébia,6001,70.23 MG,Dores de Campos,9299,124.84 MG,Dores de Guanhães,5223,382.12 MG,Dores do Indaiá,13778,1111.2 MG,Dores do Turvo,4462,231.17 MG,Doresópolis,1440,152.91 MG,Douradoquara,1841,312.88 MG,Durandé,7423,217.46 MG,Elói Mendes,25220,499.54 MG,Engenheiro Caldas,10280,187.06 MG,Engenheiro Navarro,7122,608.31 MG,Entre Folhas,5175,85.21 MG,Entre Rios de Minas,14242,456.8 MG,Ervália,17946,357.49 MG,Esmeraldas,60271,910.38 MG,Espera Feliz,22856,317.64 MG,Espinosa,31113,1868.97 MG,Espírito Santo do Dourado,4429,263.88 MG,Estiva,10845,243.87 MG,Estrela Dalva,2470,131.37 MG,Estrela do Indaiá,3516,635.98 MG,Estrela do Sul,7446,822.45 MG,Eugenópolis,10540,309.4 MG,Ewbank da Câmara,3753,103.83 MG,Extrema,28599,244.58 MG,Fama,2350,86.02 MG,Faria Lemos,3376,165.22 MG,Felício dos Santos,5142,357.62 MG,Felisburgo,6877,596.22 MG,Felixlândia,14121,1554.63 MG,Fernandes Tourinho,3030,151.88 MG,Ferros,10837,1088.8 MG,Fervedouro,10349,357.68 MG,Florestal,6600,191.42 MG,Formiga,65128,1501.92 MG,Formoso,8177,3685.7 MG,Fortaleza de Minas,4098,218.79 MG,Fortuna de Minas,2705,198.71 MG,Francisco Badaró,10248,461.35 MG,Francisco Dumont,4863,1576.13 MG,Francisco Sá,24912,2747.29 MG,Franciscópolis,5800,717.09 MG,Frei Gaspar,5879,626.67 MG,Frei Inocêncio,8920,469.56 MG,Frei Lagonegro,3329,167.47 MG,Fronteira,14041,199.99 MG,Fronteira dos Vales,4687,320.76 MG,Fruta de Leite,5940,762.79 MG,Frutal,53468,2426.97 MG,Funilândia,3855,199.8 MG,Galiléia,6951,720.36 MG,Gameleiras,5139,1733.2 MG,Glaucilândia,2962,145.86 MG,Goiabeira,3053,112.44 MG,Goianá,3659,152.04 MG,Gonçalves,4220,187.35 MG,Gonzaga,5921,209.35 MG,Gouveia,11681,866.6 MG,Governador Valadares,263689,2342.32 MG,Grão Mogol,15024,3885.29 MG,Grupiara,1373,193.14 MG,Guanhães,31262,1075.12 MG,Guapé,13872,934.35 MG,Guaraciaba,10223,348.6 MG,Guaraciama,4718,390.26 MG,Guaranésia,18714,294.83 MG,Guarani,8678,264.19 MG,Guarará,3929,88.66 MG,Guarda-Mor,6565,2069.79 MG,Guaxupé,49430,286.4 MG,Guidoval,7206,158.38 MG,Guimarânia,7265,366.83 MG,Guiricema,8707,293.58 MG,Gurinhatã,6137,1849.14 MG,Heliodora,6121,153.95 MG,Iapu,10315,340.58 MG,Ibertioga,5036,346.24 MG,Ibiá,23218,2704.13 MG,Ibiaí,7839,874.76 MG,Ibiracatu,6155,353.41 MG,Ibiraci,12176,562.09 MG,Ibirité,158954,72.57 MG,Ibitiúra de Minas,3382,68.32 MG,Ibituruna,2866,153.11 MG,Icaraí de Minas,10746,625.66 MG,Igarapé,34851,110.26 MG,Igaratinga,9264,218.34 MG,Iguatama,8029,628.2 MG,Ijaci,5859,105.25 MG,Ilicínea,11488,376.34 MG,Imbé de Minas,6424,196.74 MG,Inconfidentes,6908,149.61 MG,Indaiabira,7330,1004.15 MG,Indianópolis,6190,830.03 MG,Ingaí,2629,305.59 MG,Inhapim,24294,858.02 MG,Inhaúma,5760,245.0 MG,Inimutaba,6724,524.47 MG,Ipaba,16708,113.13 MG,Ipanema,18170,456.64 MG,Ipatinga,239468,164.88 MG,Ipiaçu,4107,466.02 MG,Ipuiúna,9521,298.2 MG,Iraí de Minas,6467,356.26 MG,Itabira,109783,1253.7 MG,Itabirinha,10692,208.98 MG,Itabirito,45449,542.61 MG,Itacambira,4988,1788.45 MG,Itacarambi,17720,1225.27 MG,Itaguara,12372,410.47 MG,Itaipé,11798,480.83 MG,Itajubá,90658,294.84 MG,Itamarandiba,32175,2735.57 MG,Itamarati de Minas,4079,94.57 MG,Itambacuri,22809,1419.21 MG,Itambé do Mato Dentro,2283,380.34 MG,Itamogi,10349,243.69 MG,Itamonte,14003,431.79 MG,Itanhandu,14175,143.36 MG,Itanhomi,11856,488.84 MG,Itaobim,21001,679.02 MG,Itapagipe,13656,1802.44 MG,Itapecerica,21377,1040.52 MG,Itapeva,8664,177.35 MG,Itatiaiuçu,9928,295.15 MG,Itaú de Minas,14945,153.42 MG,Itaúna,85463,495.77 MG,Itaverava,5799,284.22 MG,Itinga,14407,1649.62 MG,Itueta,5830,452.68 MG,Ituiutaba,97171,2598.05 MG,Itumirim,6139,234.8 MG,Iturama,34456,1404.66 MG,Itutinga,3913,372.02 MG,Jaboticatubas,17134,1114.97 MG,Jacinto,12134,1393.61 MG,Jacuí,7502,409.23 MG,Jacutinga,22772,347.75 MG,Jaguaraçu,2990,163.76 MG,Jaíba,33587,2626.33 MG,Jampruca,5067,517.1 MG,Janaúba,66803,2181.32 MG,Januária,65463,6661.67 MG,Japaraíba,3939,172.14 MG,Japonvar,8298,375.23 MG,Jeceaba,5395,236.25 MG,Jenipapo de Minas,7116,284.45 MG,Jequeri,12848,547.9 MG,Jequitaí,8005,1268.44 MG,Jequitibá,5156,445.03 MG,Jequitinhonha,24131,3514.22 MG,Jesuânia,4768,153.85 MG,Joaíma,14941,1664.19 MG,Joanésia,5425,233.29 MG,João Monlevade,73610,99.16 MG,João Pinheiro,45260,10727.47 MG,Joaquim Felício,4305,790.94 MG,Jordânia,10324,546.71 MG,José Gonçalves de Minas,4553,381.33 MG,José Raydan,4376,180.82 MG,Josenópolis,4566,541.39 MG,Juatuba,22202,99.54 MG,Juiz de Fora,516247,1435.66 MG,Juramento,4113,431.63 MG,Juruaia,9238,220.35 MG,Juvenília,5708,1064.7 MG,Ladainha,16994,866.29 MG,Lagamar,7600,1474.56 MG,Lagoa da Prata,45984,439.98 MG,Lagoa dos Patos,4225,600.55 MG,Lagoa Dourada,12256,476.69 MG,Lagoa Formosa,17161,840.92 MG,Lagoa Grande,8631,1236.3 MG,Lagoa Santa,52520,229.27 MG,Lajinha,19609,431.92 MG,Lambari,19554,213.11 MG,Lamim,3452,118.6 MG,Laranjal,6465,204.88 MG,Lassance,6484,3204.22 MG,Lavras,92200,564.74 MG,Leandro Ferreira,3205,352.11 MG,Leme do Prado,4804,280.04 MG,Leopoldina,51130,943.08 MG,Liberdade,5346,401.34 MG,Lima Duarte,16149,848.56 MG,Limeira do Oeste,6890,1319.04 MG,Lontra,8397,258.87 MG,Luisburgo,6234,145.42 MG,Luislândia,6400,411.71 MG,Luminárias,5422,500.14 MG,Luz,17486,1171.66 MG,Machacalis,6976,332.38 MG,Machado,38688,585.96 MG,Madre de Deus de Minas,4904,492.91 MG,Malacacheta,18776,727.89 MG,Mamonas,6321,291.43 MG,Manga,19813,1950.18 MG,Manhuaçu,79574,628.32 MG,Manhumirim,21382,182.9 MG,Mantena,27111,685.21 MG,Mar de Espanha,11749,371.6 MG,Maravilhas,7163,261.6 MG,Maria da Fé,14216,202.9 MG,Mariana,54219,1194.21 MG,Marilac,4219,158.81 MG,Mário Campos,13192,35.2 MG,Maripá de Minas,2788,77.34 MG,Marliéria,4012,545.81 MG,Marmelópolis,2968,107.9 MG,Martinho Campos,12611,1048.1 MG,Martins Soares,7173,113.27 MG,Mata Verde,7874,227.52 MG,Materlândia,4595,280.53 MG,Mateus Leme,27856,302.71 MG,Mathias Lobato,3370,172.3 MG,Matias Barbosa,13435,157.11 MG,Matias Cardoso,9979,1949.74 MG,Matipó,17639,266.99 MG,Mato Verde,12684,472.25 MG,Matozinhos,33955,252.28 MG,Matutina,3761,260.96 MG,Medeiros,3444,946.44 MG,Medina,21026,1435.9 MG,Mendes Pimentel,6331,305.51 MG,Mercês,10368,348.27 MG,Mesquita,6069,274.94 MG,Minas Novas,30794,1812.4 MG,Minduri,3840,219.77 MG,Mirabela,13042,723.28 MG,Miradouro,10251,301.67 MG,Miraí,13808,320.7 MG,Miravânia,4549,602.13 MG,Moeda,4689,155.11 MG,Moema,7028,202.71 MG,Monjolos,2360,650.91 MG,Monsenhor Paulo,8161,216.54 MG,Montalvânia,15862,1503.79 MG,Monte Alegre de Minas,19619,2595.96 MG,Monte Azul,21994,994.23 MG,Monte Belo,13061,421.28 MG,Monte Carmelo,45772,1343.04 MG,Monte Formoso,4656,385.55 MG,Monte Santo de Minas,21234,594.63 MG,Monte Sião,21203,291.59 MG,Montes Claros,361915,3568.94 MG,Montezuma,7464,1130.42 MG,Morada Nova de Minas,8255,2084.28 MG,Morro da Garça,2660,414.77 MG,Morro do Pilar,3399,477.55 MG,Munhoz,6257,191.56 MG,Muriaé,100765,841.69 MG,Mutum,26661,1250.82 MG,Muzambinho,20430,409.95 MG,Nacip Raydan,3154,233.49 MG,Nanuque,40834,1517.94 MG,Naque,6341,127.17 MG,Natalândia,3280,468.66 MG,Natércia,4658,188.72 MG,Nazareno,7954,329.13 MG,Nepomuceno,25733,582.55 MG,Ninheira,9815,1108.23 MG,Nova Belém,3732,146.78 MG,Nova Era,17528,361.93 MG,Nova Lima,80998,429.16 MG,Nova Módica,3790,375.97 MG,Nova Ponte,12812,1111.01 MG,Nova Porteirinha,7398,120.94 MG,Nova Resende,15374,390.15 MG,Nova Serrana,73699,282.37 MG,Nova União,5555,172.13 MG,Novo Cruzeiro,30725,1702.98 MG,Novo Oriente de Minas,10339,755.15 MG,Novorizonte,4963,271.87 MG,Olaria,1976,178.24 MG,Olhos-d`Água,5267,2092.08 MG,Olímpio Noronha,2533,54.63 MG,Oliveira,39466,897.29 MG,Oliveira Fortes,2123,111.13 MG,Onça de Pitangui,3055,246.98 MG,Oratórios,4493,89.07 MG,Orizânia,7284,121.8 MG,Ouro Branco,35268,258.73 MG,Ouro Fino,31568,533.66 MG,Ouro Preto,70281,1245.87 MG,Ouro Verde de Minas,6016,175.48 MG,Padre Carvalho,5834,446.33 MG,Padre Paraíso,18849,544.38 MG,Pai Pedro,5934,839.81 MG,Paineiras,4631,637.31 MG,Pains,8014,421.86 MG,Paiva,1558,58.42 MG,Palma,6545,316.49 MG,Palmópolis,6931,433.15 MG,Papagaios,14175,553.58 MG,Pará de Minas,84215,551.25 MG,Paracatu,84718,8229.6 MG,Paraguaçu,20245,424.3 MG,Paraisópolis,19379,331.24 MG,Paraopeba,22563,625.62 MG,Passa Quatro,15582,277.22 MG,Passa Tempo,8197,429.17 MG,Passa-Vinte,2079,246.56 MG,Passabém,1766,94.18 MG,Passos,106290,1338.07 MG,Patis,5579,444.2 MG,Patos de Minas,138710,3189.77 MG,Patrocínio,82471,2874.34 MG,Patrocínio do Muriaé,5287,108.25 MG,Paula Cândido,9271,268.32 MG,Paulistas,4918,220.56 MG,Pavão,8589,601.19 MG,Peçanha,17260,996.65 MG,Pedra Azul,23839,1594.65 MG,Pedra Bonita,6673,173.93 MG,Pedra do Anta,3365,163.45 MG,Pedra do Indaiá,3875,347.92 MG,Pedra Dourada,2191,69.99 MG,Pedralva,11467,217.99 MG,Pedras de Maria da Cruz,10315,1525.49 MG,Pedrinópolis,3490,357.89 MG,Pedro Leopoldo,58740,292.95 MG,Pedro Teixeira,1785,112.96 MG,Pequeri,3165,90.83 MG,Pequi,4076,203.99 MG,Perdigão,8912,249.32 MG,Perdizes,14404,2450.82 MG,Perdões,20087,270.66 MG,Periquito,7036,228.91 MG,Pescador,4128,317.46 MG,Piau,2841,192.2 MG,Piedade de Caratinga,7110,109.35 MG,Piedade de Ponte Nova,4062,83.73 MG,Piedade do Rio Grande,4709,322.81 MG,Piedade dos Gerais,4640,259.64 MG,Pimenta,8236,414.97 MG,Pingo-d`Água,4420,66.57 MG,Pintópolis,7211,1228.74 MG,Piracema,6406,280.34 MG,Pirajuba,4656,337.98 MG,Piranga,17232,658.81 MG,Piranguçu,5217,203.62 MG,Piranguinho,8016,124.8 MG,Pirapetinga,10364,190.68 MG,Pirapora,53368,549.51 MG,Piraúba,10862,144.29 MG,Pitangui,25311,569.61 MG,Piumhi,31883,902.47 MG,Planura,10384,317.52 MG,Poço Fundo,15959,474.24 MG,Poços de Caldas,152435,547.26 MG,Pocrane,8986,691.07 MG,Pompéu,29105,2551.07 MG,Ponte Nova,57390,470.64 MG,Ponto Chique,3966,602.8 MG,Ponto dos Volantes,11345,1212.41 MG,Porteirinha,37627,1749.68 MG,Porto Firme,10417,284.78 MG,Poté,15667,625.11 MG,Pouso Alegre,130615,543.07 MG,Pouso Alto,6213,263.03 MG,Prados,8391,264.12 MG,Prata,25802,4847.54 MG,Pratápolis,8807,215.52 MG,Pratinha,3265,622.48 MG,Presidente Bernardes,5537,236.8 MG,Presidente Juscelino,3908,695.88 MG,Presidente Kubitschek,2959,189.24 MG,Presidente Olegário,18577,3503.8 MG,Prudente de Morais,9573,124.19 MG,Quartel Geral,3303,556.44 MG,Queluzito,1861,153.56 MG,Raposos,15342,72.07 MG,Raul Soares,23818,763.36 MG,Recreio,10299,234.3 MG,Reduto,6569,151.86 MG,Resende Costa,10913,618.31 MG,Resplendor,17089,1081.8 MG,Ressaquinha,4711,184.61 MG,Riachinho,8007,1719.27 MG,Riacho dos Machados,9360,1315.54 MG,Ribeirão das Neves,296317,155.54 MG,Ribeirão Vermelho,3826,49.25 MG,Rio Acima,9090,229.81 MG,Rio Casca,14201,384.36 MG,Rio do Prado,5217,479.82 MG,Rio Doce,2465,112.09 MG,Rio Espera,6070,238.6 MG,Rio Manso,5276,231.54 MG,Rio Novo,8712,209.31 MG,Rio Paranaíba,11885,1352.35 MG,Rio Pardo de Minas,29099,3117.44 MG,Rio Piracicaba,14149,373.04 MG,Rio Pomba,17110,252.42 MG,Rio Preto,5292,348.14 MG,Rio Vermelho,13645,986.56 MG,Ritápolis,4925,404.81 MG,Rochedo de Minas,2116,79.4 MG,Rodeiro,6867,72.67 MG,Romaria,3596,407.56 MG,Rosário da Limeira,4247,111.16 MG,Rubelita,7772,1110.3 MG,Rubim,9919,965.17 MG,Sabará,126269,302.17 MG,Sabinópolis,15704,919.81 MG,Sacramento,23896,3073.27 MG,Salinas,39178,1887.65 MG,Salto da Divisa,6859,937.92 MG,Santa Bárbara,27876,684.06 MG,Santa Bárbara do Leste,7682,107.4 MG,Santa Bárbara do Monte Verde,2788,417.83 MG,Santa Bárbara do Tugúrio,4570,194.56 MG,Santa Cruz de Minas,7865,3.57 MG,Santa Cruz de Salinas,4397,589.57 MG,Santa Cruz do Escalvado,4992,258.73 MG,Santa Efigênia de Minas,4600,131.97 MG,Santa Fé de Minas,3968,2917.45 MG,Santa Helena de Minas,6055,276.43 MG,Santa Juliana,11337,723.78 MG,Santa Luzia,202942,235.33 MG,Santa Margarida,15011,255.73 MG,Santa Maria de Itabira,10552,597.44 MG,Santa Maria do Salto,5284,440.61 MG,Santa Maria do Suaçuí,14395,624.05 MG,Santa Rita de Caldas,9027,503.01 MG,Santa Rita de Ibitipoca,3583,324.23 MG,Santa Rita de Jacutinga,4993,420.94 MG,Santa Rita de Minas,6547,68.15 MG,Santa Rita do Itueto,5697,485.08 MG,Santa Rita do Sapucaí,37754,352.97 MG,Santa Rosa da Serra,3224,284.33 MG,Santa Vitória,18138,3001.36 MG,Santana da Vargem,7231,172.44 MG,Santana de Cataguases,3622,161.49 MG,Santana de Pirapama,8009,1255.83 MG,Santana do Deserto,3860,182.66 MG,Santana do Garambéu,2234,203.07 MG,Santana do Jacaré,4607,106.17 MG,Santana do Manhuaçu,8582,347.36 MG,Santana do Paraíso,27265,276.07 MG,Santana do Riacho,4023,677.21 MG,Santana dos Montes,3822,196.57 MG,Santo Antônio do Amparo,17345,488.89 MG,Santo Antônio do Aventureiro,3538,202.03 MG,Santo Antônio do Grama,4085,130.21 MG,Santo Antônio do Itambé,4135,305.74 MG,Santo Antônio do Jacinto,11775,503.38 MG,Santo Antônio do Monte,25975,1125.78 MG,Santo Antônio do Retiro,6955,796.29 MG,Santo Antônio do Rio Abaixo,1777,107.27 MG,Santo Hipólito,3238,430.66 MG,Santos Dumont,46284,637.37 MG,São Bento Abade,4577,80.4 MG,São Brás do Suaçuí,3513,110.02 MG,São Domingos das Dores,5408,60.87 MG,São Domingos do Prata,17357,743.77 MG,São Félix de Minas,3382,162.56 MG,São Francisco,53828,3308.1 MG,São Francisco de Paula,6483,316.82 MG,São Francisco de Sales,5776,1128.86 MG,São Francisco do Glória,5178,164.61 MG,São Geraldo,10263,185.58 MG,São Geraldo da Piedade,4389,152.34 MG,São Geraldo do Baixio,3486,280.95 MG,São Gonçalo do Abaeté,6264,2692.17 MG,São Gonçalo do Pará,10398,265.73 MG,São Gonçalo do Rio Abaixo,9777,363.81 MG,São Gonçalo do Rio Preto,3056,314.46 MG,São Gonçalo do Sapucaí,23906,516.68 MG,São Gotardo,31819,866.09 MG,São João Batista do Glória,6887,547.91 MG,São João da Lagoa,4656,998.02 MG,São João da Mata,2731,120.54 MG,São João da Ponte,25358,1851.1 MG,São João das Missões,11715,678.27 MG,São João del Rei,84469,1464.33 MG,São João do Manhuaçu,10245,143.1 MG,São João do Manteninha,5188,137.93 MG,São João do Oriente,7874,120.12 MG,São João do Pacuí,4060,415.92 MG,São João do Paraíso,22319,1925.58 MG,São João Evangelista,15553,478.18 MG,São João Nepomuceno,25057,407.43 MG,São Joaquim de Bicas,25537,71.56 MG,São José da Barra,6778,314.25 MG,São José da Lapa,19799,47.93 MG,São José da Safira,4075,213.88 MG,São José da Varginha,4198,205.5 MG,São José do Alegre,3996,88.79 MG,São José do Divino,3834,328.7 MG,São José do Goiabal,5636,184.51 MG,São José do Jacuri,6553,345.15 MG,São José do Mantimento,2592,54.7 MG,São Lourenço,41657,58.02 MG,São Miguel do Anta,6760,152.11 MG,São Pedro da União,5040,260.83 MG,São Pedro do Suaçuí,5570,308.11 MG,São Pedro dos Ferros,8356,402.76 MG,São Romão,10276,2434.0 MG,São Roque de Minas,6686,2098.87 MG,São Sebastião da Bela Vista,4948,167.16 MG,São Sebastião da Vargem Alegre,2798,73.63 MG,São Sebastião do Anta,5739,80.62 MG,São Sebastião do Maranhão,10647,517.83 MG,São Sebastião do Oeste,5805,408.09 MG,São Sebastião do Paraíso,64980,814.93 MG,São Sebastião do Rio Preto,1613,128.0 MG,São Sebastião do Rio Verde,2110,90.85 MG,São Thomé das Letras,6655,369.75 MG,São Tiago,10561,572.4 MG,São Tomás de Aquino,7093,277.93 MG,São Vicente de Minas,7008,392.65 MG,Sapucaí-Mirim,6241,285.08 MG,Sardoá,5594,141.9 MG,Sarzedo,25814,62.13 MG,Sem-Peixe,2847,176.63 MG,Senador Amaral,5219,151.1 MG,Senador Cortes,1988,98.34 MG,Senador Firmino,7230,166.5 MG,Senador José Bento,1868,93.89 MG,Senador Modestino Gonçalves,4574,952.06 MG,Senhora de Oliveira,5683,170.75 MG,Senhora do Porto,3497,381.33 MG,Senhora dos Remédios,10196,237.82 MG,Sericita,7128,166.01 MG,Seritinga,1789,114.77 MG,Serra Azul de Minas,4220,218.6 MG,Serra da Saudade,815,335.66 MG,Serra do Salitre,10549,1295.27 MG,Serra dos Aimorés,8412,213.55 MG,Serrania,7542,209.27 MG,Serranópolis de Minas,4425,551.95 MG,Serranos,1995,213.17 MG,Serro,20835,1217.81 MG,Sete Lagoas,214152,537.64 MG,Setubinha,10885,534.66 MG,Silveirânia,2192,157.46 MG,Silvianópolis,6027,312.17 MG,Simão Pereira,2537,135.69 MG,Simonésia,18298,486.54 MG,Sobrália,5830,206.79 MG,Soledade de Minas,5676,196.87 MG,Tabuleiro,4079,211.08 MG,Taiobeiras,30917,1194.53 MG,Taparuba,3137,193.08 MG,Tapira,4112,1179.25 MG,Tapiraí,1873,407.92 MG,Taquaraçu de Minas,3794,329.24 MG,Tarumirim,14293,731.75 MG,Teixeiras,11355,166.74 MG,Teófilo Otoni,134745,3242.27 MG,Timóteo,81243,144.38 MG,Tiradentes,6961,83.05 MG,Tiros,6906,2091.77 MG,Tocantins,15823,173.87 MG,Tocos do Moji,3950,114.71 MG,Toledo,5764,136.78 MG,Tombos,9537,285.13 MG,Três Corações,72765,828.04 MG,Três Marias,28318,2678.25 MG,Três Pontas,53860,689.79 MG,Tumiritinga,6293,500.07 MG,Tupaciguara,24188,1823.96 MG,Turmalina,18055,1153.11 MG,Turvolândia,4658,221.0 MG,Ubá,101519,407.45 MG,Ubaí,11681,820.52 MG,Ubaporanga,12040,189.05 MG,Uberaba,295988,4523.96 MG,Uberlândia,604013,4115.21 MG,Umburatiba,2705,405.83 MG,Unaí,77565,8447.11 MG,União de Minas,4418,1147.41 MG,Uruana de Minas,3235,598.5 MG,Urucânia,10291,138.79 MG,Urucuia,13604,2076.94 MG,Vargem Alegre,6461,116.66 MG,Vargem Bonita,2163,409.89 MG,Vargem Grande do Rio Pardo,4733,491.51 MG,Varginha,123081,395.4 MG,Varjão de Minas,6054,651.56 MG,Várzea da Palma,35809,2220.28 MG,Varzelândia,19116,814.99 MG,Vazante,19723,1913.4 MG,Verdelândia,8346,1570.58 MG,Veredinha,5549,631.69 MG,Veríssimo,3483,1031.82 MG,Vermelho Novo,4689,115.24 MG,Vespasiano,104527,71.22 MG,Viçosa,72220,299.42 MG,Vieiras,3731,112.69 MG,Virgem da Lapa,13619,868.91 MG,Virgínia,8623,326.52 MG,Virginópolis,10572,439.88 MG,Virgolândia,5658,281.02 MG,Visconde do Rio Branco,37942,243.35 MG,Volta Grande,5070,208.13 MG,Wenceslau Braz,2553,102.49 PA,Abaetetuba,141100,1610.61 PA,Abel Figueiredo,6780,614.27 PA,Acará,53569,4343.81 PA,Afuá,35042,8372.8 PA,Água Azul do Norte,25057,7113.96 PA,Alenquer,52626,23645.45 PA,Almeirim,33614,72954.8 PA,Altamira,99075,159533.73 PA,Anajás,24759,6921.75 PA,Ananindeua,471980,190.5 PA,Anapu,20543,11895.51 PA,Augusto Corrêa,40497,1091.54 PA,Aurora do Pará,26546,1811.84 PA,Aveiro,15849,17074.04 PA,Bagre,23864,4397.32 PA,Baião,36882,3758.3 PA,Bannach,3431,2956.65 PA,Barcarena,99859,1310.34 PA,Belém,1393399,1059.41 PA,Belterra,16318,4398.42 PA,Benevides,51651,187.83 PA,Bom Jesus do Tocantins,15298,2816.48 PA,Bonito,13630,586.74 PA,Bragança,113227,2091.93 PA,Brasil Novo,15690,6362.58 PA,Brejo Grande do Araguaia,7317,1288.48 PA,Breu Branco,52493,3941.94 PA,Breves,92860,9550.51 PA,Bujaru,25695,1005.17 PA,Cachoeira do Arari,20443,3100.26 PA,Cachoeira do Piriá,26484,2461.97 PA,Cametá,120896,3081.37 PA,Canaã dos Carajás,26716,3146.41 PA,Capanema,63639,614.69 PA,Capitão Poço,51893,2899.55 PA,Castanhal,173149,1028.89 PA,Chaves,21005,13084.96 PA,Colares,11381,609.79 PA,Conceição do Araguaia,45557,5829.48 PA,Concórdia do Pará,28216,690.95 PA,Cumaru do Norte,10466,17085.0 PA,Curionópolis,18288,2368.74 PA,Curralinho,28549,3617.25 PA,Curuá,12254,1431.16 PA,Curuçá,34294,672.68 PA,Dom Eliseu,51319,5268.82 PA,Eldorado dos Carajás,31786,2956.73 PA,Faro,8177,11770.63 PA,Floresta do Araguaia,17768,3444.29 PA,Garrafão do Norte,25034,1599.03 PA,Goianésia do Pará,30436,7023.91 PA,Gurupá,29062,8540.11 PA,Igarapé-Açu,35887,785.98 PA,Igarapé-Miri,58077,1996.84 PA,Inhangapi,10037,471.45 PA,Ipixuna do Pará,51309,5215.56 PA,Irituia,31364,1379.36 PA,Itaituba,97493,62040.71 PA,Itupiranga,51220,7880.11 PA,Jacareacanga,14103,53303.08 PA,Jacundá,51360,2008.32 PA,Juruti,47086,8306.27 PA,Limoeiro do Ajuru,25021,1490.19 PA,Mãe do Rio,27904,469.49 PA,Magalhães Barata,8115,325.27 PA,Marabá,233669,15128.42 PA,Maracanã,28376,855.66 PA,Marapanim,26605,795.99 PA,Marituba,108246,103.34 PA,Medicilândia,27328,8272.63 PA,Melgaço,24808,6774.02 PA,Mocajuba,26731,870.81 PA,Moju,70018,9094.14 PA,Monte Alegre,55462,18152.56 PA,Muaná,34204,3765.55 PA,Nova Esperança do Piriá,20158,2809.31 PA,Nova Ipixuna,14645,1564.18 PA,Nova Timboteua,13670,489.85 PA,Novo Progresso,25124,38162.13 PA,Novo Repartimento,62050,15398.71 PA,Óbidos,49333,28021.42 PA,Oeiras do Pará,28595,3852.29 PA,Oriximiná,62794,107603.29 PA,Ourém,16311,562.39 PA,Ourilândia do Norte,27359,14410.57 PA,Pacajá,39979,11832.33 PA,Palestina do Pará,7475,984.36 PA,Paragominas,97819,19342.25 PA,Parauapebas,153908,6886.21 PA,Pau d`Arco,6033,1671.42 PA,Peixe-Boi,7854,450.22 PA,Piçarra,12697,3312.66 PA,Placas,23934,7173.19 PA,Ponta de Pedras,25999,3365.15 PA,Portel,52172,25384.96 PA,Porto de Moz,33956,17423.02 PA,Prainha,29349,14786.99 PA,Primavera,10268,258.6 PA,Quatipuru,12411,326.11 PA,Redenção,75556,3823.81 PA,Rio Maria,17697,4114.61 PA,Rondon do Pará,46964,8246.44 PA,Rurópolis,40087,7021.32 PA,Salinópolis,37421,237.74 PA,Salvaterra,20183,1039.07 PA,Santa Bárbara do Pará,17141,278.15 PA,Santa Cruz do Arari,8155,1076.65 PA,Santa Isabel do Pará,59466,717.66 PA,Santa Luzia do Pará,19424,1356.12 PA,Santa Maria das Barreiras,17206,10330.21 PA,Santa Maria do Pará,23026,457.73 PA,Santana do Araguaia,56153,11591.56 PA,Santarém,294580,22886.62 PA,Santarém Novo,6141,229.51 PA,Santo Antônio do Tauá,26674,537.63 PA,São Caetano de Odivelas,16891,743.47 PA,São Domingos do Araguaia,23130,1392.46 PA,São Domingos do Capim,29846,1677.25 PA,São Félix do Xingu,91340,84213.28 PA,São Francisco do Pará,15060,479.56 PA,São Geraldo do Araguaia,25587,3168.38 PA,São João da Ponta,5265,195.92 PA,São João de Pirabas,20647,705.54 PA,São João do Araguaia,13155,1279.89 PA,São Miguel do Guamá,51567,1110.18 PA,São Sebastião da Boa Vista,22904,1632.25 PA,Sapucaia,5047,1298.19 PA,Senador José Porfírio,13045,14419.92 PA,Soure,23001,3517.32 PA,Tailândia,79297,4430.22 PA,Terra Alta,10262,206.41 PA,Terra Santa,16949,1896.51 PA,Tomé-Açu,56518,5145.36 PA,Tracuateua,27455,934.27 PA,Trairão,16875,11991.09 PA,Tucumã,33690,2512.59 PA,Tucuruí,97128,2086.19 PA,Ulianópolis,43341,5088.47 PA,Uruará,44789,10791.37 PA,Vigia,47889,539.08 PA,Viseu,56716,4915.07 PA,Vitória do Xingu,13431,3089.54 PA,Xinguara,40573,3779.36 PB,Água Branca,9449,236.61 PB,Aguiar,5530,344.71 PB,Alagoa Grande,28479,320.56 PB,Alagoa Nova,19681,122.26 PB,Alagoinha,13576,96.98 PB,Alcantil,5239,305.39 PB,Algodão de Jandaíra,2366,220.25 PB,Alhandra,18007,182.66 PB,Amparo,2088,121.98 PB,Aparecida,7676,295.71 PB,Araçagi,17224,231.16 PB,Arara,12653,99.11 PB,Araruna,18879,245.72 PB,Areia,23829,269.49 PB,Areia de Baraúnas,1927,96.34 PB,Areial,6470,33.14 PB,Aroeiras,19082,374.7 PB,Assunção,3522,126.43 PB,Baía da Traição,8012,102.37 PB,Bananeiras,21851,257.93 PB,Baraúna,4220,50.58 PB,Barra de Santa Rosa,14157,775.66 PB,Barra de Santana,8206,376.91 PB,Barra de São Miguel,5611,595.21 PB,Bayeux,99716,31.97 PB,Belém,17093,100.15 PB,Belém do Brejo do Cruz,7143,603.04 PB,Bernardino Batista,3075,50.63 PB,Boa Ventura,5751,170.58 PB,Boa Vista,6227,476.54 PB,Bom Jesus,2400,47.63 PB,Bom Sucesso,5035,184.1 PB,Bonito de Santa Fé,10804,228.33 PB,Boqueirão,16888,371.98 PB,Borborema,5111,25.98 PB,Brejo do Cruz,13123,398.92 PB,Brejo dos Santos,6198,93.85 PB,Caaporã,20362,150.17 PB,Cabaceiras,5035,452.92 PB,Cabedelo,57944,31.92 PB,Cachoeira dos Índios,9546,193.07 PB,Cacimba de Areia,3557,220.38 PB,Cacimba de Dentro,16748,163.69 PB,Cacimbas,6814,126.54 PB,Caiçara,7220,127.91 PB,Cajazeiras,58446,565.9 PB,Cajazeirinhas,3033,287.89 PB,Caldas Brandão,5637,55.85 PB,Camalaú,5749,543.69 PB,Campina Grande,385213,594.18 PB,Capim,5601,78.17 PB,Caraúbas,3899,497.2 PB,Carrapateira,2378,54.52 PB,Casserengue,7058,201.38 PB,Catingueira,4812,529.45 PB,Catolé do Rocha,28759,552.11 PB,Caturité,4543,118.08 PB,Conceição,18363,579.44 PB,Condado,6584,280.92 PB,Conde,21400,172.95 PB,Congo,4687,333.47 PB,Coremas,15149,379.49 PB,Coxixola,1771,169.88 PB,Cruz do Espírito Santo,16257,195.6 PB,Cubati,6866,136.97 PB,Cuité,19978,741.84 PB,Cuité de Mamanguape,6202,108.45 PB,Cuitegi,6889,39.3 PB,Curral de Cima,5209,85.1 PB,Curral Velho,2505,222.96 PB,Damião,4900,185.69 PB,Desterro,7991,179.39 PB,Diamante,6616,269.11 PB,Dona Inês,10517,166.17 PB,Duas Estradas,3638,26.26 PB,Emas,3317,240.9 PB,Esperança,31095,163.78 PB,Fagundes,11405,189.03 PB,Frei Martinho,2933,244.32 PB,Gado Bravo,8376,192.41 PB,Guarabira,55326,165.74 PB,Gurinhém,13872,346.07 PB,Gurjão,3159,343.2 PB,Ibiara,6031,244.49 PB,Igaracy,6156,192.26 PB,Imaculada,11352,316.98 PB,Ingá,18180,287.99 PB,Itabaiana,24481,218.85 PB,Itaporanga,23192,468.06 PB,Itapororoca,16997,146.07 PB,Itatuba,10201,244.22 PB,Jacaraú,13942,253.01 PB,Jericó,7538,179.31 PB,João Pessoa,723515,211.48 PB,Joca Claudino,2615,74.01 PB,Juarez Távora,7459,70.84 PB,Juazeirinho,16776,467.53 PB,Junco do Seridó,6643,170.42 PB,Juripiranga,10237,78.85 PB,Juru,9826,403.28 PB,Lagoa,4681,177.9 PB,Lagoa de Dentro,7370,84.51 PB,Lagoa Seca,25900,107.59 PB,Lastro,2841,102.67 PB,Livramento,7164,260.22 PB,Logradouro,3942,38.0 PB,Lucena,11730,88.94 PB,Mãe d`Água,4019,243.75 PB,Malta,5613,156.24 PB,Mamanguape,42303,340.53 PB,Manaíra,10759,352.57 PB,Marcação,7609,122.9 PB,Mari,21176,154.82 PB,Marizópolis,6173,63.61 PB,Massaranduba,12902,205.96 PB,Mataraca,7407,184.3 PB,Matinhas,4321,38.12 PB,Mato Grosso,2702,83.52 PB,Maturéia,5939,83.69 PB,Mogeiro,12491,193.94 PB,Montadas,4990,31.59 PB,Monte Horebe,4508,116.17 PB,Monteiro,30852,986.36 PB,Mulungu,9469,195.31 PB,Natuba,10566,205.04 PB,Nazarezinho,7280,191.49 PB,Nova Floresta,10533,47.38 PB,Nova Olinda,6070,84.25 PB,Nova Palmeira,4361,310.35 PB,Olho d`Água,6931,596.13 PB,Olivedos,3627,317.92 PB,Ouro Velho,2928,129.4 PB,Parari,1256,128.48 PB,Passagem,2233,111.88 PB,Patos,100674,473.06 PB,Paulista,11788,576.9 PB,Pedra Branca,3721,112.93 PB,Pedra Lavrada,7475,351.68 PB,Pedras de Fogo,27032,400.39 PB,Pedro Régis,5765,73.56 PB,Piancó,15465,564.74 PB,Picuí,18222,661.66 PB,Pilar,11191,102.4 PB,Pilões,6978,64.45 PB,Pilõezinhos,5155,43.9 PB,Pirpirituba,10326,79.84 PB,Pitimbu,17024,136.44 PB,Pocinhos,17032,628.08 PB,Poço Dantas,3751,97.25 PB,Poço de José de Moura,3978,100.97 PB,Pombal,32110,888.81 PB,Prata,3854,192.01 PB,Princesa Isabel,21283,367.98 PB,Puxinanã,12923,72.68 PB,Queimadas,41049,401.78 PB,Quixabá,1699,156.68 PB,Remígio,17581,178.0 PB,Riachão,3266,90.15 PB,Riachão do Bacamarte,4264,38.37 PB,Riachão do Poço,4164,39.91 PB,Riacho de Santo Antônio,1722,91.32 PB,Riacho dos Cavalos,8314,264.03 PB,Rio Tinto,22976,464.89 PB,Salgadinho,3508,184.24 PB,Salgado de São Félix,11976,201.85 PB,Santa Cecília,6658,227.88 PB,Santa Cruz,6471,210.17 PB,Santa Helena,5369,210.32 PB,Santa Inês,3539,324.43 PB,Santa Luzia,14719,455.7 PB,Santa Rita,120310,726.85 PB,Santa Teresinha,4581,357.95 PB,Santana de Mangueira,5331,402.15 PB,Santana dos Garrotes,7266,353.82 PB,Santo André,2638,225.17 PB,São Bentinho,4138,195.97 PB,São Bento,30879,248.2 PB,São Domingos,2855,169.11 PB,São Domingos do Cariri,2420,218.8 PB,São Francisco,3364,95.06 PB,São João do Cariri,4344,653.6 PB,São João do Rio do Peixe,18201,474.43 PB,São João do Tigre,4396,816.12 PB,São José da Lagoa Tapada,7564,341.81 PB,São José de Caiana,6010,176.33 PB,São José de Espinharas,4760,725.66 PB,São José de Piranhas,19096,677.31 PB,São José de Princesa,4219,158.02 PB,São José do Bonfim,3233,134.72 PB,São José do Brejo do Cruz,1684,253.02 PB,São José do Sabugi,4010,206.92 PB,São José dos Cordeiros,3985,417.75 PB,São José dos Ramos,5508,98.23 PB,São Mamede,7748,530.73 PB,São Miguel de Taipu,6696,92.53 PB,São Sebastião de Lagoa de Roça,11041,49.92 PB,São Sebastião do Umbuzeiro,3235,460.57 PB,São Vicente do Seridó,10230,276.47 PB,Sapé,50143,315.53 PB,Serra Branca,12973,686.92 PB,Serra da Raiz,3204,29.08 PB,Serra Grande,2975,83.47 PB,Serra Redonda,7050,55.91 PB,Serraria,6238,65.3 PB,Sertãozinho,4395,32.8 PB,Sobrado,7373,61.74 PB,Solânea,26693,232.1 PB,Soledade,13739,560.04 PB,Sossêgo,3169,154.75 PB,Sousa,65803,738.55 PB,Sumé,16060,838.07 PB,Tacima,10262,246.66 PB,Taperoá,14936,662.91 PB,Tavares,14103,237.33 PB,Teixeira,14153,160.9 PB,Tenório,2813,105.27 PB,Triunfo,9220,219.87 PB,Uiraúna,14584,294.5 PB,Umbuzeiro,9298,181.33 PB,Várzea,2504,190.45 PB,Vieirópolis,5045,146.78 PB,Vista Serrana,3512,61.36 PB,Zabelê,2075,109.39 PR,Abatiá,7764,228.72 PR,Adrianópolis,6376,1349.33 PR,Agudos do Sul,8270,192.26 PR,Almirante Tamandaré,103204,194.74 PR,Altamira do Paraná,4306,386.95 PR,Alto Paraíso,3206,967.77 PR,Alto Paraná,13663,407.72 PR,Alto Piquiri,10179,447.67 PR,Altônia,20516,661.56 PR,Alvorada do Sul,10283,424.25 PR,Amaporã,5443,384.74 PR,Ampére,17308,298.35 PR,Anahy,2874,102.65 PR,Andirá,20610,236.08 PR,Ângulo,2859,106.02 PR,Antonina,18891,882.32 PR,Antônio Olinto,7351,469.62 PR,Apucarana,120919,558.39 PR,Arapongas,104150,382.22 PR,Arapoti,25855,1360.49 PR,Arapuã,3561,217.97 PR,Araruna,13419,493.19 PR,Araucária,119123,469.24 PR,Ariranha do Ivaí,2453,239.56 PR,Assaí,16354,440.35 PR,Assis Chateaubriand,33025,969.59 PR,Astorga,24698,434.79 PR,Atalaia,3913,137.66 PR,Balsa Nova,11300,348.93 PR,Bandeirantes,32184,445.19 PR,Barbosa Ferraz,12656,538.64 PR,Barra do Jacaré,2727,115.72 PR,Barracão,9735,171.46 PR,Bela Vista da Caroba,3945,148.11 PR,Bela Vista do Paraíso,15079,242.69 PR,Bituruna,15880,1214.91 PR,Boa Esperança,4568,307.38 PR,Boa Esperança do Iguaçu,2764,151.8 PR,Boa Ventura de São Roque,6554,622.18 PR,Boa Vista da Aparecida,7911,256.3 PR,Bocaiúva do Sul,10987,826.34 PR,Bom Jesus do Sul,3796,173.97 PR,Bom Sucesso,6561,322.76 PR,Bom Sucesso do Sul,3293,195.93 PR,Borrazópolis,7878,334.38 PR,Braganey,5735,343.32 PR,Brasilândia do Sul,3209,291.04 PR,Cafeara,2695,185.8 PR,Cafelândia,14662,271.72 PR,Cafezal do Sul,4290,335.39 PR,Califórnia,8069,141.82 PR,Cambará,23886,366.17 PR,Cambé,96733,494.87 PR,Cambira,7236,163.39 PR,Campina da Lagoa,15394,796.61 PR,Campina do Simão,4076,448.42 PR,Campina Grande do Sul,38769,539.24 PR,Campo Bonito,4407,433.83 PR,Campo do Tenente,7125,304.49 PR,Campo Largo,112377,1249.67 PR,Campo Magro,24843,275.35 PR,Campo Mourão,87194,757.88 PR,Cândido de Abreu,16655,1510.16 PR,Candói,14983,1512.79 PR,Cantagalo,12952,583.54 PR,Capanema,18526,418.71 PR,Capitão Leônidas Marques,14970,275.75 PR,Carambeí,19163,649.68 PR,Carlópolis,13706,451.42 PR,Cascavel,286205,2100.83 PR,Castro,67084,2531.5 PR,Catanduvas,10202,581.76 PR,Centenário do Sul,11190,371.83 PR,Cerro Azul,16938,1341.19 PR,Céu Azul,11032,1179.45 PR,Chopinzinho,19679,959.69 PR,Cianorte,69958,811.67 PR,Cidade Gaúcha,11062,403.05 PR,Clevelândia,17240,703.64 PR,Colombo,212967,197.79 PR,Colorado,22345,403.26 PR,Congonhinhas,8279,535.96 PR,Conselheiro Mairinck,3636,204.71 PR,Contenda,15891,299.04 PR,Corbélia,16312,529.38 PR,Cornélio Procópio,46928,635.1 PR,Coronel Domingos Soares,7238,1576.22 PR,Coronel Vivida,21749,684.42 PR,Corumbataí do Sul,4002,164.34 PR,Cruz Machado,18040,1478.35 PR,Cruzeiro do Iguaçu,4278,161.86 PR,Cruzeiro do Oeste,20416,779.22 PR,Cruzeiro do Sul,4563,259.1 PR,Cruzmaltina,3162,312.3 PR,Curitiba,1751907,435.04 PR,Curiúva,13923,576.26 PR,Diamante do Norte,5516,242.89 PR,Diamante do Sul,3510,359.95 PR,Diamante d`Oeste,5027,309.11 PR,Dois Vizinhos,36179,418.65 PR,Douradina,7445,419.85 PR,Doutor Camargo,5828,118.28 PR,Doutor Ulysses,5727,781.45 PR,Enéas Marques,6103,192.2 PR,Engenheiro Beltrão,13906,467.47 PR,Entre Rios do Oeste,3926,122.07 PR,Esperança Nova,1970,138.56 PR,Espigão Alto do Iguaçu,4677,326.44 PR,Farol,3472,289.23 PR,Faxinal,16314,715.94 PR,Fazenda Rio Grande,81675,116.68 PR,Fênix,4802,234.1 PR,Fernandes Pinheiro,5932,406.5 PR,Figueira,8293,129.77 PR,Flor da Serra do Sul,4726,238.91 PR,Floraí,5050,191.13 PR,Floresta,5931,158.23 PR,Florestópolis,11222,246.33 PR,Flórida,2543,83.05 PR,Formosa do Oeste,7541,275.71 PR,Foz do Iguaçu,256088,617.7 PR,Foz do Jordão,5420,235.38 PR,Francisco Alves,6418,321.9 PR,Francisco Beltrão,78943,735.11 PR,General Carneiro,13669,1071.18 PR,Godoy Moreira,3337,131.01 PR,Goioerê,29018,564.16 PR,Goioxim,7503,702.47 PR,Grandes Rios,6625,314.2 PR,Guaíra,30704,560.49 PR,Guairaçá,6197,493.94 PR,Guamiranga,7900,244.8 PR,Guapirama,3891,189.1 PR,Guaporema,2219,200.19 PR,Guaraci,5227,211.72 PR,Guaraniaçu,14582,1225.61 PR,Guarapuava,167328,3117.01 PR,Guaraqueçaba,7871,2020.09 PR,Guaratuba,32095,1326.79 PR,Honório Serpa,5955,502.24 PR,Ibaiti,28751,897.74 PR,Ibema,6066,145.45 PR,Ibiporã,48198,297.74 PR,Icaraíma,8839,675.24 PR,Iguaraçu,3982,164.98 PR,Iguatu,2234,106.94 PR,Imbaú,11274,330.7 PR,Imbituva,28455,756.54 PR,Inácio Martins,10943,936.21 PR,Inajá,2988,194.7 PR,Indianópolis,4299,122.62 PR,Ipiranga,14150,927.09 PR,Iporã,14981,647.89 PR,Iracema do Oeste,2578,81.54 PR,Irati,56207,999.52 PR,Iretama,10622,570.46 PR,Itaguajé,4568,190.37 PR,Itaipulândia,9026,331.29 PR,Itambaracá,6759,207.34 PR,Itambé,5979,243.82 PR,Itapejara d`Oeste,10531,254.01 PR,Itaperuçu,23887,314.46 PR,Itaúna do Sul,3583,128.87 PR,Ivaí,12815,607.85 PR,Ivaiporã,31816,431.5 PR,Ivaté,7514,410.91 PR,Ivatuba,3010,96.66 PR,Jaboti,4902,139.28 PR,Jacarezinho,39121,602.53 PR,Jaguapitã,12225,475.0 PR,Jaguariaíva,32606,1453.07 PR,Jandaia do Sul,20269,187.6 PR,Janiópolis,6532,335.65 PR,Japira,4903,188.29 PR,Japurá,8549,165.19 PR,Jardim Alegre,12324,405.55 PR,Jardim Olinda,1409,128.52 PR,Jataizinho,11875,159.18 PR,Jesuítas,9001,247.5 PR,Joaquim Távora,10736,289.17 PR,Jundiaí do Sul,3433,320.82 PR,Juranda,7641,349.72 PR,Jussara,6610,210.87 PR,Kaloré,4506,193.3 PR,Lapa,44932,2093.86 PR,Laranjal,6360,559.44 PR,Laranjeiras do Sul,30777,672.08 PR,Leópolis,4145,344.92 PR,Lidianópolis,3973,158.69 PR,Lindoeste,5361,361.37 PR,Loanda,21201,722.5 PR,Lobato,4401,240.9 PR,Londrina,506701,1653.08 PR,Luiziana,7315,908.6 PR,Lunardelli,5160,199.21 PR,Lupionópolis,4592,121.07 PR,Mallet,12973,723.02 PR,Mamborê,13961,788.06 PR,Mandaguaçu,19781,294.02 PR,Mandaguari,32658,335.81 PR,Mandirituba,22220,379.18 PR,Manfrinópolis,3127,216.42 PR,Mangueirinha,17048,1055.46 PR,Manoel Ribas,13169,571.14 PR,Marechal Cândido Rondon,46819,748.0 PR,Maria Helena,5956,486.22 PR,Marialva,31959,475.56 PR,Marilândia do Sul,8863,384.42 PR,Marilena,6858,232.37 PR,Mariluz,10224,433.17 PR,Maringá,357077,487.05 PR,Mariópolis,6268,230.36 PR,Maripá,5684,283.79 PR,Marmeleiro,13900,387.38 PR,Marquinho,4981,511.15 PR,Marumbi,4603,208.47 PR,Matelândia,16078,639.75 PR,Matinhos,29428,117.74 PR,Mato Rico,3818,394.53 PR,Mauá da Serra,8555,108.32 PR,Medianeira,41817,328.73 PR,Mercedes,5046,200.86 PR,Mirador,2327,221.71 PR,Miraselva,1862,90.29 PR,Missal,10474,324.4 PR,Moreira Sales,12606,353.77 PR,Morretes,15718,684.58 PR,Munhoz de Melo,3672,137.02 PR,Nossa Senhora das Graças,3836,185.73 PR,Nova Aliança do Ivaí,1431,131.27 PR,Nova América da Colina,3478,129.48 PR,Nova Aurora,11866,474.01 PR,Nova Cantu,7425,555.49 PR,Nova Esperança,26615,401.59 PR,Nova Esperança do Sudoeste,5098,208.47 PR,Nova Fátima,8147,283.42 PR,Nova Laranjeiras,11241,1145.49 PR,Nova Londrina,13067,269.39 PR,Nova Olímpia,5503,136.35 PR,Nova Prata do Iguaçu,10377,352.57 PR,Nova Santa Bárbara,3908,71.76 PR,Nova Santa Rosa,7626,204.67 PR,Nova Tebas,7398,545.69 PR,Novo Itacolomi,2827,161.41 PR,Ortigueira,23380,2429.56 PR,Ourizona,3380,176.46 PR,Ouro Verde do Oeste,5692,293.04 PR,Paiçandu,35936,171.38 PR,Palmas,42888,1557.89 PR,Palmeira,32123,1457.26 PR,Palmital,14865,817.65 PR,Palotina,28683,651.24 PR,Paraíso do Norte,11772,204.56 PR,Paranacity,10250,348.63 PR,Paranaguá,140469,826.67 PR,Paranapoema,2791,175.88 PR,Paranavaí,81590,1202.27 PR,Pato Bragado,4822,135.29 PR,Pato Branco,72370,539.09 PR,Paula Freitas,5434,421.41 PR,Paulo Frontin,6913,369.86 PR,Peabiru,13624,468.6 PR,Perobal,5653,407.58 PR,Pérola,10208,240.64 PR,Pérola d`Oeste,6761,206.05 PR,Piên,11236,254.79 PR,Pinhais,117008,60.87 PR,Pinhal de São Bento,2625,97.46 PR,Pinhalão,6215,220.63 PR,Pinhão,30208,2001.59 PR,Piraí do Sul,23424,1403.07 PR,Piraquara,93207,227.05 PR,Pitanga,32638,1663.75 PR,Pitangueiras,2814,123.23 PR,Planaltina do Paraná,4095,356.19 PR,Planalto,13654,345.74 PR,Ponta Grossa,311611,2067.55 PR,Pontal do Paraná,20920,199.87 PR,Porecatu,14189,291.67 PR,Porto Amazonas,4514,186.58 PR,Porto Barreiro,3663,361.02 PR,Porto Rico,2530,217.68 PR,Porto Vitória,4020,213.01 PR,Prado Ferreira,3434,153.4 PR,Pranchita,5628,225.84 PR,Presidente Castelo Branco,4784,155.73 PR,Primeiro de Maio,10832,414.44 PR,Prudentópolis,48792,2308.5 PR,Quarto Centenário,4856,321.88 PR,Quatiguá,7045,112.69 PR,Quatro Barras,19851,180.47 PR,Quatro Pontes,3803,114.39 PR,Quedas do Iguaçu,30605,821.5 PR,Querência do Norte,11729,914.76 PR,Quinta do Sol,5088,326.18 PR,Quitandinha,17089,447.02 PR,Ramilândia,4134,237.2 PR,Rancho Alegre,3955,167.65 PR,Rancho Alegre d`Oeste,2847,241.39 PR,Realeza,16338,353.42 PR,Rebouças,14176,481.84 PR,Renascença,6812,425.27 PR,Reserva,25172,1635.52 PR,Reserva do Iguaçu,7307,834.23 PR,Ribeirão Claro,10678,629.22 PR,Ribeirão do Pinhal,13524,374.73 PR,Rio Azul,14093,629.75 PR,Rio Bom,3334,177.84 PR,Rio Bonito do Iguaçu,13661,746.12 PR,Rio Branco do Ivaí,3898,382.33 PR,Rio Branco do Sul,30650,812.29 PR,Rio Negro,31274,604.14 PR,Rolândia,57862,459.02 PR,Roncador,11537,742.12 PR,Rondon,8996,556.09 PR,Rosário do Ivaí,5588,371.25 PR,Sabáudia,6096,190.33 PR,Salgado Filho,4403,189.32 PR,Salto do Itararé,5178,200.52 PR,Salto do Lontra,13689,312.72 PR,Santa Amélia,3803,78.05 PR,Santa Cecília do Pavão,3646,110.2 PR,Santa Cruz de Monte Castelo,8092,442.01 PR,Santa Fé,10432,276.24 PR,Santa Helena,23413,758.23 PR,Santa Inês,1818,138.48 PR,Santa Isabel do Ivaí,8760,349.5 PR,Santa Izabel do Oeste,13132,321.18 PR,Santa Lúcia,3925,116.86 PR,Santa Maria do Oeste,11500,847.14 PR,Santa Mariana,12435,427.19 PR,Santa Mônica,3571,259.96 PR,Santa Tereza do Oeste,10332,326.19 PR,Santa Terezinha de Itaipu,20841,259.39 PR,Santana do Itararé,5249,251.27 PR,Santo Antônio da Platina,42707,721.47 PR,Santo Antônio do Caiuá,2727,219.07 PR,Santo Antônio do Paraíso,2408,165.9 PR,Santo Antônio do Sudoeste,18893,325.74 PR,Santo Inácio,5269,306.87 PR,São Carlos do Ivaí,6354,225.08 PR,São Jerônimo da Serra,11337,823.77 PR,São João,10599,388.06 PR,São João do Caiuá,5911,304.41 PR,São João do Ivaí,11525,353.33 PR,São João do Triunfo,13704,720.41 PR,São Jorge do Ivaí,5517,315.09 PR,São Jorge do Patrocínio,6041,404.69 PR,São Jorge d`Oeste,9085,379.55 PR,São José da Boa Vista,6511,399.67 PR,São José das Palmeiras,3830,182.42 PR,São José dos Pinhais,264210,946.44 PR,São Manoel do Paraná,2098,95.38 PR,São Mateus do Sul,41257,1341.71 PR,São Miguel do Iguaçu,25769,851.3 PR,São Pedro do Iguaçu,6491,308.32 PR,São Pedro do Ivaí,10167,322.69 PR,São Pedro do Paraná,2491,250.65 PR,São Sebastião da Amoreira,8626,227.98 PR,São Tomé,5349,218.62 PR,Sapopema,6736,677.61 PR,Sarandi,82847,103.46 PR,Saudade do Iguaçu,5028,152.09 PR,Sengés,18414,1437.36 PR,Serranópolis do Iguaçu,4568,483.66 PR,Sertaneja,5817,444.49 PR,Sertanópolis,15638,505.53 PR,Siqueira Campos,18454,278.04 PR,Sulina,3394,170.76 PR,Tamarana,12262,472.16 PR,Tamboara,4664,193.35 PR,Tapejara,14598,591.4 PR,Tapira,5836,434.37 PR,Teixeira Soares,10283,902.79 PR,Telêmaco Borba,69872,1382.86 PR,Terra Boa,15776,320.85 PR,Terra Rica,15221,700.59 PR,Terra Roxa,16759,800.81 PR,Tibagi,19344,2951.57 PR,Tijucas do Sul,14537,671.89 PR,Toledo,119313,1197.0 PR,Tomazina,8791,591.44 PR,Três Barras do Paraná,11824,504.17 PR,Tunas do Paraná,6256,668.48 PR,Tuneiras do Oeste,8695,698.87 PR,Tupãssi,7997,310.91 PR,Turvo,13811,916.49 PR,Ubiratã,21558,652.58 PR,Umuarama,100676,1232.77 PR,União da Vitória,52735,720.0 PR,Uniflor,2466,94.82 PR,Uraí,11472,237.81 PR,Ventania,9957,759.37 PR,Vera Cruz do Oeste,8973,327.09 PR,Verê,7878,311.8 PR,Virmond,3950,243.17 PR,Vitorino,6513,308.22 PR,Wenceslau Braz,19298,397.92 PR,Xambrê,6012,359.71 PE,Abreu e Lima,94429,126.19 PE,Afogados da Ingazeira,35088,377.7 PE,Afrânio,17586,1490.6 PE,Agrestina,22679,201.45 PE,Água Preta,33095,533.33 PE,Águas Belas,40235,885.99 PE,Alagoinha,13759,217.83 PE,Aliança,37415,272.79 PE,Altinho,22353,454.48 PE,Amaraji,21939,234.96 PE,Angelim,10202,118.04 PE,Araçoiaba,18156,96.38 PE,Araripina,77302,1892.6 PE,Arcoverde,68793,350.9 PE,Barra de Guabiraba,12776,114.65 PE,Barreiros,40732,233.37 PE,Belém de Maria,11353,73.74 PE,Belém de São Francisco,20253,1830.8 PE,Belo Jardim,72432,647.7 PE,Betânia,12003,1244.07 PE,Bezerros,58668,490.82 PE,Bodocó,35158,1616.5 PE,Bom Conselho,45503,792.19 PE,Bom Jardim,37826,223.18 PE,Bonito,37566,395.61 PE,Brejão,8844,159.79 PE,Brejinho,7307,106.28 PE,Brejo da Madre de Deus,45180,762.35 PE,Buenos Aires,12537,93.19 PE,Buíque,52105,1329.74 PE,Cabo de Santo Agostinho,185025,448.74 PE,Cabrobó,30873,1657.71 PE,Cachoeirinha,18819,179.26 PE,Caetés,26577,329.48 PE,Calçado,11125,121.95 PE,Calumbi,5648,179.31 PE,Camaragibe,144466,51.26 PE,Camocim de São Félix,17104,72.48 PE,Camutanga,8156,37.52 PE,Canhotinho,24521,423.08 PE,Capoeiras,19593,336.33 PE,Carnaíba,18574,427.8 PE,Carnaubeira da Penha,11782,1004.67 PE,Carpina,74858,144.93 PE,Caruaru,314912,920.61 PE,Casinhas,13766,115.87 PE,Catende,37820,207.24 PE,Cedro,10778,148.76 PE,Chã de Alegria,12404,48.55 PE,Chã Grande,20137,84.85 PE,Condado,24282,89.65 PE,Correntes,17419,328.66 PE,Cortês,12452,101.32 PE,Cumaru,17183,292.23 PE,Cupira,23390,105.56 PE,Custódia,33855,1404.13 PE,Dormentes,16917,1537.64 PE,Escada,63517,346.96 PE,Exu,31636,1337.5 PE,Feira Nova,20571,107.73 PE,Fernando de Noronha,2630,17.02 PE,Ferreiros,11430,89.35 PE,Flores,22169,995.56 PE,Floresta,29285,3644.17 PE,Frei Miguelinho,14293,212.71 PE,Gameleira,27912,255.96 PE,Garanhuns,129408,458.55 PE,Glória do Goitá,29019,231.83 PE,Goiana,75644,501.88 PE,Granito,6855,521.94 PE,Gravatá,76458,506.79 PE,Iati,18360,635.14 PE,Ibimirim,26954,1906.44 PE,Ibirajuba,7534,189.6 PE,Igarassu,102021,305.56 PE,Iguaraci,11779,838.13 PE,Ilha de Itamaracá,21884,66.68 PE,Inajá,19081,1182.55 PE,Ingazeira,4496,243.67 PE,Ipojuca,80637,527.11 PE,Ipubi,28120,861.42 PE,Itacuruba,4369,430.03 PE,Itaíba,26256,1084.78 PE,Itambé,35398,304.81 PE,Itapetim,13881,404.85 PE,Itapissuma,23769,74.24 PE,Itaquitinga,15692,103.42 PE,Jaboatão dos Guararapes,644620,258.69 PE,Jaqueira,11501,87.21 PE,Jataúba,15819,672.18 PE,Jatobá,13963,277.86 PE,João Alfredo,30743,135.12 PE,Joaquim Nabuco,15773,121.9 PE,Jucati,10604,120.6 PE,Jupi,13705,104.99 PE,Jurema,14541,148.25 PE,Lagoa do Carro,16007,69.67 PE,Lagoa do Itaenga,20659,57.28 PE,Lagoa do Ouro,12132,198.76 PE,Lagoa dos Gatos,15615,222.87 PE,Lagoa Grande,22760,1848.9 PE,Lajedo,36628,189.1 PE,Limoeiro,55439,273.74 PE,Macaparana,23925,108.05 PE,Machados,13596,60.04 PE,Manari,18083,380.23 PE,Maraial,12230,199.87 PE,Mirandiba,14308,821.68 PE,Moreilândia,11132,404.57 PE,Moreno,56696,196.07 PE,Nazaré da Mata,30796,150.26 PE,Olinda,377779,41.68 PE,Orobó,22878,138.66 PE,Orocó,13180,554.76 PE,Ouricuri,64358,2422.89 PE,Palmares,59526,339.29 PE,Palmeirina,8189,158.02 PE,Panelas,25645,370.94 PE,Paranatama,11001,230.89 PE,Parnamirim,20224,2597.11 PE,Passira,28628,326.76 PE,Paudalho,51357,277.51 PE,Paulista,300466,97.31 PE,Pedra,20944,803.22 PE,Pesqueira,62931,995.54 PE,Petrolândia,32492,1056.6 PE,Petrolina,293962,4561.87 PE,Poção,11242,246.75 PE,Pombos,24046,203.18 PE,Primavera,13439,110.19 PE,Quipapá,24186,230.62 PE,Quixaba,6739,210.71 PE,Recife,1537704,218.44 PE,Riacho das Almas,19162,314.0 PE,Ribeirão,44439,287.9 PE,Rio Formoso,22151,227.46 PE,Sairé,11240,189.37 PE,Salgadinho,9312,87.22 PE,Salgueiro,56629,1686.81 PE,Saloá,15309,252.08 PE,Sanharó,21955,268.69 PE,Santa Cruz,13594,1255.94 PE,Santa Cruz da Baixa Verde,11768,114.93 PE,Santa Cruz do Capibaribe,87582,335.31 PE,Santa Filomena,13371,1005.05 PE,Santa Maria da Boa Vista,39435,3001.18 PE,Santa Maria do Cambucá,13021,92.15 PE,Santa Terezinha,10991,195.59 PE,São Benedito do Sul,13941,160.48 PE,São Bento do Una,53242,719.15 PE,São Caitano,35274,382.47 PE,São João,21312,258.33 PE,São Joaquim do Monte,20488,231.8 PE,São José da Coroa Grande,18180,69.34 PE,São José do Belmonte,32617,1474.09 PE,São José do Egito,31829,798.88 PE,São Lourenço da Mata,102895,262.11 PE,São Vicente Ferrer,17000,113.99 PE,Serra Talhada,79232,2980.01 PE,Serrita,18331,1537.26 PE,Sertânia,33787,2421.53 PE,Sirinhaém,40296,374.61 PE,Solidão,5744,138.4 PE,Surubim,58515,252.86 PE,Tabira,26427,388.01 PE,Tacaimbó,12725,227.6 PE,Tacaratu,22068,1264.53 PE,Tamandaré,20715,214.31 PE,Taquaritinga do Norte,24903,475.18 PE,Terezinha,6737,151.45 PE,Terra Nova,9278,320.5 PE,Timbaúba,53825,291.52 PE,Toritama,35554,25.7 PE,Tracunhaém,13055,118.39 PE,Trindade,26116,229.54 PE,Triunfo,15006,191.52 PE,Tupanatinga,24425,950.47 PE,Tuparetama,7925,178.57 PE,Venturosa,16052,320.73 PE,Verdejante,9142,476.04 PE,Vertente do Lério,7873,73.63 PE,Vertentes,18222,196.33 PE,Vicência,30732,228.02 PE,Vitória de Santo Antão,129974,372.64 PE,Xexéu,14093,110.81 PI,Acauã,6749,1279.59 PI,Agricolândia,5098,112.43 PI,Água Branca,16451,97.04 PI,Alagoinha do Piauí,7341,532.98 PI,Alegrete do Piauí,5153,282.71 PI,Alto Longá,13646,1737.84 PI,Altos,38822,957.66 PI,Alvorada do Gurguéia,5050,2131.92 PI,Amarante,17135,1155.2 PI,Angical do Piauí,6672,223.44 PI,Anísio de Abreu,9098,337.88 PI,Antônio Almeida,3039,645.75 PI,Aroazes,5779,821.66 PI,Aroeiras do Itaim,2440,257.14 PI,Arraial,4688,682.76 PI,Assunção do Piauí,7503,1690.7 PI,Avelino Lopes,11067,1305.52 PI,Baixa Grande do Ribeiro,10516,7808.91 PI,Barra d`Alcântara,3852,263.38 PI,Barras,44850,1719.8 PI,Barreiras do Piauí,3234,2028.29 PI,Barro Duro,6607,131.12 PI,Batalha,25774,1588.9 PI,Bela Vista do Piauí,3778,499.39 PI,Belém do Piauí,3284,243.28 PI,Beneditinos,9911,788.58 PI,Bertolínia,5319,1225.34 PI,Betânia do Piauí,6015,564.71 PI,Boa Hora,6296,337.57 PI,Bocaina,4369,268.58 PI,Bom Jesus,22629,5469.18 PI,Bom Princípio do Piauí,5304,521.57 PI,Bonfim do Piauí,5393,289.21 PI,Boqueirão do Piauí,6193,278.3 PI,Brasileira,7966,880.91 PI,Brejo do Piauí,3850,2183.36 PI,Buriti dos Lopes,19074,691.18 PI,Buriti dos Montes,7974,2652.11 PI,Cabeceiras do Piauí,9928,608.53 PI,Cajazeiras do Piauí,3343,514.36 PI,Cajueiro da Praia,7163,271.71 PI,Caldeirão Grande do Piauí,5671,494.89 PI,Campinas do Piauí,5408,831.2 PI,Campo Alegre do Fidalgo,4693,657.8 PI,Campo Grande do Piauí,5592,311.83 PI,Campo Largo do Piauí,6803,477.8 PI,Campo Maior,45177,1675.71 PI,Canavieira,3921,2162.87 PI,Canto do Buriti,20020,4325.64 PI,Capitão de Campos,10953,592.17 PI,Capitão Gervásio Oliveira,3878,1134.17 PI,Caracol,10212,1610.96 PI,Caraúbas do Piauí,5525,471.45 PI,Caridade do Piauí,4826,501.36 PI,Castelo do Piauí,18336,2035.19 PI,Caxingó,5039,488.17 PI,Cocal,26036,1269.5 PI,Cocal de Telha,4525,282.11 PI,Cocal dos Alves,5572,357.69 PI,Coivaras,3811,485.5 PI,Colônia do Gurguéia,6036,430.62 PI,Colônia do Piauí,7433,947.87 PI,Conceição do Canindé,4475,831.41 PI,Coronel José Dias,4541,1914.82 PI,Corrente,25407,3048.45 PI,Cristalândia do Piauí,7831,1202.9 PI,Cristino Castro,9981,1846.34 PI,Curimatá,10761,2337.54 PI,Currais,4704,3156.66 PI,Curral Novo do Piauí,4869,752.31 PI,Curralinhos,4183,345.85 PI,Demerval Lobão,13278,216.81 PI,Dirceu Arcoverde,6675,1017.06 PI,Dom Expedito Lopes,6569,219.07 PI,Dom Inocêncio,9245,3870.17 PI,Domingos Mourão,4264,846.84 PI,Elesbão Veloso,14512,1347.48 PI,Eliseu Martins,4665,1090.45 PI,Esperantina,37767,911.22 PI,Fartura do Piauí,5074,712.92 PI,Flores do Piauí,4366,946.73 PI,Floresta do Piauí,2482,194.7 PI,Floriano,57690,3409.65 PI,Francinópolis,5235,268.7 PI,Francisco Ayres,4477,656.48 PI,Francisco Macedo,2879,155.28 PI,Francisco Santos,8592,491.86 PI,Fronteiras,11117,775.68 PI,Geminiano,5475,462.52 PI,Gilbués,10402,3494.96 PI,Guadalupe,10268,1023.59 PI,Guaribas,4401,3118.23 PI,Hugo Napoleão,3771,224.46 PI,Ilha Grande,8914,134.32 PI,Inhuma,14845,978.22 PI,Ipiranga do Piauí,9327,527.73 PI,Isaías Coelho,8221,776.05 PI,Itainópolis,11109,828.15 PI,Itaueira,10678,2554.18 PI,Jacobina do Piauí,5722,1370.7 PI,Jaicós,18035,865.14 PI,Jardim do Mulato,4309,509.85 PI,Jatobá do Piauí,4656,653.23 PI,Jerumenha,4390,1867.31 PI,João Costa,2960,1800.24 PI,Joaquim Pires,13817,739.58 PI,Joca Marques,5100,166.44 PI,José de Freitas,37085,1538.18 PI,Juazeiro do Piauí,4757,827.24 PI,Júlio Borges,5373,1297.11 PI,Jurema,4517,1271.89 PI,Lagoa Alegre,8008,394.66 PI,Lagoa de São Francisco,6422,155.64 PI,Lagoa do Barro do Piauí,4523,1261.94 PI,Lagoa do Piauí,3863,426.63 PI,Lagoa do Sítio,4850,804.7 PI,Lagoinha do Piauí,2656,67.5 PI,Landri Sales,5281,1088.58 PI,Luís Correia,28406,1070.93 PI,Luzilândia,24721,704.35 PI,Madeiro,7816,177.15 PI,Manoel Emídio,5213,1618.98 PI,Marcolândia,7812,143.88 PI,Marcos Parente,4456,677.41 PI,Massapê do Piauí,6220,521.13 PI,Matias Olímpio,10473,226.37 PI,Miguel Alves,32289,1393.71 PI,Miguel Leão,1253,93.52 PI,Milton Brandão,6769,1371.74 PI,Monsenhor Gil,10333,568.73 PI,Monsenhor Hipólito,7391,401.43 PI,Monte Alegre do Piauí,10345,2417.93 PI,Morro Cabeça no Tempo,4068,2116.94 PI,Morro do Chapéu do Piauí,6499,328.29 PI,Murici dos Portelas,8464,481.71 PI,Nazaré do Piauí,7321,1315.84 PI,Nazária,8068,363.59 PI,Nossa Senhora de Nazaré,4556,356.26 PI,Nossa Senhora dos Remédios,8206,358.49 PI,Nova Santa Rita,4187,909.74 PI,Novo Oriente do Piauí,6498,525.33 PI,Novo Santo Antônio,3260,481.71 PI,Oeiras,35640,2702.49 PI,Olho d`Água do Piauí,2626,219.6 PI,Padre Marcos,6657,272.04 PI,Paes Landim,4059,401.38 PI,Pajeú do Piauí,3363,1079.17 PI,Palmeira do Piauí,4993,2023.51 PI,Palmeirais,13745,1499.18 PI,Paquetá,4147,448.46 PI,Parnaguá,10276,3429.28 PI,Parnaíba,145705,435.57 PI,Passagem Franca do Piauí,4546,849.61 PI,Patos do Piauí,6105,751.6 PI,Pau d`Arco do Piauí,3757,430.82 PI,Paulistana,19785,1969.96 PI,Pavussu,3663,1090.7 PI,Pedro II,37496,1518.23 PI,Pedro Laurentino,2407,870.34 PI,Picos,73414,534.72 PI,Pimenteiras,11733,4563.13 PI,Pio IX,17671,1947.16 PI,Piracuruca,27553,2380.41 PI,Piripiri,61834,1408.93 PI,Porto,11897,252.72 PI,Porto Alegre do Piauí,2559,1169.44 PI,Prata do Piauí,3085,196.33 PI,Queimada Nova,8553,1352.4 PI,Redenção do Gurguéia,8400,2468.01 PI,Regeneração,17556,1251.04 PI,Riacho Frio,4241,2222.1 PI,Ribeira do Piauí,4263,1004.23 PI,Ribeiro Gonçalves,6845,3978.96 PI,Rio Grande do Piauí,6273,635.95 PI,Santa Cruz do Piauí,6027,611.62 PI,Santa Cruz dos Milagres,3794,979.66 PI,Santa Filomena,6096,5285.44 PI,Santa Luz,5513,1186.84 PI,Santa Rosa do Piauí,5149,340.2 PI,Santana do Piauí,4917,141.12 PI,Santo Antônio de Lisboa,6007,387.4 PI,Santo Antônio dos Milagres,2059,33.15 PI,Santo Inácio do Piauí,3648,852.89 PI,São Braz do Piauí,4313,656.36 PI,São Félix do Piauí,3069,657.24 PI,São Francisco de Assis do Piauí,5567,1100.4 PI,São Francisco do Piauí,6298,1340.67 PI,São Gonçalo do Gurguéia,2825,1385.3 PI,São Gonçalo do Piauí,4754,150.22 PI,São João da Canabrava,4445,480.28 PI,São João da Fronteira,5608,764.87 PI,São João da Serra,6157,1006.5 PI,São João da Varjota,4651,395.31 PI,São João do Arraial,7336,213.36 PI,São João do Piauí,19548,1527.77 PI,São José do Divino,5148,319.13 PI,São José do Peixe,3700,1287.17 PI,São José do Piauí,6591,364.95 PI,São Julião,5675,257.19 PI,São Lourenço do Piauí,4427,672.71 PI,São Luis do Piauí,2561,220.38 PI,São Miguel da Baixa Grande,2110,384.19 PI,São Miguel do Fidalgo,2976,813.44 PI,São Miguel do Tapuio,18134,5207.02 PI,São Pedro do Piauí,13639,518.29 PI,São Raimundo Nonato,32327,2415.6 PI,Sebastião Barros,3560,893.72 PI,Sebastião Leal,4116,3151.59 PI,Sigefredo Pacheco,9619,966.99 PI,Simões,14180,1071.54 PI,Simplício Mendes,12077,1345.79 PI,Socorro do Piauí,4522,761.85 PI,Sussuapara,6229,209.7 PI,Tamboril do Piauí,2753,1587.3 PI,Tanque do Piauí,2620,398.72 PI,Teresina,814230,1391.98 PI,União,42654,1173.45 PI,Uruçuí,20149,8411.91 PI,Valença do Piauí,20326,1334.63 PI,Várzea Branca,4913,450.76 PI,Várzea Grande,4336,237.01 PI,Vera Mendes,2986,341.97 PI,Vila Nova do Piauí,3076,218.32 PI,Wall Ferraz,4280,269.99 RJ,Angra dos Reis,169511,825.09 RJ,Aperibé,10213,94.64 RJ,Araruama,112008,638.02 RJ,Areal,11423,110.92 RJ,Armação dos Búzios,27560,70.28 RJ,Arraial do Cabo,27715,160.29 RJ,Barra do Piraí,94778,578.97 RJ,Barra Mansa,177813,547.23 RJ,Belford Roxo,469332,77.82 RJ,Bom Jardim,25333,384.64 RJ,Bom Jesus do Itabapoana,35411,598.83 RJ,Cabo Frio,186227,410.42 RJ,Cachoeiras de Macacu,54273,953.8 RJ,Cambuci,14827,561.7 RJ,Campos dos Goytacazes,463731,4026.7 RJ,Cantagalo,19830,749.28 RJ,Carapebus,13359,308.13 RJ,Cardoso Moreira,12600,524.63 RJ,Carmo,17434,321.94 RJ,Casimiro de Abreu,35347,460.77 RJ,Comendador Levy Gasparian,8180,106.89 RJ,Conceição de Macabu,21211,347.27 RJ,Cordeiro,20430,116.35 RJ,Duas Barras,10930,375.13 RJ,Duque de Caxias,855048,467.62 RJ,Engenheiro Paulo de Frontin,13237,132.94 RJ,Guapimirim,51483,360.77 RJ,Iguaba Grande,22851,51.95 RJ,Itaboraí,218008,430.37 RJ,Itaguaí,109091,275.87 RJ,Italva,14063,293.82 RJ,Itaocara,22899,431.34 RJ,Itaperuna,95841,1105.34 RJ,Itatiaia,28783,245.15 RJ,Japeri,95492,81.87 RJ,Laje do Muriaé,7487,249.97 RJ,Macaé,206728,1216.85 RJ,Macuco,5269,77.72 RJ,Magé,227322,388.5 RJ,Mangaratiba,36456,356.41 RJ,Maricá,127461,362.57 RJ,Mendes,17935,97.04 RJ,Mesquita,168376,39.06 RJ,Miguel Pereira,24642,289.18 RJ,Miracema,26843,304.51 RJ,Natividade,15082,386.74 RJ,Nilópolis,157425,19.39 RJ,Niterói,487562,133.92 RJ,Nova Friburgo,182082,933.41 RJ,Nova Iguaçu,796257,521.25 RJ,Paracambi,47124,179.68 RJ,Paraíba do Sul,41084,580.53 RJ,Paraty,37533,925.05 RJ,Paty do Alferes,26359,318.8 RJ,Petrópolis,295917,795.8 RJ,Pinheiral,22719,76.53 RJ,Piraí,26314,505.38 RJ,Porciúncula,17760,302.03 RJ,Porto Real,16592,50.75 RJ,Quatis,12793,286.09 RJ,Queimados,137962,75.7 RJ,Quissamã,20242,712.87 RJ,Resende,119769,1095.25 RJ,Rio Bonito,55551,456.46 RJ,Rio Claro,17425,837.27 RJ,Rio das Flores,8561,478.31 RJ,Rio das Ostras,105676,229.04 RJ,Rio de Janeiro,6320446,1200.28 RJ,Santa Maria Madalena,10321,814.76 RJ,Santo Antônio de Pádua,40589,603.36 RJ,São Fidélis,37543,1031.56 RJ,São Francisco de Itabapoana,41354,1122.44 RJ,São Gonçalo,999728,247.71 RJ,São João da Barra,32747,455.04 RJ,São João de Meriti,458673,35.22 RJ,São José de Ubá,7003,250.28 RJ,São José do Vale do Rio Preto,20251,220.43 RJ,São Pedro da Aldeia,87875,332.79 RJ,São Sebastião do Alto,8895,397.9 RJ,Sapucaia,17525,541.71 RJ,Saquarema,74234,353.57 RJ,Seropédica,78186,283.76 RJ,Silva Jardim,21349,937.55 RJ,Sumidouro,14900,395.52 RJ,Tanguá,30732,145.5 RJ,Teresópolis,163746,770.6 RJ,Trajano de Moraes,10289,589.81 RJ,Três Rios,77432,326.14 RJ,Valença,71843,1304.81 RJ,Varre-Sai,9475,190.06 RJ,Vassouras,34410,538.13 RJ,Volta Redonda,257803,182.48 RN,Acari,11035,608.57 RN,Açu,53227,1303.44 RN,Afonso Bezerra,10844,576.18 RN,Água Nova,2980,50.68 RN,Alexandria,13507,381.21 RN,Almino Afonso,4871,128.04 RN,Alto do Rodrigues,12305,191.33 RN,Angicos,11549,741.58 RN,Antônio Martins,6907,244.62 RN,Apodi,34763,1602.48 RN,Areia Branca,25315,357.63 RN,Arês,12924,115.51 RN,Augusto Severo,9289,896.95 RN,Baía Formosa,8573,245.66 RN,Baraúna,24182,825.68 RN,Barcelona,3950,152.63 RN,Bento Fernandes,5113,301.07 RN,Bodó,2425,253.52 RN,Bom Jesus,9440,122.04 RN,Brejinho,11577,61.56 RN,Caiçara do Norte,6016,189.55 RN,Caiçara do Rio do Vento,3308,261.19 RN,Caicó,62709,1228.58 RN,Campo Redondo,10266,213.73 RN,Canguaretama,30916,245.41 RN,Caraúbas,19576,1095.01 RN,Carnaúba dos Dantas,7429,245.65 RN,Carnaubais,9762,542.53 RN,Ceará-Mirim,68141,724.38 RN,Cerro Corá,10916,393.57 RN,Coronel Ezequiel,5405,185.75 RN,Coronel João Pessoa,4772,117.14 RN,Cruzeta,7967,295.83 RN,Currais Novos,42652,864.35 RN,Doutor Severiano,6492,108.28 RN,Encanto,5231,125.75 RN,Equador,5822,264.99 RN,Espírito Santo,10475,135.84 RN,Extremoz,24569,139.58 RN,Felipe Guerra,5734,268.59 RN,Fernando Pedroza,2854,322.63 RN,Florânia,8959,504.8 RN,Francisco Dantas,2874,181.56 RN,Frutuoso Gomes,4233,63.28 RN,Galinhos,2159,342.22 RN,Goianinha,22481,192.28 RN,Governador Dix-Sept Rosado,12374,1129.38 RN,Grossos,9393,126.46 RN,Guamaré,12404,258.96 RN,Ielmo Marinho,12171,312.03 RN,Ipanguaçu,13856,374.25 RN,Ipueira,2077,127.35 RN,Itajá,6932,203.62 RN,Itaú,5564,133.03 RN,Jaçanã,7925,54.56 RN,Jandaíra,6801,435.95 RN,Janduís,5345,304.9 RN,Januário Cicco,9011,187.21 RN,Japi,5522,188.99 RN,Jardim de Angicos,2607,254.02 RN,Jardim de Piranhas,13506,330.53 RN,Jardim do Seridó,12113,368.65 RN,João Câmara,32227,714.96 RN,João Dias,2601,88.17 RN,José da Penha,5868,117.64 RN,Jucurutu,17692,933.73 RN,Jundiá,3582,44.64 RN,Lagoa d`Anta,6227,105.65 RN,Lagoa de Pedras,6989,117.66 RN,Lagoa de Velhos,2668,112.85 RN,Lagoa Nova,13983,176.3 RN,Lagoa Salgada,7564,79.33 RN,Lajes,10381,676.62 RN,Lajes Pintadas,4612,130.21 RN,Lucrécia,3633,30.93 RN,Luís Gomes,9610,166.64 RN,Macaíba,69467,510.77 RN,Macau,28954,788.04 RN,Major Sales,3536,31.97 RN,Marcelino Vieira,8265,345.71 RN,Martins,8218,169.46 RN,Maxaranguape,10441,131.32 RN,Messias Targino,4188,135.1 RN,Montanhas,11413,82.21 RN,Monte Alegre,20685,210.92 RN,Monte das Gameleiras,2261,71.95 RN,Mossoró,259815,2099.33 RN,Natal,803739,167.26 RN,Nísia Floresta,23784,307.84 RN,Nova Cruz,35490,277.66 RN,Olho-d`Água do Borges,4295,141.17 RN,Ouro Branco,4699,253.3 RN,Paraná,3952,81.39 RN,Paraú,3859,383.21 RN,Parazinho,4845,274.67 RN,Parelhas,20354,513.06 RN,Parnamirim,202456,123.47 RN,Passa e Fica,11100,42.14 RN,Passagem,2895,41.22 RN,Patu,11964,319.13 RN,Pau dos Ferros,27745,259.96 RN,Pedra Grande,3521,221.42 RN,Pedra Preta,2590,294.99 RN,Pedro Avelino,7171,952.76 RN,Pedro Velho,14114,192.71 RN,Pendências,13432,419.14 RN,Pilões,3453,82.69 RN,Poço Branco,13949,230.4 RN,Portalegre,7320,110.05 RN,Porto do Mangue,5217,318.97 RN,Presidente Juscelino,8768,167.35 RN,Pureza,8424,504.3 RN,Rafael Fernandes,4692,78.23 RN,Rafael Godeiro,3063,100.07 RN,Riacho da Cruz,3165,127.22 RN,Riacho de Santana,4156,128.11 RN,Riachuelo,7067,262.89 RN,Rio do Fogo,10059,150.26 RN,Rodolfo Fernandes,4418,154.84 RN,Ruy Barbosa,3595,125.81 RN,Santa Cruz,35797,624.36 RN,Santa Maria,4762,219.57 RN,Santana do Matos,13809,1419.54 RN,Santana do Seridó,2526,188.4 RN,Santo Antônio,22216,301.08 RN,São Bento do Norte,2975,288.73 RN,São Bento do Trairí,3905,190.82 RN,São Fernando,3401,404.43 RN,São Francisco do Oeste,3874,75.59 RN,São Gonçalo do Amarante,87668,249.12 RN,São João do Sabugi,5922,277.01 RN,São José de Mipibu,39776,290.33 RN,São José do Campestre,12356,341.12 RN,São José do Seridó,4231,174.51 RN,São Miguel,22157,171.69 RN,São Miguel do Gostoso,8670,343.75 RN,São Paulo do Potengi,15843,240.43 RN,São Pedro,6235,195.24 RN,São Rafael,8111,469.1 RN,São Tomé,10827,862.59 RN,São Vicente,6028,197.82 RN,Senador Elói de Souza,5637,167.61 RN,Senador Georgino Avelino,3924,25.93 RN,Serra de São Bento,5743,96.63 RN,Serra do Mel,10287,616.51 RN,Serra Negra do Norte,7770,562.4 RN,Serrinha,6581,193.35 RN,Serrinha dos Pintos,4540,122.65 RN,Severiano Melo,5752,157.85 RN,Sítio Novo,5020,213.46 RN,Taboleiro Grande,2317,124.09 RN,Taipu,11836,352.82 RN,Tangará,14175,356.83 RN,Tenente Ananias,9883,223.67 RN,Tenente Laurentino Cruz,5406,74.38 RN,Tibau,3687,169.24 RN,Tibau do Sul,11385,101.82 RN,Timbaúba dos Batistas,2295,135.45 RN,Touros,31089,838.67 RN,Triunfo Potiguar,3368,268.73 RN,Umarizal,10659,213.58 RN,Upanema,12992,873.93 RN,Várzea,5236,72.68 RN,Venha-Ver,3821,71.62 RN,Vera Cruz,10719,83.89 RN,Viçosa,1618,37.91 RN,Vila Flor,2872,47.66 RS,Aceguá,4394,1549.38 RS,Água Santa,3722,291.79 RS,Agudo,16722,536.11 RS,Ajuricaba,7255,323.24 RS,Alecrim,7045,314.74 RS,Alegrete,77653,7803.95 RS,Alegria,4301,172.69 RS,Almirante Tamandaré do Sul,2067,265.37 RS,Alpestre,8027,324.64 RS,Alto Alegre,1848,114.45 RS,Alto Feliz,2917,79.17 RS,Alvorada,195673,71.31 RS,Amaral Ferrador,6353,506.46 RS,Ametista do Sul,7323,93.49 RS,André da Rocha,1216,324.33 RS,Anta Gorda,6073,242.96 RS,Antônio Prado,12833,347.62 RS,Arambaré,3693,519.12 RS,Araricá,4864,35.29 RS,Aratiba,6565,342.5 RS,Arroio do Meio,18783,157.96 RS,Arroio do Padre,2730,124.32 RS,Arroio do Sal,7740,120.91 RS,Arroio do Tigre,12648,318.23 RS,Arroio dos Ratos,13606,425.93 RS,Arroio Grande,18470,2513.6 RS,Arvorezinha,10225,271.64 RS,Augusto Pestana,7096,347.44 RS,Áurea,3665,158.29 RS,Bagé,116794,4095.53 RS,Balneário Pinhal,10856,103.76 RS,Barão,5741,124.49 RS,Barão de Cotegipe,6529,260.13 RS,Barão do Triunfo,7018,436.4 RS,Barra do Guarita,3089,64.38 RS,Barra do Quaraí,4012,1056.14 RS,Barra do Ribeiro,12572,728.95 RS,Barra do Rio Azul,2003,147.14 RS,Barra Funda,2367,60.03 RS,Barracão,5357,516.73 RS,Barros Cassal,11133,648.9 RS,Benjamin Constant do Sul,2307,132.4 RS,Bento Gonçalves,107278,381.96 RS,Boa Vista das Missões,2114,194.82 RS,Boa Vista do Buricá,6574,108.73 RS,Boa Vista do Cadeado,2441,701.1 RS,Boa Vista do Incra,2425,503.47 RS,Boa Vista do Sul,2776,94.35 RS,Bom Jesus,11519,2624.67 RS,Bom Princípio,11789,88.5 RS,Bom Progresso,2328,88.74 RS,Bom Retiro do Sul,11472,102.33 RS,Boqueirão do Leão,7673,265.43 RS,Bossoroca,6884,1610.57 RS,Bozano,2200,201.04 RS,Braga,3702,128.99 RS,Brochier,4675,106.73 RS,Butiá,20406,752.25 RS,Caçapava do Sul,33690,3047.11 RS,Cacequi,13676,2369.95 RS,Cachoeira do Sul,83827,3735.16 RS,Cachoeirinha,118278,44.02 RS,Cacique Doble,4868,203.91 RS,Caibaté,4954,259.66 RS,Caiçara,5071,189.2 RS,Camaquã,62764,1679.43 RS,Camargo,2592,138.07 RS,Cambará do Sul,6542,1208.65 RS,Campestre da Serra,3247,538.0 RS,Campina das Missões,6117,225.76 RS,Campinas do Sul,5506,276.16 RS,Campo Bom,60074,60.51 RS,Campo Novo,5459,222.07 RS,Campos Borges,3494,226.58 RS,Candelária,30171,943.95 RS,Cândido Godói,6535,246.28 RS,Candiota,8771,933.83 RS,Canela,39229,253.77 RS,Canguçu,53259,3525.29 RS,Canoas,323827,131.1 RS,Canudos do Vale,1807,81.91 RS,Capão Bonito do Sul,1754,527.12 RS,Capão da Canoa,42040,97.1 RS,Capão do Cipó,3104,1008.65 RS,Capão do Leão,24298,785.37 RS,Capela de Santana,11612,183.76 RS,Capitão,2636,73.97 RS,Capivari do Sul,3890,412.79 RS,Caraá,7312,294.32 RS,Carazinho,59317,665.09 RS,Carlos Barbosa,25192,228.67 RS,Carlos Gomes,1607,83.16 RS,Casca,8651,271.75 RS,Caseiros,3007,235.71 RS,Catuípe,9323,583.26 RS,Caxias do Sul,435564,1644.3 RS,Centenário,2965,134.33 RS,Cerrito,6402,451.7 RS,Cerro Branco,4454,158.77 RS,Cerro Grande,2417,73.44 RS,Cerro Grande do Sul,10268,324.79 RS,Cerro Largo,13289,177.68 RS,Chapada,9377,684.04 RS,Charqueadas,35320,216.51 RS,Charrua,3471,198.12 RS,Chiapetta,4044,396.55 RS,Chuí,5917,202.55 RS,Chuvisca,4944,220.47 RS,Cidreira,12668,245.89 RS,Ciríaco,4922,273.87 RS,Colinas,2420,58.37 RS,Colorado,3550,285.26 RS,Condor,6552,465.19 RS,Constantina,9752,203.0 RS,Coqueiro Baixo,1528,112.28 RS,Coqueiros do Sul,2457,275.55 RS,Coronel Barros,2459,162.95 RS,Coronel Bicaco,7748,492.12 RS,Coronel Pilar,1725,105.45 RS,Cotiporã,3917,172.38 RS,Coxilha,2826,422.79 RS,Crissiumal,14084,362.15 RS,Cristal,7280,681.63 RS,Cristal do Sul,2826,97.72 RS,Cruz Alta,62821,1360.37 RS,Cruzaltense,2141,166.88 RS,Cruzeiro do Sul,12320,155.55 RS,David Canabarro,4683,174.94 RS,Derrubadas,3190,361.2 RS,Dezesseis de Novembro,2866,216.85 RS,Dilermando de Aguiar,3064,600.55 RS,Dois Irmãos,27572,65.16 RS,Dois Irmãos das Missões,2157,225.68 RS,Dois Lajeados,3278,133.37 RS,Dom Feliciano,14380,1356.17 RS,Dom Pedrito,38898,5192.1 RS,Dom Pedro de Alcântara,2550,78.16 RS,Dona Francisca,3401,114.35 RS,Doutor Maurício Cardoso,5313,252.69 RS,Doutor Ricardo,2030,108.43 RS,Eldorado do Sul,34343,509.73 RS,Encantado,20510,139.16 RS,Encruzilhada do Sul,24534,3348.32 RS,Engenho Velho,1527,71.19 RS,Entre-Ijuís,8938,552.6 RS,Entre Rios do Sul,3080,120.07 RS,Erebango,2970,153.12 RS,Erechim,96087,430.67 RS,Ernestina,3088,239.15 RS,Erval Grande,5163,285.73 RS,Erval Seco,7878,363.89 RS,Esmeralda,3168,829.77 RS,Esperança do Sul,3272,148.38 RS,Espumoso,15240,783.07 RS,Estação,6011,100.27 RS,Estância Velha,42574,52.15 RS,Esteio,80755,27.68 RS,Estrela,30619,184.18 RS,Estrela Velha,3628,281.67 RS,Eugênio de Castro,2798,419.32 RS,Fagundes Varela,2579,134.3 RS,Farroupilha,63635,360.39 RS,Faxinal do Soturno,6672,169.9 RS,Faxinalzinho,2567,143.38 RS,Fazenda Vilanova,3697,84.79 RS,Feliz,12359,95.37 RS,Flores da Cunha,27126,273.45 RS,Floriano Peixoto,2018,168.43 RS,Fontoura Xavier,10719,583.47 RS,Formigueiro,7014,581.99 RS,Forquetinha,2479,93.57 RS,Fortaleza dos Valos,4575,650.33 RS,Frederico Westphalen,28843,264.98 RS,Garibaldi,30689,169.24 RS,Garruchos,3234,799.85 RS,Gaurama,5862,204.26 RS,General Câmara,8447,510.01 RS,Gentil,1677,184.01 RS,Getúlio Vargas,16154,286.57 RS,Giruá,17075,855.92 RS,Glorinha,6891,323.64 RS,Gramado,32273,237.83 RS,Gramado dos Loureiros,2269,131.4 RS,Gramado Xavier,3970,217.53 RS,Gravataí,255660,463.5 RS,Guabiju,1598,148.39 RS,Guaíba,95204,376.95 RS,Guaporé,22814,297.66 RS,Guarani das Missões,8115,290.5 RS,Harmonia,4254,44.76 RS,Herval,6753,1757.84 RS,Herveiras,2954,118.28 RS,Horizontina,18348,232.48 RS,Hulha Negra,6043,822.9 RS,Humaitá,4919,134.51 RS,Ibarama,4371,193.11 RS,Ibiaçá,4710,348.82 RS,Ibiraiaras,7171,300.65 RS,Ibirapuitã,4061,307.03 RS,Ibirubá,19310,607.45 RS,Igrejinha,31660,135.86 RS,Ijuí,78915,689.13 RS,Ilópolis,4102,116.48 RS,Imbé,17670,39.4 RS,Imigrante,3023,73.36 RS,Independência,6618,357.44 RS,Inhacorá,2267,114.11 RS,Ipê,6016,599.25 RS,Ipiranga do Sul,1944,157.88 RS,Iraí,8078,180.96 RS,Itaara,5010,172.99 RS,Itacurubi,3441,1120.87 RS,Itapuca,2344,184.25 RS,Itaqui,38159,3404.04 RS,Itati,2584,206.91 RS,Itatiba do Sul,4171,212.24 RS,Ivorá,2156,122.93 RS,Ivoti,19874,63.15 RS,Jaboticaba,4098,128.05 RS,Jacuizinho,2507,338.54 RS,Jacutinga,3633,179.3 RS,Jaguarão,27931,2054.38 RS,Jaguari,11473,673.4 RS,Jaquirana,4177,907.94 RS,Jari,3575,856.46 RS,Jóia,8331,1235.88 RS,Júlio de Castilhos,19579,1929.38 RS,Lagoa Bonita do Sul,2662,108.5 RS,Lagoa dos Três Cantos,1598,138.64 RS,Lagoa Vermelha,27525,1263.5 RS,Lagoão,6185,383.6 RS,Lajeado,71445,90.09 RS,Lajeado do Bugre,2487,67.93 RS,Lavras do Sul,7679,2600.6 RS,Liberato Salzano,5780,245.63 RS,Lindolfo Collor,5227,32.99 RS,Linha Nova,1624,63.73 RS,Maçambara,4738,1682.82 RS,Machadinho,5510,335.03 RS,Mampituba,3003,157.92 RS,Manoel Viana,7072,1390.7 RS,Maquiné,6905,621.69 RS,Maratá,2527,81.18 RS,Marau,36364,649.3 RS,Marcelino Ramos,5134,229.76 RS,Mariana Pimentel,3768,337.79 RS,Mariano Moro,2210,98.98 RS,Marques de Souza,4068,125.18 RS,Mata,5111,311.88 RS,Mato Castelhano,2470,238.37 RS,Mato Leitão,3865,45.9 RS,Mato Queimado,1799,114.64 RS,Maximiliano de Almeida,4911,208.44 RS,Minas do Leão,7631,424.34 RS,Miraguaí,4855,130.39 RS,Montauri,1542,82.08 RS,Monte Alegre dos Campos,3102,549.74 RS,Monte Belo do Sul,2670,68.37 RS,Montenegro,59415,424.01 RS,Mormaço,2749,146.11 RS,Morrinhos do Sul,3182,165.44 RS,Morro Redondo,6227,244.65 RS,Morro Reuter,5676,87.64 RS,Mostardas,12124,1982.99 RS,Muçum,4791,110.89 RS,Muitos Capões,2988,1197.93 RS,Muliterno,1813,111.13 RS,Não-Me-Toque,15936,361.67 RS,Nicolau Vergueiro,1721,155.82 RS,Nonoai,12074,468.91 RS,Nova Alvorada,3182,149.36 RS,Nova Araçá,4001,74.36 RS,Nova Bassano,8840,211.61 RS,Nova Boa Vista,1960,94.24 RS,Nova Bréscia,3184,102.82 RS,Nova Candelária,2751,97.83 RS,Nova Esperança do Sul,4671,191.0 RS,Nova Hartz,18346,62.56 RS,Nova Pádua,2450,103.24 RS,Nova Palma,6342,313.51 RS,Nova Petrópolis,19045,291.3 RS,Nova Prata,22830,258.74 RS,Nova Ramada,2437,254.76 RS,Nova Roma do Sul,3343,149.05 RS,Nova Santa Rita,22716,217.87 RS,Novo Barreiro,3978,123.58 RS,Novo Cabrais,3855,192.29 RS,Novo Hamburgo,238940,223.82 RS,Novo Machado,3925,218.67 RS,Novo Tiradentes,2277,75.4 RS,Novo Xingu,1757,80.59 RS,Osório,40906,663.55 RS,Paim Filho,4243,182.18 RS,Palmares do Sul,10969,949.21 RS,Palmeira das Missões,34328,1419.43 RS,Palmitinho,6920,144.05 RS,Panambi,38058,490.86 RS,Pantano Grande,9895,841.23 RS,Paraí,6812,120.42 RS,Paraíso do Sul,7336,337.84 RS,Pareci Novo,3511,57.41 RS,Parobé,51502,108.65 RS,Passa Sete,5154,304.54 RS,Passo do Sobrado,6011,265.11 RS,Passo Fundo,184826,783.42 RS,Paulo Bento,2196,148.36 RS,Paverama,8044,171.86 RS,Pedras Altas,2212,1377.37 RS,Pedro Osório,7811,608.79 RS,Pejuçara,3973,414.24 RS,Pelotas,328275,1610.08 RS,Picada Café,5182,85.15 RS,Pinhal,2513,68.21 RS,Pinhal da Serra,2130,438.0 RS,Pinhal Grande,4471,477.13 RS,Pinheirinho do Vale,4497,105.61 RS,Pinheiro Machado,12780,2249.56 RS,Pirapó,2757,291.74 RS,Piratini,19841,3539.69 RS,Planalto,10524,230.42 RS,Poço das Antas,2017,65.06 RS,Pontão,3857,505.71 RS,Ponte Preta,1750,99.87 RS,Portão,30920,159.89 RS,Porto Alegre,1409351,496.68 RS,Porto Lucena,5413,250.08 RS,Porto Mauá,2542,105.56 RS,Porto Vera Cruz,1852,113.65 RS,Porto Xavier,10558,280.51 RS,Pouso Novo,1875,106.53 RS,Presidente Lucena,2484,49.43 RS,Progresso,6163,255.86 RS,Protásio Alves,2000,172.82 RS,Putinga,4141,205.05 RS,Quaraí,23021,3147.63 RS,Quatro Irmãos,1775,267.99 RS,Quevedos,2710,543.36 RS,Quinze de Novembro,3653,223.64 RS,Redentora,10222,302.68 RS,Relvado,2155,123.44 RS,Restinga Seca,15849,956.05 RS,Rio dos Índios,3616,235.32 RS,Rio Grande,197228,2709.52 RS,Rio Pardo,37591,2050.59 RS,Riozinho,4330,239.56 RS,Roca Sales,10284,208.63 RS,Rodeio Bonito,5743,83.2 RS,Rolador,2546,295.01 RS,Rolante,19485,295.64 RS,Ronda Alta,10221,419.34 RS,Rondinha,5518,252.21 RS,Roque Gonzales,7203,346.62 RS,Rosário do Sul,39707,4369.65 RS,Sagrada Família,2595,78.25 RS,Saldanha Marinho,2869,221.61 RS,Salto do Jacuí,11880,507.42 RS,Salvador das Missões,2669,94.04 RS,Salvador do Sul,6747,99.82 RS,Sananduva,15373,504.55 RS,Santa Bárbara do Sul,8829,975.51 RS,Santa Cecília do Sul,1655,199.4 RS,Santa Clara do Sul,5697,86.64 RS,Santa Cruz do Sul,118374,733.41 RS,Santa Margarida do Sul,2352,955.3 RS,Santa Maria,261031,1788.12 RS,Santa Maria do Herval,6053,139.6 RS,Santa Rosa,68587,489.8 RS,Santa Tereza,1720,72.39 RS,Santa Vitória do Palmar,30990,5244.35 RS,Santana da Boa Vista,8242,1420.62 RS,Santana do Livramento,82464,6950.35 RS,Santiago,49071,2413.13 RS,Santo Ângelo,76275,680.5 RS,Santo Antônio da Patrulha,39685,1049.81 RS,Santo Antônio das Missões,11210,1710.87 RS,Santo Antônio do Palma,2139,126.09 RS,Santo Antônio do Planalto,1987,203.44 RS,Santo Augusto,13968,468.1 RS,Santo Cristo,14378,366.89 RS,Santo Expedito do Sul,2461,125.74 RS,São Borja,61671,3616.02 RS,São Domingos do Sul,2926,78.95 RS,São Francisco de Assis,19254,2508.45 RS,São Francisco de Paula,20537,3272.98 RS,São Gabriel,60425,5023.82 RS,São Jerônimo,22134,936.38 RS,São João da Urtiga,4726,171.18 RS,São João do Polêsine,2635,85.17 RS,São Jorge,2774,118.05 RS,São José das Missões,2720,98.07 RS,São José do Herval,2204,103.09 RS,São José do Hortêncio,4094,64.11 RS,São José do Inhacorá,2200,77.81 RS,São José do Norte,25503,1118.1 RS,São José do Ouro,6904,334.77 RS,São José do Sul,2082,59.03 RS,São José dos Ausentes,3290,1173.95 RS,São Leopoldo,214087,102.74 RS,São Lourenço do Sul,43111,2036.13 RS,São Luiz Gonzaga,34556,1295.68 RS,São Marcos,20103,256.25 RS,São Martinho,5773,171.66 RS,São Martinho da Serra,3201,669.55 RS,São Miguel das Missões,7421,1229.84 RS,São Nicolau,5727,485.32 RS,São Paulo das Missões,6364,223.89 RS,São Pedro da Serra,3315,35.39 RS,São Pedro das Missões,1886,79.97 RS,São Pedro do Butiá,2873,107.63 RS,São Pedro do Sul,16368,873.59 RS,São Sebastião do Caí,21932,111.44 RS,São Sepé,23798,2200.69 RS,São Valentim,3632,154.19 RS,São Valentim do Sul,2168,92.24 RS,São Valério do Sul,2647,107.97 RS,São Vendelino,1944,32.09 RS,São Vicente do Sul,8440,1175.23 RS,Sapiranga,74985,138.31 RS,Sapucaia do Sul,130957,58.31 RS,Sarandi,21285,353.39 RS,Seberi,10897,301.42 RS,Sede Nova,3011,119.3 RS,Segredo,7158,247.44 RS,Selbach,4929,177.64 RS,Senador Salgado Filho,2814,147.21 RS,Sentinela do Sul,5198,281.96 RS,Serafina Corrêa,14253,163.28 RS,Sério,2281,99.63 RS,Sertão,6294,439.47 RS,Sertão Santana,5850,251.85 RS,Sete de Setembro,2124,129.99 RS,Severiano de Almeida,3842,167.6 RS,Silveira Martins,2449,118.42 RS,Sinimbu,10068,510.12 RS,Sobradinho,14283,130.39 RS,Soledade,30044,1213.41 RS,Tabaí,4131,94.75 RS,Tapejara,19250,238.8 RS,Tapera,10448,179.66 RS,Tapes,16629,806.3 RS,Taquara,54643,457.86 RS,Taquari,26092,349.97 RS,Taquaruçu do Sul,2966,76.85 RS,Tavares,5351,604.25 RS,Tenente Portela,13719,338.08 RS,Terra de Areia,9878,141.77 RS,Teutônia,27272,178.62 RS,Tio Hugo,2724,114.24 RS,Tiradentes do Sul,6461,234.48 RS,Toropi,2952,202.98 RS,Torres,34656,160.57 RS,Tramandaí,41585,144.41 RS,Travesseiro,2314,81.12 RS,Três Arroios,2855,148.58 RS,Três Cachoeiras,10217,251.06 RS,Três Coroas,23848,185.54 RS,Três de Maio,23726,422.2 RS,Três Forquilhas,2914,217.26 RS,Três Palmeiras,4381,180.6 RS,Três Passos,23965,268.4 RS,Trindade do Sul,5787,268.42 RS,Triunfo,25793,818.8 RS,Tucunduva,5898,180.81 RS,Tunas,4395,218.07 RS,Tupanci do Sul,1573,135.12 RS,Tupanciretã,22281,2251.86 RS,Tupandi,3924,59.54 RS,Tuparendi,8557,307.68 RS,Turuçu,3522,253.64 RS,Ubiretama,2296,126.69 RS,União da Serra,1487,130.99 RS,Unistalda,2450,602.39 RS,Uruguaiana,125435,5715.76 RS,Vacaria,61342,2124.58 RS,Vale do Sol,11077,328.23 RS,Vale Real,5118,45.09 RS,Vale Verde,3253,329.73 RS,Vanini,1984,64.87 RS,Venâncio Aires,65946,773.24 RS,Vera Cruz,23983,309.62 RS,Veranópolis,22810,289.34 RS,Vespasiano Correa,1974,113.89 RS,Viadutos,5311,268.36 RS,Viamão,239384,1497.02 RS,Vicente Dutra,5285,193.06 RS,Victor Graeff,3036,238.27 RS,Vila Flores,3207,107.91 RS,Vila Lângaro,2152,152.17 RS,Vila Maria,4221,181.44 RS,Vila Nova do Sul,4221,507.94 RS,Vista Alegre,2832,77.46 RS,Vista Alegre do Prata,1569,119.33 RS,Vista Gaúcha,2759,88.72 RS,Vitória das Missões,3485,259.61 RS,Westfália,2793,64.0 RS,Xangri-lá,12434,60.69 RO,Alta Floresta d`Oeste,24392,7067.03 RO,Alto Alegre dos Parecis,12816,3958.27 RO,Alto Paraíso,17135,2651.82 RO,Alvorada d`Oeste,16853,3029.19 RO,Ariquemes,90353,4426.57 RO,Buritis,32383,3265.81 RO,Cabixi,6313,1314.36 RO,Cacaulândia,5736,1961.78 RO,Cacoal,78574,3792.8 RO,Campo Novo de Rondônia,12665,3442.01 RO,Candeias do Jamari,19779,6843.87 RO,Castanheiras,3575,892.84 RO,Cerejeiras,17029,2783.3 RO,Chupinguaia,8301,5126.72 RO,Colorado do Oeste,18591,1451.06 RO,Corumbiara,8783,3060.32 RO,Costa Marques,13678,4987.18 RO,Cujubim,15854,3863.94 RO,Espigão d`Oeste,28729,4518.03 RO,Governador Jorge Teixeira,10512,5067.38 RO,Guajará-Mirim,41656,24855.72 RO,Itapuã do Oeste,8566,4081.58 RO,Jaru,52005,2944.13 RO,Ji-Paraná,116610,6896.74 RO,Machadinho d`Oeste,31135,8509.31 RO,Ministro Andreazza,10352,798.08 RO,Mirante da Serra,11878,1191.88 RO,Monte Negro,14091,1931.38 RO,Nova Brasilândia d`Oeste,19874,1703.01 RO,Nova Mamoré,22546,10071.64 RO,Nova União,7493,807.13 RO,Novo Horizonte do Oeste,10240,843.45 RO,Ouro Preto do Oeste,37928,1969.85 RO,Parecis,4810,2548.68 RO,Pimenta Bueno,33822,6240.93 RO,Pimenteiras do Oeste,2315,6014.73 RO,Porto Velho,428527,34096.39 RO,Presidente Médici,22319,1758.47 RO,Primavera de Rondônia,3524,605.69 RO,Rio Crespo,3316,1717.64 RO,Rolim de Moura,50648,1457.89 RO,Santa Luzia d`Oeste,8886,1197.8 RO,São Felipe d`Oeste,6018,541.65 RO,São Francisco do Guaporé,16035,10959.77 RO,São Miguel do Guaporé,21828,7460.22 RO,Seringueiras,11629,3773.51 RO,Teixeirópolis,4888,459.98 RO,Theobroma,10649,2197.41 RO,Urupá,12974,831.86 RO,Vale do Anari,9384,3135.14 RO,Vale do Paraíso,8210,965.68 RO,Vilhena,76202,11518.94 RR,Alto Alegre,16448,25567.02 RR,Amajari,9327,28472.33 RR,Boa Vista,284313,5687.04 RR,Bonfim,10943,8095.42 RR,Cantá,13902,7664.83 RR,Caracaraí,18398,47411.03 RR,Caroebe,8114,12065.75 RR,Iracema,8696,14409.58 RR,Mucajaí,14792,12461.21 RR,Normandia,8940,6966.81 RR,Pacaraima,10433,8028.48 RR,Rorainópolis,24279,33594.05 RR,São João da Baliza,6769,4284.51 RR,São Luiz,6750,1526.89 RR,Uiramutã,8375,8065.56 SC,Abdon Batista,2653,235.83 SC,Abelardo Luz,17100,953.06 SC,Agrolândia,9323,207.55 SC,Agronômica,4904,130.53 SC,Água Doce,6961,1314.27 SC,Águas de Chapecó,6110,139.83 SC,Águas Frias,2424,76.14 SC,Águas Mornas,5548,327.36 SC,Alfredo Wagner,9410,732.77 SC,Alto Bela Vista,2005,103.98 SC,Anchieta,6380,228.34 SC,Angelina,5250,500.04 SC,Anita Garibaldi,8623,587.77 SC,Anitápolis,3214,542.12 SC,Antônio Carlos,7458,228.65 SC,Apiúna,9600,493.34 SC,Arabutã,4193,132.84 SC,Araquari,24810,383.99 SC,Araranguá,61310,303.3 SC,Armazém,7753,173.58 SC,Arroio Trinta,3502,94.3 SC,Arvoredo,2260,90.77 SC,Ascurra,7412,110.9 SC,Atalanta,3300,94.19 SC,Aurora,5549,206.61 SC,Balneário Arroio do Silva,9586,95.26 SC,Balneário Barra do Sul,8430,111.27 SC,Balneário Camboriú,108089,46.24 SC,Balneário Gaivota,8234,145.76 SC,Balneário Piçarras,17078,99.41 SC,Bandeirante,2906,147.37 SC,Barra Bonita,1878,93.48 SC,Barra Velha,22386,140.1 SC,Bela Vista do Toldo,6004,538.13 SC,Belmonte,2635,92.39 SC,Benedito Novo,10336,388.8 SC,Biguaçu,58206,370.87 SC,Blumenau,309011,518.5 SC,Bocaina do Sul,3290,512.85 SC,Bom Jardim da Serra,4395,935.87 SC,Bom Jesus,2526,63.47 SC,Bom Jesus do Oeste,2132,67.09 SC,Bom Retiro,8942,1055.55 SC,Bombinhas,14293,35.91 SC,Botuverá,4468,296.19 SC,Braço do Norte,29018,211.86 SC,Braço do Trombudo,3457,90.32 SC,Brunópolis,2850,337.04 SC,Brusque,105503,283.22 SC,Caçador,70762,984.29 SC,Caibi,6219,174.84 SC,Calmon,3387,638.18 SC,Camboriú,62361,212.34 SC,Campo Alegre,11748,499.07 SC,Campo Belo do Sul,7483,1027.65 SC,Campo Erê,9370,479.09 SC,Campos Novos,32824,1719.37 SC,Canelinha,10603,152.56 SC,Canoinhas,52765,1140.4 SC,Capão Alto,2753,1335.84 SC,Capinzal,20769,244.2 SC,Capivari de Baixo,21674,53.34 SC,Catanduvas,9555,197.3 SC,Caxambu do Sul,4411,140.71 SC,Celso Ramos,2771,208.27 SC,Cerro Negro,3581,417.34 SC,Chapadão do Lageado,2762,124.76 SC,Chapecó,183530,626.06 SC,Cocal do Sul,15159,71.13 SC,Concórdia,68621,799.88 SC,Cordilheira Alta,3767,82.86 SC,Coronel Freitas,10213,233.97 SC,Coronel Martins,2458,107.3 SC,Correia Pinto,14785,651.12 SC,Corupá,13852,402.79 SC,Criciúma,192308,235.71 SC,Cunha Porã,10613,217.92 SC,Cunhataí,1882,55.77 SC,Curitibanos,37748,948.74 SC,Descanso,8634,286.14 SC,Dionísio Cerqueira,14811,379.19 SC,Dona Emma,3721,181.17 SC,Doutor Pedrinho,3604,374.63 SC,Entre Rios,3018,104.55 SC,Ermo,2050,63.44 SC,Erval Velho,4352,207.36 SC,Faxinal dos Guedes,10661,339.7 SC,Flor do Sertão,1588,58.89 SC,Florianópolis,421240,675.41 SC,Formosa do Sul,2601,100.11 SC,Forquilhinha,22548,183.13 SC,Fraiburgo,34553,547.85 SC,Frei Rogério,2474,159.22 SC,Galvão,3472,121.96 SC,Garopaba,18138,115.41 SC,Garuva,14761,501.97 SC,Gaspar,57981,386.78 SC,Governador Celso Ramos,12999,117.18 SC,Grão Pará,6223,338.16 SC,Gravatal,10635,164.75 SC,Guabiruba,18430,174.68 SC,Guaraciaba,10498,330.37 SC,Guaramirim,35172,268.5 SC,Guarujá do Sul,4908,100.22 SC,Guatambú,4679,205.88 SC,Herval d`Oeste,21239,217.33 SC,Ibiam,1945,146.72 SC,Ibicaré,3373,155.79 SC,Ibirama,17330,247.35 SC,Içara,58833,293.55 SC,Ilhota,12355,252.88 SC,Imaruí,11672,542.63 SC,Imbituba,40170,182.93 SC,Imbuia,5707,123.04 SC,Indaial,54854,430.79 SC,Iomerê,2739,113.75 SC,Ipira,4752,154.57 SC,Iporã do Oeste,8409,199.72 SC,Ipuaçu,6798,260.89 SC,Ipumirim,7220,247.37 SC,Iraceminha,4253,163.23 SC,Irani,9531,325.74 SC,Irati,2096,78.28 SC,Irineópolis,10448,589.56 SC,Itá,6426,165.84 SC,Itaiópolis,20301,1295.43 SC,Itajaí,183373,288.27 SC,Itapema,45797,57.8 SC,Itapiranga,15409,282.7 SC,Itapoá,14763,248.41 SC,Ituporanga,22250,336.93 SC,Jaborá,4041,191.93 SC,Jacinto Machado,10609,431.38 SC,Jaguaruna,17290,328.35 SC,Jaraguá do Sul,143123,529.54 SC,Jardinópolis,1766,67.68 SC,Joaçaba,27020,232.23 SC,Joinville,515288,1126.11 SC,José Boiteux,4721,405.23 SC,Jupiá,2148,92.05 SC,Lacerdópolis,2199,68.89 SC,Lages,156727,2631.5 SC,Laguna,51562,441.57 SC,Lajeado Grande,1490,65.28 SC,Laurentino,6004,79.59 SC,Lauro Muller,14367,270.78 SC,Lebon Régis,11838,941.49 SC,Leoberto Leal,3365,291.21 SC,Lindóia do Sul,4642,188.64 SC,Lontras,10244,197.11 SC,Luiz Alves,10438,259.88 SC,Luzerna,5600,118.38 SC,Macieira,1826,259.64 SC,Mafra,52912,1404.03 SC,Major Gercino,3279,285.72 SC,Major Vieira,7479,525.5 SC,Maracajá,6404,62.46 SC,Maravilha,22101,171.28 SC,Marema,2203,104.07 SC,Massaranduba,14674,374.08 SC,Matos Costa,2839,433.07 SC,Meleiro,7000,187.06 SC,Mirim Doce,2513,335.73 SC,Modelo,4045,91.11 SC,Mondaí,10231,202.15 SC,Monte Carlo,9312,193.52 SC,Monte Castelo,8346,573.59 SC,Morro da Fumaça,16126,83.12 SC,Morro Grande,2890,258.18 SC,Navegantes,60556,112.02 SC,Nova Erechim,4275,64.89 SC,Nova Itaberaba,4267,137.55 SC,Nova Trento,12190,402.89 SC,Nova Veneza,13309,295.04 SC,Novo Horizonte,2750,151.85 SC,Orleans,21393,548.79 SC,Otacílio Costa,16337,845.01 SC,Ouro,7372,213.67 SC,Ouro Verde,2271,189.22 SC,Paial,1763,85.76 SC,Painel,2353,740.18 SC,Palhoça,137334,395.13 SC,Palma Sola,7765,330.1 SC,Palmeira,2373,289.3 SC,Palmitos,16020,352.51 SC,Papanduva,17928,747.86 SC,Paraíso,4080,181.24 SC,Passo de Torres,6627,95.11 SC,Passos Maia,4425,619.16 SC,Paulo Lopes,6692,449.68 SC,Pedras Grandes,4107,159.31 SC,Penha,25141,58.76 SC,Peritiba,2988,95.84 SC,Petrolândia,6131,305.87 SC,Pinhalzinho,16332,128.16 SC,Pinheiro Preto,3147,65.86 SC,Piratuba,4786,145.98 SC,Planalto Alegre,2654,62.46 SC,Pomerode,27759,214.73 SC,Ponte Alta,4894,568.96 SC,Ponte Alta do Norte,3303,399.24 SC,Ponte Serrada,11031,564.49 SC,Porto Belo,16083,93.63 SC,Porto União,33493,845.34 SC,Pouso Redondo,14810,359.39 SC,Praia Grande,7267,284.13 SC,Presidente Castello Branco,1725,65.61 SC,Presidente Getúlio,14887,294.27 SC,Presidente Nereu,2284,225.66 SC,Princesa,2758,86.15 SC,Quilombo,10248,280.26 SC,Rancho Queimado,2748,286.29 SC,Rio das Antas,6143,318.0 SC,Rio do Campo,6192,506.25 SC,Rio do Oeste,7090,247.81 SC,Rio do Sul,61198,260.36 SC,Rio dos Cedros,10284,554.08 SC,Rio Fortuna,4446,302.87 SC,Rio Negrinho,39846,907.31 SC,Rio Rufino,2436,282.5 SC,Riqueza,4838,191.97 SC,Rodeio,10922,129.93 SC,Romelândia,5551,225.85 SC,Salete,7370,179.35 SC,Saltinho,3961,156.53 SC,Salto Veloso,4301,105.07 SC,Sangão,10400,82.89 SC,Santa Cecília,15757,1145.81 SC,Santa Helena,2382,81.7 SC,Santa Rosa de Lima,2065,202.0 SC,Santa Rosa do Sul,8054,151.03 SC,Santa Terezinha,8767,715.26 SC,Santa Terezinha do Progresso,2896,118.81 SC,Santiago do Sul,1465,73.84 SC,Santo Amaro da Imperatriz,19823,344.05 SC,São Bento do Sul,74801,501.63 SC,São Bernardino,2677,144.86 SC,São Bonifácio,3008,460.36 SC,São Carlos,10291,161.29 SC,São Cristovão do Sul,5012,351.1 SC,São Domingos,9491,384.59 SC,São Francisco do Sul,42520,498.65 SC,São João Batista,26260,221.05 SC,São João do Itaperiú,3435,151.42 SC,São João do Oeste,6036,163.3 SC,São João do Sul,7002,183.36 SC,São Joaquim,24812,1892.26 SC,São José,209804,152.39 SC,São José do Cedro,13684,281.03 SC,São José do Cerrito,9273,944.92 SC,São Lourenço do Oeste,21792,360.48 SC,São Ludgero,10993,107.66 SC,São Martinho,3209,223.89 SC,São Miguel da Boa Vista,1904,71.41 SC,São Miguel do Oeste,36306,234.06 SC,São Pedro de Alcântara,4704,140.02 SC,Saudades,9016,206.6 SC,Schroeder,15316,164.38 SC,Seara,16936,311.39 SC,Serra Alta,3285,92.35 SC,Siderópolis,12998,261.66 SC,Sombrio,26613,143.33 SC,Sul Brasil,2766,112.87 SC,Taió,17260,692.88 SC,Tangará,8674,388.24 SC,Tigrinhos,1757,57.94 SC,Tijucas,30960,279.58 SC,Timbé do Sul,5308,330.09 SC,Timbó,36774,127.41 SC,Timbó Grande,7167,598.47 SC,Três Barras,18129,437.56 SC,Treviso,3527,157.08 SC,Treze de Maio,6876,161.67 SC,Treze Tílias,6341,186.64 SC,Trombudo Central,6553,108.62 SC,Tubarão,97235,301.76 SC,Tunápolis,4633,133.23 SC,Turvo,11854,235.52 SC,União do Oeste,2910,92.62 SC,Urubici,10699,1017.64 SC,Urupema,2482,350.04 SC,Urussanga,20223,254.87 SC,Vargeão,3532,166.65 SC,Vargem,2808,350.15 SC,Vargem Bonita,4793,298.5 SC,Vidal Ramos,6290,342.89 SC,Videira,47188,380.27 SC,Vitor Meireles,5207,370.52 SC,Witmarsum,3600,151.98 SC,Xanxerê,44128,377.76 SC,Xavantina,4142,216.69 SC,Xaxim,25713,293.28 SC,Zortéa,2991,189.72 SP,Adamantina,33797,411.39 SP,Adolfo,3557,211.08 SP,Aguaí,32148,474.74 SP,Águas da Prata,7584,142.96 SP,Águas de Lindóia,17266,60.13 SP,Águas de Santa Bárbara,5601,404.94 SP,Águas de São Pedro,2707,5.54 SP,Agudos,34524,966.16 SP,Alambari,4884,159.27 SP,Alfredo Marcondes,3891,118.4 SP,Altair,3815,313.86 SP,Altinópolis,15607,928.96 SP,Alto Alegre,4102,319.04 SP,Alumínio,16839,83.66 SP,Álvares Florence,3897,362.94 SP,Álvares Machado,23513,347.38 SP,Álvaro de Carvalho,4650,153.17 SP,Alvinlândia,3000,84.8 SP,Americana,210638,133.93 SP,Américo Brasiliense,34478,122.74 SP,Américo de Campos,5706,253.1 SP,Amparo,65829,445.55 SP,Analândia,4293,325.67 SP,Andradina,55334,964.19 SP,Angatuba,22210,1027.98 SP,Anhembi,5653,736.56 SP,Anhumas,3738,320.45 SP,Aparecida,35007,121.08 SP,Aparecida d`Oeste,4450,179.02 SP,Apiaí,25191,974.32 SP,Araçariguama,17080,145.2 SP,Araçatuba,181579,1167.44 SP,Araçoiaba da Serra,27299,255.43 SP,Aramina,5152,202.89 SP,Arandu,6123,285.91 SP,Arapeí,2493,156.9 SP,Araraquara,208662,1003.67 SP,Araras,118843,644.83 SP,Arco-Íris,1925,264.73 SP,Arealva,7841,504.97 SP,Areias,3696,305.23 SP,Areiópolis,10579,85.77 SP,Ariranha,8547,133.15 SP,Artur Nogueira,44177,178.03 SP,Arujá,74905,96.11 SP,Aspásia,1809,69.34 SP,Assis,95144,460.31 SP,Atibaia,126603,478.52 SP,Auriflama,14202,433.99 SP,Avaí,4959,540.46 SP,Avanhandava,11310,338.64 SP,Avaré,82934,1213.06 SP,Bady Bassitt,14603,110.36 SP,Balbinos,3702,91.64 SP,Bálsamo,8160,150.6 SP,Bananal,10223,616.43 SP,Barão de Antonina,3116,153.14 SP,Barbosa,6593,205.15 SP,Bariri,31593,444.07 SP,Barra Bonita,35246,149.91 SP,Barra do Chapéu,5244,405.68 SP,Barra do Turvo,7729,1007.82 SP,Barretos,112101,1565.64 SP,Barrinha,28496,145.64 SP,Barueri,240749,65.69 SP,Bastos,20445,171.89 SP,Batatais,56476,849.53 SP,Bauru,343937,667.68 SP,Bebedouro,75035,683.3 SP,Bento de Abreu,2674,301.4 SP,Bernardino de Campos,10775,244.2 SP,Bertioga,47645,490.15 SP,Bilac,7048,157.9 SP,Birigui,108728,530.92 SP,Biritiba-Mirim,28575,317.41 SP,Boa Esperança do Sul,13645,690.76 SP,Bocaina,10859,363.93 SP,Bofete,9618,653.54 SP,Boituva,48314,248.95 SP,Bom Jesus dos Perdões,19708,108.37 SP,Bom Sucesso de Itararé,3571,133.58 SP,Borá,805,118.45 SP,Boracéia,4268,122.11 SP,Borborema,14529,552.26 SP,Borebi,2293,347.99 SP,Botucatu,127328,1482.64 SP,Bragança Paulista,146744,512.62 SP,Braúna,5021,195.33 SP,Brejo Alegre,2573,105.4 SP,Brodowski,21107,278.46 SP,Brotas,21580,1101.38 SP,Buri,18563,1195.91 SP,Buritama,15418,326.76 SP,Buritizal,4053,266.42 SP,Cabrália Paulista,4365,239.91 SP,Cabreúva,41604,260.23 SP,Caçapava,84752,369.03 SP,Cachoeira Paulista,30091,287.99 SP,Caconde,18538,469.98 SP,Cafelândia,16607,920.1 SP,Caiabu,4072,252.84 SP,Caieiras,86529,96.1 SP,Caiuá,5039,549.89 SP,Cajamar,64114,131.33 SP,Cajati,28372,454.44 SP,Cajobi,9768,176.9 SP,Cajuru,23371,660.09 SP,Campina do Monte Alegre,5567,185.03 SP,Campinas,1080113,794.43 SP,Campo Limpo Paulista,74074,79.4 SP,Campos do Jordão,47789,290.06 SP,Campos Novos Paulista,4539,483.98 SP,Cananéia,12226,1239.38 SP,Canas,4385,53.26 SP,Cândido Mota,29884,596.21 SP,Cândido Rodrigues,2668,70.31 SP,Canitar,4369,57.23 SP,Capão Bonito,46178,1640.23 SP,Capela do Alto,17532,169.89 SP,Capivari,48576,322.88 SP,Caraguatatuba,100840,485.1 SP,Carapicuíba,369584,34.55 SP,Cardoso,11805,639.73 SP,Casa Branca,28307,864.18 SP,Cássia dos Coqueiros,2634,191.68 SP,Castilho,18003,1065.8 SP,Catanduva,112820,290.6 SP,Catiguá,7127,148.39 SP,Cedral,7972,197.69 SP,Cerqueira César,17532,511.62 SP,Cerquilho,39617,127.8 SP,Cesário Lange,15540,190.77 SP,Charqueada,15085,175.85 SP,Chavantes,12114,188.1 SP,Clementina,7065,168.84 SP,Colina,17371,422.57 SP,Colômbia,5994,729.25 SP,Conchal,25229,182.79 SP,Conchas,16288,466.02 SP,Cordeirópolis,21080,137.58 SP,Coroados,5238,246.36 SP,Coronel Macedo,5001,303.93 SP,Corumbataí,3874,278.62 SP,Cosmópolis,58827,154.66 SP,Cosmorama,7214,441.71 SP,Cotia,201150,324.01 SP,Cravinhos,31691,311.4 SP,Cristais Paulista,7588,385.23 SP,Cruzália,2274,149.05 SP,Cruzeiro,77039,305.7 SP,Cubatão,118720,142.88 SP,Cunha,21866,1407.32 SP,Descalvado,31056,753.71 SP,Diadema,386089,30.8 SP,Dirce Reis,1689,88.35 SP,Divinolândia,11208,222.13 SP,Dobrada,7939,149.73 SP,Dois Córregos,24761,632.97 SP,Dolcinópolis,2096,78.34 SP,Dourado,8609,205.87 SP,Dracena,43258,488.04 SP,Duartina,12251,264.56 SP,Dumont,8143,111.36 SP,Echaporã,6318,515.43 SP,Eldorado,14641,1654.26 SP,Elias Fausto,15775,202.69 SP,Elisiário,3120,93.98 SP,Embaúba,2423,83.13 SP,Embu das Artes,240230,70.39 SP,Embu-Guaçu,62769,155.63 SP,Emilianópolis,3020,224.49 SP,Engenheiro Coelho,15721,109.94 SP,Espírito Santo do Pinhal,41907,389.42 SP,Espírito Santo do Turvo,4244,193.66 SP,Estiva Gerbi,10044,74.21 SP,Estrela do Norte,2658,263.42 SP,Estrela d`Oeste,8208,296.41 SP,Euclides da Cunha Paulista,9585,575.21 SP,Fartura,15320,429.17 SP,Fernando Prestes,5534,170.67 SP,Fernandópolis,64696,550.03 SP,Fernão,1563,100.76 SP,Ferraz de Vasconcelos,168306,29.57 SP,Flora Rica,1752,225.3 SP,Floreal,3003,204.3 SP,Flórida Paulista,12848,525.08 SP,Florínia,2829,225.63 SP,Franca,318640,605.68 SP,Francisco Morato,154472,49.07 SP,Franco da Rocha,131604,134.16 SP,Gabriel Monteiro,2708,138.55 SP,Gália,7011,356.01 SP,Garça,43115,555.63 SP,Gastão Vidigal,4193,180.94 SP,Gavião Peixoto,4419,243.77 SP,General Salgado,10669,493.35 SP,Getulina,10765,678.7 SP,Glicério,4565,273.56 SP,Guaiçara,10670,271.14 SP,Guaimbê,5425,218.01 SP,Guaíra,37404,1258.48 SP,Guapiaçu,17869,324.92 SP,Guapiara,17998,408.29 SP,Guará,19858,362.48 SP,Guaraçaí,8435,569.87 SP,Guaraci,9976,641.5 SP,Guarani d`Oeste,1970,85.53 SP,Guarantã,6404,461.15 SP,Guararapes,30597,956.34 SP,Guararema,25844,270.82 SP,Guaratinguetá,112072,752.64 SP,Guareí,14565,566.35 SP,Guariba,35486,270.29 SP,Guarujá,290752,143.45 SP,Guarulhos,1221979,318.68 SP,Guatapará,6966,413.74 SP,Guzolândia,4754,252.01 SP,Herculândia,8696,364.64 SP,Holambra,11299,65.58 SP,Hortolândia,192692,62.28 SP,Iacanga,10013,547.39 SP,Iacri,6419,322.63 SP,Iaras,6376,401.31 SP,Ibaté,30734,290.66 SP,Ibirá,10896,271.91 SP,Ibirarema,6725,228.32 SP,Ibitinga,53158,689.25 SP,Ibiúna,71217,1058.08 SP,Icém,7462,362.59 SP,Iepê,7628,595.49 SP,Igaraçu do Tietê,23362,97.72 SP,Igarapava,27952,468.25 SP,Igaratá,8831,292.95 SP,Iguape,28841,1977.95 SP,Ilha Comprida,9025,191.97 SP,Ilha Solteira,25064,652.45 SP,Ilhabela,28196,347.54 SP,Indaiatuba,201619,312.05 SP,Indiana,4825,126.62 SP,Indiaporã,3903,279.6 SP,Inúbia Paulista,3630,87.41 SP,Ipaussu,13663,209.66 SP,Iperó,28300,170.28 SP,Ipeúna,6016,190.01 SP,Ipiguá,4463,135.69 SP,Iporanga,4299,1152.05 SP,Ipuã,14148,465.88 SP,Iracemápolis,20029,115.12 SP,Irapuã,7275,257.91 SP,Irapuru,7789,214.9 SP,Itaberá,17858,1110.5 SP,Itaí,24008,1082.78 SP,Itajobi,14556,502.07 SP,Itaju,3246,229.82 SP,Itanhaém,87057,601.67 SP,Itaóca,3228,183.02 SP,Itapecerica da Serra,152614,150.87 SP,Itapetininga,144377,1790.21 SP,Itapeva,87753,1826.26 SP,Itapevi,200769,82.66 SP,Itapira,68537,518.39 SP,Itapirapuã Paulista,3880,406.48 SP,Itápolis,40051,996.85 SP,Itaporanga,14549,507.71 SP,Itapuí,12173,140.8 SP,Itapura,4357,301.37 SP,Itaquaquecetuba,321770,82.61 SP,Itararé,47934,1003.58 SP,Itariri,15471,273.67 SP,Itatiba,101471,322.23 SP,Itatinga,18052,979.82 SP,Itirapina,15524,564.76 SP,Itirapuã,5914,161.12 SP,Itobi,7546,139.21 SP,Itu,154147,639.58 SP,Itupeva,44859,200.82 SP,Ituverava,38695,705.24 SP,Jaborandi,6592,273.44 SP,Jaboticabal,71662,706.6 SP,Jacareí,211214,464.27 SP,Jaci,5657,145.52 SP,Jacupiranga,17208,704.09 SP,Jaguariúna,44311,141.4 SP,Jales,47012,368.52 SP,Jambeiro,5349,184.41 SP,Jandira,108344,17.45 SP,Jardinópolis,37661,502.22 SP,Jarinu,23847,207.64 SP,Jaú,131040,685.76 SP,Jeriquara,3160,141.97 SP,Joanópolis,11768,374.28 SP,João Ramalho,4150,415.25 SP,José Bonifácio,32763,859.95 SP,Júlio Mesquita,4430,128.22 SP,Jumirim,2798,56.69 SP,Jundiaí,370126,431.17 SP,Junqueirópolis,18726,582.96 SP,Juquiá,19246,812.75 SP,Juquitiba,28737,522.18 SP,Lagoinha,4841,255.47 SP,Laranjal Paulista,25251,384.02 SP,Lavínia,8779,537.73 SP,Lavrinhas,6590,167.07 SP,Leme,91756,402.87 SP,Lençóis Paulista,61428,809.49 SP,Limeira,276022,580.71 SP,Lindóia,6712,48.76 SP,Lins,71432,571.54 SP,Lorena,82537,414.16 SP,Lourdes,2128,113.74 SP,Louveira,37125,55.13 SP,Lucélia,19882,314.76 SP,Lucianópolis,2249,189.82 SP,Luís Antônio,11286,598.77 SP,Luiziânia,5030,166.55 SP,Lupércio,4353,154.49 SP,Lutécia,2714,474.93 SP,Macatuba,16259,225.21 SP,Macaubal,7663,248.13 SP,Macedônia,3664,327.72 SP,Magda,3200,311.71 SP,Mairinque,43223,210.31 SP,Mairiporã,80956,320.7 SP,Manduri,8992,229.05 SP,Marabá Paulista,4812,918.77 SP,Maracaí,13332,533.94 SP,Marapoama,2633,111.27 SP,Mariápolis,3916,185.9 SP,Marília,216745,1170.25 SP,Marinópolis,2113,77.83 SP,Martinópolis,24219,1252.71 SP,Matão,76786,524.86 SP,Mauá,417064,61.87 SP,Mendonça,4640,195.04 SP,Meridiano,3855,229.25 SP,Mesópolis,1886,148.86 SP,Miguelópolis,20451,821.96 SP,Mineiros do Tietê,12038,213.24 SP,Mira Estrela,2820,216.83 SP,Miracatu,20592,1001.54 SP,Mirandópolis,27483,918.8 SP,Mirante do Paranapanema,17059,1239.08 SP,Mirassol,53792,243.29 SP,Mirassolândia,4295,166.17 SP,Mococa,66290,854.86 SP,Mogi das Cruzes,387779,712.67 SP,Mogi Guaçu,137245,812.16 SP,Moji Mirim,86505,497.8 SP,Mombuca,3266,133.7 SP,Monções,2132,104.24 SP,Mongaguá,46293,142.01 SP,Monte Alegre do Sul,7152,110.31 SP,Monte Alto,46642,346.5 SP,Monte Aprazível,21746,496.91 SP,Monte Azul Paulista,18931,263.44 SP,Monte Castelo,4063,232.57 SP,Monte Mor,48949,240.41 SP,Monteiro Lobato,4120,332.74 SP,Morro Agudo,29116,1388.2 SP,Morungaba,11769,146.75 SP,Motuca,4290,228.7 SP,Murutinga do Sul,4186,250.84 SP,Nantes,2707,286.16 SP,Narandiba,4288,358.03 SP,Natividade da Serra,6678,833.37 SP,Nazaré Paulista,16414,326.29 SP,Neves Paulista,8772,218.34 SP,Nhandeara,10725,435.77 SP,Nipoã,4274,137.82 SP,Nova Aliança,5891,217.31 SP,Nova Campina,8515,385.38 SP,Nova Canaã Paulista,2114,124.42 SP,Nova Castilho,1125,183.23 SP,Nova Europa,9300,160.35 SP,Nova Granada,19180,531.88 SP,Nova Guataporanga,2177,34.12 SP,Nova Independência,3068,265.78 SP,Nova Luzitânia,3441,74.06 SP,Nova Odessa,51242,74.32 SP,Novais,4592,117.77 SP,Novo Horizonte,36593,931.67 SP,Nuporanga,6817,348.27 SP,Ocauçu,4163,300.35 SP,Óleo,2673,198.14 SP,Olímpia,50024,802.65 SP,Onda Verde,3884,242.31 SP,Oriente,6097,218.61 SP,Orindiúva,5675,248.11 SP,Orlândia,39781,291.77 SP,Osasco,666740,64.95 SP,Oscar Bressane,2537,221.34 SP,Osvaldo Cruz,30917,248.39 SP,Ourinhos,103035,296.27 SP,Ouro Verde,7800,267.61 SP,Ouroeste,8405,288.84 SP,Pacaembu,13226,338.5 SP,Palestina,11051,695.46 SP,Palmares Paulista,10934,82.13 SP,Palmeira d`Oeste,9584,319.22 SP,Palmital,21186,547.81 SP,Panorama,14583,356.31 SP,Paraguaçu Paulista,42278,1001.3 SP,Paraibuna,17388,809.58 SP,Paraíso,5898,155.84 SP,Paranapanema,17808,1018.72 SP,Paranapuã,3815,140.48 SP,Parapuã,10844,365.69 SP,Pardinho,5582,209.89 SP,Pariquera-Açu,18446,359.3 SP,Parisi,2032,84.52 SP,Patrocínio Paulista,13000,602.85 SP,Paulicéia,6339,373.57 SP,Paulínia,82146,138.72 SP,Paulistânia,1779,256.65 SP,Paulo de Faria,8589,738.29 SP,Pederneiras,41497,729.0 SP,Pedra Bela,5780,158.59 SP,Pedranópolis,2558,260.19 SP,Pedregulho,15700,712.6 SP,Pedreira,41558,108.59 SP,Pedrinhas Paulista,2940,152.52 SP,Pedro de Toledo,10204,670.44 SP,Penápolis,58510,710.83 SP,Pereira Barreto,24962,978.88 SP,Pereiras,7454,223.27 SP,Peruíbe,59773,324.14 SP,Piacatu,5287,232.36 SP,Piedade,52143,746.87 SP,Pilar do Sul,26406,681.12 SP,Pindamonhangaba,146995,729.89 SP,Pindorama,15039,184.83 SP,Pinhalzinho,13105,154.53 SP,Piquerobi,3537,482.57 SP,Piquete,14107,176.0 SP,Piracaia,25116,385.53 SP,Piracicaba,364571,1378.5 SP,Piraju,28475,504.5 SP,Pirajuí,22704,824.2 SP,Pirangi,10623,215.46 SP,Pirapora do Bom Jesus,15733,108.52 SP,Pirapozinho,24694,477.99 SP,Pirassununga,70081,727.12 SP,Piratininga,12072,402.41 SP,Pitangueiras,35307,430.64 SP,Planalto,4463,290.1 SP,Platina,3192,326.73 SP,Poá,106013,17.26 SP,Poloni,5395,133.54 SP,Pompéia,19964,784.06 SP,Pongaí,3481,183.33 SP,Pontal,40244,356.32 SP,Pontalinda,4074,210.19 SP,Pontes Gestal,2518,217.38 SP,Populina,4223,315.95 SP,Porangaba,8326,265.69 SP,Porto Feliz,48893,556.71 SP,Porto Ferreira,51400,244.91 SP,Potim,19397,44.47 SP,Potirendaba,15449,342.38 SP,Pracinha,2858,62.84 SP,Pradópolis,17377,167.38 SP,Praia Grande,262051,147.07 SP,Pratânia,4599,175.1 SP,Presidente Alves,4123,287.19 SP,Presidente Bernardes,13570,748.95 SP,Presidente Epitácio,41318,1260.24 SP,Presidente Prudente,207610,562.79 SP,Presidente Venceslau,37910,756.74 SP,Promissão,35674,779.28 SP,Quadra,3236,205.68 SP,Quatá,12799,650.37 SP,Queiroz,2808,233.79 SP,Queluz,11309,249.83 SP,Quintana,6004,319.57 SP,Rafard,8612,121.65 SP,Rancharia,28804,1587.47 SP,Redenção da Serra,3873,309.37 SP,Regente Feijó,18494,265.07 SP,Reginópolis,7323,410.82 SP,Registro,54261,722.41 SP,Restinga,6587,245.75 SP,Ribeira,3358,335.75 SP,Ribeirão Bonito,12135,471.55 SP,Ribeirão Branco,18269,697.5 SP,Ribeirão Corrente,4273,148.33 SP,Ribeirão do Sul,4446,203.69 SP,Ribeirão dos Índios,2187,196.34 SP,Ribeirão Grande,7422,333.36 SP,Ribeirão Pires,113068,99.12 SP,Ribeirão Preto,604682,650.96 SP,Rifaina,3436,162.51 SP,Rincão,10414,315.95 SP,Rinópolis,9935,358.33 SP,Rio Claro,186253,498.42 SP,Rio das Pedras,29501,226.66 SP,Rio Grande da Serra,43974,36.34 SP,Riolândia,10575,633.38 SP,Riversul,6163,386.2 SP,Rosana,19691,742.87 SP,Roseira,9599,130.65 SP,Rubiácea,2729,236.93 SP,Rubinéia,2862,242.9 SP,Sabino,5217,310.9 SP,Sagres,2395,147.8 SP,Sales,5451,308.46 SP,Sales Oliveira,10568,305.64 SP,Salesópolis,15635,425.0 SP,Salmourão,4818,172.29 SP,Saltinho,7059,99.74 SP,Salto,105516,133.21 SP,Salto de Pirapora,40132,280.61 SP,Salto Grande,8787,188.4 SP,Sandovalina,3699,455.12 SP,Santa Adélia,14333,330.9 SP,Santa Albertina,5723,272.77 SP,Santa Bárbara d`Oeste,180009,270.9 SP,Santa Branca,13763,272.24 SP,Santa Clara d`Oeste,2084,183.43 SP,Santa Cruz da Conceição,4002,150.13 SP,Santa Cruz da Esperança,1953,148.06 SP,Santa Cruz das Palmeiras,29932,295.34 SP,Santa Cruz do Rio Pardo,43921,1113.5 SP,Santa Ernestina,5568,134.42 SP,Santa Fé do Sul,29239,206.19 SP,Santa Gertrudes,21634,98.29 SP,Santa Isabel,50453,363.3 SP,Santa Lúcia,8248,154.03 SP,Santa Maria da Serra,5413,252.62 SP,Santa Mercedes,2831,166.87 SP,Santa Rita do Passa Quatro,26478,754.14 SP,Santa Rita d`Oeste,2543,210.08 SP,Santa Rosa de Viterbo,23862,288.58 SP,Santa Salete,1447,79.39 SP,Santana da Ponte Pensa,1641,130.26 SP,Santana de Parnaíba,108813,179.93 SP,Santo Anastácio,20475,552.54 SP,Santo André,676407,175.78 SP,Santo Antônio da Alegria,6304,310.29 SP,Santo Antônio de Posse,20650,154.0 SP,Santo Antônio do Aracanguá,7626,1308.24 SP,Santo Antônio do Jardim,5943,109.96 SP,Santo Antônio do Pinhal,6486,133.01 SP,Santo Expedito,2803,94.44 SP,Santópolis do Aguapeí,4277,127.91 SP,Santos,419400,280.67 SP,São Bento do Sapucaí,10468,253.05 SP,São Bernardo do Campo,765463,409.48 SP,São Caetano do Sul,149263,15.33 SP,São Carlos,221950,1137.33 SP,São Francisco,2793,75.62 SP,São João da Boa Vista,83639,516.42 SP,São João das Duas Pontes,2566,129.34 SP,São João de Iracema,1780,178.61 SP,São João do Pau d`Alho,2103,117.72 SP,São Joaquim da Barra,46512,410.6 SP,São José da Bela Vista,8406,276.95 SP,São José do Barreiro,4077,570.69 SP,São José do Rio Pardo,51900,419.19 SP,São José do Rio Preto,408258,431.96 SP,São José dos Campos,629921,1099.41 SP,São Lourenço da Serra,13973,186.33 SP,São Luís do Paraitinga,10397,617.32 SP,São Manuel,38342,650.77 SP,São Miguel Arcanjo,31450,930.34 SP,São Paulo,11253503,1521.1 SP,São Pedro,31662,609.09 SP,São Pedro do Turvo,7198,731.76 SP,São Roque,78821,306.91 SP,São Sebastião,73942,399.68 SP,São Sebastião da Grama,12099,252.38 SP,São Simão,14346,617.25 SP,São Vicente,332445,147.89 SP,Sarapuí,9027,352.69 SP,Sarutaiá,3622,141.61 SP,Sebastianópolis do Sul,3031,168.08 SP,Serra Azul,11256,283.14 SP,Serra Negra,26387,203.74 SP,Serrana,38878,126.05 SP,Sertãozinho,110074,402.87 SP,Sete Barras,13005,1062.7 SP,Severínia,15501,140.43 SP,Silveiras,5792,414.78 SP,Socorro,36686,449.03 SP,Sorocaba,586625,449.8 SP,Sud Mennucci,7435,591.3 SP,Sumaré,241311,153.5 SP,Suzanápolis,3383,330.21 SP,Suzano,262480,206.2 SP,Tabapuã,11363,345.58 SP,Tabatinga,14686,369.56 SP,Taboão da Serra,244528,20.39 SP,Taciba,5714,607.31 SP,Taguaí,10828,145.33 SP,Taiaçu,5894,106.64 SP,Taiúva,5447,132.46 SP,Tambaú,22406,561.79 SP,Tanabi,24055,745.8 SP,Tapiraí,8012,755.1 SP,Tapiratiba,12737,222.54 SP,Taquaral,2726,53.89 SP,Taquaritinga,53988,593.58 SP,Taquarituba,22291,448.43 SP,Taquarivaí,5151,231.79 SP,Tarabai,6607,201.54 SP,Tarumã,12885,303.18 SP,Tatuí,107326,523.48 SP,Taubaté,278686,624.89 SP,Tejupá,4809,296.28 SP,Teodoro Sampaio,21386,1555.99 SP,Terra Roxa,8505,221.54 SP,Tietê,36835,404.4 SP,Timburi,2646,196.79 SP,Torre de Pedra,2254,71.35 SP,Torrinha,9330,315.27 SP,Trabiju,1544,63.42 SP,Tremembé,40984,191.36 SP,Três Fronteiras,5427,151.19 SP,Tuiuti,5930,126.7 SP,Tupã,63476,628.51 SP,Tupi Paulista,14269,245.34 SP,Turiúba,1930,153.13 SP,Turmalina,1978,147.94 SP,Ubarana,5289,209.63 SP,Ubatuba,78801,723.83 SP,Ubirajara,4427,282.37 SP,Uchoa,9471,252.46 SP,União Paulista,1599,79.11 SP,Urânia,8836,208.94 SP,Uru,1251,146.97 SP,Urupês,12714,323.75 SP,Valentim Gentil,11036,149.69 SP,Valinhos,106793,148.59 SP,Valparaíso,22576,857.5 SP,Vargem,8801,142.61 SP,Vargem Grande do Sul,39266,267.23 SP,Vargem Grande Paulista,42997,42.48 SP,Várzea Paulista,107089,35.12 SP,Vera Cruz,10769,248.07 SP,Vinhedo,63611,81.6 SP,Viradouro,17297,217.73 SP,Vista Alegre do Alto,6886,94.98 SP,Vitória Brasil,1737,49.7 SP,Votorantim,108809,184.1 SP,Votuporanga,84692,421.03 SP,Zacarias,2335,319.14 SE,Amparo de São Francisco,2275,35.13 SE,Aquidabã,20056,359.29 SE,Aracaju,571149,181.86 SE,Arauá,10878,198.75 SE,Areia Branca,16857,146.68 SE,Barra dos Coqueiros,24976,90.32 SE,Boquim,25533,205.94 SE,Brejo Grande,7742,148.86 SE,Campo do Brito,16749,201.73 SE,Canhoba,3956,170.29 SE,Canindé de São Francisco,24686,902.25 SE,Capela,30761,442.74 SE,Carira,20007,636.4 SE,Carmópolis,13503,45.91 SE,Cedro de São João,5633,83.71 SE,Cristinápolis,16519,236.19 SE,Cumbe,3813,128.6 SE,Divina Pastora,4326,91.79 SE,Estância,64409,644.08 SE,Feira Nova,5324,184.93 SE,Frei Paulo,13874,400.36 SE,Gararu,11405,654.99 SE,General Maynard,2929,19.98 SE,Gracho Cardoso,5645,242.06 SE,Ilha das Flores,8348,54.64 SE,Indiaroba,15831,313.53 SE,Itabaiana,86967,336.69 SE,Itabaianinha,38910,493.31 SE,Itabi,4972,184.42 SE,Itaporanga d`Ajuda,30419,739.93 SE,Japaratuba,16864,364.9 SE,Japoatã,12938,407.42 SE,Lagarto,94861,969.58 SE,Laranjeiras,26902,162.28 SE,Macambira,6401,136.94 SE,Malhada dos Bois,3456,63.2 SE,Malhador,12042,100.94 SE,Maruim,16343,93.77 SE,Moita Bonita,11001,95.82 SE,Monte Alegre de Sergipe,13627,407.41 SE,Muribeca,7344,75.86 SE,Neópolis,18506,265.95 SE,Nossa Senhora Aparecida,8508,340.38 SE,Nossa Senhora da Glória,32497,756.49 SE,Nossa Senhora das Dores,24580,483.35 SE,Nossa Senhora de Lourdes,6238,81.06 SE,Nossa Senhora do Socorro,160827,156.77 SE,Pacatuba,13137,373.82 SE,Pedra Mole,2974,82.03 SE,Pedrinhas,8833,33.94 SE,Pinhão,5973,155.89 SE,Pirambu,8369,205.88 SE,Poço Redondo,30880,1232.12 SE,Poço Verde,21983,440.13 SE,Porto da Folha,27146,877.3 SE,Propriá,28451,89.12 SE,Riachão do Dantas,19386,531.47 SE,Riachuelo,9355,78.94 SE,Ribeirópolis,17173,258.53 SE,Rosário do Catete,9221,105.66 SE,Salgado,19365,247.83 SE,Santa Luzia do Itanhy,12969,325.73 SE,Santa Rosa de Lima,3749,67.61 SE,Santana do São Francisco,7038,45.62 SE,Santo Amaro das Brotas,11410,234.16 SE,São Cristóvão,78864,436.86 SE,São Domingos,10271,102.47 SE,São Francisco,3393,83.85 SE,São Miguel do Aleixo,3698,144.09 SE,Simão Dias,38702,564.69 SE,Siriri,8004,165.81 SE,Telha,2957,49.03 SE,Tobias Barreto,48040,1021.31 SE,Tomar do Geru,12855,304.9 SE,Umbaúba,22434,118.86 TO,Abreulândia,2391,1895.21 TO,Aguiarnópolis,5162,235.39 TO,Aliança do Tocantins,5671,1579.75 TO,Almas,7586,4013.24 TO,Alvorada,8374,1212.17 TO,Ananás,9865,1576.97 TO,Angico,3175,451.73 TO,Aparecida do Rio Negro,4213,1160.37 TO,Aragominas,5882,1173.06 TO,Araguacema,6317,2778.48 TO,Araguaçu,8786,5167.95 TO,Araguaína,150484,4000.42 TO,Araguanã,5030,836.03 TO,Araguatins,31329,2625.29 TO,Arapoema,6742,1552.22 TO,Arraias,10645,5786.87 TO,Augustinópolis,15950,394.98 TO,Aurora do Tocantins,3446,752.83 TO,Axixá do Tocantins,9275,150.21 TO,Babaçulândia,10424,1788.46 TO,Bandeirantes do Tocantins,3122,1541.84 TO,Barra do Ouro,4123,1106.35 TO,Barrolândia,5349,713.3 TO,Bernardo Sayão,4456,926.89 TO,Bom Jesus do Tocantins,3768,1332.67 TO,Brasilândia do Tocantins,2064,641.47 TO,Brejinho de Nazaré,5185,1724.45 TO,Buriti do Tocantins,9768,251.92 TO,Cachoeirinha,2148,352.35 TO,Campos Lindos,8139,3240.18 TO,Cariri do Tocantins,3756,1128.6 TO,Carmolândia,2316,339.41 TO,Carrasco Bonito,3688,192.94 TO,Caseara,4601,1691.61 TO,Centenário,2566,1954.7 TO,Chapada da Natividade,3277,1646.47 TO,Chapada de Areia,1335,659.25 TO,Colinas do Tocantins,30838,843.85 TO,Colméia,8611,990.72 TO,Combinado,4669,209.58 TO,Conceição do Tocantins,4182,2500.74 TO,Couto Magalhães,5009,1585.79 TO,Cristalândia,7234,1848.24 TO,Crixás do Tocantins,1564,986.69 TO,Darcinópolis,5273,1639.16 TO,Dianópolis,19112,3217.31 TO,Divinópolis do Tocantins,6363,2347.43 TO,Dois Irmãos do Tocantins,7161,3757.04 TO,Dueré,4592,3424.85 TO,Esperantina,9476,504.02 TO,Fátima,3805,382.91 TO,Figueirópolis,5340,1930.07 TO,Filadélfia,8505,1988.08 TO,Formoso do Araguaia,18427,13423.38 TO,Fortaleza do Tabocão,2419,621.56 TO,Goianorte,4956,1800.98 TO,Goiatins,12064,6408.6 TO,Guaraí,23200,2268.16 TO,Gurupi,76755,1836.09 TO,Ipueiras,1639,815.25 TO,Itacajá,7104,3051.36 TO,Itaguatins,6029,739.85 TO,Itapiratins,3532,1243.96 TO,Itaporã do Tocantins,2445,972.98 TO,Jaú do Tocantins,3507,2173.05 TO,Juarina,2231,481.05 TO,Lagoa da Confusão,10210,10564.66 TO,Lagoa do Tocantins,3525,911.34 TO,Lajeado,2773,322.49 TO,Lavandeira,1605,519.61 TO,Lizarda,3725,5723.23 TO,Luzinópolis,2622,279.56 TO,Marianópolis do Tocantins,4352,2091.37 TO,Mateiros,2223,9681.46 TO,Maurilândia do Tocantins,3154,738.11 TO,Miracema do Tocantins,20684,2656.09 TO,Miranorte,12623,1031.62 TO,Monte do Carmo,6716,3616.67 TO,Monte Santo do Tocantins,2085,1091.55 TO,Muricilândia,3152,1186.65 TO,Natividade,9000,3240.72 TO,Nazaré,4386,395.91 TO,Nova Olinda,10686,1566.18 TO,Nova Rosalândia,3770,516.31 TO,Novo Acordo,3762,2674.68 TO,Novo Alegre,2286,200.1 TO,Novo Jardim,2457,1309.67 TO,Oliveira de Fátima,1037,205.85 TO,Palmas,228332,2218.94 TO,Palmeirante,4954,2640.82 TO,Palmeiras do Tocantins,5740,747.9 TO,Palmeirópolis,7339,1703.94 TO,Paraíso do Tocantins,44417,1268.06 TO,Paranã,10338,11260.21 TO,Pau d`Arco,4588,1377.41 TO,Pedro Afonso,11539,2010.9 TO,Peixe,10384,5291.21 TO,Pequizeiro,5054,1209.8 TO,Pindorama do Tocantins,4506,1559.09 TO,Piraquê,2920,1367.61 TO,Pium,6694,10013.79 TO,Ponte Alta do Bom Jesus,4544,1806.14 TO,Ponte Alta do Tocantins,7180,6491.13 TO,Porto Alegre do Tocantins,2796,501.86 TO,Porto Nacional,49146,4449.92 TO,Praia Norte,7659,289.05 TO,Presidente Kennedy,3681,770.42 TO,Pugmil,2369,401.83 TO,Recursolândia,3768,2216.66 TO,Riachinho,4191,517.48 TO,Rio da Conceição,1714,787.12 TO,Rio dos Bois,2570,845.07 TO,Rio Sono,6254,6354.37 TO,Sampaio,3864,222.29 TO,Sandolândia,3326,3528.62 TO,Santa Fé do Araguaia,6599,1678.09 TO,Santa Maria do Tocantins,2894,1410.46 TO,Santa Rita do Tocantins,2128,3274.95 TO,Santa Rosa do Tocantins,4568,1796.26 TO,Santa Tereza do Tocantins,2523,539.91 TO,Santa Terezinha do Tocantins,2474,269.68 TO,São Bento do Tocantins,4608,1105.9 TO,São Félix do Tocantins,1437,1908.68 TO,São Miguel do Tocantins,10481,398.82 TO,São Salvador do Tocantins,2910,1422.03 TO,São Sebastião do Tocantins,4283,287.28 TO,São Valério,4383,2519.59 TO,Silvanópolis,5068,1258.83 TO,Sítio Novo do Tocantins,9148,324.11 TO,Sucupira,1742,1025.52 TO,Taguatinga,15051,2437.4 TO,Taipas do Tocantins,1945,1116.2 TO,Talismã,2562,2156.9 TO,Tocantínia,6736,2601.6 TO,Tocantinópolis,22619,1077.07 TO,Tupirama,1574,712.21 TO,Tupiratins,2097,895.31 TO,Wanderlândia,10981,1373.06 TO,Xambioá,11484,1186.43 rows-0.4.1/examples/data/tesouro-direto.csv000066400000000000000000000035521343135453400207500ustar00rootroot00000000000000timestamp,titulo,vencimento,taxa_compra,taxa_venda,preco_compra,preco_venda 2015-11-06T17:43:00,Tesouro IPCA+ com Juros Semestrais 2017 (NTNB),2017-05-15,7.02%,6.3%,0.0,2792.97 2015-11-06T17:43:00,Tesouro IPCA+ 2019 (NTNB Princ),2019-05-15,7.02%,7.06%,2150.33,2147.53 2015-11-06T17:43:00,Tesouro IPCA+ com Juros Semestrais 2020 (NTNB),2020-08-15,7.14%,7.18%,2644.22,2640.14 2015-11-06T17:43:00,Tesouro IPCA+ com Juros Semestrais 2024 (NTNB),2024-08-15,7.02%,7.39%,0.0,2531.91 2015-11-06T17:43:00,Tesouro IPCA+ 2024 (NTNB Princ),2024-08-15,7.38%,7.44%,1463.24,1456.12 2015-11-06T17:43:00,Tesouro IPCA+ com Juros Semestrais 2035 (NTNB),2035-05-15,7.03%,7.11%,2520.76,2500.2 2015-11-06T17:43:00,Tesouro IPCA+ 2035 (NTNB Princ),2035-05-15,6.97%,7.05%,735.21,724.6 2015-11-06T17:43:00,Tesouro IPCA+ com Juros Semestrais 2045 (NTNB),2045-05-15,7.02%,7.1%,0.0,2449.17 2015-11-06T17:43:00,Tesouro IPCA+ com Juros Semestrais 2050 (NTNB),2050-08-15,6.94%,7.04%,2440.99,2409.92 2015-11-06T17:43:00,Tesouro Prefixado 2016 (LTN),2016-01-01,7.02%,14.27%,0.0,980.08 2015-11-06T17:43:00,Tesouro Prefixado com Juros Semestrais 2017 (NTNF),2017-01-01,7.02%,15.32%,0.0,982.94 2015-11-06T17:43:00,Tesouro Prefixado 2017 (LTN),2017-01-01,7.02%,15.33%,0.0,849.1 2015-11-06T17:43:00,Tesouro Prefixado 2018 (LTN),2018-01-01,15.59%,15.65%,733.95,733.14 2015-11-06T17:43:00,Tesouro Prefixado 2021 (LTN),2021-01-01,15.58%,15.64%,475.99,474.72 2015-11-06T17:43:00,Tesouro Prefixado com Juros Semestrais 2021 (NTNF),2021-01-01,7.02%,15.61%,0.0,851.42 2015-11-06T17:43:00,Tesouro Prefixado com Juros Semestrais 2023 (NTNF),2023-01-01,7.02%,15.61%,0.0,809.9 2015-11-06T17:43:00,Tesouro Prefixado com Juros Semestrais 2025 (NTNF),2025-01-01,15.59%,15.65%,779.41,777.19 2015-11-06T17:43:00,Tesouro Selic 2017 (LFT),2017-03-07,7.02%,0.02%,0.0,7259.64 2015-11-06T17:43:00,Tesouro Selic 2021 (LFT),2021-03-01,0%,0.04%,7261.57,7246.25 rows-0.4.1/examples/library/000077500000000000000000000000001343135453400157755ustar00rootroot00000000000000rows-0.4.1/examples/library/airports.py000066400000000000000000000015531343135453400202160ustar00rootroot00000000000000# coding: utf-8 # This script downloads the list of airport codes and cities from # worldnetlogistics.com and creates a `dict` called `code_to_city` with the # correspondent mapping. # # Install dependencies: # pip install requests rows # or # aptitude install python-requests python-rows from __future__ import unicode_literals from io import BytesIO import requests import rows # Get data url = "http://www.worldnetlogistics.com/information/iata-city-airport-codes/" response = requests.get(url) html = response.content # Parse/normalize data table = rows.import_from_html(BytesIO(html), index=4) code_to_city = {} for row in table: code_to_city[row.code] = row.city if row.city_2 is not None: code_to_city[row.code_2] = row.city_2 codes = sorted(code_to_city.keys()) for code in codes: print("{} = {}".format(code, code_to_city[code])) rows-0.4.1/examples/library/brazilian_cities_wikipedia.py000066400000000000000000000024151343135453400237120ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals import re from collections import OrderedDict from io import BytesIO import requests import rows try: from urlparse import urljoin # Python 2 except ImportError: from urllib.parse import urljoin # Python 3 # Get data from Portuguese Wikipedia city_list_url = "https://pt.wikipedia.org/wiki/Lista_de_munic%C3%ADpios_do_Brasil" response = requests.get(city_list_url) html = response.content # Extract desired data using XPath cities = rows.import_from_xpath( BytesIO(html), rows_xpath="//table/tr/td/ul/li", fields_xpath=OrderedDict([("name", ".//text()"), ("link", ".//a/@href")]), ) regexp_city_state = re.compile(r"(.*) \(([A-Z]{2})\)") def transform(row, table): 'Transform row "link" into full URL and add "state" based on "name"' data = row._asdict() data["link"] = urljoin("https://pt.wikipedia.org", data["link"]) data["name"], data["state"] = regexp_city_state.findall(data["name"])[0] return data new_fields = OrderedDict() new_fields["name"] = cities.fields["name"] new_fields["state"] = rows.fields.TextField # new field new_fields["link"] = cities.fields["link"] cities = rows.transform(new_fields, transform, cities) rows.export_to_csv(cities, "brazilian-cities.csv") rows-0.4.1/examples/library/custom_field.py000066400000000000000000000015131343135453400210240ustar00rootroot00000000000000from __future__ import unicode_literals import rows class MyIntegerField(rows.fields.IntegerField): """Weird integer represetation, having a `#` just before the number""" @classmethod def serialize(cls, value): return "#" + str(value) @classmethod def deserialize(cls, value): return int(value.replace("#", "")) class PtBrDateField(rows.fields.DateField): INPUT_FORMAT = "%d/%m/%Y" data = [ ["name", "age", "birthdate"], ["alvaro", "#30", "29/04/1987"], ["joao", "#17", "01/02/2000"], ] table = rows.plugins.utils.create_table( data, force_types={"age": MyIntegerField, "birthdate": PtBrDateField} ) print(type(table[0].age)) # `` print(type(table[0].birthdate)) # `` print(rows.export_to_txt(table)) # "age" values will start with "#" rows-0.4.1/examples/library/ecuador_radiodifusoras.py000066400000000000000000000016511343135453400230720ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals import os from collections import OrderedDict import rows # taken from: # http://www.supercom.gob.ec/es/informate-y-participa/directorio-de-medios/21-radiodifusoras filename = os.path.join( os.path.dirname(__file__), "../../tests/data/ecuador-medios-radiodifusoras.html" ) rows_xpath = '//*[@class="entry-container"]/*[@class="row-fluid"]/*[@class="span6"]' fields_xpath = OrderedDict( [ ("url", ".//h2/a/@href"), ("name", ".//h2/a/text()"), ("address", './/div[@class="spField field_direccion"]/text()'), ("phone", './/div[@class="spField field_telefono"]/text()'), ("website", './/div[@class="spField field_sitio_web"]/text()'), ("email", './/div[@class="spField field_email"]/text()'), ] ) table = rows.import_from_xpath(filename, rows_xpath, fields_xpath) rows.export_to_csv(table, "ecuador-radiodifusoras.csv") rows-0.4.1/examples/library/extract_links.py000066400000000000000000000016601343135453400212240ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals from io import BytesIO import requests import six import rows extract_links = rows.plugins.html.extract_links extract_text = rows.plugins.html.extract_text # Get the HTML url = "http://wnpp.debian.net/" response = requests.get(url) html = response.content # Import data, preserving cell's HTML packages = rows.import_from_html(BytesIO(html), index=10, preserve_html=True) def transform(row, table): 'Extract links from "project" field and remove HTML from all' data = row._asdict() data["links"] = " ".join(extract_links(row.project)) for key, value in data.items(): if isinstance(value, six.text_type): data[key] = extract_text(value) return data new_fields = packages.fields.copy() new_fields["links"] = rows.fields.TextField packages = rows.transform(new_fields, transform, packages) rows.export_to_csv(packages, "debian-wnpp.csv") rows-0.4.1/examples/library/extract_pdf.py000066400000000000000000000024031343135453400206510ustar00rootroot00000000000000# You must install the PDF dependencies for this script to work: there are two # available backends: pymupdf (recommended) and pdfminer.six (slowest). import io import requests import rows url = "http://balneabilidade.inema.ba.gov.br/index.php/relatoriodebalneabilidade/geraBoletim?idcampanha=42041" print("*** Downloading PDF...") response = requests.get(url) # The line below will automatically identify the table in all PDF pages - it # works for this file but not for all cases. You can be more specific defining # the page numbers, a start/end string (like the header/footer strings) and # also change the table identification algorithm. Check `backend`, `algorithm`, # `starts_after`, `ends_before` and `page_numbers` parameters. # For this simple case you could also install rows' CLI (`pip install # rows[cli]`) and run: `rows print ` table = rows.import_from_pdf(io.BytesIO(response.content)) rows.export_to_csv(table, "beach-data.csv") print("*** Table exported to beach-data.csv") print("*** Extracted table:") print(rows.export_to_txt(table)) # You could also iterate over the object, like: # for row in table: print(row) print("\n\n*** Extracted text:") text_pages = rows.plugins.pdf.pdf_to_text(io.BytesIO(response.content)) print("\n\n".join(text_pages)) rows-0.4.1/examples/library/organizaciones.py000066400000000000000000000013741343135453400213670ustar00rootroot00000000000000# coding: utf-8 # This example downloads some Ecuatorian organizations in JSON, extracts the # desired `dict`s, then import then into a `rows.Table` object to finally # export as XLS. # Install dependencies by running: pip install requests rows[xls] import requests import rows URL = "http://www.onumujeres-ecuador.org/geovisor/data/organizaciones.php" def download_organizations(): "Download organizations JSON and extract its properties" response = requests.get(URL) data = response.json() organizations = [organization["properties"] for organization in data["features"]] return rows.import_from_dicts(organizations) if __name__ == "__main__": table = download_organizations() rows.export_to_xls(table, "organizaciones.xls") rows-0.4.1/examples/library/slip_opinions.py000066400000000000000000000012731343135453400212370ustar00rootroot00000000000000# coding: utf-8 from __future__ import print_function, unicode_literals from io import BytesIO import requests import rows # This example was based on: # https://github.com/compjour/search-script-scrape/blob/master/scripts/42.py try: from urlparse import urljoin # Python 2 except ImportError: from urllib.parse import urljoin # Python 3 tag_to_dict = rows.plugins.html.tag_to_dict url = "http://www.supremecourt.gov/opinions/slipopinions.aspx" html = requests.get(url).content table = rows.import_from_html(BytesIO(html), index=1, preserve_html=True) for element in table: attributes = tag_to_dict(element.name) print(attributes["text"], urljoin(url, attributes["href"])) rows-0.4.1/examples/library/tests_uwsgi_log.py000066400000000000000000000020641343135453400215720ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals import datetime import unittest from uwsgi_log_plugin import import_from_uwsgi_log class UwsgiLogPluginTestCase(unittest.TestCase): def test_import_from_uwsgi_log(self): filename = "uwsgi.log" table = import_from_uwsgi_log(filename, "utf-8") self.assertEqual(len(table), 2) first = table.Row( pid=879, ip="127.0.0.1", datetime=datetime.datetime(2015, 6, 1, 11, 23, 16), generation_time=0.17378, http_path="/something", http_verb="GET", http_version=1.1, http_status=404, ) second = table.Row( pid=31460, ip="127.0.1.1", datetime=datetime.datetime(2015, 7, 15, 23, 49, 20), generation_time=0.000466, http_path="/about", http_verb="OPTIONS", http_version=1.1, http_status=200, ) self.assertEqual(table[0], first) self.assertEqual(table[1], second) rows-0.4.1/examples/library/usa_legislators.py000066400000000000000000000022611343135453400215500ustar00rootroot00000000000000# coding: utf-8 # This example was based on: # https://github.com/compjour/search-script-scrape/blob/master/scripts/101.py from io import BytesIO import requests import rows # Capture url = "http://unitedstates.sunlightfoundation.com/legislators/legislators.csv" csv = BytesIO(requests.get(url).content) # Normalize table = rows.import_from_csv(csv) # Analyze total = len(table) total_in_office = sum(1 for row in table if row.in_office) men = sum(1 for row in table if row.gender == "M") men_in_office = sum(1 for row in table if row.gender == "M" and row.in_office) women = sum(1 for row in table if row.gender == "F") women_in_office = sum(1 for row in table if row.gender == "F" and row.in_office) # View print( " Men: {}/{} ({:02.2f}%), in office: {}/{} ({:02.2f}%)".format( men, total, 100 * men / float(total), men_in_office, total_in_office, 100 * men_in_office / float(total), ) ) print( "Women: {}/{} ({:02.2f}%), in office: {}/{} ({:02.2f}%)".format( women, total, 100 * women / float(total), women_in_office, total_in_office, 100 * women_in_office / float(total), ) ) rows-0.4.1/examples/library/uwsgi.log000066400000000000000000000075351343135453400176500ustar00rootroot00000000000000*** Starting uWSGI 2.0.8 (64bit) on [Mon Jun 1 11:08:58 2015] *** compiled with version: 4.8.2 on 01 June 2015 11:01:28 os: Linux-3.13.0-48-generic #80-Ubuntu SMP Thu Mar 12 11:16:15 UTC 2015 nodename: localhost.localdomain machine: x86_64 clock source: unix detected number of CPU cores: 1 current working directory: /home/ubuntu writing pidfile to /home/ubuntu/uwsgi/uwsgi.pid detected binary path: /home/ubuntu/venv/bin/uwsgi !!! no internal routing support, rebuild with pcre support !!! chdir() to /home/ubuntu/repo your processes number limit is 7862 your memory page size is 4096 bytes detected max file descriptor number: 1024 lock engine: pthread robust mutexes thunder lock: disabled (you can enable it with --thunder-lock) uwsgi socket 0 bound to TCP address 127.0.0.1:8001 fd 3 Python version: 2.7.6 (default, Mar 22 2014, 23:03:41) [GCC 4.8.2] Set PythonHome to /home/ubuntu/venv *** Python threads support is disabled. You can enable it with --enable-threads *** Python main interpreter initialized at 0x14d9cf0 your server socket listen backlog is limited to 100 connections your mercy for graceful operations on workers is 60 seconds mapped 363840 bytes (355 KB) for 4 cores *** Operational MODE: preforking *** WSGI app 0 (mountpoint='') ready in 0 seconds on interpreter 0x14d9cf0 pid: 32310 (default app) *** uWSGI is running in multiple interpreter mode *** spawned uWSGI master process (pid: 32310) spawned uWSGI worker 1 (pid: 32317, cores: 1) spawned uWSGI worker 2 (pid: 32318, cores: 1) spawned uWSGI worker 3 (pid: 32319, cores: 1) spawned uWSGI worker 4 (pid: 32320, cores: 1) *** Stats server enabled on 127.0.0.1:8002 fd: 16 *** SIGINT/SIGQUIT received...killing workers... worker 1 buried after 1 seconds worker 2 buried after 1 seconds worker 3 buried after 1 seconds worker 4 buried after 1 seconds goodbye to uWSGI. VACUUM: pidfile removed. *** Starting uWSGI 2.0.8 (64bit) on [Mon Jun 1 11:20:46 2015] *** compiled with version: 4.8.2 on 01 June 2015 11:01:28 os: Linux-3.13.0-48-generic #80-Ubuntu SMP Thu Mar 12 11:16:15 UTC 2015 nodename: localhost.localdomain machine: x86_64 clock source: unix detected number of CPU cores: 1 current working directory: /home/ubuntu writing pidfile to /home/ubuntu/uwsgi/uwsgi.pid detected binary path: /home/ubuntu/venv/bin/uwsgi !!! no internal routing support, rebuild with pcre support !!! chdir() to /home/ubuntu/repo your processes number limit is 7862 your memory page size is 4096 bytes detected max file descriptor number: 1024 lock engine: pthread robust mutexes thunder lock: disabled (you can enable it with --thunder-lock) uwsgi socket 0 bound to TCP address 127.0.0.1:8001 fd 3 Python version: 2.7.6 (default, Mar 22 2014, 23:03:41) [GCC 4.8.2] Set PythonHome to /home/ubuntu/venv *** Python threads support is disabled. You can enable it with --enable-threads *** Python main interpreter initialized at 0xf6dcf0 your server socket listen backlog is limited to 100 connections your mercy for graceful operations on workers is 60 seconds mapped 363840 bytes (355 KB) for 4 cores *** Operational MODE: preforking *** WSGI app 0 (mountpoint='') ready in 1 seconds on interpreter 0xf6dcf0 pid: 872 (default app) *** uWSGI is running in multiple interpreter mode *** spawned uWSGI master process (pid: 872) spawned uWSGI worker 1 (pid: 879, cores: 1) spawned uWSGI worker 2 (pid: 880, cores: 1) spawned uWSGI worker 3 (pid: 881, cores: 1) spawned uWSGI worker 4 (pid: 882, cores: 1) *** Stats server enabled on 127.0.0.1:8002 fd: 16 *** [pid: 879|app: 0|req: 1/1] 127.0.0.1 () {40 vars in 743 bytes} [Mon Jun 1 11:23:16 2015] GET /something => generated 93 bytes in 173780 micros (HTTP/1.1 404) 2 headers in 80 bytes (1 switches on core 0) [pid: 31460|app: 0|req: 21/2786] 127.0.1.1 () {46 vars in 840 bytes} [Wed Jul 15 23:49:20 2015] OPTIONS /about => generated 0 bytes in 466 micros (HTTP/1.1 200) 6 headers in 327 bytes (1 switches on core 0) rows-0.4.1/examples/library/uwsgi_log_plugin.py000066400000000000000000000033361343135453400217310ustar00rootroot00000000000000# coding: utf-8 from __future__ import unicode_literals import codecs import datetime import re from collections import OrderedDict import rows.fields from rows.table import Table REGEXP_UWSGI_LOG = re.compile( r"\[pid: ([0-9]+)\|app: [0-9]+\|req: " r"[0-9]+/[0-9]+\] " r"([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+) .+ \[(.+)\] " r"([^ ]+) (.+) => generated .+ in ([0-9]+) " r"micros \(HTTP/([^ ]+) ([^)]+)\)" ) UWSGI_FIELDS = OrderedDict( [ ("pid", rows.fields.IntegerField), ("ip", rows.fields.TextField), ("datetime", rows.fields.DatetimeField), ("http_verb", rows.fields.TextField), ("http_path", rows.fields.TextField), ("generation_time", rows.fields.FloatField), ("http_version", rows.fields.FloatField), ("http_status", rows.fields.IntegerField), ] ) UWSGI_DATETIME_FORMAT = "%a %b %d %H:%M:%S %Y" strptime = datetime.datetime.strptime def import_from_uwsgi_log(filename, encoding): table = Table(fields=UWSGI_FIELDS) field_names = list(UWSGI_FIELDS.keys()) with codecs.open(filename, encoding=encoding) as fobj: for line in fobj: result = REGEXP_UWSGI_LOG.findall(line) if result: data = list(result[0]) # Convert datetime data[2] = strptime(data[2], UWSGI_DATETIME_FORMAT) # Convert generation time (micros -> seconds) data[5] = float(data[5]) / 1000000 table.append( {field_name: value for field_name, value in zip(field_names, data)} ) return table if __name__ == "__main__": table = import_from_uwsgi_log("uwsgi.log", "utf-8") for row in table: print(row) rows-0.4.1/mkdocs.yml000066400000000000000000000000671343135453400145210ustar00rootroot00000000000000site_name: rows 0.4.1 documentation theme: readthedocs rows-0.4.1/requirements-development.txt000066400000000000000000000006231343135453400203200ustar00rootroot00000000000000-r requirements.txt # Everything except install_requires click file-magic lxml openpyxl pymupdf https://github.com/turicas/parquet-python/archive/enhancement/move-to-thriftpy2.zip#egg=parquet pdfminer.six requests requests-cache xlrd xlwt cached-property psycopg2-binary tqdm # Test and lint tools autoflake coverage ipdb isort mock nose pylint tox yanc # Doc tools click-man ghp-import mkdocs pycco rows-0.4.1/requirements.txt000066400000000000000000000000051343135453400157720ustar00rootroot00000000000000-e . rows-0.4.1/rows.1.txt000066400000000000000000000055041343135453400144110ustar00rootroot00000000000000.TH ROWS "1" "Sep 2015" "ROWS 0.1.0" "common, beautiful interface to tabular data, no matter the format" NAME rows - common, beautiful interface to tabular data, no matter the format SYNOPSIS rows [OPTIONS] COMMAND [ARGS] ... DESCRIPTION No matter in which format your tabular data is: rows will import it, automatically detect types and give you high-level Python objects so you can start working with the data instead of trying to parse it. It is also locale-and-unicode aware. OPTIONS --help Show this message and exit COMMANDS . convert - Convert table on `source` URI to... join - Join tables from `source` URIs using `key(s)`... sort - Sort from `source` by `key(s)` and save into... sum - Sum tables from `source` URIs and save into... SYNOPSIS rows convert [OPTIONS] SOURCE DESTINATION DESCRIPTION Convert table on `source` URI to `destination` OPTIONS --input-encoding TEXT --output-encoding TEXT --input-locale TEXT --output-locale TEXT --help Show this message and exit. SYNOPSIS rows join [OPTIONS] KEYS SOURCES ... DESTINATION DESCRIPTION Join tables from `source` URIs using `key(s)` to group rows and save into destination` OPTIONS --input-encoding TEXT --output-encoding TEXT --input-locale TEXT --output-locale TEXT --help Show this message and exit. SYNOPSIS rows sort [OPTIONS] KEY SOURCE DESTINATION DESCRIPTION Sort from `source` by `key(s)` and save into `destination` OPTIONS --input-encoding TEXT --output-encoding TEXT --input-locale TEXT --output-locale TEXT --help Show this message and exit. SYNOPSIS rows sum [OPTIONS] SOURCES ... DESTINATION DESCRIPTION Sum tables from `source` URIs and save into `destination` OPTIONS --input-encoding TEXT --output-encoding TEXT --input-locale TEXT --output-locale TEXT --help Show this message and exit. EXAMPLES - To export csv from site and converting locales from pt_BR to en both UTF-8 rows convert \-\-input-locale pt_BR.UTF-8 \-\-output-locale en.UTF-8 "" data.csv - To export xls from site and no changes locales rows convert "" data.xls - To convert csv to xls rows convert file.csv file.xls REPORTING BUGS To report a bug please visit rows' issue tracking system at . AUTHOR Written by Álvaro Justen . This manual page was written by Paulo Roberto Alves de Oliveira (aka kretcheu) for the Debian project (but may be used by others). COPYRIGHT Copyright © 2014-2019 Álvaro Justen. License LGPLv3+: GNU LGPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. rows-0.4.1/rows/000077500000000000000000000000001343135453400135055ustar00rootroot00000000000000rows-0.4.1/rows/__init__.py000066400000000000000000000046461343135453400156300ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals # General imports import rows.plugins as plugins from rows.operations import join, transform, transpose # NOQA from rows.table import Table, FlexibleTable # NOQA from rows.localization import locale_context # NOQA # Don't have dependencies or dependencies installed on `install_requires` import_from_json = plugins.json.import_from_json export_to_json = plugins.json.export_to_json import_from_dicts = plugins.dicts.import_from_dicts export_to_dicts = plugins.dicts.export_to_dicts import_from_csv = plugins.csv.import_from_csv export_to_csv = plugins.csv.export_to_csv import_from_txt = plugins.txt.import_from_txt export_to_txt = plugins.txt.export_to_txt # Have dependencies if plugins.html: import_from_html = plugins.html.import_from_html export_to_html = plugins.html.export_to_html if plugins.xpath: import_from_xpath = plugins.xpath.import_from_xpath if plugins.ods: import_from_ods = plugins.ods.import_from_ods if plugins.sqlite: import_from_sqlite = plugins.sqlite.import_from_sqlite export_to_sqlite = plugins.sqlite.export_to_sqlite if plugins.xls: import_from_xls = plugins.xls.import_from_xls export_to_xls = plugins.xls.export_to_xls if plugins.xlsx: import_from_xlsx = plugins.xlsx.import_from_xlsx export_to_xlsx = plugins.xlsx.export_to_xlsx if plugins.parquet: import_from_parquet = plugins.parquet.import_from_parquet if plugins.postgresql: import_from_postgresql = plugins.postgresql.import_from_postgresql export_to_postgresql = plugins.postgresql.export_to_postgresql if plugins.pdf: import_from_pdf = plugins.pdf.import_from_pdf __version__ = "0.4.1" rows-0.4.1/rows/cli.py000077500000000000000000000651431343135453400146420ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # TODO: define exit codes # TODO: move default options to base command # TODO: may move all 'destination' to '--output' # TODO: test this whole module # TODO: add option to pass 'create_table' options in command-line (like force # fields) import os import pathlib import sqlite3 import sys from io import BytesIO import click import requests.exceptions import requests_cache import six from tqdm import tqdm import rows from rows.fields import make_header from rows.utils import ( ProgressBar, csv_to_sqlite, detect_source, download_file, export_to_uri, generate_schema, import_from_source, import_from_uri, load_schema, pgexport, pgimport, sqlite_to_csv, uncompressed_size, ) DEFAULT_INPUT_ENCODING = "utf-8" DEFAULT_INPUT_LOCALE = "C" DEFAULT_OUTPUT_ENCODING = "utf-8" DEFAULT_OUTPUT_LOCALE = "C" HOME_PATH = pathlib.Path.home() CACHE_PATH = HOME_PATH / ".cache" / "rows" / "http" def _import_table(source, encoding, verify_ssl=True, *args, **kwargs): # TODO: add `--quiet|-q` or `--progress|-p` to set `progress` properly try: table = import_from_uri( source, default_encoding=DEFAULT_INPUT_ENCODING, verify_ssl=verify_ssl, encoding=encoding, progress=True, *args, **kwargs, ) except requests.exceptions.SSLError: click.echo( "ERROR: SSL verification failed! " "Use `--verify-ssl=no` if you want to ignore.", err=True, ) sys.exit(2) else: return table def _get_field_names(field_names, table_field_names, permit_not=False): new_field_names = make_header(field_names.split(","), permit_not=permit_not) if not permit_not: diff = set(new_field_names) - set(table_field_names) else: diff = set(field_name.replace("^", "") for field_name in new_field_names) - set( table_field_names ) if diff: missing = ", ".join(['"{}"'.format(field) for field in diff]) click.echo("Table does not have fields: {}".format(missing), err=True) sys.exit(1) else: return new_field_names def _get_import_fields(fields, fields_exclude): if fields is not None and fields_exclude is not None: click.echo("ERROR: `--fields` cannot be used with `--fields-exclude`", err=True) sys.exit(20) elif fields is not None: return make_header(fields.split(","), permit_not=False) else: return None def _get_export_fields(table_field_names, fields_exclude): if fields_exclude is not None: fields_exclude = _get_field_names(fields_exclude, table_field_names) return [ field_name for field_name in table_field_names if field_name not in fields_exclude ] else: return None def _get_schemas_for_inputs(schemas, inputs): if schemas is None: schemas = [None for _ in inputs] else: schemas = [schema.strip() or None for schema in schemas.split(",")] if len(schemas) > len(inputs): click.echo("ERROR: number of schemas is greater than sources", err=True) sys.exit(9) elif len(schemas) < len(inputs): diff = len(inputs) - len(schemas) for _ in range(diff): schemas.append(None) return [load_schema(schema) if schema else None for schema in schemas] class AliasedGroup(click.Group): def get_command(self, ctx, cmd_name): return click.Group.get_command(self, ctx, cmd_name) or click.Group.get_command( self, ctx, cmd_name.replace("2", "-to-") ) @click.group(cls=AliasedGroup) @click.option("--http-cache", type=bool, default=True) @click.option("--http-cache-path", default=str(CACHE_PATH.absolute())) @click.version_option(version=rows.__version__, prog_name="rows") def cli(http_cache, http_cache_path): if http_cache: http_cache_path = pathlib.Path(http_cache_path).absolute() if not http_cache_path.parent.exists(): os.makedirs(str(http_cache_path.parent), exist_ok=True) if str(http_cache_path).lower().endswith(".sqlite"): http_cache_path = pathlib.Path(str(http_cache_path)[:-7]).absolute() requests_cache.install_cache(str(http_cache_path)) @cli.command(help="Convert table on `source` URI to `destination`") @click.option("--input-encoding", default="utf-8") @click.option("--output-encoding", default="utf-8") @click.option("--input-locale") @click.option("--output-locale") @click.option("--verify-ssl", type=bool, default=True) @click.option("--order-by") @click.option("--fields", help="A comma-separated list of fields to import") @click.option("--fields-exclude", help="A comma-separated list of fields to exclude") @click.argument("source") @click.argument("destination") def convert( input_encoding, output_encoding, input_locale, output_locale, verify_ssl, order_by, fields, fields_exclude, source, destination, ): import_fields = _get_import_fields(fields, fields_exclude) if input_locale is not None: with rows.locale_context(input_locale): table = _import_table( source, encoding=input_encoding, verify_ssl=verify_ssl, import_fields=import_fields, ) else: table = _import_table( source, encoding=input_encoding, verify_ssl=verify_ssl, import_fields=import_fields, ) if order_by is not None: order_by = _get_field_names(order_by, table.field_names, permit_not=True) # TODO: use complete list of `order_by` fields table.order_by(order_by[0].replace("^", "-")) export_fields = _get_export_fields(table.field_names, fields_exclude) # TODO: may use sys.stdout.encoding if output_file = '-' output_encoding = output_encoding or DEFAULT_OUTPUT_ENCODING if output_locale is not None: with rows.locale_context(output_locale): export_to_uri( table, destination, encoding=output_encoding, export_fields=export_fields, ) else: export_to_uri( table, destination, encoding=output_encoding, export_fields=export_fields ) @cli.command( help="Join tables from `source` URIs using `key(s)` to group " "rows and save into `destination`" ) @click.option("--input-encoding", default="utf-8") @click.option("--output-encoding", default="utf-8") @click.option("--input-locale") @click.option("--output-locale") @click.option("--verify-ssl", type=bool, default=True) @click.option("--order-by") @click.option("--fields", help="A comma-separated list of fields to export") @click.option( "--fields-exclude", help="A comma-separated list of fields to exclude when exporting", ) @click.argument("keys") @click.argument("sources", nargs=-1, required=True) @click.argument("destination") def join( input_encoding, output_encoding, input_locale, output_locale, verify_ssl, order_by, fields, fields_exclude, keys, sources, destination, ): export_fields = _get_import_fields(fields, fields_exclude) keys = make_header(keys.split(","), permit_not=False) if input_locale is not None: with rows.locale_context(input_locale): tables = [ _import_table(source, encoding=input_encoding, verify_ssl=verify_ssl) for source in sources ] else: tables = [ _import_table(source, encoding=input_encoding, verify_ssl=verify_ssl) for source in sources ] result = rows.join(keys, tables) if order_by is not None: order_by = _get_field_names(order_by, result.field_names, permit_not=True) # TODO: use complete list of `order_by` fields result.order_by(order_by[0].replace("^", "-")) if export_fields is None: export_fields = _get_export_fields(result.field_names, fields_exclude) # TODO: may use sys.stdout.encoding if output_file = '-' output_encoding = output_encoding or DEFAULT_OUTPUT_ENCODING if output_locale is not None: with rows.locale_context(output_locale): export_to_uri( result, destination, encoding=output_encoding, export_fields=export_fields, ) else: export_to_uri( result, destination, encoding=output_encoding, export_fields=export_fields ) @cli.command( name="sum", help="Sum tables from `source` URIs and save into `destination`" ) @click.option("--input-encoding", default="utf-8") @click.option("--output-encoding", default="utf-8") @click.option("--input-locale") @click.option("--output-locale") @click.option("--verify-ssl", type=bool, default=True) @click.option("--order-by") @click.option("--fields", help="A comma-separated list of fields to import") @click.option("--fields-exclude", help="A comma-separated list of fields to exclude") @click.argument("sources", nargs=-1, required=True) @click.argument("destination") def sum_( input_encoding, output_encoding, input_locale, output_locale, verify_ssl, order_by, fields, fields_exclude, sources, destination, ): import_fields = _get_import_fields(fields, fields_exclude) if input_locale is not None: with rows.locale_context(input_locale): tables = [ _import_table( source, encoding=input_encoding, verify_ssl=verify_ssl, import_fields=import_fields, ) for source in sources ] else: tables = [ _import_table( source, encoding=input_encoding, verify_ssl=verify_ssl, import_fields=import_fields, ) for source in sources ] result = sum(tables) if order_by is not None: order_by = _get_field_names(order_by, result.field_names, permit_not=True) # TODO: use complete list of `order_by` fields result.order_by(order_by[0].replace("^", "-")) export_fields = _get_export_fields(result.field_names, fields_exclude) # TODO: may use sys.stdout.encoding if output_file = '-' output_encoding = output_encoding or DEFAULT_OUTPUT_ENCODING if output_locale is not None: with rows.locale_context(output_locale): export_to_uri( result, destination, encoding=output_encoding, export_fields=export_fields, ) else: export_to_uri( result, destination, encoding=output_encoding, export_fields=export_fields ) @cli.command(name="print", help="Print a table") @click.option("--input-encoding", default="utf-8") @click.option("--output-encoding", default="utf-8") @click.option("--input-locale") @click.option("--output-locale") @click.option( "--frame-style", default="ascii", help="Options: ascii, single, double, none" ) @click.option("--table-index", default=0) @click.option("--verify-ssl", type=bool, default=True) @click.option("--fields", help="A comma-separated list of fields to import") @click.option("--fields-exclude", help="A comma-separated list of fields to exclude") @click.option("--order-by") @click.argument("source", required=True) def print_( input_encoding, output_encoding, input_locale, output_locale, frame_style, table_index, verify_ssl, fields, fields_exclude, order_by, source, ): import_fields = _get_import_fields(fields, fields_exclude) # TODO: if create_table implements `fields_exclude` this _import_table call # will import only the desired data if input_locale is not None: with rows.locale_context(input_locale): table = _import_table( source, encoding=input_encoding, verify_ssl=verify_ssl, index=table_index, import_fields=import_fields, ) else: table = _import_table( source, encoding=input_encoding, verify_ssl=verify_ssl, index=table_index, import_fields=import_fields, ) if order_by is not None: order_by = _get_field_names(order_by, table.field_names, permit_not=True) # TODO: use complete list of `order_by` fields table.order_by(order_by[0].replace("^", "-")) export_fields = _get_export_fields(table.field_names, fields_exclude) output_encoding = output_encoding or sys.stdout.encoding or DEFAULT_OUTPUT_ENCODING fobj = BytesIO() if output_locale is not None: with rows.locale_context(output_locale): rows.export_to_txt( table, fobj, encoding=output_encoding, export_fields=export_fields, frame_style=frame_style, ) else: rows.export_to_txt( table, fobj, encoding=output_encoding, export_fields=export_fields, frame_style=frame_style, ) fobj.seek(0) # TODO: may pass unicode to click.echo if output_encoding is not provided click.echo(fobj.read()) @cli.command(name="query", help="Query a table using SQL") @click.option("--input-encoding", default="utf-8") @click.option("--output-encoding", default="utf-8") @click.option("--input-locale") @click.option("--output-locale") @click.option("--verify-ssl", type=bool, default=True) @click.option( "--samples", type=int, default=5000, help="Number of rows to determine the field types (0 = all)", ) @click.option("--output") @click.option( "--frame-style", default="ascii", help="Options: ascii, single, double, none" ) @click.argument("query", required=True) @click.argument("sources", nargs=-1, required=True) def query( input_encoding, output_encoding, input_locale, output_locale, verify_ssl, samples, output, frame_style, query, sources, ): samples = samples if samples > 0 else None if not query.lower().startswith("select"): table_names = ", ".join( ["table{}".format(index) for index in range(1, len(sources) + 1)] ) query = "SELECT * FROM {} WHERE {}".format(table_names, query) if len(sources) == 1: source = detect_source(sources[0], verify_ssl=verify_ssl, progress=True) if source.plugin_name in ("sqlite", "postgresql"): # Optimization: query the db directly result = import_from_source( source, DEFAULT_INPUT_ENCODING, query=query, samples=samples ) else: if input_locale is not None: with rows.locale_context(input_locale): table = import_from_source( source, DEFAULT_INPUT_ENCODING, samples=samples ) else: table = import_from_source( source, DEFAULT_INPUT_ENCODING, samples=samples ) sqlite_connection = sqlite3.Connection(":memory:") rows.export_to_sqlite(table, sqlite_connection, table_name="table1") result = rows.import_from_sqlite(sqlite_connection, query=query) else: # TODO: if all sources are SQLite we can also optimize the import if input_locale is not None: with rows.locale_context(input_locale): tables = [ _import_table( source, encoding=input_encoding, verify_ssl=verify_ssl, samples=samples, ) for source in sources ] else: tables = [ _import_table( source, encoding=input_encoding, verify_ssl=verify_ssl, samples=samples, ) for source in sources ] sqlite_connection = sqlite3.Connection(":memory:") for index, table in enumerate(tables, start=1): rows.export_to_sqlite( table, sqlite_connection, table_name="table{}".format(index) ) result = rows.import_from_sqlite(sqlite_connection, query=query) # TODO: may use sys.stdout.encoding if output_file = '-' output_encoding = output_encoding or sys.stdout.encoding or DEFAULT_OUTPUT_ENCODING if output is None: fobj = BytesIO() if output_locale is not None: with rows.locale_context(output_locale): rows.export_to_txt( result, fobj, encoding=output_encoding, frame_style=frame_style ) else: rows.export_to_txt( result, fobj, encoding=output_encoding, frame_style=frame_style ) fobj.seek(0) click.echo(fobj.read()) else: if output_locale is not None: with rows.locale_context(output_locale): export_to_uri(result, output, encoding=output_encoding) else: export_to_uri(result, output, encoding=output_encoding) @cli.command(name="schema", help="Identifies table schema") @click.option("--input-encoding", default="utf-8") @click.option("--input-locale") @click.option("--verify-ssl", type=bool, default=True) @click.option( "-f", "--format", "output_format", default="txt", type=click.Choice(("txt", "sql", "django")), ) @click.option("--fields", help="A comma-separated list of fields to inspect") @click.option( "--fields-exclude", help="A comma-separated list of fields to exclude from inspection", ) @click.option( "--samples", type=int, default=5000, help="Number of rows to determine the field types (0 = all)", ) @click.argument("source", required=True) @click.argument("output", required=False, default="-") def schema( input_encoding, input_locale, verify_ssl, output_format, fields, fields_exclude, samples, source, output, ): samples = samples if samples > 0 else None import_fields = _get_import_fields(fields, fields_exclude) source = detect_source(source, verify_ssl=verify_ssl, progress=True) # TODO: make it lazy if input_locale is not None: with rows.locale_context(input_locale): table = import_from_source( source, DEFAULT_INPUT_ENCODING, samples=samples, import_fields=import_fields, ) else: table = import_from_source( source, DEFAULT_INPUT_ENCODING, samples=samples, import_fields=import_fields ) export_fields = _get_export_fields(table.field_names, fields_exclude) if export_fields is None: export_fields = table.field_names if output in ("-", None): output = sys.stdout else: output = open(output, mode="w", encoding="utf-8") generate_schema(table, export_fields, output_format, output) @cli.command(name="csv-to-sqlite", help="Convert one or more CSV files to SQLite") @click.option("--batch-size", default=10000) @click.option( "--samples", type=int, default=5000, help="Number of rows to determine the field types (0 = all)", ) @click.option("--input-encoding", default="utf-8") @click.option("--dialect", default=None) @click.option("--schemas", default=None) @click.argument("sources", nargs=-1, required=True) @click.argument("output", required=True) def command_csv_to_sqlite( batch_size, samples, input_encoding, dialect, schemas, sources, output ): inputs = [pathlib.Path(filename) for filename in sources] output = pathlib.Path(output) table_names = make_header([filename.name.split(".")[0] for filename in inputs]) schemas = _get_schemas_for_inputs(schemas, inputs) for filename, table_name, schema in zip(inputs, table_names, schemas): prefix = "[{filename} -> {db_filename}#{tablename}]".format( db_filename=output.name, tablename=table_name, filename=filename.name ) pre_prefix = "{} (detecting data types)".format(prefix) progress = ProgressBar(prefix=prefix, pre_prefix=pre_prefix) csv_to_sqlite( six.text_type(filename), six.text_type(output), dialect=dialect, table_name=table_name, samples=samples, batch_size=batch_size, callback=progress.update, encoding=input_encoding, schema=schema, ) progress.close() @cli.command(name="sqlite-to-csv", help="Convert a SQLite table into CSV") @click.option("--batch-size", default=10000) @click.option("--dialect", default="excel") @click.argument("source", required=True) @click.argument("table_name", required=True) @click.argument("output", required=True) def command_sqlite_to_csv(batch_size, dialect, source, table_name, output): input_filename = pathlib.Path(source) output_filename = pathlib.Path(output) prefix = "[{db_filename}#{tablename} -> {filename}]".format( db_filename=input_filename.name, tablename=table_name, filename=output_filename.name, ) progress = ProgressBar(prefix=prefix, pre_prefix="") sqlite_to_csv( input_filename=six.text_type(input_filename), table_name=table_name, dialect=dialect, output_filename=six.text_type(output_filename), batch_size=batch_size, callback=progress.update, ) progress.close() @cli.command(name="pgimport", help="Import a CSV file into a PostgreSQL table") @click.option("--input-encoding", default="utf-8") @click.option("--no-create-table", default=False, is_flag=True) @click.option("--dialect", default=None) @click.option("--schema", default=None) @click.argument("source", required=True) @click.argument("database_uri", required=True) @click.argument("table_name", required=True) def command_pgimport( input_encoding, no_create_table, dialect, schema, source, database_uri, table_name ): progress = ProgressBar( prefix="Importing data", pre_prefix="Detecting file size", unit="bytes" ) try: total_size = uncompressed_size(source) except (RuntimeError, ValueError): total_size = None else: progress.total = total_size progress.description = "Analyzing source file" schemas = _get_schemas_for_inputs([schema] if schema else None, [source]) import_meta = pgimport( filename=source, encoding=input_encoding, dialect=dialect, database_uri=database_uri, create_table=not no_create_table, table_name=table_name, callback=progress.update, schema=schemas[0], ) progress.description = "{} rows imported".format(import_meta["rows_imported"]) progress.close() @cli.command(name="pgexport", help="Export a PostgreSQL table into a CSV file") @click.option("--output-encoding", default="utf-8") @click.option("--dialect", default="excel") @click.argument("database_uri", required=True) @click.argument("table_name", required=True) @click.argument("destination", required=True) def command_pgexport(output_encoding, dialect, database_uri, table_name, destination): updater = ProgressBar(prefix="Exporting data", unit="bytes") pgexport( database_uri=database_uri, table_name=table_name, filename=destination, encoding=output_encoding, dialect=dialect, callback=updater.update, ) updater.close() def extract_intervals(text, repeat=False, sort=True): """ >>> extract_intervals("1,2,3") [1, 2, 3] >>> extract_intervals("1,2,5-10") [1, 2, 5, 6, 7, 8, 9, 10] >>> extract_intervals("1,2,5-10,3") [1, 2, 3, 5, 6, 7, 8, 9, 10] >>> extract_intervals("1,2,5-10,6,7") [1, 2, 5, 6, 7, 8, 9, 10] """ result = [] for value in text.split(","): value = value.strip() if "-" in value: start_value, end_value = value.split("-") start_value = int(start_value.strip()) end_value = int(end_value.strip()) result.extend(range(start_value, end_value + 1)) else: result.append(int(value.strip())) if not repeat: result = list(set(result)) if sort: result.sort() return result @cli.command(name="pdf-to-text", help="Extract text from a PDF") @click.option("--output-encoding", default="utf-8") @click.option("--quiet", is_flag=True) @click.option("--backend", default="pymupdf") @click.option("--pages") @click.argument("source", required=True) @click.argument("output", required=False) def command_pdf_to_text(output_encoding, quiet, backend, pages, source, output): # Define page range if pages: pages = extract_intervals(pages) # Define if output is file or stdout if output: output = open(output, mode="w", encoding=output_encoding) write = output.write else: write = click.echo quiet = True progress = not quiet # Download the file if source is an HTTP URL downloaded = False if source.lower().startswith("http:") or source.lower().startswith("https:"): result = download_file(source, progress=progress, detect=False) source = result.uri downloaded = True reader = rows.plugins.pdf.pdf_to_text(source, page_numbers=pages, backend=backend) if progress: # Calculate total number of pages and create a progress bar if pages: total_pages = len(pages) else: total_pages = rows.plugins.pdf.number_of_pages(source, backend=backend) reader = tqdm(reader, desc="Extracting text", total=total_pages) for page in reader: write(page) if output: output.close() if downloaded: os.unlink(source) if __name__ == "__main__": cli() rows-0.4.1/rows/fields.py000066400000000000000000000472771343135453400153460ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import binascii import datetime import json import locale import re from base64 import b64decode, b64encode from collections import OrderedDict, defaultdict from decimal import Decimal, InvalidOperation from unicodedata import normalize import six if six.PY2: from itertools import izip_longest as zip_longest else: from itertools import zip_longest # Order matters here __all__ = [ "BoolField", "IntegerField", "FloatField", "DatetimeField", "DateField", "DecimalField", "PercentField", "JSONField", "EmailField", "TextField", "BinaryField", "Field", ] NULL = ("-", "null", "none", "nil", "n/a", "na") NULL_BYTES = (b"-", b"null", b"none", b"nil", b"n/a", b"na") REGEXP_ONLY_NUMBERS = re.compile("[^0-9\-]") SHOULD_NOT_USE_LOCALE = True # This variable is changed by rows.locale_manager SLUG_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_" def value_error(value, cls): value = repr(value) if len(value) > 50: value = value[:50] + "..." raise ValueError("Value '{}' can't be {}".format(value, cls.__name__)) class Field(object): """Base Field class - all fields should inherit from this As the fallback for all other field types are the BinaryField, this Field actually implements what is expected in the BinaryField """ TYPE = (type(None),) @classmethod def serialize(cls, value, *args, **kwargs): """Serialize a value to be exported `cls.serialize` should always return an unicode value, except for BinaryField """ if value is None: value = "" return value @classmethod def deserialize(cls, value, *args, **kwargs): """Deserialize a value just after importing it `cls.deserialize` should always return a value of type `cls.TYPE` or `None`. """ if isinstance(value, cls.TYPE): return value elif is_null(value): return None else: return value class BinaryField(Field): """Field class to represent byte arrays Is not locale-aware (does not need to be) """ TYPE = (six.binary_type,) @classmethod def serialize(cls, value, *args, **kwargs): if value is not None: if not isinstance(value, six.binary_type): value_error(value, cls) else: try: return b64encode(value).decode("ascii") except (TypeError, binascii.Error): return value else: return "" @classmethod def deserialize(cls, value, *args, **kwargs): if value is not None: if isinstance(value, six.binary_type): return value elif isinstance(value, six.text_type): try: return b64decode(value) except (TypeError, ValueError, binascii.Error): raise ValueError("Can't decode base64") else: value_error(value, cls) else: return b"" class BoolField(Field): """Base class to representing boolean Is not locale-aware (if you need to, please customize by changing its attributes like `TRUE_VALUES` and `FALSE_VALUES`) """ TYPE = (bool,) SERIALIZED_VALUES = {True: "true", False: "false", None: ""} TRUE_VALUES = ("true", "yes") FALSE_VALUES = ("false", "no") @classmethod def serialize(cls, value, *args, **kwargs): # TODO: should we serialize `None` as well or give it to the plugin? return cls.SERIALIZED_VALUES[value] @classmethod def deserialize(cls, value, *args, **kwargs): value = super(BoolField, cls).deserialize(value) if value is None or isinstance(value, cls.TYPE): return value value = as_string(value).lower() if value in cls.TRUE_VALUES: return True elif value in cls.FALSE_VALUES: return False else: raise ValueError("Value is not boolean") class IntegerField(Field): """Field class to represent integer Is locale-aware """ TYPE = (int,) @classmethod def serialize(cls, value, *args, **kwargs): if value is None: return "" if SHOULD_NOT_USE_LOCALE: return six.text_type(value) else: grouping = kwargs.get("grouping", None) return locale.format("%d", value, grouping=grouping) @classmethod def deserialize(cls, value, *args, **kwargs): value = super(IntegerField, cls).deserialize(value) if value is None or isinstance(value, cls.TYPE): return value elif isinstance(value, float): new_value = int(value) if new_value != value: raise ValueError("It's float, not integer") else: value = new_value value = as_string(value) if value != "0" and value.startswith("0"): raise ValueError("It's string, not integer") return int(value) if SHOULD_NOT_USE_LOCALE else locale.atoi(value) class FloatField(Field): """Field class to represent float Is locale-aware """ TYPE = (float,) @classmethod def serialize(cls, value, *args, **kwargs): if value is None: return "" if SHOULD_NOT_USE_LOCALE: return six.text_type(value) else: grouping = kwargs.get("grouping", None) return locale.format("%f", value, grouping=grouping) @classmethod def deserialize(cls, value, *args, **kwargs): value = super(FloatField, cls).deserialize(value) if value is None or isinstance(value, cls.TYPE): return value value = as_string(value) if SHOULD_NOT_USE_LOCALE: return float(value) else: return locale.atof(value) class DecimalField(Field): """Field class to represent decimal data (as Python's decimal.Decimal) Is locale-aware """ TYPE = (Decimal,) @classmethod def serialize(cls, value, *args, **kwargs): if value is None: return "" value_as_string = six.text_type(value) if SHOULD_NOT_USE_LOCALE: return value_as_string else: grouping = kwargs.get("grouping", None) has_decimal_places = value_as_string.find(".") != -1 if not has_decimal_places: string_format = "%d" else: decimal_places = len(value_as_string.split(".")[1]) string_format = "%.{}f".format(decimal_places) return locale.format(string_format, value, grouping=grouping) @classmethod def deserialize(cls, value, *args, **kwargs): value = super(DecimalField, cls).deserialize(value) if value is None or isinstance(value, cls.TYPE): return value elif type(value) in (int, float): return Decimal(six.text_type(value)) if SHOULD_NOT_USE_LOCALE: try: return Decimal(value) except InvalidOperation: value_error(value, cls) else: locale_vars = locale.localeconv() decimal_separator = locale_vars["decimal_point"] interesting_vars = ( "decimal_point", "mon_decimal_point", "mon_thousands_sep", "negative_sign", "positive_sign", "thousands_sep", ) chars = ( locale_vars[x].replace(".", r"\.").replace("-", r"\-") for x in interesting_vars ) interesting_chars = "".join(set(chars)) regexp = re.compile(r"[^0-9{} ]".format(interesting_chars)) value = as_string(value) if regexp.findall(value): value_error(value, cls) parts = [ REGEXP_ONLY_NUMBERS.subn("", number)[0] for number in value.split(decimal_separator) ] if len(parts) > 2: raise ValueError("Can't deserialize with this locale.") try: value = Decimal(parts[0]) if len(parts) == 2: decimal_places = len(parts[1]) value = value + (Decimal(parts[1]) / (10 ** decimal_places)) except InvalidOperation: value_error(value, cls) return value class PercentField(DecimalField): """Field class to represent percent values Is locale-aware (inherit this behaviour from `rows.DecimalField`) """ @classmethod def serialize(cls, value, *args, **kwargs): if value is None: return "" elif value == Decimal("0"): return "0.00%" value = Decimal(six.text_type(value * 100)[:-2]) value = super(PercentField, cls).serialize(value, *args, **kwargs) return "{}%".format(value) @classmethod def deserialize(cls, value, *args, **kwargs): if isinstance(value, cls.TYPE): return value elif is_null(value): return None value = as_string(value) if "%" not in value: value_error(value, cls) value = value.replace("%", "") return super(PercentField, cls).deserialize(value) / 100 class DateField(Field): """Field class to represent date Is not locale-aware (does not need to be) """ TYPE = (datetime.date,) INPUT_FORMAT = "%Y-%m-%d" OUTPUT_FORMAT = "%Y-%m-%d" @classmethod def serialize(cls, value, *args, **kwargs): if value is None: return "" return six.text_type(value.strftime(cls.OUTPUT_FORMAT)) @classmethod def deserialize(cls, value, *args, **kwargs): value = super(DateField, cls).deserialize(value) if value is None or isinstance(value, cls.TYPE): return value value = as_string(value) dt_object = datetime.datetime.strptime(value, cls.INPUT_FORMAT) return datetime.date(dt_object.year, dt_object.month, dt_object.day) class DatetimeField(Field): """Field class to represent date-time Is not locale-aware (does not need to be) """ TYPE = (datetime.datetime,) DATETIME_REGEXP = re.compile( "^([0-9]{4})-([0-9]{2})-([0-9]{2})[ T]" "([0-9]{2}):([0-9]{2}):([0-9]{2})$" ) @classmethod def serialize(cls, value, *args, **kwargs): if value is None: return "" return six.text_type(value.isoformat()) @classmethod def deserialize(cls, value, *args, **kwargs): value = super(DatetimeField, cls).deserialize(value) if value is None or isinstance(value, cls.TYPE): return value value = as_string(value) # TODO: may use iso8601 groups = cls.DATETIME_REGEXP.findall(value) if not groups: value_error(value, cls) else: return datetime.datetime(*[int(x) for x in groups[0]]) class TextField(Field): """Field class to represent unicode strings Is not locale-aware (does not need to be) """ TYPE = (six.text_type,) @classmethod def deserialize(cls, value, *args, **kwargs): if value is None or isinstance(value, cls.TYPE): return value else: return as_string(value) class EmailField(TextField): """Field class to represent e-mail addresses Is not locale-aware (does not need to be) """ EMAIL_REGEXP = re.compile( r"^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]+$", flags=re.IGNORECASE ) @classmethod def serialize(cls, value, *args, **kwargs): if value is None: return "" return six.text_type(value) @classmethod def deserialize(cls, value, *args, **kwargs): value = super(EmailField, cls).deserialize(value) if value is None or not value.strip(): return None result = cls.EMAIL_REGEXP.findall(value) if not result: value_error(value, cls) else: return result[0] class JSONField(Field): """Field class to represent JSON-encoded strings Is not locale-aware (does not need to be) """ TYPE = (list, dict) @classmethod def serialize(cls, value, *args, **kwargs): return json.dumps(value) @classmethod def deserialize(cls, value, *args, **kwargs): value = super(JSONField, cls).deserialize(value) if value is None or isinstance(value, cls.TYPE): return value else: return json.loads(value) def as_string(value): if isinstance(value, six.binary_type): raise ValueError("Binary is not supported") elif isinstance(value, six.text_type): return value else: return six.text_type(value) def is_null(value): if value is None: return True elif type(value) is six.binary_type: value = value.strip().lower() return not value or value in NULL_BYTES else: value_str = as_string(value).strip().lower() return not value_str or value_str in NULL def unique_values(values): result = [] for value in values: if not is_null(value) and value not in result: result.append(value) return result def get_items(*indexes): """Return a callable that fetches the given indexes of an object Always return a tuple even when len(indexes) == 1. Similar to `operator.itemgetter`, but will insert `None` when the object does not have the desired index (instead of raising IndexError). """ return lambda obj: tuple( obj[index] if len(obj) > index else None for index in indexes ) def slug(text, separator="_", permitted_chars=SLUG_CHARS, replace_with_separator=" -_"): """Generate a slug for the `text`. >>> slug(' ÁLVARO justen% ') 'alvaro_justen' >>> slug(' ÁLVARO justen% ', separator='-') 'alvaro-justen' """ text = six.text_type(text or "") # Strip non-ASCII characters # Example: u' ÁLVARO justen% ' -> ' ALVARO justen% ' text = normalize("NFKD", text.strip()).encode("ascii", "ignore").decode("ascii") # Replace spaces and other chars with separator # Example: u' ALVARO justen% ' -> u'_ALVARO__justen%_' for char in replace_with_separator: text = text.replace(char, separator) # Remove non-permitted characters and put everything to lowercase # Example: u'_ALVARO__justen%_' -> u'_alvaro__justen_' text = "".join(char for char in text if char in permitted_chars).lower() # Remove double occurrencies of separator # Example: u'_alvaro__justen_' -> u'_alvaro_justen_' double_separator = separator + separator while double_separator in text: text = text.replace(double_separator, separator) # Strip separators # Example: u'_alvaro_justen_' -> u'alvaro_justen' return text.strip(separator) def make_unique_name(name, existing_names, name_format="{name}_{index}", start=2): """Return a unique name based on `name_format` and `name`.""" index = start new_name = name while new_name in existing_names: new_name = name_format.format(name=name, index=index) index += 1 return new_name def make_header(field_names, permit_not=False): """Return unique and slugged field names.""" slug_chars = SLUG_CHARS if not permit_not else SLUG_CHARS + "^" header = [ slug(field_name, permitted_chars=slug_chars) for field_name in field_names ] result = [] for index, field_name in enumerate(header): if not field_name: field_name = "field_{}".format(index) elif field_name[0].isdigit(): field_name = "field_{}".format(field_name) if field_name in result: field_name = make_unique_name( name=field_name, existing_names=result, start=2 ) result.append(field_name) return result DEFAULT_TYPES = ( BoolField, IntegerField, FloatField, DecimalField, PercentField, DecimalField, DatetimeField, DateField, JSONField, TextField, BinaryField, ) class TypeDetector(object): """Detect data types based on a list of Field classes""" def __init__( self, field_names=None, field_types=DEFAULT_TYPES, fallback_type=TextField, skip_indexes=None, ): self.field_names = field_names or [] self.field_types = list(field_types) self.fallback_type = fallback_type self._possible_types = defaultdict(lambda: list(self.field_types)) self._samples = [] self._skip = skip_indexes or tuple() def check_type(self, index, value): for type_ in self._possible_types[index][:]: try: type_.deserialize(value) except (ValueError, TypeError): self._possible_types[index].remove(type_) def process_row(self, row): for index, value in enumerate(row): if index in self._skip: continue self.check_type(index, value) def feed(self, data): for row in data: self.process_row(row) def priority(self, *field_types): """Decide the priority between each possible type""" return field_types[0] if field_types else self.fallback_type @property def fields(self): possible, skip = self._possible_types, self._skip if possible: # Create a header with placeholder values for each detected column # and then join this placeholders with original header - the # original header may have less columns then the detected ones, so # we end with a full header having a name for every possible # column. placeholders = make_header(range(max(possible.keys()) + 1)) header = [a or b for a, b in zip_longest(self.field_names, placeholders)] else: header = self.field_names return OrderedDict( [ ( field_name, self.priority(*(possible[index] if index in possible else [])), ) for index, field_name in enumerate(header) if index not in skip ] ) def detect_types( field_names, field_values, field_types=DEFAULT_TYPES, skip_indexes=None, type_detector=TypeDetector, fallback_type=TextField, *args, **kwargs ): """Detect column types (or "where the magic happens")""" # TODO: look strategy of csv.Sniffer.has_header # TODO: may receive 'type hints' detector = type_detector( field_names, field_types=field_types, fallback_type=fallback_type, skip_indexes=skip_indexes, ) detector.feed(field_values) return detector.fields def identify_type(value): """Identify the field type for a specific value""" return detect_types(["name"], [[value]])["name"] rows-0.4.1/rows/localization.py000066400000000000000000000025671343135453400165610ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import contextlib import locale import six import rows.fields @contextlib.contextmanager def locale_context(name, category=locale.LC_ALL): old_name = locale.getlocale() if None not in old_name: old_name = ".".join(old_name) if isinstance(name, six.text_type): name = str(name) if old_name != name: locale.setlocale(category, name) rows.fields.SHOULD_NOT_USE_LOCALE = False try: yield finally: if old_name != name: locale.setlocale(category, old_name) rows.fields.SHOULD_NOT_USE_LOCALE = True rows-0.4.1/rows/operations.py000066400000000000000000000053531343135453400162500ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals from collections import OrderedDict from rows.plugins.utils import create_table from rows.table import Table def join(keys, tables): """Merge a list of `Table` objects using `keys` to group rows""" # Make new (merged) Table fields fields = OrderedDict() for table in tables: fields.update(table.fields) # TODO: may raise an error if a same field is different in some tables # Check if all keys are inside merged Table's fields fields_keys = set(fields.keys()) for key in keys: if key not in fields_keys: raise ValueError('Invalid key: "{}"'.format(key)) # Group rows by key, without missing ordering none_fields = lambda: OrderedDict({field: None for field in fields.keys()}) data = OrderedDict() for table in tables: for row in table: row_key = tuple([getattr(row, key) for key in keys]) if row_key not in data: data[row_key] = none_fields() data[row_key].update(row._asdict()) merged = Table(fields=fields) merged.extend(data.values()) return merged def transform(fields, function, *tables): "Return a new table based on other tables and a transformation function" new_table = Table(fields=fields) for table in tables: for row in filter(bool, map(lambda row: function(row, table), table)): new_table.append(row) return new_table def transpose(table, fields_column, *args, **kwargs): field_names = [] new_rows = [{} for _ in range(len(table.fields) - 1)] for row in table: row = row._asdict() field_name = row[fields_column] field_names.append(field_name) del row[fields_column] for index, value in enumerate(row.values()): new_rows[index][field_name] = value table_rows = [[row[field_name] for field_name in field_names] for row in new_rows] return create_table([field_names] + table_rows, *args, **kwargs) rows-0.4.1/rows/plugins/000077500000000000000000000000001343135453400151665ustar00rootroot00000000000000rows-0.4.1/rows/plugins/__init__.py000066400000000000000000000031371343135453400173030ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from . import plugin_json as json # NOQA from . import dicts as dicts # NOQA from . import plugin_csv as csv # NOQA from . import txt as txt # NOQA try: from . import plugin_html as html except ImportError: html = None try: from . import xpath as xpath except ImportError: xpath = None try: from . import ods as ods except ImportError: ods = None try: from . import sqlite as sqlite except ImportError: sqlite = None try: from . import xls as xls except ImportError: xls = None try: from . import xlsx as xlsx except ImportError: xlsx = None try: from . import plugin_parquet as parquet except ImportError: parquet = None try: from . import postgresql as postgresql except ImportError: postgresql = None try: from . import plugin_pdf as pdf except ImportError: pdf = None rows-0.4.1/rows/plugins/dicts.py000066400000000000000000000036121343135453400166500ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals from itertools import chain from rows.plugins.utils import create_table def import_from_dicts(data, samples=None, *args, **kwargs): """Import data from a iterable of dicts The algorithm will use the `samples` first `dict`s to determine the field names (if `samples` is `None` all `dict`s will be used). """ data = iter(data) cached_rows, headers = [], [] for index, row in enumerate(data, start=1): cached_rows.append(row) for key in row.keys(): if key not in headers: headers.append(key) if samples and index == samples: break data_rows = ( [row.get(header, None) for header in headers] for row in chain(cached_rows, data) ) kwargs["samples"] = samples meta = {"imported_from": "dicts"} return create_table(chain([headers], data_rows), meta=meta, *args, **kwargs) def export_to_dicts(table, *args, **kwargs): """Export a `rows.Table` to a list of dicts""" field_names = table.field_names return [{key: getattr(row, key) for key in field_names} for row in table] rows-0.4.1/rows/plugins/ods.py000066400000000000000000000075201343135453400163310ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import zipfile from decimal import Decimal from lxml.etree import fromstring as xml_from_string from lxml.etree import tostring as xml_to_string from rows.plugins.utils import create_table, get_filename_and_fobj def xpath(element, xpath, namespaces): return xml_from_string(xml_to_string(element)).xpath(xpath, namespaces=namespaces) def attrib(cell, namespace, name): return cell.attrib["{{{}}}{}".format(namespace, name)] def complete_with_None(lists, size): for element in lists: element.extend([None] * (size - len(element))) yield element def import_from_ods(filename_or_fobj, index=0, *args, **kwargs): # TODO: import spreadsheet by name # TODO: unescape values filename, _ = get_filename_and_fobj(filename_or_fobj) ods_file = zipfile.ZipFile(filename) content_fobj = ods_file.open("content.xml") xml = content_fobj.read() # will return bytes content_fobj.close() document = xml_from_string(xml) namespaces = document.nsmap spreadsheet = document.xpath("//office:spreadsheet", namespaces=namespaces)[0] tables = xpath(spreadsheet, "//table:table", namespaces) table = tables[index] table_rows_obj = xpath(table, "//table:table-row", namespaces) table_rows = [] for row_obj in table_rows_obj: row = [] for cell in xpath(row_obj, "//table:table-cell", namespaces): children = cell.getchildren() if not children: continue # TODO: evalute 'boolean' and 'time' types value_type = attrib(cell, namespaces["office"], "value-type") if value_type == "date": cell_value = attrib(cell, namespaces["office"], "date-value") elif value_type == "float": cell_value = attrib(cell, namespaces["office"], "value") elif value_type == "percentage": cell_value = attrib(cell, namespaces["office"], "value") cell_value = Decimal(cell_value) cell_value = "{:%}".format(cell_value) elif value_type == "string": try: # get computed string (from formula, for example) cell_value = attrib(cell, namespaces["office"], "string-value") except KeyError: # computed string not present => get from

...

cell_value = children[0].text else: # value_type == some type we don't know cell_value = children[0].text try: repeat = attrib(cell, namespaces["table"], "number-columns-repeated") except KeyError: row.append(cell_value) else: for _ in range(int(repeat)): row.append(cell_value) if row: table_rows.append(row) max_length = max(len(row) for row in table_rows) full_rows = complete_with_None(table_rows, max_length) meta = {"imported_from": "ods", "filename": filename} return create_table(full_rows, meta=meta, *args, **kwargs) rows-0.4.1/rows/plugins/plugin_csv.py000066400000000000000000000130771343135453400177210ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import sys from io import BytesIO import six import unicodecsv from rows.plugins.utils import ( create_table, get_filename_and_fobj, ipartition, serialize, ) sniffer = unicodecsv.Sniffer() unicodecsv.field_size_limit(sys.maxsize) def fix_dialect(dialect): if not dialect.doublequote and dialect.escapechar is None: dialect.doublequote = True if dialect.quoting == unicodecsv.QUOTE_MINIMAL and dialect.quotechar == "'": # Python csv's Sniffer seems to detect a wrong quotechar when # quoting is minimal dialect.quotechar = '"' if six.PY2: def discover_dialect(sample, encoding=None, delimiters=(b",", b";", b"\t", b"|")): """Discover a CSV dialect based on a sample size. `encoding` is not used (Python 2) """ try: dialect = sniffer.sniff(sample, delimiters=delimiters) except unicodecsv.Error: # Couldn't detect: fall back to 'excel' dialect = unicodecsv.excel fix_dialect(dialect) return dialect elif six.PY3: def discover_dialect(sample, encoding, delimiters=(",", ";", "\t", "|")): """Discover a CSV dialect based on a sample size. `sample` must be `bytes` and an `encoding must be provided (Python 3) """ # `csv.Sniffer.sniff` on Python 3 requires a `str` object. If we take a # sample from the `bytes` object and it happens to end in the middle of # a character which has more than one byte, we're going to have an # `UnicodeDecodeError`. This `while` avoid this problem by removing the # last byte until this error stops. finished = False while not finished: try: decoded = sample.decode(encoding) except UnicodeDecodeError as exception: _, _, _, pos, error = exception.args if error == "unexpected end of data" and pos == len(sample): sample = sample[:-1] else: raise else: finished = True try: dialect = sniffer.sniff(decoded, delimiters=delimiters) except unicodecsv.Error: # Couldn't detect: fall back to 'excel' dialect = unicodecsv.excel fix_dialect(dialect) return dialect def read_sample(fobj, sample): """Read `sample` bytes from `fobj` and return the cursor to where it was.""" cursor = fobj.tell() data = fobj.read(sample) fobj.seek(cursor) return data def import_from_csv( filename_or_fobj, encoding="utf-8", dialect=None, sample_size=262144, *args, **kwargs ): """Import data from a CSV file (automatically detects dialect). If a file-like object is provided it MUST be in binary mode, like in `open(filename, mode='rb')`. """ filename, fobj = get_filename_and_fobj(filename_or_fobj, mode="rb") if dialect is None: dialect = discover_dialect( sample=read_sample(fobj, sample_size), encoding=encoding ) reader = unicodecsv.reader(fobj, encoding=encoding, dialect=dialect) meta = {"imported_from": "csv", "filename": filename, "encoding": encoding} return create_table(reader, meta=meta, *args, **kwargs) def export_to_csv( table, filename_or_fobj=None, encoding="utf-8", dialect=unicodecsv.excel, batch_size=100, callback=None, *args, **kwargs ): """Export a `rows.Table` to a CSV file. If a file-like object is provided it MUST be in binary mode, like in `open(filename, mode='wb')`. If not filename/fobj is provided, the function returns a string with CSV contents. """ # TODO: will work only if table.fields is OrderedDict # TODO: should use fobj? What about creating a method like json.dumps? if filename_or_fobj is not None: _, fobj = get_filename_and_fobj(filename_or_fobj, mode="wb") else: fobj = BytesIO() # TODO: may use `io.BufferedWriter` instead of `ipartition` so user can # choose the real size (in Bytes) when to flush to the file system, instead # number of rows writer = unicodecsv.writer(fobj, encoding=encoding, dialect=dialect) if callback is None: for batch in ipartition(serialize(table, *args, **kwargs), batch_size): writer.writerows(batch) else: serialized = serialize(table, *args, **kwargs) writer.writerow(next(serialized)) # First, write the header total = 0 for batch in ipartition(serialized, batch_size): writer.writerows(batch) total += len(batch) callback(total) if filename_or_fobj is not None: fobj.flush() return fobj else: fobj.seek(0) result = fobj.read() fobj.close() return result rows-0.4.1/rows/plugins/plugin_html.py000066400000000000000000000124421343135453400200650ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import six from lxml.etree import strip_tags from lxml.etree import tostring as to_string from lxml.html import document_fromstring from rows.plugins.utils import ( create_table, export_data, get_filename_and_fobj, serialize, ) try: from HTMLParser import HTMLParser # Python 2 except: from html.parser import HTMLParser # Python 3 try: from html import escape # Python 3 except: from cgi import escape # Python 2 unescape = HTMLParser().unescape def _get_content(element): return (element.text if element.text is not None else "") + "".join( to_string(child, encoding=six.text_type) for child in element.getchildren() ) def _get_row(row, column_tag, preserve_html, properties): if not preserve_html: data = list(map(_extract_node_text, row.xpath(column_tag))) else: data = list(map(_get_content, row.xpath(column_tag))) if properties: data.append(dict(row.attrib)) return data def import_from_html( filename_or_fobj, encoding="utf-8", index=0, ignore_colspan=True, preserve_html=False, properties=False, table_tag="table", row_tag="tr", column_tag="td|th", *args, **kwargs ): """Return rows.Table from HTML file.""" filename, fobj = get_filename_and_fobj(filename_or_fobj, mode="rb") html = fobj.read().decode(encoding) html_tree = document_fromstring(html) tables = html_tree.xpath("//{}".format(table_tag)) table = tables[index] strip_tags(table, "thead") strip_tags(table, "tbody") row_elements = table.xpath(row_tag) table_rows = [ _get_row( row, column_tag=column_tag, preserve_html=preserve_html, properties=properties, ) for row in row_elements ] if properties: table_rows[0][-1] = "properties" if preserve_html and kwargs.get("fields", None) is None: # The field names will be the first table row, so we need to strip HTML # from it even if `preserve_html` is `True` (it's `True` only for rows, # not for the header). table_rows[0] = list(map(_extract_node_text, row_elements[0])) if ignore_colspan: max_columns = max(map(len, table_rows)) table_rows = [row for row in table_rows if len(row) == max_columns] meta = {"imported_from": "html", "filename": filename, "encoding": encoding} return create_table(table_rows, meta=meta, *args, **kwargs) def export_to_html(table, filename_or_fobj=None, encoding="utf-8", *args, **kwargs): """Export and return rows.Table data to HTML file.""" serialized_table = serialize(table, *args, **kwargs) fields = next(serialized_table) result = ["\n\n", " \n", " \n"] header = [" \n".format(field) for field in fields] result.extend(header) result.extend([" \n", " \n", "\n", " \n", "\n"]) for index, row in enumerate(serialized_table, start=1): css_class = "odd" if index % 2 == 1 else "even" result.append(' \n'.format(css_class)) for value in row: result.extend([" \n"]) result.append(" \n\n") result.append(" \n\n
{}
", escape(value), "
\n") html = "".join(result).encode(encoding) return export_data(filename_or_fobj, html, mode="wb") def _extract_node_text(node): """Extract text from a given lxml node.""" texts = map( six.text_type.strip, map(six.text_type, map(unescape, node.xpath(".//text()"))) ) return " ".join(text for text in texts if text) def count_tables(filename_or_fobj, encoding="utf-8", table_tag="table"): """Read a file passed by arg and return your table HTML tag count.""" filename, fobj = get_filename_and_fobj(filename_or_fobj) html = fobj.read().decode(encoding) html_tree = document_fromstring(html) tables = html_tree.xpath("//{}".format(table_tag)) return len(tables) def tag_to_dict(html): """Extract tag's attributes into a `dict`.""" element = document_fromstring(html).xpath("//html/body/child::*")[0] attributes = dict(element.attrib) attributes["text"] = element.text_content() return attributes def extract_text(html): """Extract text from a given HTML.""" return _extract_node_text(document_fromstring(html)) def extract_links(html): """Extract the href values from a given HTML (returns a list of strings).""" return document_fromstring(html).xpath(".//@href") rows-0.4.1/rows/plugins/plugin_json.py000066400000000000000000000063651343135453400201010ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import json import six from rows import fields from rows.plugins.utils import ( create_table, export_data, get_filename_and_fobj, prepare_to_export, ) def import_from_json(filename_or_fobj, encoding="utf-8", *args, **kwargs): """Import a JSON file or file-like object into a `rows.Table`. If a file-like object is provided it MUST be open in text (non-binary) mode on Python 3 and could be open in both binary or text mode on Python 2. """ filename, fobj = get_filename_and_fobj(filename_or_fobj) json_obj = json.load(fobj, encoding=encoding) field_names = list(json_obj[0].keys()) table_rows = [[item[key] for key in field_names] for item in json_obj] meta = {"imported_from": "json", "filename": filename, "encoding": encoding} return create_table([field_names] + table_rows, meta=meta, *args, **kwargs) def _convert(value, field_type, *args, **kwargs): if value is None or field_type in ( fields.BinaryField, fields.BoolField, fields.FloatField, fields.IntegerField, fields.JSONField, fields.TextField, ): # If the field_type is one of those, the value can be passed directly # to the JSON encoder return value else: # The field type is not represented natively in JSON, then it needs to # be serialized (converted to a string) return field_type.serialize(value, *args, **kwargs) def export_to_json( table, filename_or_fobj=None, encoding="utf-8", indent=None, *args, **kwargs ): """Export a `rows.Table` to a JSON file or file-like object. If a file-like object is provided it MUST be open in binary mode (like in `open('myfile.json', mode='wb')`). """ # TODO: will work only if table.fields is OrderedDict fields = table.fields prepared_table = prepare_to_export(table, *args, **kwargs) field_names = next(prepared_table) data = [ { field_name: _convert(value, fields[field_name], *args, **kwargs) for field_name, value in zip(field_names, row) } for row in prepared_table ] result = json.dumps(data, indent=indent) if type(result) is six.text_type: # Python 3 result = result.encode(encoding) if indent is not None: # clean up empty spaces at the end of lines result = b"\n".join(line.rstrip() for line in result.splitlines()) return export_data(filename_or_fobj, result, mode="wb") rows-0.4.1/rows/plugins/plugin_parquet.py000066400000000000000000000044761343135453400206120ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import logging from collections import OrderedDict from rows import fields from rows.plugins.utils import create_table, get_filename_and_fobj class NullHandler(logging.Handler): def emit(self, record): pass logging.getLogger("parquet").addHandler(NullHandler()) import parquet # NOQA PARQUET_TO_ROWS = { parquet.parquet_thrift.Type.BOOLEAN: fields.BoolField, parquet.parquet_thrift.Type.BYTE_ARRAY: fields.BinaryField, parquet.parquet_thrift.Type.DOUBLE: fields.FloatField, parquet.parquet_thrift.Type.FIXED_LEN_BYTE_ARRAY: fields.BinaryField, parquet.parquet_thrift.Type.FLOAT: fields.FloatField, parquet.parquet_thrift.Type.INT32: fields.IntegerField, parquet.parquet_thrift.Type.INT64: fields.IntegerField, parquet.parquet_thrift.Type.INT96: fields.IntegerField, } def import_from_parquet(filename_or_fobj, *args, **kwargs): """Import data from a Parquet file and return with rows.Table.""" filename, fobj = get_filename_and_fobj(filename_or_fobj, mode="rb") # TODO: should look into `schema.converted_type` also types = OrderedDict( [ (schema.name, PARQUET_TO_ROWS[schema.type]) for schema in parquet._read_footer(fobj).schema if schema.type is not None ] ) header = list(types.keys()) table_rows = list(parquet.reader(fobj)) # TODO: be lazy meta = {"imported_from": "parquet", "filename": filename} return create_table( [header] + table_rows, meta=meta, force_types=types, *args, **kwargs ) rows-0.4.1/rows/plugins/plugin_pdf.py000066400000000000000000000564371343135453400177060ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import io import six from cached_property import cached_property from rows.plugins.utils import create_table, get_filename_and_fobj try: import fitz as pymupdf pymupdf_imported = True except ImportError: pymupdf_imported = False try: from pdfminer.converter import PDFPageAggregator, TextConverter from pdfminer.layout import LAParams, LTTextBox, LTTextLine, LTChar, LTRect from pdfminer.pdfdocument import PDFDocument from pdfminer.pdfinterp import PDFResourceManager, PDFPageInterpreter, resolve1 from pdfminer.pdfpage import PDFPage from pdfminer.pdfparser import PDFParser import logging logging.getLogger("pdfminer").setLevel(logging.ERROR) PDFMINER_TEXT_TYPES = (LTTextBox, LTTextLine, LTChar) PDFMINER_ALL_TYPES = (LTTextBox, LTTextLine, LTChar, LTRect) pdfminer_imported = True except ImportError: pdfminer_imported = False PDFMINER_TEXT_TYPES, PDFMINER_ALL_TYPES = None, None def default_backend(): if pymupdf_imported: return "pymupdf" elif pdfminer_imported: return "pdfminer.six" else: raise ImportError( "No PDF backend found. Did you install the dependencies (pymupdf or pdfminer.six)?" ) def number_of_pages(filename_or_fobj, backend=None): backend = backend or default_backend() Backend = get_backend(backend) pdf_doc = Backend(filename_or_fobj) return pdf_doc.number_of_pages def pdf_to_text(filename_or_fobj, page_numbers=None, backend=None): backend = backend or default_backend() Backend = get_backend(backend) pdf_doc = Backend(filename_or_fobj) for page in pdf_doc.extract_text(page_numbers=page_numbers): yield page class PDFBackend(object): """Base Backend class to parse PDF files""" x_order = 1 y_order = 1 def __init__(self, filename_or_fobj): self.filename_or_fobj = filename_or_fobj @property def number_of_pages(self): "Number of pages in the document" raise NotImplementedError() def extract_text(self): "Return a string for each page in the document (generator)" raise NotImplementedError() def objects(self): "Return a list of objects for each page in the document (generator)" raise NotImplementedError() def text_objects(self): "Return a list of text objects for each page in the document (generator)" raise NotImplementedError() @property def text(self): return "\n\n".join(self.extract_text()) def get_cell_text(self, cell): if not cell: return "" if self.y_order == 1: cell.sort(key=lambda obj: obj.y0) else: cell.sort(key=lambda obj: -obj.y0) return "\n".join(obj.text.strip() for obj in cell) class PDFMinerBackend(PDFBackend): name = "pdfminer.six" y_order = -1 @cached_property def document(self): filename, fobj = get_filename_and_fobj(self.filename_or_fobj, mode="rb") parser = PDFParser(fobj) doc = PDFDocument(parser) parser.set_document(doc) return doc @cached_property def number_of_pages(self): return resolve1(self.document.catalog["Pages"])["Count"] def extract_text(self, page_numbers=None): for page_number, page in enumerate( PDFPage.create_pages(self.document), start=1 ): if page_numbers is not None and page_number not in page_numbers: continue rsrcmgr = PDFResourceManager() laparams = LAParams() result = io.StringIO() device = TextConverter(rsrcmgr, result, laparams=laparams) interpreter = PDFPageInterpreter(rsrcmgr, device) interpreter.process_page(page) yield result.getvalue() @staticmethod def convert_object(obj): if isinstance(obj, PDFMINER_TEXT_TYPES): return TextObject( x0=obj.x0, y0=obj.y0, x1=obj.x1, y1=obj.y1, text=obj.get_text() ) elif isinstance(obj, LTRect): return RectObject(x0=obj.x0, y0=obj.y0, x1=obj.x1, y1=obj.y1, fill=obj.fill) def objects( self, page_numbers=None, starts_after=None, ends_before=None, desired_types=PDFMINER_ALL_TYPES, ): doc = self.document rsrcmgr = PDFResourceManager() laparams = LAParams() device = PDFPageAggregator(rsrcmgr, laparams=laparams) interpreter = PDFPageInterpreter(rsrcmgr, device) started, finished = False, False if starts_after is None: started = True else: starts_after = get_delimiter_function(starts_after) if ends_before is not None: ends_before = get_delimiter_function(ends_before) for page_number, page in enumerate(PDFPage.create_pages(doc), start=1): if page_numbers is not None and page_number not in page_numbers: continue interpreter.process_page(page) layout = device.get_result() objs = [ PDFMinerBackend.convert_object(obj) for obj in layout if isinstance(obj, desired_types) ] objs.sort(key=lambda obj: -obj.y0) objects_in_page = [] for obj in objs: if not started and starts_after is not None and starts_after(obj): started = True if started and ends_before is not None and ends_before(obj): finished = True break if started: objects_in_page.append(obj) yield objects_in_page if finished: break def text_objects(self, page_numbers=None, starts_after=None, ends_before=None): return self.objects( page_numbers=page_numbers, starts_after=starts_after, ends_before=ends_before, desired_types=PDFMINER_TEXT_TYPES, ) class PyMuPDFBackend(PDFBackend): name = "pymupdf" @cached_property def document(self): filename, fobj = get_filename_and_fobj(self.filename_or_fobj, mode="rb") if not filename: data = fobj.read() # TODO: may use a lot of memory doc = pymupdf.open(stream=data, filetype="pdf") else: doc = pymupdf.open(filename=filename, filetype="pdf") return doc @cached_property def number_of_pages(self): return self.document.pageCount def extract_text(self, page_numbers=None): doc = self.document for page_number, page_index in enumerate(range(doc.pageCount), start=1): if page_numbers is not None and page_number not in page_numbers: continue page = doc.loadPage(page_index) page_text = "\n".join(block[4] for block in page.getTextBlocks()) yield page_text def objects(self, page_numbers=None, starts_after=None, ends_before=None): doc = self.document started, finished = False, False if starts_after is None: started = True else: starts_after = get_delimiter_function(starts_after) if ends_before is not None: ends_before = get_delimiter_function(ends_before) for page_number, page_index in enumerate(range(doc.pageCount), start=1): if page_numbers is not None and page_number not in page_numbers: continue page = doc.loadPage(page_index) text_objs = [] for block in page.getText("dict")["blocks"]: if block["type"] != 0: continue for line in block["lines"]: line_text = " ".join(span["text"] for span in line["spans"]) text_objs.append(list(line["bbox"]) + [line_text]) objs = [ TextObject(x0=obj[0], y0=obj[1], x1=obj[2], y1=obj[3], text=obj[4]) for obj in text_objs ] objs.sort(key=lambda obj: (obj.y0, obj.x0)) objects_in_page = [] for obj in objs: if not started and starts_after is not None and starts_after(obj): started = True if started and ends_before is not None and ends_before(obj): finished = True break if started: objects_in_page.append(obj) yield objects_in_page if finished: break text_objects = objects def get_delimiter_function(value): if isinstance(value, str): # regular string, match exactly return lambda obj: (isinstance(obj, TextObject) and obj.text.strip() == value) elif hasattr(value, "search"): # regular expression return lambda obj: bool( isinstance(obj, TextObject) and value.search(obj.text.strip()) ) elif callable(value): # function return lambda obj: bool(value(obj)) class TextObject(object): def __init__(self, x0, y0, x1, y1, text): self.x0 = x0 self.x1 = x1 self.y0 = y0 self.y1 = y1 self.text = text @property def bbox(self): return (self.x0, self.y0, self.x1, self.y1) def __repr__(self): text = repr(self.text) if len(text) > 50: text = repr(self.text[:45] + "[...]") bbox = ", ".join("{:.3f}".format(value) for value in self.bbox) return "".format(bbox, text) class RectObject(object): def __init__(self, x0, y0, x1, y1, fill): self.x0 = x0 self.x1 = x1 self.y0 = y0 self.y1 = y1 self.fill = fill @property def bbox(self): return (self.x0, self.y0, self.x1, self.y1) def __repr__(self): bbox = ", ".join("{:.3f}".format(value) for value in self.bbox) return "".format(bbox, self.fill) class Group(object): "Helper class to group objects based on its positions and sizes" def __init__(self, minimum=float("inf"), maximum=float("-inf"), threshold=0): self.minimum = minimum self.maximum = maximum self.threshold = threshold self.objects = [] @property def min(self): return self.minimum - self.threshold @property def max(self): return self.maximum + self.threshold def contains(self, obj): d0 = getattr(obj, self.dimension_0) d1 = getattr(obj, self.dimension_1) middle = d0 + (d1 - d0) / 2.0 return self.min <= middle <= self.max def add(self, obj): self.objects.append(obj) d0 = getattr(obj, self.dimension_0) d1 = getattr(obj, self.dimension_1) if d0 < self.minimum: self.minimum = d0 if d1 > self.maximum: self.maximum = d1 class HorizontalGroup(Group): dimension_0 = "y0" dimension_1 = "y1" class VerticalGroup(Group): dimension_0 = "x0" dimension_1 = "x1" def group_objects(objs, threshold, axis): if axis == "x": GroupClass = VerticalGroup elif axis == "y": GroupClass = HorizontalGroup groups = [] for obj in objs: found = False for group in groups: if group.contains(obj): group.add(obj) found = True break if not found: group = GroupClass(threshold=threshold) group.add(obj) groups.append(group) return {group.minimum: group.objects for group in groups} def contains_or_overlap(a, b): x1min, y1min, x1max, y1max = a x2min, y2min, x2max, y2max = b contains = x2min >= x1min and x2max <= x1max and y2min >= y1min and y2max <= y1max overlaps = ( (x1min <= x2min <= x1max and y1min <= y2min <= y1max) or (x1min <= x2min <= x1max and y1min <= y2max <= y1max) or (x1min <= x2max <= x1max and y1min <= y2min <= y1max) or (x1min <= x2max <= x1max and y1min <= y2max <= y1max) ) return contains or overlaps class ExtractionAlgorithm(object): def __init__( self, objects, text_objects, x_threshold, y_threshold, x_order, y_order ): self.objects = objects self.text_objects = text_objects self.x_threshold = x_threshold self.y_threshold = y_threshold self.x_order = x_order self.y_order = y_order @property def table_bbox(self): raise NotImplementedError @property def x_intervals(self): raise NotImplementedError @property def y_intervals(self): raise NotImplementedError @cached_property def selected_objects(self): """Filter out objects outside table boundaries""" return [ obj for obj in self.text_objects if contains_or_overlap(self.table_bbox, obj.bbox) ] def get_lines(self): x_intervals = list(self.x_intervals) if self.x_order == -1: x_intervals = list(reversed(x_intervals)) y_intervals = list(self.y_intervals) if self.y_order == -1: y_intervals = list(reversed(y_intervals)) objs = list(self.selected_objects) matrix = [] for y0, y1 in y_intervals: line = [] for x0, x1 in x_intervals: cell = [ obj for obj in objs if x0 <= obj.x0 <= x1 and y0 <= obj.y0 <= y1 ] if not cell: line.append(None) else: line.append(cell) for obj in cell: objs.remove(obj) matrix.append(line) return matrix class YGroupsAlgorithm(ExtractionAlgorithm): """Extraction algorithm based on objects' y values""" name = "y-groups" # TODO: filter out objects with empty text before grouping by y0 (but # consider them if inside table's bbox) # TODO: get y0 groups bbox and merge overlapping ones (overlapping only on # y, not on x). ex: imgs-33281.pdf/06.png should not remove bigger cells @cached_property def table_bbox(self): groups = group_objects(self.text_objects, self.y_threshold, "y") desired_objs = [] for group_objs in groups.values(): if len(group_objs) < 2: # Ignore floating text objects continue desired_objs.extend(group_objs) if not desired_objs: return (0, 0, 0, 0) x_min = min(obj.x0 for obj in desired_objs) x_max = max(obj.x1 for obj in desired_objs) y_min = min(obj.y0 for obj in desired_objs) y_max = max(obj.y1 for obj in desired_objs) return (x_min, y_min, x_max, y_max) @staticmethod def _define_intervals(objs, min_attr, max_attr, threshold, axis): groups = group_objects(objs, threshold, axis) intervals = [ (key, max_attr(max(value, key=max_attr))) for key, value in groups.items() ] intervals.sort() if not intervals: return [] # Merge overlapping intervals result = [intervals[0]] for current in intervals[1:]: previous = result.pop() if current[0] <= previous[1] or current[1] <= previous[1]: result.append((previous[0], max((previous[1], current[1])))) else: result.extend((previous, current)) return result @cached_property def x_intervals(self): objects = self.selected_objects objects.sort(key=lambda obj: obj.x0) return self._define_intervals( objects, min_attr=lambda obj: obj.x0, max_attr=lambda obj: obj.x1, threshold=self.x_threshold, axis="x", ) @cached_property def y_intervals(self): objects = self.selected_objects objects.sort(key=lambda obj: -obj.y1) return self._define_intervals( objects, min_attr=lambda obj: obj.y0, max_attr=lambda obj: obj.y1, threshold=self.y_threshold, axis="y", ) class HeaderPositionAlgorithm(YGroupsAlgorithm): name = "header-position" @property def x_intervals(self): raise NotImplementedError def get_lines(self): objects = self.selected_objects objects.sort(key=lambda obj: obj.x0) y_intervals = list(self.y_intervals) if self.y_order == -1: y_intervals = list(reversed(y_intervals)) used, lines = [], [] header_interval = y_intervals[0] header_objs = [ obj for obj in objects if header_interval[0] <= obj.y0 <= header_interval[1] ] used.extend(header_objs) lines.append([[obj] for obj in header_objs]) def x_intersects(a, b): return a.x0 < b.x1 and a.x1 > b.x0 for y0, y1 in y_intervals[1:]: line_objs = [ obj for obj in objects if obj not in used and y0 <= obj.y0 <= y1 ] line = [] for column in header_objs: y_objs = [ obj for obj in line_objs if obj not in used and x_intersects(column, obj) ] used.extend(y_objs) line.append(y_objs) lines.append(line) # TODO: may check if one of objects in line_objs is not in used and # raise an exception return lines class RectsBoundariesAlgorithm(ExtractionAlgorithm): """Extraction algorithm based on rectangles present in the page""" name = "rects-boundaries" def __init__(self, *args, **kwargs): super(RectsBoundariesAlgorithm, self).__init__(*args, **kwargs) self.rects = [ obj for obj in self.objects if isinstance(obj, RectObject) and obj.fill ] @cached_property def table_bbox(self): y0 = min(obj.y0 for obj in self.rects) y1 = max(obj.y1 for obj in self.rects) x0 = min(obj.x0 for obj in self.rects) x1 = max(obj.x1 for obj in self.rects) return (x0, y0, x1, y1) @staticmethod def _clean_intersections(lines): def other_line_contains(all_lines, search_line): for line2 in all_lines: if search_line == line2: continue elif search_line[0] >= line2[0] and search_line[1] <= line2[1]: return True return False final = [] for line in lines: if not other_line_contains(lines, line): final.append(line) return final @cached_property def x_intervals(self): x_intervals = set((obj.x0, obj.x1) for obj in self.rects) return sorted(self._clean_intersections(x_intervals)) @cached_property def y_intervals(self): y_intervals = set((obj.y0, obj.y1) for obj in self.rects) return sorted(self._clean_intersections(y_intervals)) def subclasses(cls): children = cls.__subclasses__() return set(children).union( set(grandchild for child in children for grandchild in subclasses(child)) ) def algorithms(): return {Class.name: Class for Class in subclasses(ExtractionAlgorithm)} def get_algorithm(algorithm): available_algorithms = algorithms() if isinstance(algorithm, six.text_type): if algorithm not in available_algorithms: raise ValueError( 'Unknown algorithm "{}" (options are: {})'.format( algorithm, ", ".join(available_algorithms.keys()) ) ) return available_algorithms[algorithm] elif issubclass(algorithm, ExtractionAlgorithm): return algorithm else: raise ValueError( 'Unknown algorithm "{}" (options are: {})'.format( algorithm, ", ".join(available_algorithms.keys()) ) ) def backends(): return {Class.name: Class for Class in subclasses(PDFBackend)} def get_backend(backend): available_backends = backends() if isinstance(backend, six.text_type): if backend not in available_backends: raise ValueError( 'Unknown PDF backend "{}" (options are: {})'.format( backend, ", ".join(available_backends.keys()) ) ) return available_backends[backend] elif issubclass(backend, PDFBackend): return backend else: raise ValueError( 'Unknown PDF backend "{}" (options are: {})'.format( backend, ", ".join(available_backends.keys()) ) ) def pdf_table_lines( filename_or_fobj, page_numbers=None, algorithm="y-groups", starts_after=None, ends_before=None, x_threshold=0.5, y_threshold=0.5, backend=None, ): backend = backend or default_backend() # TODO: check if both backends accepts filename or fobj Backend = get_backend(backend) Algorithm = get_algorithm(algorithm) pdf_doc = Backend(filename_or_fobj) pages = pdf_doc.objects( page_numbers=page_numbers, starts_after=starts_after, ends_before=ends_before ) header = line_size = None for page_index, page in enumerate(pages): objs = list(page) text_objs = [obj for obj in objs if isinstance(obj, TextObject)] extractor = Algorithm( objs, text_objs, x_threshold, y_threshold, pdf_doc.x_order, pdf_doc.y_order ) lines = [ [pdf_doc.get_cell_text(cell) for cell in row] for row in extractor.get_lines() ] for line_index, line in enumerate(lines): if line_index == 0: if page_index == 0: header = line elif page_index > 0 and line == header: # skip header repetition continue yield line def import_from_pdf( filename_or_fobj, page_numbers=None, starts_after=None, ends_before=None, backend=None, algorithm="y-groups", x_threshold=0.5, y_threshold=0.5, *args, **kwargs ): backend = backend or default_backend() meta = {"imported_from": "pdf"} table_rows = pdf_table_lines( filename_or_fobj, page_numbers, starts_after=starts_after, ends_before=ends_before, algorithm=algorithm, x_threshold=x_threshold, y_threshold=y_threshold, backend=backend, ) return create_table(table_rows, meta=meta, *args, **kwargs) # Call the function so it'll raise ImportError if no backend is available default_backend() rows-0.4.1/rows/plugins/postgresql.py000066400000000000000000000132501343135453400177440ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import string import six from psycopg2 import connect as pgconnect import rows.fields as fields from rows.plugins.utils import ( create_table, get_filename_and_fobj, ipartition, make_unique_name, prepare_to_export, ) SQL_TABLE_NAMES = """ SELECT tablename FROM pg_tables WHERE schemaname NOT IN ('pg_catalog', 'information_schema') """ SQL_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " '"{table_name}" ({field_types})' SQL_SELECT_ALL = 'SELECT * FROM "{table_name}"' SQL_INSERT = 'INSERT INTO "{table_name}" ({field_names}) ' "VALUES ({placeholders})" SQL_TYPES = { fields.BinaryField: "BYTEA", fields.BoolField: "BOOLEAN", fields.DateField: "DATE", fields.DatetimeField: "TIMESTAMP(0) WITHOUT TIME ZONE", fields.DecimalField: "NUMERIC", fields.FloatField: "REAL", fields.IntegerField: "INTEGER", fields.PercentField: "REAL", fields.TextField: "TEXT", fields.JSONField: "JSONB", } DEFAULT_TYPE = "BYTEA" def _python_to_postgresql(field_types): def convert_value(field_type, value): if field_type in ( fields.BinaryField, fields.BoolField, fields.DateField, fields.DatetimeField, fields.DecimalField, fields.FloatField, fields.IntegerField, fields.PercentField, fields.TextField, fields.JSONField, ): return value else: # don't know this field return field_type.serialize(value) def convert_row(row): return [ convert_value(field_type, value) for field_type, value in zip(field_types, row) ] return convert_row def _get_connection(connection): if isinstance(connection, (six.binary_type, six.text_type)): return pgconnect(connection) else: # already a connection return connection def _valid_table_name(name): """Verify if a given table name is valid for `rows` Rules: - Should start with a letter or '_' - Letters can be capitalized or not - Accepts letters, numbers and _ """ if name[0] not in "_" + string.ascii_letters or not set(name).issubset( "_" + string.ascii_letters + string.digits ): return False else: return True def import_from_postgresql( connection_or_uri, table_name="table1", query=None, query_args=None, close_connection=False, *args, **kwargs ): if query is None: if not _valid_table_name(table_name): raise ValueError("Invalid table name: {}".format(table_name)) query = SQL_SELECT_ALL.format(table_name=table_name) if query_args is None: query_args = tuple() connection = _get_connection(connection_or_uri) cursor = connection.cursor() cursor.execute(query, query_args) table_rows = list(cursor.fetchall()) # TODO: make it lazy header = [six.text_type(info[0]) for info in cursor.description] cursor.close() connection.commit() # WHY? meta = {"imported_from": "postgresql", "source": connection_or_uri} if close_connection: connection.close() return create_table([header] + table_rows, meta=meta, *args, **kwargs) def export_to_postgresql( table, connection_or_uri, table_name=None, table_name_format="table{index}", batch_size=100, close_connection=False, *args, **kwargs ): # TODO: should add transaction support? if table_name is not None and not _valid_table_name(table_name): raise ValueError("Invalid table name: {}".format(table_name)) prepared_table = prepare_to_export(table, *args, **kwargs) connection = _get_connection(connection_or_uri) cursor = connection.cursor() if table_name is None: cursor.execute(SQL_TABLE_NAMES) table_names = [item[0] for item in cursor.fetchall()] table_name = make_unique_name( table_name_format.format(index=1), existing_names=table_names, name_format=table_name_format, start=1, ) field_names = next(prepared_table) field_types = list(map(table.fields.get, field_names)) columns = [ "{} {}".format(field_name, SQL_TYPES.get(field_type, DEFAULT_TYPE)) for field_name, field_type in zip(field_names, field_types) ] cursor.execute( SQL_CREATE_TABLE.format(table_name=table_name, field_types=", ".join(columns)) ) insert_sql = SQL_INSERT.format( table_name=table_name, field_names=", ".join(field_names), placeholders=", ".join("%s" for _ in field_names), ) _convert_row = _python_to_postgresql(field_types) for batch in ipartition(prepared_table, batch_size): cursor.executemany(insert_sql, map(_convert_row, batch)) connection.commit() cursor.close() if close_connection: connection.close() return connection, table_name rows-0.4.1/rows/plugins/sqlite.py000066400000000000000000000142251343135453400170450ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import datetime import sqlite3 import string import six import rows.fields as fields from rows.plugins.utils import ( create_table, get_filename_and_fobj, ipartition, make_unique_name, prepare_to_export, ) SQL_TABLE_NAMES = 'SELECT name FROM sqlite_master WHERE type="table"' SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS "{table_name}" ({field_types})' SQL_SELECT_ALL = 'SELECT * FROM "{table_name}"' SQL_INSERT = 'INSERT INTO "{table_name}" ({field_names}) VALUES ({placeholders})' SQLITE_TYPES = { fields.BinaryField: "BLOB", fields.BoolField: "INTEGER", fields.DateField: "TEXT", fields.DatetimeField: "TEXT", fields.DecimalField: "REAL", fields.FloatField: "REAL", fields.IntegerField: "INTEGER", fields.PercentField: "REAL", fields.TextField: "TEXT", } DEFAULT_TYPE = "BLOB" def _python_to_sqlite(field_types): def convert_value(field_type, value): if field_type in ( fields.BinaryField, fields.BoolField, fields.FloatField, fields.IntegerField, fields.TextField, ): return value elif field_type in (fields.DateField, fields.DatetimeField): if value is None: return None elif isinstance(value, (datetime.date, datetime.datetime)): return value.isoformat() elif isinstance(value, (six.binary_type, six.text_type)): return value else: raise ValueError("Cannot serialize date value: {}".format(repr(value))) elif field_type in (fields.DecimalField, fields.PercentField): return float(value) if value is not None else None else: # don't know this field return field_type.serialize(value) def convert_row(row): return [ convert_value(field_type, value) for field_type, value in zip(field_types, row) ] return convert_row def _get_connection(filename_or_connection): if isinstance(filename_or_connection, (six.binary_type, six.text_type)): return sqlite3.connect(filename_or_connection) # filename else: # already a connection return filename_or_connection def _valid_table_name(name): """Verify if a given table name is valid for `rows`. Rules: - Should start with a letter or '_' - Letters can be capitalized or not - Acceps letters, numbers and _ """ if name[0] not in "_" + string.ascii_letters or not set(name).issubset( "_" + string.ascii_letters + string.digits ): return False else: return True def import_from_sqlite( filename_or_connection, table_name="table1", query=None, query_args=None, *args, **kwargs ): """Return a rows.Table with data from SQLite database.""" connection = _get_connection(filename_or_connection) cursor = connection.cursor() if query is None: if not _valid_table_name(table_name): raise ValueError("Invalid table name: {}".format(table_name)) query = SQL_SELECT_ALL.format(table_name=table_name) if query_args is None: query_args = tuple() table_rows = list(cursor.execute(query, query_args)) # TODO: may be lazy header = [six.text_type(info[0]) for info in cursor.description] cursor.close() # TODO: should close connection also? meta = {"imported_from": "sqlite", "filename": filename_or_connection} return create_table([header] + table_rows, meta=meta, *args, **kwargs) def export_to_sqlite( table, filename_or_connection, table_name=None, table_name_format="table{index}", batch_size=100, callback=None, *args, **kwargs ): # TODO: should add transaction support? prepared_table = prepare_to_export(table, *args, **kwargs) connection = _get_connection(filename_or_connection) cursor = connection.cursor() if table_name is None: table_names = [item[0] for item in cursor.execute(SQL_TABLE_NAMES)] table_name = make_unique_name( table_name_format.format(index=1), existing_names=table_names, name_format=table_name_format, start=1, ) elif not _valid_table_name(table_name): raise ValueError("Invalid table name: {}".format(table_name)) field_names = next(prepared_table) field_types = list(map(table.fields.get, field_names)) columns = [ "{} {}".format(field_name, SQLITE_TYPES.get(field_type, DEFAULT_TYPE)) for field_name, field_type in zip(field_names, field_types) ] cursor.execute( SQL_CREATE_TABLE.format(table_name=table_name, field_types=", ".join(columns)) ) insert_sql = SQL_INSERT.format( table_name=table_name, field_names=", ".join(field_names), placeholders=", ".join("?" for _ in field_names), ) _convert_row = _python_to_sqlite(field_types) if callback is None: for batch in ipartition(prepared_table, batch_size): cursor.executemany(insert_sql, map(_convert_row, batch)) else: total_written = 0 for batch in ipartition(prepared_table, batch_size): cursor.executemany(insert_sql, map(_convert_row, batch)) written = len(batch) total_written += written callback(written, total_written) connection.commit() return connection rows-0.4.1/rows/plugins/txt.py000066400000000000000000000210741343135453400163630ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import re import unicodedata from collections import defaultdict from rows.plugins.utils import ( create_table, export_data, get_filename_and_fobj, serialize, ) single_frame_prefix = "BOX DRAWINGS LIGHT" double_frame_prefix = "BOX DRAWINGS DOUBLE" frame_parts = [ name.strip() for name in """ VERTICAL, HORIZONTAL, DOWN AND RIGHT, DOWN AND LEFT, UP AND RIGHT, UP AND LEFT, VERTICAL AND LEFT, VERTICAL AND RIGHT, DOWN AND HORIZONTAL, UP AND HORIZONTAL, VERTICAL AND HORIZONTAL""".split( "," ) ] # Rendered characters inserted in comments # for grepping/visualization purposes. # ['│', '─', '┌', '┐', '└', '┘', '┤', '├', '┬', '┴', '┼'] SINGLE_FRAME = { name.strip(): unicodedata.lookup(" ".join((single_frame_prefix, name.strip()))) for name in frame_parts } # ['║', '═', '╔', '╗', '╚', '╝', '╣', '╠', '╦', '╩', '╬'] DOUBLE_FRAME = { name.strip(): unicodedata.lookup(" ".join((double_frame_prefix, name.strip()))) for name in frame_parts } ASCII_FRAME = {name: "+" for name in frame_parts} ASCII_FRAME["HORIZONTAL"] = "-" ASCII_FRAME["VERTICAL"] = "|" NONE_FRAME = defaultdict(lambda: " ") FRAMES = { "none": NONE_FRAME, "ascii": ASCII_FRAME, "single": SINGLE_FRAME, "double": DOUBLE_FRAME, } del single_frame_prefix, double_frame_prefix, frame_parts del NONE_FRAME, ASCII_FRAME, SINGLE_FRAME, DOUBLE_FRAME FRAME_SENTINEL = object() def _parse_frame_style(frame_style): if frame_style is None: frame_style = "None" try: FRAMES[frame_style.lower()] except KeyError: raise ValueError( "Invalid frame style '{}'. Use one of 'None', " "'ASCII', 'single' or 'double'.".format(frame_style) ) return frame_style def _guess_frame_style(contents): first_line_chars = set(contents.split("\n")[0].strip()) for frame_style, frame_dict in FRAMES.items(): if first_line_chars <= set(frame_dict.values()): return frame_style return "None" def _parse_col_positions(frame_style, header_line): """Find the position for each column separator in the given line If frame_style is 'None', this won work for column names that _start_ with whitespace (which includes non-lefthand aligned column titles) """ separator = re.escape(FRAMES[frame_style.lower()]["VERTICAL"]) if frame_style == "None": separator = r"[\s]{2}[^\s]" # Matches two whitespaces followed by a non-whitespace. # Our column headers are serated by 3 spaces by default. col_positions = [] # Abuse regexp engine to anotate vertical-separator positions: re.sub(separator, lambda group: col_positions.append(group.start()), header_line) if frame_style == "None": col_positions.append(len(header_line) - 1) return col_positions def _max_column_sizes(field_names, table_rows): columns = zip(*([field_names] + table_rows)) return { field_name: max(len(value) for value in column) for field_name, column in zip(field_names, columns) } def import_from_txt( filename_or_fobj, encoding="utf-8", frame_style=FRAME_SENTINEL, *args, **kwargs ): """Return a rows.Table created from imported TXT file.""" # TODO: (maybe) # enable parsing of non-fixed-width-columns # with old algorithm - that would just split columns # at the vertical separator character for the frame. # (if doing so, include an optional parameter) # Also, this fixes an outstanding unreported issue: # trying to parse tables which fields values # included a Pipe char - "|" - would silently # yield bad results. filename, fobj = get_filename_and_fobj(filename_or_fobj, mode="rb") raw_contents = fobj.read().decode(encoding).rstrip("\n") if frame_style is FRAME_SENTINEL: frame_style = _guess_frame_style(raw_contents) else: frame_style = _parse_frame_style(frame_style) contents = raw_contents.splitlines() del raw_contents if frame_style != "None": contents = contents[1:-1] del contents[1] else: # the table is possibly generated from other source. # check if the line we reserve as a separator is realy empty. if not contents[1].strip(): del contents[1] col_positions = _parse_col_positions(frame_style, contents[0]) table_rows = [ [ row[start + 1 : end].strip() for start, end in zip(col_positions, col_positions[1:]) ] for row in contents ] # # Variable columns - old behavior: # table_rows = [[value.strip() for value in row.split(vertical_char)[1:-1]] # for row in contents] meta = { "imported_from": "txt", "filename": filename, "encoding": encoding, "frame_style": frame_style, } return create_table(table_rows, meta=meta, *args, **kwargs) def export_to_txt( table, filename_or_fobj=None, encoding=None, frame_style="ASCII", safe_none_frame=True, *args, **kwargs ): """Export a `rows.Table` to text. This function can return the result as a string or save into a file (via filename or file-like object). `encoding` could be `None` if no filename/file-like object is specified, then the return type will be `six.text_type`. `frame_style`: will select the frame style to be printed around data. Valid values are: ('None', 'ASCII', 'single', 'double') - ASCII is default. Warning: no checks are made to check the desired encoding allows the characters needed by single and double frame styles. `safe_none_frame`: bool, defaults to True. Affects only output with frame_style == "None": column titles are left-aligned and have whitespace replaced for "_". This enables the output to be parseable. Otherwise, the generated table will look prettier but can not be imported back. """ # TODO: will work only if table.fields is OrderedDict frame_style = _parse_frame_style(frame_style) frame = FRAMES[frame_style.lower()] serialized_table = serialize(table, *args, **kwargs) field_names = next(serialized_table) table_rows = list(serialized_table) max_sizes = _max_column_sizes(field_names, table_rows) dashes = [frame["HORIZONTAL"] * (max_sizes[field] + 2) for field in field_names] if frame_style != "None" or not safe_none_frame: header = [field.center(max_sizes[field]) for field in field_names] else: header = [ field.replace(" ", "_").ljust(max_sizes[field]) for field in field_names ] header = "{0} {1} {0}".format( frame["VERTICAL"], " {} ".format(frame["VERTICAL"]).join(header) ) top_split_line = ( frame["DOWN AND RIGHT"] + frame["DOWN AND HORIZONTAL"].join(dashes) + frame["DOWN AND LEFT"] ) body_split_line = ( frame["VERTICAL AND RIGHT"] + frame["VERTICAL AND HORIZONTAL"].join(dashes) + frame["VERTICAL AND LEFT"] ) botton_split_line = ( frame["UP AND RIGHT"] + frame["UP AND HORIZONTAL"].join(dashes) + frame["UP AND LEFT"] ) result = [] if frame_style != "None": result += [top_split_line] result += [header, body_split_line] for row in table_rows: values = [ value.rjust(max_sizes[field_name]) for field_name, value in zip(field_names, row) ] row_data = " {} ".format(frame["VERTICAL"]).join(values) result.append("{0} {1} {0}".format(frame["VERTICAL"], row_data)) if frame_style != "None": result.append(botton_split_line) result.append("") data = "\n".join(result) if encoding is not None: data = data.encode(encoding) return export_data(filename_or_fobj, data, mode="wb") rows-0.4.1/rows/plugins/utils.py000066400000000000000000000164521343135453400167100ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals from collections import OrderedDict from itertools import chain, islice import six # 'slug' and 'make_unique_name' are required here to maintain backwards compatibility from rows.fields import ( TextField, detect_types, get_items, # NOQA make_header, make_unique_name, slug, ) from rows.table import FlexibleTable, Table if six.PY2: from collections import Iterator elif six.PY3: from collections.abc import Iterator def ipartition(iterable, partition_size): if not isinstance(iterable, Iterator): iterator = iter(iterable) else: iterator = iterable finished = False while not finished: data = [] for _ in range(partition_size): try: data.append(next(iterator)) except StopIteration: finished = True break if data: yield data def get_filename_and_fobj(filename_or_fobj, mode="r", dont_open=False): if getattr(filename_or_fobj, "read", None) is not None: fobj = filename_or_fobj filename = getattr(fobj, "name", None) else: fobj = open(filename_or_fobj, mode=mode) if not dont_open else None filename = filename_or_fobj return filename, fobj def create_table( data, meta=None, fields=None, skip_header=True, import_fields=None, samples=None, force_types=None, *args, **kwargs ): """Create a rows.Table object based on data rows and some configurations - `skip_header` is only used if `fields` is set - `samples` is only used if `fields` is `None`. If samples=None, all data is filled in memory - use with caution. - `force_types` is only used if `fields` is `None` - `import_fields` can be used either if `fields` is set or not, the resulting fields will seek its order - `fields` must always be in the same order as the data """ table_rows = iter(data) force_types = force_types or {} if import_fields is not None: import_fields = make_header(import_fields) if fields is None: # autodetect field types # TODO: may add `type_hints` parameter so autodetection can be easier # (plugins may specify some possible field types). header = make_header(next(table_rows)) if samples is not None: sample_rows = list(islice(table_rows, 0, samples)) table_rows = chain(sample_rows, table_rows) else: sample_rows = table_rows = list(table_rows) # Detect field types using only the desired columns detected_fields = detect_types( header, sample_rows, skip_indexes=[ index for index, field in enumerate(header) if field in force_types or field not in (import_fields or header) ], *args, **kwargs ) # Check if any field was added during detecting process new_fields = [ field_name for field_name in detected_fields.keys() if field_name not in header ] # Finally create the `fields` with both header and new field names, # based on detected fields `and force_types` fields = OrderedDict( [ (field_name, detected_fields.get(field_name, TextField)) for field_name in header + new_fields ] ) fields.update(force_types) # Update `header` and `import_fields` based on new `fields` header = list(fields.keys()) if import_fields is None: import_fields = header else: # using provided field types if not isinstance(fields, OrderedDict): raise ValueError("`fields` must be an `OrderedDict`") if skip_header: # If we're skipping the header probably this row is not trustable # (can be data or garbage). _ = next(table_rows) header = make_header(list(fields.keys())) if import_fields is None: import_fields = header fields = OrderedDict( [(field_name, fields[key]) for field_name, key in zip(header, fields)] ) diff = set(import_fields) - set(header) if diff: field_names = ", ".join('"{}"'.format(field) for field in diff) raise ValueError("Invalid field names: {}".format(field_names)) fields = OrderedDict( [(field_name, fields[field_name]) for field_name in import_fields] ) get_row = get_items(*map(header.index, import_fields)) table = Table(fields=fields, meta=meta) table.extend(dict(zip(import_fields, get_row(row))) for row in table_rows) return table def prepare_to_export(table, export_fields=None, *args, **kwargs): # TODO: optimize for more used cases (export_fields=None) table_type = type(table) if table_type not in (FlexibleTable, Table): raise ValueError("Table type not recognized") if export_fields is None: # we use already slugged-fieldnames export_fields = table.field_names else: # we need to slug all the field names export_fields = make_header(export_fields) table_field_names = table.field_names diff = set(export_fields) - set(table_field_names) if diff: field_names = ", ".join('"{}"'.format(field) for field in diff) raise ValueError("Invalid field names: {}".format(field_names)) yield export_fields if table_type is Table: field_indexes = list(map(table_field_names.index, export_fields)) for row in table._rows: yield [row[field_index] for field_index in field_indexes] elif table_type is FlexibleTable: for row in table._rows: yield [row[field_name] for field_name in export_fields] def serialize(table, *args, **kwargs): prepared_table = prepare_to_export(table, *args, **kwargs) field_names = next(prepared_table) yield field_names field_types = [table.fields[field_name] for field_name in field_names] for row in prepared_table: yield [ field_type.serialize(value, *args, **kwargs) for value, field_type in zip(row, field_types) ] def export_data(filename_or_fobj, data, mode="w"): """Return the object ready to be exported or only data if filename_or_fobj is not passed.""" if filename_or_fobj is not None: _, fobj = get_filename_and_fobj(filename_or_fobj, mode=mode) fobj.write(data) fobj.flush() return fobj else: return data rows-0.4.1/rows/plugins/xls.py000066400000000000000000000166211343135453400163540ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import datetime from io import BytesIO import xlrd import xlwt import rows.fields as fields from rows.plugins.utils import create_table, get_filename_and_fobj, prepare_to_export CELL_TYPES = { xlrd.XL_CELL_BLANK: fields.TextField, xlrd.XL_CELL_DATE: fields.DatetimeField, xlrd.XL_CELL_ERROR: None, xlrd.XL_CELL_TEXT: fields.TextField, xlrd.XL_CELL_BOOLEAN: fields.BoolField, xlrd.XL_CELL_EMPTY: None, xlrd.XL_CELL_NUMBER: fields.FloatField, } # TODO: add more formatting styles for other types such as currency # TODO: styles may be influenced by locale FORMATTING_STYLES = { fields.DateField: xlwt.easyxf(num_format_str="yyyy-mm-dd"), fields.DatetimeField: xlwt.easyxf(num_format_str="yyyy-mm-dd hh:mm:ss"), fields.PercentField: xlwt.easyxf(num_format_str="0.00%"), } def _python_to_xls(field_types): def convert_value(field_type, value): data = {} if field_type in FORMATTING_STYLES: data["style"] = FORMATTING_STYLES[field_type] if field_type in ( fields.BinaryField, fields.BoolField, fields.DateField, fields.DatetimeField, fields.DecimalField, fields.FloatField, fields.IntegerField, fields.PercentField, fields.TextField, ): return value, data else: # don't know this field return field_type.serialize(value), data def convert_row(row): return [ convert_value(field_type, value) for field_type, value in zip(field_types, row) ] return convert_row def cell_value(sheet, row, col): """Return the cell value of the table passed by argument, based in row and column.""" cell = sheet.cell(row, col) field_type = CELL_TYPES[cell.ctype] # TODO: this approach will not work if using locale value = cell.value if field_type is None: return None elif field_type is fields.TextField: if cell.ctype != xlrd.XL_CELL_BLANK: return value else: return "" elif field_type is fields.DatetimeField: time_tuple = xlrd.xldate_as_tuple(value, sheet.book.datemode) value = field_type.serialize(datetime.datetime(*time_tuple)) return value.split("T00:00:00")[0] elif field_type is fields.BoolField: if value == 0: return False elif value == 1: return True elif cell.xf_index is None: return value # TODO: test else: book = sheet.book xf = book.xf_list[cell.xf_index] fmt = book.format_map[xf.format_key] if fmt.format_str.endswith("%"): # TODO: we may optimize this approach: we're converting to string # and the library is detecting the type when we could just say to # the library this value is PercentField if value is not None: try: decimal_places = len(fmt.format_str[:-1].split(".")[-1]) except IndexError: decimal_places = 2 return "{}%".format(str(round(value * 100, decimal_places))) else: return None elif type(value) == float and int(value) == value: return int(value) else: return value def get_table_start(sheet): empty_cell_type = xlrd.empty_cell.ctype start_column, start_row = 0, 0 for col in range(sheet.ncols): if any(cell for cell in sheet.col(col) if cell.ctype != empty_cell_type): start_column = col break for row in range(sheet.nrows): if any(cell for cell in sheet.row(row) if cell.ctype != empty_cell_type): start_row = row break return start_row, start_column def import_from_xls( filename_or_fobj, sheet_name=None, sheet_index=0, start_row=None, start_column=None, end_row=None, end_column=None, *args, **kwargs ): """Return a rows.Table created from imported XLS file.""" filename, _ = get_filename_and_fobj(filename_or_fobj, mode="rb") book = xlrd.open_workbook(filename, formatting_info=True) if sheet_name is not None: sheet = book.sheet_by_name(sheet_name) else: sheet = book.sheet_by_index(sheet_index) # TODO: may re-use Excel data types # Get header and rows # xlrd library reads rows and columns starting from 0 and ending on # sheet.nrows/ncols - 1. rows accepts the same pattern # The xlrd library reads rows and columns starting from 0 and ending on # sheet.nrows/ncols - 1. rows also uses 0-based indexes, so no # transformation is needed min_row, min_column = get_table_start(sheet) max_row, max_column = sheet.nrows - 1, sheet.ncols - 1 # TODO: consider adding a parameter `ignore_padding=True` and when it's # True, consider `start_row` starting from `min_row` and `start_column` # starting from `min_col`. start_row = start_row if start_row is not None else min_row end_row = end_row if end_row is not None else max_row start_column = start_column if start_column is not None else min_column end_column = end_column if end_column is not None else max_column table_rows = [ [ cell_value(sheet, row_index, column_index) for column_index in range(start_column, end_column + 1) ] for row_index in range(start_row, end_row + 1) ] meta = {"imported_from": "xls", "filename": filename, "sheet_name": sheet.name} return create_table(table_rows, meta=meta, *args, **kwargs) def export_to_xls(table, filename_or_fobj=None, sheet_name="Sheet1", *args, **kwargs): """Export the rows.Table to XLS file and return the saved file.""" work_book = xlwt.Workbook() sheet = work_book.add_sheet(sheet_name) prepared_table = prepare_to_export(table, *args, **kwargs) field_names = next(prepared_table) for column_index, field_name in enumerate(field_names): sheet.write(0, column_index, field_name) _convert_row = _python_to_xls([table.fields.get(field) for field in field_names]) for row_index, row in enumerate(prepared_table, start=1): for column_index, (value, data) in enumerate(_convert_row(row)): sheet.write(row_index, column_index, value, **data) if filename_or_fobj is not None: _, fobj = get_filename_and_fobj(filename_or_fobj, mode="wb") work_book.save(fobj) fobj.flush() return fobj else: fobj = BytesIO() work_book.save(fobj) fobj.seek(0) result = fobj.read() fobj.close() return result rows-0.4.1/rows/plugins/xlsx.py000066400000000000000000000136261343135453400165460ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals from decimal import Decimal from io import BytesIO from numbers import Number from openpyxl import Workbook, load_workbook from openpyxl.cell.read_only import EmptyCell from openpyxl.utils import get_column_letter from rows import fields from rows.plugins.utils import create_table, get_filename_and_fobj, prepare_to_export def _cell_to_python(cell): """Convert a PyOpenXL's `Cell` object to the corresponding Python object.""" data_type, value = cell.data_type, cell.value if type(cell) is EmptyCell: return None elif data_type == "f" and value == "=TRUE()": return True elif data_type == "f" and value == "=FALSE()": return False elif cell.number_format.lower() == "yyyy-mm-dd": return str(value).split(" 00:00:00")[0] elif cell.number_format.lower() == "yyyy-mm-dd hh:mm:ss": return str(value).split(".")[0] elif cell.number_format.endswith("%") and isinstance(value, Number): value = Decimal(str(value)) return "{:%}".format(value) elif value is None: return "" else: return value def sheet_cell(sheet, row, col): return sheet[get_column_letter(col) + str(row)] def import_from_xlsx( filename_or_fobj, sheet_name=None, sheet_index=0, start_row=None, start_column=None, end_row=None, end_column=None, *args, **kwargs ): """Return a rows.Table created from imported XLSX file.""" workbook = load_workbook(filename_or_fobj, read_only=True) if sheet_name is None: sheet_name = workbook.sheetnames[sheet_index] sheet = workbook[sheet_name] # The openpyxl library reads rows and columns starting from 1 and ending on # sheet.max_row/max_col. rows uses 0-based indexes (from 0 to N - 1), so we # need to adjust the ranges accordingly. min_row, min_column = sheet.min_row - 1, sheet.min_column - 1 max_row, max_column = sheet.max_row - 1, sheet.max_column - 1 # TODO: consider adding a parameter `ignore_padding=True` and when it's # True, consider `start_row` starting from `sheet.min_row` and # `start_column` starting from `sheet.min_col`. start_row = start_row if start_row is not None else min_row end_row = end_row if end_row is not None else max_row start_column = start_column if start_column is not None else min_column end_column = end_column if end_column is not None else max_column table_rows = [] is_empty = lambda row: all(cell is None for cell in row) for row_index in range(start_row + 1, end_row + 2): row = [ _cell_to_python(sheet_cell(sheet, row_index, col_index)) for col_index in range(start_column + 1, end_column + 2) ] if not is_empty(row): table_rows.append(row) filename, _ = get_filename_and_fobj(filename_or_fobj, dont_open=True) metadata = {"imported_from": "xlsx", "filename": filename, "sheet_name": sheet_name} return create_table(table_rows, meta=metadata, *args, **kwargs) FORMATTING_STYLES = { fields.DateField: "YYYY-MM-DD", fields.DatetimeField: "YYYY-MM-DD HH:MM:SS", fields.PercentField: "0.00%", } def _python_to_cell(field_types): def convert_value(field_type, value): number_format = FORMATTING_STYLES.get(field_type, None) if field_type not in ( fields.BoolField, fields.DateField, fields.DatetimeField, fields.DecimalField, fields.FloatField, fields.IntegerField, fields.PercentField, fields.TextField, ): # BinaryField, DatetimeField, JSONField or unknown value = field_type.serialize(value) return value, number_format def convert_row(row): return [ convert_value(field_type, value) for field_type, value in zip(field_types, row) ] return convert_row def export_to_xlsx(table, filename_or_fobj=None, sheet_name="Sheet1", *args, **kwargs): """Export the rows.Table to XLSX file and return the saved file.""" workbook = Workbook() sheet = workbook.active sheet.title = sheet_name prepared_table = prepare_to_export(table, *args, **kwargs) # Write header field_names = next(prepared_table) for col_index, field_name in enumerate(field_names): cell = sheet.cell(row=1, column=col_index + 1) cell.value = field_name # Write sheet rows _convert_row = _python_to_cell(list(map(table.fields.get, field_names))) for row_index, row in enumerate(prepared_table, start=1): for col_index, (value, number_format) in enumerate(_convert_row(row)): cell = sheet.cell(row=row_index + 1, column=col_index + 1) cell.value = value if number_format is not None: cell.number_format = number_format if filename_or_fobj is not None: _, fobj = get_filename_and_fobj(filename_or_fobj, mode="wb") workbook.save(fobj) fobj.flush() return fobj else: fobj = BytesIO() workbook.save(fobj) fobj.seek(0) result = fobj.read() fobj.close() return result rows-0.4.1/rows/plugins/xpath.py000066400000000000000000000047131343135453400166710ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import six from lxml.html import fromstring as tree_from_string from rows.plugins.utils import create_table, get_filename_and_fobj try: from HTMLParser import HTMLParser # Python 2 except ImportError: from html.parser import HTMLParser # Python 3 unescape = HTMLParser().unescape def _get_row_data(fields_xpath): fields = list(fields_xpath.items()) def get_data(row): data = [] for field_name, field_xpath in fields: result = row.xpath(field_xpath) if result: result = " ".join( text for text in map( six.text_type.strip, map(six.text_type, map(unescape, result)) ) if text ) else: result = None data.append(result) return data return get_data def import_from_xpath( filename_or_fobj, rows_xpath, fields_xpath, encoding="utf-8", *args, **kwargs ): types = set([type(rows_xpath)] + [type(xpath) for xpath in fields_xpath.values()]) if types != set([six.text_type]): raise TypeError("XPath must be {}".format(six.text_type.__name__)) filename, fobj = get_filename_and_fobj(filename_or_fobj, mode="rb") xml = fobj.read().decode(encoding) tree = tree_from_string(xml) row_elements = tree.xpath(rows_xpath) header = list(fields_xpath.keys()) row_data = _get_row_data(fields_xpath) result_rows = list(map(row_data, row_elements)) meta = {"imported_from": "xpath", "filename": filename, "encoding": encoding} return create_table([header] + result_rows, meta=meta, *args, **kwargs) rows-0.4.1/rows/table.py000066400000000000000000000177421343135453400151610ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import os from collections import OrderedDict, namedtuple from operator import itemgetter import six if six.PY2: from collections import MutableSequence, Sized elif six.PY3: from collections.abc import MutableSequence, Sized class Table(MutableSequence): def __init__(self, fields, meta=None): from rows.plugins import utils # TODO: should we really use OrderedDict here? # TODO: should use slug on each field name automatically or inside each # plugin? self.fields = OrderedDict( [ (utils.slug(field_name), field_type) for field_name, field_type in OrderedDict(fields).items() ] ) # TODO: should be able to customize row return type (namedtuple, dict # etc.) self.Row = namedtuple("Row", self.field_names) self._rows = [] self.meta = dict(meta) if meta is not None else {} @property def field_names(self): return list(self.fields.keys()) @property def field_types(self): return list(self.fields.values()) @property def name(self): """Define table name based on its metadata (filename used on import) If `filename` is not available, return `table1`. """ from rows.plugins import utils # TODO: may try read meta['name'] also (some plugins may set it) name = os.path.basename(self.meta.get("filename", "table1")) return utils.slug(os.path.splitext(name)[0]) def __repr__(self): length = len(self._rows) if isinstance(self._rows, Sized) else "?" imported = "" if "imported_from" in self.meta: imported = " (from {})".format(self.meta["imported_from"]) return "".format( imported, len(self.fields), length ) def _make_row(self, row): # TODO: should be able to customize row type (namedtuple, dict etc.) return [ field_type.deserialize(row.get(field_name, None)) for field_name, field_type in self.fields.items() ] def append(self, row): """Add a row to the table. Should be a dict""" self._rows.append(self._make_row(row)) def __len__(self): return len(self._rows) def __getitem__(self, key): key_type = type(key) if key_type == int: return self.Row(*self._rows[key]) elif key_type == slice: return [self.Row(*row) for row in self._rows[key]] elif key_type is six.text_type: try: field_index = self.field_names.index(key) except ValueError: raise KeyError(key) # TODO: should change the line below to return a generator exp? return [row[field_index] for row in self._rows] else: raise ValueError("Unsupported key type: {}".format(type(key).__name__)) def __setitem__(self, key, value): key_type = type(key) if key_type == int: self._rows[key] = self._make_row(value) elif key_type is six.text_type: from rows import fields from rows.plugins import utils values = list(value) # I'm not lazy, sorry if len(values) != len(self): raise ValueError( "Values length ({}) should be the same as " "Table length ({})".format(len(values), len(self)) ) field_name = utils.slug(key) is_new_field = field_name not in self.field_names field_type = fields.detect_types( [field_name], [[value] for value in values] )[field_name] self.fields[field_name] = field_type self.Row = namedtuple("Row", self.field_names) if is_new_field: for row, value in zip(self._rows, values): row.append(field_type.deserialize(value)) else: field_index = self.field_names.index(field_name) for row, value in zip(self._rows, values): row[field_index] = field_type.deserialize(value) else: raise ValueError("Unsupported key type: {}".format(type(key).__name__)) def __delitem__(self, key): key_type = type(key) if key_type == int: del self._rows[key] elif key_type is six.text_type: try: field_index = self.field_names.index(key) except ValueError: raise KeyError(key) del self.fields[key] self.Row = namedtuple("Row", self.field_names) for row in self._rows: row.pop(field_index) else: raise ValueError("Unsupported key type: {}".format(type(key).__name__)) def insert(self, index, row): self._rows.insert(index, self._make_row(row)) def __radd__(self, other): if other == 0: return self raise ValueError() def __iadd__(self, other): return self + other def __add__(self, other): if other == 0: return self if not isinstance(self, type(other)) or self.fields != other.fields: raise ValueError("Tables have incompatible fields") else: table = Table(fields=self.fields) table._rows = self._rows + other._rows return table def order_by(self, key): # TODO: implement locale # TODO: implement for more than one key reverse = False if key.startswith("-"): key = key[1:] reverse = True field_names = self.field_names if key not in field_names: raise ValueError('Field "{}" does not exist'.format(key)) key_index = field_names.index(key) self._rows.sort(key=itemgetter(key_index), reverse=reverse) class FlexibleTable(Table): def __init__(self, fields=None, meta=None): if fields is None: fields = {} super(FlexibleTable, self).__init__(fields, meta) def __getitem__(self, key): if isinstance(key, int): return self.Row(**self._rows[key]) elif isinstance(key, slice): return [self.Row(**row) for row in self._rows[key]] else: raise ValueError("Unsupported key type: {}".format(type(key).__name__)) def _add_field(self, field_name, field_type): self.fields[field_name] = field_type self.Row = namedtuple("Row", self.field_names) def _make_row(self, row): from rows import fields for field_name in row.keys(): if field_name not in self.field_names: self._add_field(field_name, fields.identify_type(row[field_name])) return { field_name: field_type.deserialize(row.get(field_name, None)) for field_name, field_type in self.fields.items() } def insert(self, index, row): self._rows.insert(index, self._make_row(row)) def __setitem__(self, key, value): self._rows[key] = self._make_row(value) def append(self, row): """Add a row to the table. Should be a dict""" self._rows.append(self._make_row(row)) rows-0.4.1/rows/utils.py000066400000000000000000000763071343135453400152340ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import cgi import csv import gzip import io import itertools import mimetypes import os import re import shlex import sqlite3 import subprocess import tempfile from collections import OrderedDict from itertools import islice from textwrap import dedent import requests import six from tqdm import tqdm import rows from rows.plugins.utils import make_header, slug try: import lzma except ImportError: lzma = None try: import bz2 except ImportError: bz2 = None try: from urlparse import urlparse # Python 2 except ImportError: from urllib.parse import urlparse # Python 3 try: import magic # TODO: check if it's from file-magic library except ImportError: magic = None else: if not hasattr(magic, "detect_from_content"): # This is not the file-magic library magic = None chardet = requests.compat.chardet try: import urllib3 except ImportError: from requests.packages import urllib3 else: try: urllib3.disable_warnings() except AttributeError: # old versions of urllib3 or requests pass # TODO: should get this information from the plugins TEXT_PLAIN = { "txt": "text/txt", "text": "text/txt", "csv": "text/csv", "json": "application/json", } OCTET_STREAM = { "microsoft ooxml": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "par archive data": "application/parquet", } FILE_EXTENSIONS = { "csv": "text/csv", "db": "application/x-sqlite3", "htm": "text/html", "html": "text/html", "json": "application/json", "ods": "application/vnd.oasis.opendocument.spreadsheet", "parquet": "application/parquet", "sqlite": "application/x-sqlite3", "text": "text/txt", "tsv": "text/csv", "txt": "text/txt", "xls": "application/vnd.ms-excel", "xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "pdf": "application/pdf", } MIME_TYPE_TO_PLUGIN_NAME = { "application/json": "json", "application/parquet": "parquet", "application/vnd.ms-excel": "xls", "application/vnd.oasis.opendocument.spreadsheet": "ods", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "xlsx", "application/x-sqlite3": "sqlite", "text/csv": "csv", "text/html": "html", "text/txt": "txt", "application/pdf": "pdf", } regexp_sizes = re.compile("([0-9,.]+ [a-zA-Z]+B)") MULTIPLIERS = {"B": 1, "KiB": 1024, "MiB": 1024 ** 2, "GiB": 1024 ** 3} POSTGRESQL_TYPES = { rows.fields.BinaryField: "BYTEA", rows.fields.BoolField: "BOOLEAN", rows.fields.DateField: "DATE", rows.fields.DatetimeField: "TIMESTAMP(0) WITHOUT TIME ZONE", rows.fields.DecimalField: "NUMERIC", rows.fields.FloatField: "REAL", rows.fields.IntegerField: "BIGINT", # TODO: detect when it's really needed rows.fields.PercentField: "REAL", rows.fields.TextField: "TEXT", rows.fields.JSONField: "JSONB", } DEFAULT_POSTGRESQL_TYPE = "BYTEA" SQL_CREATE_TABLE = "CREATE TABLE IF NOT EXISTS " '"{table_name}" ({field_types})' class ProgressBar: def __init__(self, prefix, pre_prefix="", total=None, unit=" rows"): self.prefix = prefix self.progress = tqdm( desc=pre_prefix, total=total, unit=unit, unit_scale=True, dynamic_ncols=True ) self.started = False def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() @property def description(self): return self.progress.desc @description.setter def description(self, value): self.progress.desc = value self.progress.refresh() @property def total(self): return self.progress.total @total.setter def total(self, value): self.progress.total = value self.progress.refresh() def update(self, last_done=1, total_done=None): if not last_done and not total_done: raise ValueError("Either last_done or total_done must be specified") if not self.started: self.started = True self.progress.desc = self.prefix self.progress.unpause() if last_done: self.progress.n += last_done else: self.progress.n = total_done self.progress.refresh() def close(self): self.progress.close() class Source(object): "Define a source to import a `rows.Table`" __slots__ = ["plugin_name", "uri", "encoding", "delete"] def __init__(self, plugin_name=None, uri=None, encoding=None, delete=False): self.plugin_name = plugin_name self.uri = uri self.delete = delete self.encoding = encoding def __repr__(self): return "Source(plugin_name={}, uri={}, encoding={}, delete={})".format( self.plugin_name, self.uri, self.encoding, self.delete ) def plugin_name_by_uri(uri): "Return the plugin name based on the URI" # TODO: parse URIs like 'sqlite://' also parsed = urlparse(uri) basename = os.path.basename(parsed.path) if not basename.strip(): raise RuntimeError("Could not identify file format.") plugin_name = basename.split(".")[-1].lower() if plugin_name in FILE_EXTENSIONS: plugin_name = MIME_TYPE_TO_PLUGIN_NAME[FILE_EXTENSIONS[plugin_name]] return plugin_name def extension_by_source(source, mime_type): "Return the file extension used by this plugin" # TODO: should get this information from the plugin extension = source.plugin_name if extension: return extension if mime_type: return mime_type.split("/")[-1] def normalize_mime_type(mime_type, mime_name, file_extension): file_extension = file_extension.lower() if file_extension else "" mime_name = mime_name.lower() if mime_name else "" mime_type = mime_type.lower() if mime_type else "" if mime_type == "text/plain" and file_extension in TEXT_PLAIN: return TEXT_PLAIN[file_extension] elif mime_type == "application/octet-stream" and mime_name in OCTET_STREAM: return OCTET_STREAM[mime_name] elif file_extension in FILE_EXTENSIONS: return FILE_EXTENSIONS[file_extension] else: return mime_type def plugin_name_by_mime_type(mime_type, mime_name, file_extension): "Return the plugin name based on the MIME type" return MIME_TYPE_TO_PLUGIN_NAME.get( normalize_mime_type(mime_type, mime_name, file_extension), None ) def detect_local_source(path, content, mime_type=None, encoding=None): # TODO: may add sample_size filename = os.path.basename(path) parts = filename.split(".") extension = parts[-1] if len(parts) > 1 else None if magic is not None: detected = magic.detect_from_content(content) encoding = detected.encoding or encoding mime_name = detected.name mime_type = detected.mime_type or mime_type else: encoding = chardet.detect(content)["encoding"] or encoding mime_name = None mime_type = mime_type or mimetypes.guess_type(filename)[0] plugin_name = plugin_name_by_mime_type(mime_type, mime_name, extension) if encoding == "binary": encoding = None return Source(uri=path, plugin_name=plugin_name, encoding=encoding) def local_file(path, sample_size=1048576): # TODO: may change sample_size with open(path, "rb") as fobj: content = fobj.read(sample_size) source = detect_local_source(path, content, mime_type=None, encoding=None) return Source( uri=path, plugin_name=source.plugin_name, encoding=source.encoding, delete=False ) def download_file( uri, filename=None, verify_ssl=True, timeout=5, progress=False, detect=False, chunk_size=8192, sample_size=1048576, ): response = requests.get( uri, verify=verify_ssl, timeout=timeout, stream=True, headers={"user-agent": "rows-{}".format(rows.__version__)}, ) if not response.ok: raise RuntimeError("HTTP response: {}".format(response.status_code)) # Get data from headers (if available) to help plugin + encoding detection real_filename, encoding, mime_type = uri, None, None headers = response.headers if "content-type" in headers: mime_type, options = cgi.parse_header(headers["content-type"]) encoding = options.get("charset", encoding) if "content-disposition" in headers: _, options = cgi.parse_header(headers["content-disposition"]) real_filename = options.get("filename", real_filename) if progress: total = response.headers.get("content-length", None) total = int(total) if total else None progress_bar = ProgressBar(prefix="Downloading file", total=total, unit="bytes") if filename is None: tmp = tempfile.NamedTemporaryFile(delete=False) fobj = tmp.file else: fobj = open(filename, mode="wb") sample_data = b"" for data in response.iter_content(chunk_size=chunk_size): fobj.write(data) if detect and len(sample_data) <= sample_size: sample_data += data if progress: progress_bar.update(len(data)) fobj.close() if progress: progress_bar.close() # TODO: add ability to continue download # Detect file type and rename temporary file to have the correct extension if detect: source = detect_local_source(real_filename, sample_data, mime_type, encoding) extension = extension_by_source(source, mime_type) plugin_name = source.plugin_name encoding = source.encoding else: extension, plugin_name, encoding = None, None, None if mime_type: extension = mime_type.split("/")[-1] if filename is None: filename = tmp.name if extension: filename += "." + extension os.rename(tmp.name, filename) return Source(uri=filename, plugin_name=plugin_name, encoding=encoding, delete=True) def detect_source(uri, verify_ssl, progress, timeout=5): """Return a `rows.Source` with information for a given URI If URI starts with "http" or "https" the file will be downloaded. This function should only be used if the URI already exists because it's going to download/open the file to detect its encoding and MIME type. """ # TODO: should also supporte other schemes, like file://, sqlite:// etc. if uri.lower().startswith("http://") or uri.lower().startswith("https://"): return download_file( uri, verify_ssl=verify_ssl, timeout=timeout, progress=progress, detect=True ) elif uri.startswith("postgres://"): return Source(delete=False, encoding=None, plugin_name="postgresql", uri=uri) else: return local_file(uri) def import_from_source(source, default_encoding, *args, **kwargs): "Import data described in a `rows.Source` into a `rows.Table`" plugin_name = source.plugin_name kwargs["encoding"] = ( kwargs.get("encoding", None) or source.encoding or default_encoding ) try: import_function = getattr(rows, "import_from_{}".format(plugin_name)) except AttributeError: raise ValueError('Plugin (import) "{}" not found'.format(plugin_name)) table = import_function(source.uri, *args, **kwargs) if source.delete: os.unlink(source.uri) return table def import_from_uri( uri, default_encoding="utf-8", verify_ssl=True, progress=False, *args, **kwargs ): "Given an URI, detects plugin and encoding and imports into a `rows.Table`" # TODO: support '-' also # TODO: (optimization) if `kwargs.get('encoding', None) is not None` we can # skip encoding detection. source = detect_source(uri, verify_ssl=verify_ssl, progress=progress) return import_from_source(source, default_encoding, *args, **kwargs) def export_to_uri(table, uri, *args, **kwargs): "Given a `rows.Table` and an URI, detects plugin (from URI) and exports" # TODO: support '-' also plugin_name = plugin_name_by_uri(uri) try: export_function = getattr(rows, "export_to_{}".format(plugin_name)) except AttributeError: raise ValueError('Plugin (export) "{}" not found'.format(plugin_name)) return export_function(table, uri, *args, **kwargs) def open_compressed(filename, mode="r", encoding=None): "Return a text-based file object from a filename, even if compressed" # TODO: integrate this function in the library itself, using # get_filename_or_fobj # TODO: accept .gz/.xz/.bz2 extensions on CLI (convert, print, plugin # detection etc.) binary_mode = "b" in mode extension = str(filename).split(".")[-1].lower() if binary_mode and encoding: raise ValueError("encoding should not be specified in binary mode") if extension == "xz": if lzma is None: raise RuntimeError("lzma support is not installed") fobj = lzma.open(filename, mode=mode) if binary_mode: return fobj else: return io.TextIOWrapper(fobj, encoding=encoding) elif extension == "gz": fobj = gzip.GzipFile(filename, mode=mode) if binary_mode: return fobj else: return io.TextIOWrapper(fobj, encoding=encoding) elif extension == "bz2": if bz2 is None: raise RuntimeError("bzip2 support is not installed") if binary_mode: # ignore encoding return bz2.open(filename, mode=mode) else: if "t" not in mode: # For some reason, passing only mode='r' to bzip2 is equivalent # to 'rb', not 'rt', so we force it here. mode += "t" return bz2.open(filename, mode=mode, encoding=encoding) else: if binary_mode: return open(filename, mode=mode) else: return open(filename, mode=mode, encoding=encoding) def csv_to_sqlite( input_filename, output_filename, samples=None, dialect=None, batch_size=10000, encoding="utf-8", callback=None, force_types=None, chunk_size=8388608, table_name="table1", schema=None, ): "Export a CSV file to SQLite, based on field type detection from samples" # TODO: automatically detect encoding if encoding == `None` # TODO: should be able to specify fields if dialect is None: # Get a sample to detect dialect fobj = open_compressed(input_filename, mode="rb") sample = fobj.read(chunk_size) dialect = rows.plugins.csv.discover_dialect(sample, encoding=encoding) elif isinstance(dialect, six.text_type): dialect = csv.get_dialect(dialect) if schema is None: # Identify data types fobj = open_compressed(input_filename, encoding=encoding) data = list(islice(csv.DictReader(fobj, dialect=dialect), samples)) schema = rows.import_from_dicts(data).fields if force_types is not None: schema.update(force_types) # Create lazy table object to be converted # TODO: this lazyness feature will be incorported into the library soon so # we can call here `rows.import_from_csv` instead of `csv.reader`. reader = csv.reader( open_compressed(input_filename, encoding=encoding), dialect=dialect ) header = make_header(next(reader)) # skip header table = rows.Table(fields=OrderedDict([(field, schema[field]) for field in header])) table._rows = reader # Export to SQLite return rows.export_to_sqlite( table, output_filename, table_name=table_name, batch_size=batch_size, callback=callback, ) def sqlite_to_csv( input_filename, table_name, output_filename, dialect=csv.excel, batch_size=10000, encoding="utf-8", callback=None, query=None, ): """Export a table inside a SQLite database to CSV""" # TODO: should be able to specify fields # TODO: should be able to specify custom query if isinstance(dialect, six.text_type): dialect = csv.get_dialect(dialect) if query is None: query = "SELECT * FROM {}".format(table_name) connection = sqlite3.Connection(input_filename) cursor = connection.cursor() result = cursor.execute(query) header = [item[0] for item in cursor.description] fobj = open_compressed(output_filename, mode="w", encoding=encoding) writer = csv.writer(fobj, dialect=dialect) writer.writerow(header) total_written = 0 for batch in rows.plugins.utils.ipartition(result, batch_size): writer.writerows(batch) written = len(batch) total_written += written if callback: callback(written, total_written) fobj.close() class CsvLazyDictWriter: """Lazy CSV dict writer, with compressed output option This class is almost the same as `csv.DictWriter` with the following differences: - You don't need to pass `fieldnames` (it's extracted on the first `.writerow` call); - You can pass either a filename or a fobj (like `sys.stdout`); - If passing a filename, it can end with `.gz`, `.xz` or `.bz2` and the output file will be automatically compressed. """ def __init__(self, filename_or_fobj, encoding="utf-8"): self.writer = None self.filename_or_fobj = filename_or_fobj self.encoding = encoding self._fobj = None def __enter__(self): return self def __exit__(self, exc_type, exc_value, traceback): self.close() @property def fobj(self): if self._fobj is None: if getattr(self.filename_or_fobj, "read", None) is not None: self._fobj = self.filename_or_fobj else: self._fobj = open_compressed( self.filename_or_fobj, mode="w", encoding=self.encoding ) return self._fobj def writerow(self, row): if self.writer is None: self.writer = csv.DictWriter(self.fobj, fieldnames=list(row.keys())) self.writer.writeheader() self.writerow = self.writer.writerow return self.writerow(row) def __del__(self): self.close() def close(self): if self._fobj and not self._fobj.closed: self._fobj.close() def execute_command(command): """Execute a command and return its output""" command = shlex.split(command) try: process = subprocess.Popen( command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) except FileNotFoundError: raise RuntimeError("Command not found: {}".format(repr(command))) process.wait() # TODO: may use another codec to decode if process.returncode > 0: stderr = process.stderr.read().decode("utf-8") raise ValueError("Error executing command: {}".format(repr(stderr))) return process.stdout.read().decode("utf-8") def uncompressed_size(filename): """Return the uncompressed size for a file by executing commands Note: due to a limitation in gzip format, uncompressed files greather than 4GiB will have a wrong value. """ quoted_filename = shlex.quote(filename) # TODO: get filetype from file-magic, if available if str(filename).lower().endswith(".xz"): output = execute_command('xz --list "{}"'.format(quoted_filename)) compressed, uncompressed = regexp_sizes.findall(output) value, unit = uncompressed.split() value = float(value.replace(",", "")) return int(value * MULTIPLIERS[unit]) elif str(filename).lower().endswith(".gz"): # XXX: gzip only uses 32 bits to store uncompressed size, so if the # uncompressed size is greater than 4GiB, the value returned will be # incorrect. output = execute_command('gzip --list "{}"'.format(quoted_filename)) lines = [line.split() for line in output.splitlines()] header, data = lines[0], lines[1] gzip_data = dict(zip(header, data)) return int(gzip_data["uncompressed"]) else: raise ValueError('Unrecognized file type for "{}".'.format(filename)) def get_psql_command( command, user=None, password=None, host=None, port=None, database_name=None, database_uri=None, ): if database_uri is None: if None in (user, password, host, port, database_name): raise ValueError( "Need to specify either `database_uri` or the complete information" ) database_uri = "postgres://{user}:{password}@{host}:{port}/{name}".format( user=user, password=password, host=host, port=port, name=database_name ) return "psql -c {} {}".format(shlex.quote(command), shlex.quote(database_uri)) def get_psql_copy_command( table_name, header, encoding="utf-8", user=None, password=None, host=None, port=None, database_name=None, database_uri=None, dialect=csv.excel, direction="FROM", ): direction = direction.upper() if direction not in ("FROM", "TO"): raise ValueError('`direction` must be "FROM" or "TO"') table_name = slug(table_name) if header is None: header = "" else: header = ", ".join(slug(field_name) for field_name in header) header = "({header}) ".format(header=header) copy = ( "\copy {table_name} {header}{direction} STDIN " "DELIMITER '{delimiter}' " "QUOTE '{quote}' " "ENCODING '{encoding}' " "CSV HEADER;" ).format( table_name=table_name, header=header, direction=direction, delimiter=dialect.delimiter.replace("'", "''"), quote=dialect.quotechar.replace("'", "''"), encoding=encoding, ) return get_psql_command( copy, user=user, password=password, host=host, port=port, database_name=database_name, database_uri=database_uri, ) def pgimport( filename, database_uri, table_name, encoding="utf-8", dialect=None, create_table=True, schema=None, callback=None, timeout=0.1, chunk_size=8388608, max_samples=10000, ): """Import data from CSV into PostgreSQL using the fastest method Required: psql command """ fobj = open_compressed(filename, mode="r", encoding=encoding) sample = fobj.read(chunk_size) if dialect is None: # Detect dialect dialect = rows.plugins.csv.discover_dialect( sample.encode(encoding), encoding=encoding ) elif isinstance(dialect, six.text_type): dialect = csv.get_dialect(dialect) if schema is None: # Detect field names reader = csv.reader(io.StringIO(sample), dialect=dialect) field_names = [slug(field_name) for field_name in next(reader)] else: field_names = list(schema.keys()) if create_table: if schema is None: data = [ dict(zip(field_names, row)) for row in itertools.islice(reader, max_samples) ] table = rows.import_from_dicts(data) field_types = [table.fields[field_name] for field_name in field_names] else: field_types = list(schema.values()) columns = [ "{} {}".format(name, POSTGRESQL_TYPES.get(type_, DEFAULT_POSTGRESQL_TYPE)) for name, type_ in zip(field_names, field_types) ] create_table = SQL_CREATE_TABLE.format( table_name=table_name, field_types=", ".join(columns) ) execute_command(get_psql_command(create_table, database_uri=database_uri)) # Prepare the `psql` command to be executed based on collected metadata command = get_psql_copy_command( database_uri=database_uri, dialect=dialect, direction="FROM", encoding=encoding, header=field_names, table_name=table_name, ) rows_imported, error = 0, None fobj = open_compressed(filename, mode="rb") try: process = subprocess.Popen( shlex.split(command), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) data = fobj.read(chunk_size) total_written = 0 while data != b"": written = process.stdin.write(data) total_written += written if callback: callback(written, total_written) data = fobj.read(chunk_size) stdout, stderr = process.communicate() if stderr != b"": raise RuntimeError(stderr.decode("utf-8")) rows_imported = int(stdout.replace(b"COPY ", b"").strip()) except FileNotFoundError: raise RuntimeError("Command `psql` not found") except BrokenPipeError: raise RuntimeError(process.stderr.read().decode("utf-8")) return {"bytes_written": total_written, "rows_imported": rows_imported} def pgexport( database_uri, table_name, filename, encoding="utf-8", dialect=csv.excel, callback=None, timeout=0.1, chunk_size=8388608, ): """Export data from PostgreSQL into a CSV file using the fastest method Required: psql command """ if isinstance(dialect, six.text_type): dialect = csv.get_dialect(dialect) # Prepare the `psql` command to be executed to export data command = get_psql_copy_command( database_uri=database_uri, direction="TO", encoding=encoding, header=None, # Needed when direction = 'TO' table_name=table_name, dialect=dialect, ) fobj = open_compressed(filename, mode="wb") try: process = subprocess.Popen( shlex.split(command), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, ) total_written = 0 data = process.stdout.read(chunk_size) while data != b"": written = fobj.write(data) total_written += written if callback: callback(written, total_written) data = process.stdout.read(chunk_size) stdout, stderr = process.communicate() if stderr != b"": raise RuntimeError(stderr.decode("utf-8")) except FileNotFoundError: raise RuntimeError("Command `psql` not found") except BrokenPipeError: raise RuntimeError(process.stderr.read().decode("utf-8")) return {"bytes_written": total_written} def generate_schema(table, export_fields, output_format, output_fobj): """Generate table schema for a specific output format and write Current supported output formats: 'txt', 'sql' and 'django'. The table name and all fields names pass for a slugifying process (table name is taken from file name). """ if output_format == "txt": from rows.plugins.dicts import import_from_dicts from rows.plugins.txt import export_to_txt data = [ { "field_name": fieldname, "field_type": fieldtype.__name__.replace("Field", "").lower(), } for fieldname, fieldtype in table.fields.items() if fieldname in export_fields ] table = import_from_dicts(data, import_fields=["field_name", "field_type"]) export_to_txt(table, output_fobj) elif output_format == "sql": # TODO: may use dict from rows.plugins.sqlite or postgresql sql_fields = { rows.fields.BinaryField: "BLOB", rows.fields.BoolField: "BOOL", rows.fields.IntegerField: "INT", rows.fields.FloatField: "FLOAT", rows.fields.PercentField: "FLOAT", rows.fields.DateField: "DATE", rows.fields.DatetimeField: "DATETIME", rows.fields.TextField: "TEXT", rows.fields.DecimalField: "FLOAT", rows.fields.EmailField: "TEXT", rows.fields.JSONField: "TEXT", } fields = [ " {} {}".format(field_name, sql_fields[field_type]) for field_name, field_type in table.fields.items() if field_name in export_fields ] sql = ( dedent( """ CREATE TABLE IF NOT EXISTS {name} ( {fields} ); """ ) .strip() .format(name=table.name, fields=",\n".join(fields)) + "\n" ) output_fobj.write(sql) elif output_format == "django": django_fields = { rows.fields.BinaryField: "BinaryField", rows.fields.BoolField: "BooleanField", rows.fields.IntegerField: "IntegerField", rows.fields.FloatField: "FloatField", rows.fields.PercentField: "DecimalField", rows.fields.DateField: "DateField", rows.fields.DatetimeField: "DateTimeField", rows.fields.TextField: "TextField", rows.fields.DecimalField: "DecimalField", rows.fields.EmailField: "EmailField", rows.fields.JSONField: "JSONField", } table_name = "".join(word.capitalize() for word in table.name.split("_")) lines = ["from django.db import models"] if rows.fields.JSONField in [ table.fields[field_name] for field_name in export_fields ]: lines.append("from django.contrib.postgres.fields import JSONField") lines.append("") lines.append("class {}(models.Model):".format(table_name)) for field_name, field_type in table.fields.items(): if field_name not in export_fields: continue if field_type is not rows.fields.JSONField: django_type = "models.{}()".format(django_fields[field_type]) else: django_type = "JSONField()" lines.append(" {} = {}".format(field_name, django_type)) result = "\n".join(lines) + "\n" output_fobj.write(result) def load_schema(filename): """Load schema from file in any of the supported formats The table must have at least the fields `field_name` and `field_type`. """ table = import_from_uri(filename) field_names = table.field_names assert "field_name" in field_names assert "field_type" in field_names types = { key: getattr(rows.fields, key) for key in dir(rows.fields) if "Field" in key and key != "Field" } return OrderedDict( [ (row.field_name, types[row.field_type.capitalize() + "Field"]) for row in table ] ) # Shortcuts csv2sqlite = csv_to_sqlite sqlite2csv = sqlite_to_csv rows-0.4.1/setup.py000066400000000000000000000065201343135453400142300ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals from setuptools import find_packages, setup EXTRA_REQUIREMENTS = { "csv": ["unicodecsv"], "cli": ["click", "requests", "requests-cache", "tqdm"], "detect": ["file-magic"], "html": ["lxml"], # apt: libxslt-dev libxml2-dev "ods": ["lxml"], "parquet": ["parquet"], "postgresql": ["psycopg2-binary"], "pdf": ["cached-property", "pymupdf"], "pdf-pymupdf": ["cached-property", "pymupdf"], "pdf-pdfminer.six": ["cached-property", "pdfminer.six"], "xls": ["xlrd", "xlwt"], "xlsx": ["openpyxl"], "xpath": ["lxml"], } EXTRA_REQUIREMENTS["all"] = sum(EXTRA_REQUIREMENTS.values(), []) INSTALL_REQUIREMENTS = ["six", "pathlib"] + EXTRA_REQUIREMENTS["csv"] LONG_DESCRIPTION = """ No matter in which format your tabular data is: rows will import it, automatically detect types and give you high-level Python objects so you can start working with the data instead of trying to parse it. It is also locale-and-unicode aware. :) Read the documentation and learn how simple is to use it: http://turicas.info/rows """.strip() setup( name="rows", description=( "A common, beautiful interface to tabular data, " "no matter the format" ), long_description=LONG_DESCRIPTION, version="0.4.1", author="Álvaro Justen", author_email="alvarojusten@gmail.com", url="https://github.com/turicas/rows/", packages=find_packages(exclude=["*.tests", "*.tests.*", "tests.*", "tests"]), install_requires=INSTALL_REQUIREMENTS, extras_require=EXTRA_REQUIREMENTS, keywords="tabular table csv xls xlsx xpath ods sqlite html pdf rows data opendata", dependency_links=[ "https://github.com/turicas/parquet-python/archive/enhancement/move-to-thriftpy2.zip#egg=parquet" ], entry_points={"console_scripts": ["rows = rows.cli:cli"]}, classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Intended Audience :: System Administrators", "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Database", "Topic :: Software Development :: Libraries :: Python Modules", "Topic :: Text Processing :: Markup :: HTML", "Topic :: Utilities", ], ) rows-0.4.1/tests/000077500000000000000000000000001343135453400136555ustar00rootroot00000000000000rows-0.4.1/tests/__init__.py000066400000000000000000000000001343135453400157540ustar00rootroot00000000000000rows-0.4.1/tests/data/000077500000000000000000000000001343135453400145665ustar00rootroot00000000000000rows-0.4.1/tests/data/all-field-types.csv000066400000000000000000000010171343135453400202750ustar00rootroot00000000000000bool_column,integer_column,float_column,decimal_column,percent_column,date_column,datetime_column,unicode_column True,1,3.141592,3.141592,1%,2015-01-01,2015-08-18T15:10:00,Álvaro False,2,1.234,1.234,11.69%,1999-02-03,1999-02-03T00:01:02,àáãâä¹²³ true,3,4.56,4.56,12%,2050-01-02,2050-01-02T23:45:31,éèẽêë false,4,7.89,7.89,13.64%,2015-08-18,2015-08-18T22:21:33,~~~~ yes,5,9.87,9.87,13.14%,2015-03-04,2015-03-04T16:00:01,álvaro no,6,1.2345,1.2345,2%,2015-05-06,2015-05-06T12:01:02,test ,-,null,nil,none,n/a,null, rows-0.4.1/tests/data/all-field-types.html000066400000000000000000000035611343135453400204540ustar00rootroot00000000000000
bool_column integer_column float_column decimal_column percent_column date_column datetime_column unicode_column
True 1 3.141592 3.141592 1% 2015-01-01 2015-08-18T15:10:00 Álvaro
False 2 1.234 1.234 11.69% 1999-02-03 1999-02-03T00:01:02 àáãâä¹²³
true 3 4.56 4.56 12% 2050-01-02 2050-01-02T23:45:31 éèẽêë
false 4 7.89 7.89 13.64% 2015-08-18 2015-08-18T22:21:33 ~~~~
yes 5 9.87 9.87 13.14% 2015-03-04 2015-03-04T16:00:01 álvaro
no 6 1.2345 1.2345 2% 2015-05-06 2015-05-06T12:01:02 test
- null nil none n/a null
rows-0.4.1/tests/data/all-field-types.json000066400000000000000000000040741343135453400204610ustar00rootroot00000000000000[ { "float_column": 3.141592, "decimal_column": 3.141592, "bool_column": "True", "integer_column": 1, "date_column": "2015-01-01", "datetime_column": "2015-08-18T15:10:00", "percent_column": "1%", "unicode_column": "Álvaro" }, { "float_column": 1.234, "decimal_column": 1.234, "bool_column": "False", "integer_column": 2, "date_column": "1999-02-03", "datetime_column": "1999-02-03T00:01:02", "percent_column": "11.69%", "unicode_column": "àáãâä¹²³" }, { "float_column": 4.56, "decimal_column": 4.56, "bool_column": true, "integer_column": 3, "date_column": "2050-01-02", "datetime_column": "2050-01-02T23:45:31", "percent_column": "12%", "unicode_column": "éèẽêë" }, { "float_column": 7.89, "decimal_column": 7.89, "bool_column": false, "integer_column": 4, "date_column": "2015-08-18", "datetime_column": "2015-08-18T22:21:33", "percent_column": "13.64%", "unicode_column": "~~~~" }, { "float_column": 9.87, "decimal_column": 9.87, "bool_column": "yes", "integer_column": 5, "date_column": "2015-03-04", "datetime_column": "2015-03-04T16:00:01", "percent_column": "13.14%", "unicode_column": "álvaro" }, { "float_column": 1.2345, "decimal_column": 1.2345, "bool_column": "no", "integer_column": 6, "date_column": "2015-05-06", "datetime_column": "2015-05-06T12:01:02", "percent_column": "2%", "unicode_column": "test" }, { "float_column": "", "decimal_column": "-", "bool_column": "null", "integer_column": "nil", "date_column": "none", "datetime_column": "n/a", "percent_column": "null", "unicode_column": "" } ] rows-0.4.1/tests/data/all-field-types.ods000066400000000000000000000251221343135453400202720ustar00rootroot00000000000000PKCIl9..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPKCI$$Thumbnails/thumbnail.pngPNG  IHDRkyܒPLTE===DDDKKKTTT[[[ccclllsss|||rXIDATxά64/N"EIb`_Qdz 6lذaÆ 6lذaÆ 6lذaÆ 6l~e;r_͎gR.]1Ҕ间Q(<џʦmW;vheۤof~F#~*=Uϩ~.pY7 -N)}D=H}Hkg[U?C(DkMvJlMB+G/$XxA_?OKɽظ {kv3-D5s}[}w(i>:]lyx4Uvb^J%4B'a+y6Gpi%ҹөQVby^ٸz/#Nn$!aasn,Ǝz\n5vIߗcOοAU}HRIذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6ߕC>IENDB`PKCI settings.xmlY]s8}_NqRR3qJC>lpd[,y$C~)v;tѹW՗eOqDIC $6ѰsYN&ȃuzQ8Pم1R#^' .: !]_K,1"2"W*qgM+U]+mW k*d@ژrqMh4vͫ $9vgQ&tG~F0w}3hv#Q_lci'u-͉7˪8 t<уZxZ~/HPMzwd`ۚW~i#c퐱s`Tv5mNt3 GzzqmYջִ8aW#5xV[ TUN\(|W$֍%tkIjO3V1Tav ױ푕V;$d(NuF~q%kQlDŕ2 :]{bU_W6Ä2l3C"ttlbª,ɗSa[8!-U$ZU94kܺ{?,N`[B:|OY4'=B#i<;8,4CCR͘7=N=ź~oIϝ8X4=6OKLqm򋡈 ඬ r.V55>2G:j? 7$|q-0lUS!1(t@!54d>'GmvJqG0 |~~ 톥@P02qh^^A#iXVF/a0Cߔ~ T y)eq2(-A3"j,7'~6爟mP.=0ed4A$~.lΥ]Mx?l'{%t-*2>EbpS3 '|7",ad Q?3p3^Ƽ@L/ ٧ !3TfAH $<:wwf5"&wEwx9'N.2A9HA`WA>V!q ijCD"+ݭK@MäĠ ]<E%TcδUi<tNԓ3ڙ{96۞=%Y@Es{7E$dkRDUSV>>,CQBy\(%7%&z/⍀*5\'6̋Ss"99"$V&~V(i[pXh*~ U"^'7q˚U {DtN=3Cbj% yFtOu^?{5;Nm@^ 5eQ>|ȸ#!IoE)gէ+>nѭza\o}.z^fwo_qY;KBJ~&oZh+YTKiwiX%oz Kb(r*>8f;#09Z(KCsԊpԄiRbж:J Ʀ .Ұ3!G:90ٶ䭙Q]=Bh31 D0tܑg >l2l(bڎi:rFg-s-Z53J˲{_onZ<OD]xn2/n -Bf8yTmYiQ,NRH=W0ݚey/Ͽ}OioR;CBഫᴣj p;Ha+ɨUv:>[`8 &'F9K6e$$(نLT7r)5".9c' \t3~e\0t;YOwt,vi"NZE$j=NWz\uFP>HJe8=E {+DŌh> A Md%]t4fwt$QTg_Ml(iT9P@CקXшDnXp'Mߟd:\TnjPL3D$$<'Ysqߵ~s4;742#fx+QP!>/Qdlm\m5[ 5d4OJZUV1c5lD 9%2*VUމQZ$GuEQ߾1?]Ciig[5X3LW9G?oDMw2,L`t޲x-M, .EY%4g8e_ F#Wb;+U|M,+w(ܑy%:D";iH+؃ N> v*N?~Lj_OaOgݓeM3Vqq\v1be"L ntMx5gO]*?VSPlM57=ޟ?ux10z_6xLtT;ǏK,p}-3:C^l\!O<ܿףԎ&|6>{':3~x]gjчIJi3З'B٨נpv'8tu`~(08=OjLu(ѯxzaێCqբI9ԈOu9[8nZbT;P$ФwP.zot^]j_a2z=JJsRGSW aiJXqުs>H+:m'$," 6<Hr ZrN{8t5=`MPxSw( 2K LgyP+97v(8H8 j {z55{u)PBڮ ׇ ;ɥ&c\ZV$ꯝv㢡~ao(Vik6}LcwQ `Xm"$I^0qlb77eOmlG)DS u~mwvj4꓀5o4*aiJ0Cv{EC^/Oxฤ*63E%"/Q 춖m G4**!MP%ʎNS;&+ Rzq_WhPb,D=(9&Zq;HsKT+#i#y,rҜH3ʶb߄-l^Y2R vˏ\Yr^˳pSPΞU|ʢߵQ[6$kacuqw ɒ/:\X|8v`cf7hbr7{wy\b# WdUFVu^WI+"8EخxXH!IQ\ )Js!绫 teaX#=Sؤ^& 'sJq(f k@@ݵ=1d?PK qDPKCIConfigurations2/images/Bitmaps/PKCIConfigurations2/popupmenu/PKCIConfigurations2/statusbar/PKCIConfigurations2/toolpanel/PKCI'Configurations2/accelerator/current.xmlPKPKCIConfigurations2/floater/PKCIConfigurations2/menubar/PKCIConfigurations2/toolbar/PKCIConfigurations2/progressbar/PKCI manifest.rdf͓n02]sUWy|Vw:6B|HwPvʣ`ySR`ZPSl%|qM#ӃXݐyajLOy|qy:(gع\kƬlYrvU/a.ԯ` PKK!EPKCIl9..mimetypePKCI$$TThumbnails/thumbnail.pngPKCI;0c settings.xmlPKCI_!GA  content.xmlPKCIM5meta.xmlPKCI qD styles.xmlPKCI!Configurations2/images/Bitmaps/PKCIN!Configurations2/popupmenu/PKCI!Configurations2/statusbar/PKCI!Configurations2/toolpanel/PKCI'!Configurations2/accelerator/current.xmlPKCIM"Configurations2/floater/PKCI"Configurations2/menubar/PKCI"Configurations2/toolbar/PKCI"Configurations2/progressbar/PKCIh )#manifest.rdfPKCIK!Eh$META-INF/manifest.xmlPKp%rows-0.4.1/tests/data/all-field-types.sqlite000066400000000000000000000200001343135453400207740ustar00rootroot00000000000000SQLite format 3@ .$ ''Vtabletable1table1CREATE TABLE "table1" (bool_column INTEGER, integer_column INTEGER, float_column REAL, decimal_column REAL, percent_column REAL, date_column TEXT, datetime_column TEXT, unicode_column TEXT) ?hJ?  C !3?n?n?zG{2015-05-062015-05-06T12:01:02testF !3@#p =@#p =?ѷX2015-03-042015-03-04T16:00:01álvaroC !3@\(@\(?u!R2015-08-182015-08-18T22:21:33~~~~J !3#@=p =@=p =?Q2050-01-022050-01-02T23:45:31éèẽêëO !3-?vȴ9X?vȴ9X?(1999-02-031999-02-03T00:01:02àáãâä¹²³E !3@ !z@ !z?zG{2015-01-012015-08-18T15:10:00Álvarorows-0.4.1/tests/data/all-field-types.txt000066400000000000000000000027351343135453400203310ustar00rootroot00000000000000+-------------+----------------+--------------+----------------+----------------+-------------+---------------------+----------------+ | bool_column | integer_column | float_column | decimal_column | percent_column | date_column | datetime_column | unicode_column | +-------------+----------------+--------------+----------------+----------------+-------------+---------------------+----------------+ | True | 1 | 3.141592 | 3.141592 | 1% | 2015-01-01 | 2015-08-18T15:10:00 | Álvaro | | False | 2 | 1.234 | 1.234 | 11.69% | 1999-02-03 | 1999-02-03T00:01:02 | àáãâä¹²³ | | true | 3 | 4.56 | 4.56 | 12% | 2050-01-02 | 2050-01-02T23:45:31 | éèẽêë | | false | 4 | 7.89 | 7.89 | 13.64% | 2015-08-18 | 2015-08-18T22:21:33 | ~~~~ | | yes | 5 | 9.87 | 9.87 | 13.14% | 2015-03-04 | 2015-03-04T16:00:01 | álvaro | | no | 6 | 1.2345 | 1.2345 | 2% | 2015-05-06 | 2015-05-06T12:01:02 | test | | | - | null | nil | none | n/a | null | | +-------------+----------------+--------------+----------------+----------------+-------------+---------------------+----------------+ rows-0.4.1/tests/data/all-field-types.xls000066400000000000000000000160001343135453400203060ustar00rootroot00000000000000ࡱ;   Root Entry  !"#$%&'()*+,-./013679  \pCalc Ba==@ 8@"1Arial1Arial1Arial1Arial General 0.00%MMM\ D", "YYYYYYYY\-MM\-DD\ HH:MM:SS@                + ) , *           `"Sheet1TZR3  @@   bool_columninteger_column float_columndecimal_columnpercent_column date_columndatetime_columnunicode_columnlvaro乲truefalse~~~~yeslvaronotest-nullnilnonen/a  cc   dMbP?_%*+&C&P&C&F&333333?'333333?(-؂-؂?)[[?" d,,??U }  } } v}    %%% % % % % % %  %  %         ~ z! @z! @V98T@  ~  X9v?X9v?(?~ 5@@    ~ > ףp=@> ףp=@3X@    W W R!u?~ ]@   ~ = ףp#@= ףp#@X?~ NmUu@   ~  n? n?  J P@       PH0(  >@gg  FMicrosoft Excel 97-TabelleBiff8Oh+'0|8 @ L X d p8@'@@@<R՜.+,D՜.+,\Root EntryFWorkbookz CompObj2IOle 4SummaryInformation(5DocumentSummaryInformation88trows-0.4.1/tests/data/all-field-types.xlsx000066400000000000000000000126501343135453400205050ustar00rootroot00000000000000PK1Mxl/_rels/workbook.xml.relsMk0 FIc8AQ6ֵ~.[ eГH4C vV͢h%؀I/Z8Y.b;]JNɺQ/IӜS[n+Psc _{0 r3Rb+/2m=?"#ʯR6w _fz o ۭP~$E|PKOz%PK1Mxl/sharedStrings.xml=N1{Nr8P)8xĒ=^<4H܄?T '&Dlw|ocO=pZA ㉨ .q|t8NYЈK 1oj"K~&%%8EcșNqچ>h d'2(*FLwE59`G51QtNY D{=km-x %3:ƩzE1l\&""I+|{.e%0@^s^Wie䕺ο4Q`mh24D@8MPKuv7PK1Mxl/worksheets/sheet1.xmlX]s8}_|Gv$6:"+d+1h:Gp'Ǭ&.ij~yXFQxj|7mLٷz17 AYO-ՕmhKQV ɮ+Q*o*r{8] RM+vNe$ \& 9P~%Um&32s>P^cV'K% lb7&)p65խ DCso[Bw9dpHzGJ Qvm3}ŌvFPx qqڿ~s ټ48CC|ORCJZ 95D`o JMrPuGyzޡWdypIB[!1*LAtjaը,ëpg%g݊_c!,61Bʴq7H~ lFap& ]-9jaS4՜_Iʷ#$Mq9H+9 }jBSaIYA(4x[Ơe ] [p2")2q 2)]g畁̕y8M}6u w3;vEZ %B ю~ kh&n|D⑹9{/fAXSJGxO:b|)VGĆm\YX v=|ω T#O6e3X`ʡD>xGWg-NyP7u멻_ U5#jH),js իT0791o3R=yFa Z1RJv-F-yl=[kjdyLAK9.9f3N~>ĞX^ۑksZa:'.pbw]eTlC~Qe\t>G|.f8v .id0އ-. [6NfEgp>GɷeuKx5Rz=[6b ]Wju9($"0VR #%Y\?tߧr\h MӦ:sibdCV'v8޳PKΌ4PK1Mxl/workbook.xmlSr0}Wxp)0 5x&ɳl,y%@:e>͹3+Y8 *ׅP}{H3YR+,Y[9dZIe:xk:[mċV.7Zʘ9^Q"vP<$TO1]O$ hlnA+,GCݷ$g0]©gzE o:r;OOaKλ*; $f!(`!0nN}TD!l9(? քv.jo@"' [X8G*Ij&)2~w4"f?Oi2Fha >ƓAMS\IOC]/[4Cjϝ\;f>%w c PKҧu|uPK1M xl/styles.xmlYQo0|WX-iץf JEI ƃ85q& " Iw˵N,=YbFoC#x9 H%b\x/T)EKX:ɑe~c3u%"FR JQjRLm;V˖$)tJc9=;(+0Qh=Y|X/~co&f9Eة rKi^:N؈>[؃:[W^) MG}ˬbY \iÕ>5aA}o-Μ!cB$"%Sq{]ֈxG9PK "]WPK1MOz%xl/_rels/workbook.xml.relsPK1Muv7xl/sharedStrings.xmlPK1MΌ4xl/worksheets/sheet1.xmlPK1Mҧu|uexl/workbook.xmlPK1MŸ xl/styles.xmlPK1Mf; _rels/.relsPK1MaJ`{docProps/app.xmlPK1M8mR$docProps/core.xmlPK1M "]W[Content_Types].xmlPK ?Srows-0.4.1/tests/data/balneabilidade-26-2010.csv000066400000000000000000000137771343135453400210270ustar00rootroot00000000000000ponto_codigo,local_da_coleta,categoria São Tomé de Paripe - SSA IN 100,"Em frente à casa Vila Maria, ao lado da rampa de acesso à praia.",Imprópria São Tomé de Paripe - SSA IN 100,"Em frente à casa Vila Maria, ao lado da rampa de acesso à praia.",Própria Periperi - SSA PR 100,Na saída de acesso à praia após travessia da via férrea.,Própria Periperi - SSA PR 100,Na saída de acesso à praia após travessia da via férrea.,Imprópria Penha - SSA PE 100,Situada em frente à barraca do Valença,Imprópria Penha - SSA PE 100,Situada em frente à barraca do Valença,Própria Bogari - SSA BO 100,Em frente ao Colégio da PM (antigo Colégio João Florêncio Gomes).,Própria Bogari - SSA BO 100,Em frente ao Colégio da PM (antigo Colégio João Florêncio Gomes).,Própria Pedra Furada - SSA FU 100,"Atrás do Hospital Sagrada Familia, em frente a ladeira que dá acesso a praia",Imprópria Pedra Furada - SSA FU 100,"Atrás do Hospital Sagrada Familia, em frente a ladeira que dá acesso a praia",Imprópria Boa Viagem - SSA BV 100,"Ao lado do forte Monte Serrat e em frente ao muro lateral da Fundação Luís Eduardo , junto a rampa de acesso à praia.",Própria Boa Viagem - SSA BV 100,"Ao lado do forte Monte Serrat e em frente ao muro lateral da Fundação Luís Eduardo , junto a rampa de acesso à praia.",Própria Roma - SSA RO 100,"Próximo a descida de acesso à praia, atrás do Hospital São Jorge.",Imprópria Roma - SSA RO 100,"Próximo a descida de acesso à praia, atrás do Hospital São Jorge.",Imprópria Canta Galo - SSA CG 100,"Atrás das antigas instalações da FIB, Rua Agrário Menezes.",Própria Canta Galo - SSA CG 100,"Atrás das antigas instalações da FIB, Rua Agrário Menezes.",Própria Porto da Barra - SSA PB 100,"Em frente à Rua César Zama, junto a escada de acesso à praia, Av. Sete de Setembro.",Própria Porto da Barra - SSA PB 100,"Em frente à Rua César Zama, junto a escada de acesso à praia, Av. Sete de Setembro.",Própria Santa Maria - SSA SM 100,"Em frente ao Mar Azul hotel, limítrofe ao Hospital Espanhol, em frente a escada de acesso à praia.",Própria Santa Maria - SSA SM 100,"Em frente ao Mar Azul hotel, limítrofe ao Hospital Espanhol, em frente a escada de acesso à praia.",Própria Farol da Barra - SSA FB 100,"Em frente as escadas de acesso à praia, na Rua Dias D'Ávila.",Imprópria Farol da Barra - SSA FB 100,"Em frente as escadas de acesso à praia, na Rua Dias D'Ávila.",Própria Farol da Barra - SSA FB 200,"Próximo ao Barra Vento e escada de acesso à praia, em frente a Av. Oceânica",Própria Farol da Barra - SSA FB 200,"Próximo ao Barra Vento e escada de acesso à praia, em frente a Av. Oceânica",Própria Ondina - SSA ON 100,"Próximo a escada de acesso à praia, em frente ao posto BR e Hotel Bahia Sol.",Própria Ondina - SSA ON 100,"Próximo a escada de acesso à praia, em frente ao posto BR e Hotel Bahia Sol.",Própria Ondina - SSA ON 200,Situada próximo ao Morro da Sereia em frente ao Ed. Maria José.,Própria Ondina - SSA ON 200,Situada próximo ao Morro da Sereia em frente ao Ed. Maria José.,Própria Rio Vermelho - SSA RV 100,"Em frente a Rua Bartolomeu de Gusmão. Próximo a escada de acesso à praia, ao lado da Rua Morro da Paciência.",Própria Rio Vermelho - SSA RV 100,"Em frente a Rua Bartolomeu de Gusmão. Próximo a escada de acesso à praia, ao lado da Rua Morro da Paciência.",Própria Rio Vermelho - SSA RV 200,"Próximo a escada de acesso à praia, em frente à igreja Nossa Senhora de Santana.",Própria Rio Vermelho - SSA RV 200,"Próximo a escada de acesso à praia, em frente à igreja Nossa Senhora de Santana.",Própria Amaralina - SSA AM 100,"No fundo da Escola Cupertino de Lacerda, em frente do painel do artista plástico Bel Borba.",Própria Amaralina - SSA AM 100,"No fundo da Escola Cupertino de Lacerda, em frente do painel do artista plástico Bel Borba.",Própria Amaralina - SSA AM 200,Em frente à rua do Balneário e ao Edifício Atlântico,Própria Amaralina - SSA AM 200,Em frente à rua do Balneário e ao Edifício Atlântico,Própria Pituba - SSA PI 100,"Em frente a escada de acesso à praia, em frente a Portinox, na Rua Paraíba.",Própria Pituba - SSA PI 100,"Em frente a escada de acesso à praia, em frente a Portinox, na Rua Paraíba.",Imprópria Pituba - SSA PI 200,Atrás da Praça (antigo Clube Português).,Imprópria Pituba - SSA PI 200,Atrás da Praça (antigo Clube Português).,Própria Armação - SSA AR 200,Em frente ao Hotel Alah Mar e a Rua João Mendes da Costa.,Própria Armação - SSA AR 200,Em frente ao Hotel Alah Mar e a Rua João Mendes da Costa.,Imprópria Boca do Rio - SSA BR 100,Em frente ao posto Salva Vidas.,Imprópria Boca do Rio - SSA BR 100,Em frente ao posto Salva Vidas.,Imprópria Corsário - SSA CO 100,Em frente ao Posto Salva Vidas,Própria Corsário - SSA CO 100,Em frente ao Posto Salva Vidas,Própria Patamares - SSA CO 200,"Em frente ao posto Salva Vidas Patamares. Próximo ao Coliseu do Forró e ao Caranguejo de Sergipe.",Imprópria Patamares - SSA CO 200,"Em frente ao posto Salva Vidas Patamares. Próximo ao Coliseu do Forró e ao Caranguejo de Sergipe.",Própria Piatã - SSA PA 100,"Em frente ao Posto Salva Vidas, próximo ao Clube Costa Verde.",Própria Piatã - SSA PA 100,"Em frente ao Posto Salva Vidas, próximo ao Clube Costa Verde.",Própria Placafor - SSA PF 100,Em frente ao posto Salva Vidas.,Própria Placafor - SSA PF 100,Em frente ao posto Salva Vidas.,Própria Itapuã - SSA IT 100,Próximo a escada de acesso à praia e em frente a Rua Sargento Waldir Xavier.,Própria Itapuã - SSA IT 100,Próximo a escada de acesso à praia e em frente a Rua Sargento Waldir Xavier.,Própria Itapuã - SSA IT 200,Em frente à Sereia de Itapuã,Própria Itapuã - SSA IT 200,Em frente à Sereia de Itapuã,Própria Farol de Itapuã - SSA FI 100,Em frente à Rua da Música (Antiga Rua K).,Própria Farol de Itapuã - SSA FI 100,Em frente à Rua da Música (Antiga Rua K).,Própria Stella Mares - SSA ST 100,Em frente ao Hotel Grande Stella Maris.,Própria Stella Mares - SSA ST 100,Em frente ao Hotel Grande Stella Maris.,Própria rows-0.4.1/tests/data/balneabilidade-26-2010.pdf000066400000000000000000002324211343135453400207720ustar00rootroot00000000000000%PDF-1.4 % 3 0 obj <> /Contents 4 0 R>> endobj 4 0 obj <> stream x][-v=7qـy8fe]{OE{[3#9ZiP2G-MIZ0yoyiY`Z0YN:W{>J K>6$z.DA逽[;~෵Mu;7n3ixY}NmRε;DEӝͿӈFNex'+I $]VLR~'Btp7(]w.›s-|~'w.y~KǙ˞I-54LeN%"0#J$]$]oSL4Lex'N.;Im b98w.;J Wz03w.;J-pnQ(]wcEp' '=sr'p=ΦєF]F_"3Qьҹ=vX+>T(y4M4j:v> ?K츗XႭAltWNbGlZ\8Qp@j]xE~J5G4LFZ):zW^&rZS5h]|BG'4z[税'hv٣)%r)ASG՛\Fm_=Xd;Hj0dRc>>FC0j*:3u iPPzVTҨ>As,݀s(< : .o!gB*H]2V[mӲyrxLc`΄Zw`@dd>6i\QOd [8'=fZs+\ +A:DЌc`*zԆsh#S4J| 8ҸE+*$92#=%#.$mӧ,gV Fu#;~tQqo@p ~~0ThrTf[VFu: dNy[q4d ao&j:Opo&FGIW3}O=˶RrssHYrxLc7̻pE V )Xc?X,9}de&/`I+$`Z] E9MF' )vH3VS`_,If+[%7$z91+:!\ 7$fWVsؕ~ dUNHFyG~Dai ٨N}! (87X4n(Z֫Ua%my TT $IA~-m\NG>HV]ʒ ʴ@ endstream endobj 5 0 obj <> /Contents 6 0 R>> endobj 6 0 obj <> stream x]sݶYL7cvǵ4igKv3)KO|HPGz%X,4p/XT8Fy %<Ӵ䇭{AUR^'p3pBc),Γ؆0TT{1Q[sAY[{j嵷pVصzK=׽vs*VʩIk}Ef5T+7HGN7{o]w.[J,;jk(eJڅxGG-w.;im (q^w.;iJ Ex C-w.;j45̗ %xkP]w.[AGNex'"!̖ Ex FڅxGwq~]w.[A +w.;i eҍ#1/)$N.Ho¬IIjsp]]o%t ExKA]w.›t> %x Ꭵ]w.[ϢR~'2v]w.[h||'if^Q:IڅxGԚ|.;i T|.;i_6y}.#B;^{"$q "!_4OfՐ氹hNxҼ gm=ix%{E=NN3wF@fV N(4I`[,Dd?%Q`!TƐ/~gg\D̢l1wr@qE`&i^YF +b y҃p| 5?4?3çA g?砹z(? Z=@˳O[CK]x\lq <ܜvIPߴ!(1Қ( fh-Rhq߶Qh+мǔ$A+!t96w\P,a~@4zcY7~* ҤߟN:KdEлwB7Nx?Côb8|g&%h0r]p*01MYp_Le`x +\10Yĩ$y#%ksx+)0ȸiũpsALX̝ xLG|w4xʊ܁ :`0*?Ey>X v@{o 3d/B>7O˘cT nhbtC1c/\-[Õx/v_׍^Z¬Y2<WeT cֳȬn襥],yhL=MӪUew v4V&+_!jQFGfvyF\VeŌftt1 Z׋ +IhgŌ& tg1hs^MWp tF<ĕzq%qNkLuK ~Uf 4޺ zB|| _Ē kLuK$ ˇ]1i<)Ը^LaT n`oc!q}*F'a-8IA<鯾܋dzj&=QI`Oݰ} 0K͎Y2\oGtsT&nKBRAq; :{vD4_#gN$ R"Ě~{VWre=6^^)^)S656a"w).JJkS꜒%ӽ2]5x5vYE-RCȧ+3c73>_vyjSdWKaFK$,cV!I9؃Bi\Fe@p7q2 h='k/I6K#;',leHNe?$ 4ǫُ˓5$%ӑ[A9<ǫ^!G8{UmZjjf:nߏxa v_o3h7"e% cz Q}pzW׮%a Hosž'sYLI. K$<.y-g;^[]qՆ[gߠYҎYۉ*iU(XŸ,<(p?`SKOmZ|1npL9$M8g,mvpwUi|{g5gd šhxbdKc&"pʽoBE6o:XWtWtHgZ|"@KV|@I)ǁEU~_\z l _}tB#4ĕ&j뜼JRU߆-3m6S}KOc]Xb GOTۍgIJKz?o^2oNX,mexyCP_ endstream endobj 7 0 obj <> /Contents 8 0 R>> endobj 8 0 obj <> stream xWn#Eu HIݞlpHf"BlvIw;XΒ /XAb5;VuC"WsO-y+e5sbʨ9~RzNpAWtA>}(N軽>)uȥn1;,}߉Ra@uTU(RCVsGs*S o&o=j-G2@OαnM ؗ9Vo$e= Լoh{=Gf];-4xB@h+ݣ쯅P5+/l :Y SC![0^u^?a\feYc |xTĦi5ߡE]P9q&tlO<7҃;wӖ%KawBOrGmY%CWu|wWn,$HgvqZfrC7> endobj 9 0 obj <> endobj 10 0 obj <> endobj 11 0 obj <> endobj 12 0 obj <> endobj 13 0 obj <> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo <> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 1 beginbfrange <0000> <0000> endbfrange endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 14 0 obj <> endobj 15 0 obj < >> /FontFile2 17 0 R >> endobj 16 0 obj <> stream xeF'AwRNq-in BIf٭4hd4эil߄&6MijӚތf6ٜ6-haZܒheZݚֶmhcݖhg?io߁vh:މNvӝl:߅.v]j׺ލnvnʃg7~c O6>E/{z oz;XԻ>|~'& endstream endobj 17 0 obj <> stream x} \T9bqA=AaQ`ƙCsɍ\TL" M+-,{fffK!sϹw`P쵽}>;s9ݿ=g!4ڳOm=\YzszĭpUfdnB9朂}"{#Go5#x!q9ܻdw1e?P\:~rýXPw-=,<0?曲[ sP]a;Dt{P_`lX,7Gef<4 b.¤ BhB{|M%A\B"{ Qr΀!^GpKpH#g[1` D2 UzE @o='7aeg _[֗/_X?~Ns#`-WI y#P+ZPt?zdiڣ_:` uF]PW uG! E=Q/Aa/ GPhAc$P4ҡ4Ţ84B(%$FƢqh  )$h>rG7& QuW +Cp2{;C<_\jk\ X>׮FI2L'g"Mf&)  pW9l\,]&%p${(k d%)mNe6HmM0 A=H9*L\^mxfVj &:A5WAྨ}.ZQCBdBpaczq+{ȻQ%qŶInMpWGȨqn.ZŪˈqdw h>ƈ_\n9+W^4hR/,*FDZNn[ ;;8;`Y_}{_Zq'|B<- { &A۷ _JS)l/ja#;0yߟSBwvJȓ7FZ+ȗ_"h(2d2>j2nC;ÓWM?M~| _`$!T7R}ݳf Z+FӓNY dH(}EVӺoo=; WtpOH}zԨS -6={=z;˟9q[M^cV>wwE˩3N$ r!W/h--n oZ ֧ <: _1qp '܊U۶]M?.1iUuV-{mk_vs??x̙Y?SWvЁV xkaaZ7 DL\a} 8V)[nSRo_xt_OKس':8<7&o?nYHzay_e=8{;v4.,|3ãqoOS܌m?> Z$?ŋ|C^D]9%~'0w5 i' +YUxqZ/сW./]oy$ t 7-K0$v#D`E_QlQ_E_{fP͗omx[s Ô~T?\(Pu *(J[X'Йa+WKL'pa/Dt1-Vx} Еo1<8i"J5k]m?T5/P'`8e V0[guW?%kCͩ =[@ fIy {]| D]v gq#mۛZ&3 ?+B9)Ϩ{«>I~zCUSYYӱm$ۿcӌ@A>P@!cfYYeeWG=2|S+7޿|dЁ-ܼPc'6v2vwLl{kȥrS#W{YG |;yOJuZ׬+ػ_Ϸ-X}/_\paQ|ҊV{/#Sٷ~VϷQrU׾/˜,)af{:]qMz^%v@I=|ym :s.)kr_[>|ŷ&r78L 8 Y(g 9"h[p8uuͷhqT?x 1lQ @:πo![_Wb o8fH9HОUg .`QS@"A`݁ + ,V8c߾wZyܘ]ލ=ER0O>˜C1~Izz6U⯄Vj_Nށ: ^xR"tԑ,8<"Zk!Ut,>:@+/"g2͝_X0oxbG0` +6fNiܔ鿖TV3px)j=_'){fn q?;V;*~]i,M_~c#njn&}΂ٳqܫ5x칥6e~3D?Ԃgy2.&,Äktzu}E{;Ke16Ӈ2@]я_*C*,PaMM=앂p>@ӔZ j~c ./b\%YU׏ot)k?9ى߁޻fH/fc.{ ae! >..qw_XGYȱbO<Þ/gk賻SuVmwCҠگzإ& 3577;ajkA`1ٺH>yww8-vM|󪾑[mSr5Ab=c PGW||J *־l֝9 > ߩُ}p!Ush.[i_"Bj)dauiّ'rڡ'vo9Ǭ<7m{ƧV_so[ַ#+2vh.9t;Z-dՎ%1Ӡ[{JTbm8oʚ^*i/PUOeATC v~󿯾>5ygNGh2>qos6?0˾SbO徊'ѼmBBȮ>@8 rj:Խ@o#OU6UWhG^[+U^69OZFc9O9<(\MeZMSvؓq* 嗩^ƗAx-b1\#;NoκtgY^s]f1 Gҥ6.<B~D3 _S:GScPӚGxݧlUUUTͅߖ?5~gN aٳ2o:[ƖW_,4s:Noc(cQI xԙ)~`ώkoٞ |o7(҃ ,:4S#*gǎמ()77웖$VlX(Cx}ʏ;Ꚃ~l*}jƩśʮ߸VaM;j-{uxƒv_i2ۛ Po8^sˑREooG`F9ʮۛHyT% ucg A|RG=Bпoa7ɰi隣HNl~xl!z`%W'ºO}&c{ݙI0!/@΀Ldf'7H|܎5ܓyf}kزg];b w |܉*EݔHmUtilL“ACR$f}EB,a?f~0tzRĉs۶}=*iX*K; Huv]v:'bbc #LԭkUIɖgE)9 )h -]|@>"\&OVpgjDKm~͛t˔)恳vïn~lѦM@ZW-.|:1orAŚ(\_{c9X'>a EՅ6ET_rM#gFZYQA*^xbFw3j?̂=GWgرEGi'>E#~Əs;<:_&Ä{o %ؘ}jxS^QcՉMz0#b9uv kg={a߭ÚpYO];ޯSסyOtw_+!?*}ȗ虥za0=5.:;{˖ &3b;6W睕J<[Ylw 01%h@@_vx0)%p8!7.tuG5ԥ^Yx% ` I?#$MSy:G_]yk[.[A2rmx6Psnm~]O֤|ɮ+؟$hf Zŧ עVz4> :֠ރpM:fepd8-t #uWtA92WpBvo~LQf ^X[ԡM K{4Ds>;|pY1%N}Uk&m2§QScoBPJۅm_18e 0VP|69@اBʁ3.Ɨ+TCQN?X 8& dp%@p=xs4 1 ؂Wc=$_(S Gґd9@nj\55}5IrQ;ĥ=OMN>ݭ=qbvy[wMvuԵupmѿŚF(H$R>~pk[iZ}ܓܷۖٲ^C=Fz0x=xc&=so=~<<==S< Oxn0(EvYhZA+>t?8yQR6AXR-"w"_vEޤvCHntԶ;SwTިhDjE܋Sgmj[@BKMP7@{}&Ej@2@m HVm;@m{!j sk%Ȃm6G]P*2z+ Z!cϷYQxP!B+Kǁi9nHP@5 (-4V!@3>0QT" `sdRfy'|P1 (Ēkdu&glVŨ/ B|9AYh)6BcSSM9p}=&FӋ\}a*-F9P6eeS>8k*b* Z,j10U_hLc҄AdG ҹ}Mf-Kq$M`qg D=ePqPkO pG Frm6= (j*dMchc8p8#8A6Ƥ31ۚ e8=4FJ&̃13ʴf3XH2whN9IPK ^ɮZZ;HxI(ey{7ʜ#oxig ;^d_hy\>fTT UvWPS|LΗ[7P0Vcy9EӒƹӟ8C?`_vD?Vry`VWѫI< C 8q'Zj$ui౑KwbNXG(dn \LQ3BhΊ8E'3dS5S%r`i E\!NanO֒S{ gOAdYwզyS57xu(Eѐͳz*щ3!ib*@dq| ~̏PV5 + 4; 6pE;'h6uJƜs<ˬK<775EZ{*(/W"UBh2kB-Dw>wl.5XFkG:è9#Ai66} m)1k?)Ѽf4fDs=lzyF56p³Gj6RryU=^~un_ Y_iUrҜ kZytՍ$V97uFSf=G22S[L5FlzݠXtP"1:IpBO:.F-%@c0&1#pݍD8NCR$h3 :͈1p#BzgviTG8.kkO;i7,;Tk*X"('5Z?Q4N9屨P֩ 17A|L51&5pT8rܡrnjhO* P .kY|ckMWn窱u;Crs%d^eY{ڭcw򎪷PrwzXx}Ԁֆ@SCe26fd(@˱5RJ=5k3ڼ %ݵ34^2mje+RaY;vÎm 7k,Up{սT0'CUؗ5i@9w+ǰ Dw*08qnຖrhJ<_9θNAyw~ۉts .PI鰻PbZ8]Cyr(gM3ʿiPyD~9*M g[LrX>+Rs&#IӍgwPγJzfz4ٔ}'IJ6Z y`Z9՘ec=LlS>8(ThcrXJRg\ŋ8 NPhJ/1ٚϗ2ր }9Md16+l+1@(TahmzzK|-#d0Zr 99J$,@be3XPJ@+L<uFl^a~bd]XVHfGx>ibʁ qh;@\e`x5^2I k؀ؔaD7!F6YCj4J&ꭀXD' TY8s?gU)E573^gbhgM`&s& alTNIJLSbFT99%)=.Z-F}`<6.-6iL )i92q<*.1:D֍KNѥJI)r\Br|D%üĤ49>.!. %*8]*CKqqiCD ̥ȑrrdJZ\Ԙ9yLJrRpDĸĘKЁ(*)y|J܈ش!RZJd.!2eT Ȓ@C֥ɩԴ]deb$FG%%u Jx"GG&D`800EFuHl].%2>DNMEű1.E!AxnTRbn8il"_猋2c:֌f ƂE=쨕ZIR$%`W$X+ɿVVU+ɿVj%mR.zI*$\W$5aI*4ɿdВIRK&Lҝ%[J&ْI5%02#QSIH=Ց\ɿ:S1gm( tG >Mk_x G`O~n7 ߯鷅? Þ3̃dp9S͘/3Qs?t)#~~~aް۔ɭ>_Aɏ3AQNkK#k\-'/'ג+;J.$'(ux| _ {j+JQe9[N>lArO_Gf '?~@dl dS9HɆ $1@yp֊b/1>GFGSF(IL)I"񔌂QGv-8/ے$I4%QB1 ?H"GaP?2d8ć R<ޓ jIR2~bZK#~^$܍mOZ>>v#zZn$G 1ԋhAB݂ҭ-t!]:]"I`)MIHGJ( $ g C-i"7v-I[`[J%'m %HkTkJaҊ?J|)JAVk64JZ-)qhwFEZP `h`PЊ@/D{^Qcâ?fg?t' endstream endobj 18 0 obj <> endobj 19 0 obj <> endobj 20 0 obj <> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo <> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 1 beginbfrange <0000> <0000> endbfrange endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 21 0 obj <> endobj 22 0 obj < >> /FontFile2 24 0 R >> endobj 23 0 obj <> stream xυNEѻ`"a&؉] :mhc@[ڶ 6Ԯvk:p:ܑvwN6xMvӝl:߅.v]i]z7٭vwl߃=izދ^6׫^}cܗhŖZg{~ ~ƟVKLT endstream endobj 24 0 obj <> stream x} xEpUW$4I`NB8$HH}@.p_d&@pf!xDEDD@dYAȲeQD@d1d*dvW~^WfaPZ*-sis%]o2B8%]mEH(dg2Av!qo,38$8+Uz2\Pv&2@(l&(˺3[\3kޣCQyELnY,vg7t/D*}a<!H~b@z'R HWGt _e'`ByF ^Dp*B"hZ#\1h9}>mAgܩS[փIDҁ}Q $ZHڠ@Qjڡ Pg0pH uG=POt ꅢPoA?C@t+`4 Ea(%$RP*JC(ehB(<4TFh Ɓ'h"d@ŨZcq)F_A{(ZjI^@q!xX5*ȅݸq `|x Xu;"5DaBXʐ3h+벴_ X" ŭQ) Ol4q@Ux8@UBNqpUxA58 7Ɨp<oYe8| ?ø f%iF8g Z,If"t#izZ.v'+wA H1z @N(H>:I$FQJ-BdqKȱʁqayTZ([PYʎb4nq "FFQ#*;p mjQ t&{nOMc)~3(%)7Lws t9,6d@}>2L6{Oxݟ. rɡɽAH AzA`B=%L4NR{?WߥO1l zc}?f; n.使H{ ct8KbH '??H? #zdx`U}}G>]x!_C/|H 5=B +h]wq ñzvA:{H6&OcCxm?]QNN={K=n0ݲm_ox(DOzھ%15np2}II)ɌG ?sC쵄 y3  11!VDx7`b06빭hBZZBbjZº{׽n$U} ٳ>ڽǻw[7;mk?%o {6<0L'8"ccB꣡CalO/s#' vzzOEgeE?dŴ!3z[&+KZ >ԸϽѩ=7z1h=ỹqwk>VKv̓ q0惃u夗~t4]s7s/t i$<=@/]o8<=v8MA+dg@gzx>Խ7(c!B_AG^ڴx]Xůt+k?8H$o|)+,҆?/@ߝs'T","8|w<^Dq%)B8;gfkc,a\6꓄՚cޖj~?%1Tk(LAz~%ySl)S"<~Ns`bE8۝>Mgvt1e؉]xPeΗӘ>#C·:SWefe\K`N Ԏg0=A @xƈ,@!/>4ly>ki뷸PzIR pFqew6j=X.wcyE ^8Nmx)pC=q?J {'=t޹`pè}c~{8|e¤ѓo;(k{c2dzwA". .<'/ܻ(>~l˧_ы5C0j`8O&wu۬odɼ R =?KB'-꭮ Jеi6jaD!P1!“g 8oO0ZKO}d75hܸ)E)bY;%< Ūz =VCw8}B7S^&LUNzbbpˏt:}>Du.,Zt]wݹhj(i5,}{8bXDҵ,0Xk5hПAaSL#ÆJi-`ԺIyF_Ň~]J#=|cx':܇pa9TKj +`NnMS3q+ K2!Sc\?ѲCjW/?1:-79 ׊7bH@SUS@[03d7mUݺ2>Vw[޵\ )%`#̛o׫fVE3wϣEuT^ҏΙwWgڒ!tC1kb:>=jG|ڸ9󎻆.j,.Ph\<|^|<-} ֗8a!B57%ؾ^Bbo=hU6U{¢0 G3#?_,/'{$},.1,}TS}زBy W'E S?ӽx谝n3}8~Ȱl,ex+~[w2S3_+)L Tea^39SĶj͠9qVC{b+M8qJ'Kŭu382+/?\o'nXO^poz?R!gܠlH{N }on$ ׼n#_=0xe&u=*tqJ4ZyJ[쵂%MnF<C1bƛma,#j2ZV?AK=KPajX{lЧta|bei,g3!=e;LQu5i]í[0~ ZnDp쯂t}.ԝ$]j{)uIF&{k3 `7\XJ<\I7|fgp %`x֝asXm>FS#~ OSo8Q 1"9 07}Oxdgvk=thx5V𖃤 =D(, c;Nb+}֯j"?+{.N[t"r^ybd^ m]0BX^1eJR L}P5}B2l|YX^v#ռ{zN{>]Sփ.L>v }襓RIII[ĉG>ƟG;ЛӣH=͂HU])=gU(^u|,0t>JJ?7ǻt `V2y~a$[C !dUSw~NK$ xnuD 2iة~^rkšHRm!nr| .NoLl%ĖΛB5r[חέ"OX#Q|MYJb <ݽjՎ}ԣUU]4q8 4C K^熩,TH^H&dǷ*4t~2qV|h(3{'ګ8qb:=DO)a퀸5Dfy3k[PdWXj\^㋏jĞ&'5%&lpˬ"}L^W<邺VN|p6yz'tXtu.~a_i%l<m>sTm޶翞x*anz&a'N@:Π{tAu/'W㣴:Os9xᄒW d az5w1q:ӦWh5'Z?| SWM*-\8-dw0cںg ;}L,Z3lO.1iux5iSmX]']Gd>";198[q2b p7+N0Nr2@etC$Ch4~l[}ŽM.SbWruǾQִ1Xw"պQP࿠[x3]P݄րښ NlCm.0l{g,1eϷܲo\|rCk~}CO}x 8K;N {te сѸF^aG# f2O=>4}>\wJi$`<ݻ%8TmCԥ)ώ)ZKa^8êE tGe?AK]`1 W$ 6t_v9yJ,|(q[{>tMDcFFqIzqul3SgƬWsM7f>Ȉ^g|a𦓆鶗Yѧ܉?uOi5{E(N{W6 jaX < IɀA^PLb> p.ً6|\I@ @u*Gped? jT,Q ܫg4p.s6p>C{9E8ރpxN7fIKQ iZ/e R-Z/~4AC녗YBz>_Ӵ%~>6 i@Vda<}.Ig;V@}JQP. yЖT%`g~YV~:𜉦Q;{D|֢q vؗew&?^p8}Q  ߊ*N&}R|sև;ߥ|}B6-ZirWU=|eL-k?_r~k_?#{/q3 ̈́L,J:Ԏx}jmjZkyG@Z5j9d֣!٧sD:kct ~Gk W:jm-vC}ZZ%'Z?2bPmUZ5 2HkQ!P2!;ȌP9r!*1=}Q,BAIBN8Ȅ Ȃ7Y>Z2?n9jH'P@(MZSa(@k**` 0pb`o>{>a.+w)=Jz*1P )ų$r (%Z$VT( ʩ䛜&t1Zn602f-S 7bj]e&bpUWWKb0["pp\qFrTEX`:dh:MНl^ģ eDÔ#4ØL[ m&c~&1T67F9Uo1@[k"\Oo}Vmލ2aD-a^hẞ}6IY8Ƹ3sISjVZKQ/ϷkRVcf iY\\O%݃AIZ^^-g ݝcyZ8ZZ$h౑KbNXG(n\buQ| QZ4gyVri>o<28xm%auXZ Nu9316kZmjKќʭ]Fkhׇ'QDC)VMBE#2Q41 J8>cR$cm27 + B;h 6E>XޥE 'V5)\f\湹PMi㫠W"4hfsNfikJ鼔h޻4厏v|ge;>׎~uPN鰧h؇>pLJ6g>zW̲;nsLFa(tC`~Gr#&AQYk?,JTlv0Mcb+,TM*s zb4Иl:KN`+v̠RL*7yTRb;h}NUi+1lTZLV)5Wz0|R`+u8LvXYbhf\\21&%F Vf,fPU h+ĉR,&&YE#cs(N&5sUqB3PZAh)N[,j*q&_ TbLg,:CmKzg 62=@S冊 ؤi ؀(14fp(Ԭ؊kTjB*SMG-Y-0h.53G3TH F#\U P08dFhrˬ25VaPC q~Rb(e fh6G#6`Z1K1{q?(aY 'Ͱ9N%!mπ6 ,K "a0L3tA(P\ab쀙5F\J M&:a^FjndU̩̪N[jn6f$RĊn(f( 6s& aRTFSIOU2 љ))JxbUCZe'gcbRfVf(9-0psJ_<*+1_[ 8RmNfNZ>PIN!Qrn޸(TQra~bJjvb( .:M.HR2 S,NzNnv;*'%037GIJQRU@(%%1;1!Tq! 9YQJA^jr&k3S 9$4M)H9 :C"JI9瀸 Oan~a+c2 Rf\`37{('3^/;LIM 萛w,1]̷VS#Oj^&pt+Ǜ,AdUGn 6[xwSK&ȀNJlƒ G:,)NCY,8JCLs6$dbhwa D1TB| ;eK4J4&iU6BZbu>6B}|/ ?~e3t[%ȡH=_QB\R](Ej)J\r%PK.S=%Q]%Rro: ?'K)9's gerV&_Ւ3KB3|YK%4%%$%S%֒N'UFHZr,|t$R|AaAiM?Rz_!G[?k$Q?{AnCmȡ6]~y'.<0I:\ H;R:0Hߎ$oQ򦑼Hk Jw$SK{kk/Jœ=;H{bjJ֓];]N?*{l&!d%dk[r{%l z):gL-Y ¯H'wIOQd$]bÑR$R '(y<,;'ԓ`c yԏ,e#R-d aIy5y8> endobj 26 0 obj <> endobj 27 0 obj <> stream /CIDInit /ProcSet findresource begin 12 dict begin begincmap /CIDSystemInfo <> def /CMapName /Adobe-Identity-UCS def /CMapType 2 def 1 begincodespacerange <0000> endcodespacerange 1 beginbfrange <0000> <0000> endbfrange endcmap CMapName currentdict /CMap defineresource pop end end endstream endobj 28 0 obj <> endobj 29 0 obj < >> /FontFile2 31 0 R >> endobj 30 0 obj <> stream xR!e͞}_K,ERd ff9iS@[PlW5tCHGX;Nu3\.u+]ZkMt[N^{T=Q3=IO{^z| M]RJ_~_?_ڟ [k T endstream endobj 31 0 obj <> stream x \T8=g"rEGPPP@ePd5+W˴rˌ̬,Zfy[[^oYykȼ{Aoۭ|L<>p/!T[3RVmUL4fBx~Z[^oCH-7k| ʫXG\}Ra1Y>&ܷ1n7nVfZdQBjZxVjL1xИѵ6Հ!dek,k=.&:rs-tpX7%IHVF>HFuuF(`(uCQ35h Zq!:E ъ0 ЍX\/'{h:(nϋqef4ތGJHSC{(Ȁ[OÄaBǣMR$ZEd#7ۄ)t Eh9![@/ $! 6:+^0쳐pL8(@U!,6C) n6EɵUԡoH.w)&z< Kh7FQ޺^h=H(@'aBJDA.tH J>!2Ӽ/yRb~G%KهuZt0xs'FF˳ }4ƒ4+(!{i7 b#!0Nד]0JgM- :^.9b2xX\`'z|#6Z>nic!w:_[a#zByJ.F6"x+"8.}b|pD>l0B)_M=>`!)a).Kc1B*et)z /a;8^և!x.?d5p{Pw,/$q+ ýk0n?W`r*齸'DM?0b#?A%m+.I' i`%qbo;ta42֋~wMg}HyO=8>FIS&Ək[\toAB Zr@_\.!BDx԰AIzP8ii9qxNو\8 SXtU|ܛSS6\}ԩo_<KI]>_쁧n_QTgeax94U18QqDxaC-㺆b"yq+` KFV mȚ CwNsi gLŚ0'0g-}fNZ78 W^1cuJoǣߨ<[E2(aX@P^Ɓ!AB?Ko6^lc~B6/;羚`kݍ5K@=pPwƴsKo>XA||¨0lC' ID)0>0l4&z2 6axVq=.c?1z聖~>]ߝw~'":J?Bݖ B@?<@_@w>,l>d}0,!ޱmi.9qD=Go:ћҩ!^ƹΉGAPXSQ$D & 54p#…A 7"9pIYtJh&r83]S~Xmei ӕwsxuO 9eCg%m MF䊤O_=^ü]&qC߳WYt]Vp><#5DuЎZ󞞽WbX^{u~Ӑ9Yvc=X2R)+0PMwNkhƓPN,\x#Lgڹ<ԒO?<ۥȿ hR^ &=N'38?ykMyoW3.嶬+SO:$}7$f{Sɼؚ: sb_J5z w0)ú"6C,$=B,U8w=* <ݎUl5 z`zꘞ^V!}36 ǝ7HN_։OkLɳ~G5CH^j F[zzU3&6`gCM}~K3jRf-Kپ<(vۂOwЭ4XkD`CD3^G jybsozZ虃wr8d@n8+t`7릗>}X<頓茍PBF4zX,h&YVa k NF?:r]DV&G>B< KƖbq99AuncNNk}P9&.g_݃f0̈r b!'tH3KnP9΂PvP&:CC\8m,=C^sƍOLxbPb7f{a3݂/ [dM='aq8i@l0`[ii~v>q˾g(z9}6xX *j`ٍt )V]h7ҩBa,(X+}V^fV7c513+28qiWBEQ $Q mןLUL9>/nA{Y-V:[^k-[DF7ZԞݐ8[zjnYG*NV $*}4~?~W($q$*9't4\t8x*,aiԢ+ )N|e!'Mt+tu3@~-ھm3viw" |w0{H* @uwxô&7V{m{"!6i`OZ893{} q*},cDt^|8SGm:bif*i3my\1@pZa,{*m# PZHO23Gq27]ڟòUf #=-NH+j.ҧBw@'lv5 9/`L0=GrO1%ݛ9-oˏz،3݀a^AS̿y(h x[]~nkn[[T="!chͳ7wPY'yBqq3%>N?@/۾ |ΗvB]2֝Aؓ_e㾏z>8مfIydlg IS8zʩZۏ<`A+t!]'Ք87Na=mO`w1 QUѧ- #n<{U5_A[PgކnK1&a胏oNQGZ@^0HH>KnEn+1ˠ/Z7t}j߶>/33@;U|yYoO6X,-΅i3uq Xj O?"_MOYU~s=R)x{wiItjr9G|.X{`Cyn2z"KZιwdtWAn=`;] lqP.qO}!jqX4yj<_x ^㬄R 5!ev|p&\O/9[6 o\M8 SZF.8WOׅW D7_tF;օmqdkv5S"8a>[{DzhtP}e[Q-vF YxrU;>y}N0HKOE?mGO? ϷaG`G:+]1oB[ő`꫏Of/X\zSW/ uZ儝%{^0P~;it:sֹ΁;u!ݥ3y}wgrXйg#`'(cSy׎ۙu.*MY/_7oB>5 ѭK>#n&LLe]Hz{)=1"~<*ڙХZ^+uIP ]dś}BC Mʝ'D2a֍n.TW]J{ߚ_Db[NyjO_k0;+2/,hɹ 탶u֟.\jAJ0yj/ӥ/MݤBUD#D1y'>$  6YN)oY &O߿=yeM-$HЄa~ܯfz 5՝ň>t3{ӧIzH,_j9E&{, MvSX? @<=RĒ;t!G ulYO]=|za(/}9}4>RqEB~ލ2s)Pz=iQy'i*wM!̨HL=!cbvc|O,57jv7ƧCYk\})gʼnWXE;•t r_ϙ#Yrl\e FӋ5;櫾@`^;Xhx}j [Cȴ+XUǝ:yW[ @ip"' Ҋm3")ah]oˡdM+[#aڋ`aC"C$1M`Di ѨrLZgƐ/@L,aîhVE 8*M2 3`(8`/_|rD^~w2#"G{s@sB<޹> \$wp=Wr;ptQ$u4{U(R"8*\AU)r ~sþ|6GНh!YGq)Ήa 7wv.vcux_9=ޚ[ D/'ˑFpo׽Dl*вp@;%.,Xճ/PMoFzܬq&17/^'W&E_УhF^as㼼{yKg.:މ]2>t?qSomr[.Ӳ{o:텟jz]b-Te D Nm=xF^aUi^@QQK! +FD',i?Mw<-[ڠqAig[st LGSc~TJ'"va/}Xcڕ}@R8%uVm.6R_$[C\Ԇ #z(V݅ުr(&ofHcCT;u4"1N #u#xV&W*VlXfƫmAWxXX&+c{> Ra 6e]XRǬ#T~ey7JNjs/Mj|W5[W\cl<b>ǀA@$Fx% n<t+zC}upuyp wr~\ 5Q8.F}г^(_ZEEI@E_ӡ(Avפ;`~=ؼ7k|, ?GEһh=/v#% 4&qA- @64#C4q?8 ru_R^Kp6/^\Y ϙ([(F|9tMh88 _}1 Ct>QZᰗ]g0ނAH ['(iGbB(?fHIOt!8]&.~ ^K5k'=gxyow||)iYsϻc߼`Ν1_B'mLP:aUKicCico1}Q:$bqȳ^"èP*ZաJT*` WPT4è 4`W@FD5T?YnmHgpMhZh& p~4) ĩY8k] 0@w;T[킺 ҿ,Z<8^)]tuSAɬ)QR|eW-vK]#_P M sl58S5,sLSꕲ SMŮ,JeR[_ZUYmզ\A\*7pT%X`+nqYhAUZxa2+n^0RMC?-v@GC^iQb⇴-ղ jCI r br\C_+CK86V[ Dw@&X5 @#pmp@pzu鮆 FV#ݦs%gXVs[υ9xiUsjmXe,^Kuw[*75x7pl5VKx :Ԣi5.ŕTX* %s&^E*̳ "Sq7nXaTeRV8w@8ل125pX+V!e Y=ڤ@J2|S#uR6T}-{T;`UX^ANYvfiݖSmh-4jY`UF3~ge Sjes{L[*[aeg&RW6x֢6 \] jޡe;W,Y<K.>Tkk'i㫠񅃯Dle5iŴO2,;h"i]*)Q^AM|E5?ɭ_5(o5ģG]7+c?urK)kEKОϕH6*Wyj.zY+mt3ֈt˕EsS#;X[Cw6#Ԝɺb})n^w4,fMC[ct-|+L^w&Gҵ]:f}:a9O\4LZR͝Isj=4kyDυEZUZj9Ck2E9S! }d> s qf ڙ4mkzvvbkcM|7rEmԾĻށ5BW kzric֙05X6a>P:[:Oqj{JnaOht{_ffܭ E8<=O|tsם'>?u{8Nd·= οžǾ@bQJ-Ug|/F_®TVbU+)uK`n{=r):eDyOWmgQs]6):RmجWRe 2:juk[rlPK//v,Pd+&vV5O`,PP+MUfo]BŰP*PLS=U^-u25P4`\ڊ*^ TejA ֱKRYcUks9ܭC)6l+Qdw\uA) WA}V8%{AmkX+)^Id^IV7J+m+{%]_+z%Z{%٣WLvP$~vI%7Kr;qn[&wmdeR~}$_2)e;l_2Ʌ)S'2S2~Uw$i[#);=#WuGrݑ[#(Q~F#Ƨ}747|2ovsggfz1Wka_6Vέb5?6VO?D@/{ K]@r+9~t{f!%MNu'oSr(y7(y5uJ^'Hz^\%DKGK/"/.%/&Ǔ$#YL76IHZjLRHj8L%)H2%c12d$%I'#)I# d)!E0 KP?/8JҐEdpl4Lb)6P2v @؝ ۀ$ݍ'KI?DȾRdO"@#%}3Q,)qO7'YKzw&dӇLÂ0t3PJv!]B.$ğLz0AI$#pI?tF(D%y=OLtf"DƄ,,Pb?Y^ endstream endobj 32 0 obj <> /Length 3600>> stream x]=H#$~EMD=!!"""p ! pp  ,)+,,RoaaaacqE6)?3Yctq2̳gf #/bu4UjcoxC|T?[%Pt2ǐ[9~@fkV5+0뷲0sWD:|/S*`|(wE,tIܙz0🲸35*=qZ6w7vi~[4ŝi>!L<(PGrgr~GJxR*ykY Րw 9xşt>={tzù>y͝iΕ'M^G$vJ,=z$(˻;&~GQ9F N鞅JgݙH4~)s7TLtV{2&~hƠS B䝸V&~W/AuL }w\7%i&~W4Yf!ݣ5SoS=yv ڸ Iӓ\1L3XBaL@ 0ZAKѐg쓸-o\sfёC<jOUdnƩy`Ay4 I1䅔g]"ʣ%jea=yRү%oI+y=F+L^U"d7Oۘ< Y[`WHh2~ĩEԐxa9 1KB^t\-L#[>']^cKU±e3S`I`(k? Q>B}ĖuJQ;{oX'$tetKQߋϨ'y,J#k8@G^/[~ {euK/};N~iuM{ LH#'q\Ed@I>t+ͬ,vus5/+EGOy,_)cj *$#Zny$n4Cvd(YK mhLQN^BP>i 5+)t3!>z`{y;J?*ؾ"4ٍ$k bq)BV#ޟ#W.R|F= W-Z<}zIA Gbo}<<ֆ=[-mC- '6wj.:"?2Y 8$bGO;zy ?bme.~d2hi}ty=kʒV.LӪF"z"hc;6jx^e&ႆ3X#ji `曳HlaLX'v=B\ ǔa'80y FLjMC*g{yԐbJZ NR8;zV%aEbC c&oeؤ@(&GpYGbys$KLb_X$ꎄ>~CB +e IȈ$+!MebFL.bFQ˯Gu!H(R|&HN9zgօGl MRY1`ǔ"aDp9A| EjArɻpP0vӑ"fFI Sd'lb (#aiG0"/rJ޲+RB$A b&AԶ7{K"䙇&way0Wzh'֬<&k9D?HO >: 1G[XTytɤ fW ) Fɼ]<,%;G?ֽNt0K beGQW4Zs2ytDjBbC #PFR1ltYsȃx{'" 1ػ>hs:3c4zknOӾ.ϦOB]tKr{Q/Tw,Y[!7?F>m1/3"&;c2y=70I]rquXة\| ۮN"fn4B{+-+q`qhSa(M?a$QO/L*EU?G~)qo }7~0nhO!9q$&Kֺ5/JM#M{Ϛ L0:0´a=g[CoǝQ'Da\ &Xcz\(/`Y:N[ttwB&k0oj&}IԯF[Hݵ2dKPFa<#wȴ~%xZ/'}b8՝I/fk WyOvS/'~v[Ek}_R6gҺJͷtrUND/0"ɳ/!mUëɳ7Y6[K'3\¯ ~q\W+E_k_3 _1_3;WAfL&KD/^~׌jjU?w <ߨ[r>&L;~e >=`/VJm,חN @D\N endstream endobj 33 0 obj <> /Length 10945>> stream xi\d*DD@DDhy)"WZDT^JR-.]VEmk)R!EĈG@ qMBo;3d2|> *juN h4&>֦ʂ---ŇBUSҝ &N %èPВ{f Ň'Nlnn֝:o#4bddf蛋KW %%/111T@Y}lIqizZC|7Vӵ"((o]^^b hlnnR(z>C5&!xٌu ʟ9rD##XZZ&%%)+ӧ,X]]]NuQ7S&DM@s{Z|H~zPmmmmmmEEڕA_aͰ'''(%Ol1Eĸu֍UEEa4wTzzq^ޫv^^^ӦMS,@PRRr .(;A `jj k!AٛBJFzKb+U4q=Vi!Xܹs@]ma-uuuc@YYj(k5ƦZ8'Nzj||A*/^]V9[lܸqc_i~XYh}c+򄩜GΝ;%T*9::Qd2/^9P*jjj\\\"##L :::Twޢ R^^j3 xp'j8x\90̜9sm6w\hÇ UxACf=zT- P(zNvjlo|BmvhKHHAAAQ z>Z3, t,넱LXbws,Hff&MVO$h362*nhHOqvr5n6sGfwݥ{m=01 z9v0ښV)T3}cad2S)BAvttdA .D9DT{{$ xlooWd%:c[XXP(>588h s dCm̨l2d{u/7vu='k)'ȗZPYV ?<<\^^.\.0 L$^m ْxŋ#""̙cdd$n|Ç1̀e˖L8Qp***rrrΞ=+]azd#-֫QY>/zTZ|gǗY!#zc52,?͆KA&"&&FjALLLCkkqQw:uX.%BIHHhnnV(mpppzyF"jRiii\.Wp6-VRd:/^%x[P!nٲ72]pAwmmݻML,hX|z,--EYp&ΝKJJ:sիW---^K΋?shP$lذ!::IO.gϞq}Yhх Tvʐ3QI>sof$)???D(͛;~O?mxx>{|otÚJW'I_ WC'~{i:1++Ɂqn}?xt-OJ-Uw}W|ѣ˗/Kvknnm /k֬011122_pk`~WjX(g.];̟?O>{.ɓ'GFFvڝ;wB[z{{?s{{{*jggGR?ӞfffHrΟ?riӦFiҤIK,T2--M44` ,5k֖-[>} ;k… kKKBCE /K,4iĉgΜf͚O<<XS/ܐ\m@)QP\PR#ژSZu㖗QZÌw%r(;v@tuuMLLd0G?9Ndd4IVVNOLL盖vIh wO-ZhR7ϟ?m3gOe`222χq lݺ6LMMmkk;v옝]JJYP|%h#QzWrr?رAH$&&&"!yh˃|||*+絶.^xՒRINNܷoNOMMe|~BBHj yjj*ehh(88Xrݵkd_SSSZ dy/TTTBJm=K~"0zzSl&O20&q}i񨵴MaL92s vH:W75w0f w*.`@i>~s`0T*JZ[[K\vm555BPzXZZ>2iҤgϞ!C[:;;􄽅͘1 "K$qêU|kGo 011lAW\Drb{eoo7(AJAIī455>#\  6nmYbӧ ?ćRk߿Æ ì,|?fy<%%R& G]*K@ tzK9SmMi$}>j`?iWxKL#GN:%5$}޽ ݫ4*PTee/[0 [yfsΥm}XXh"꺺:'eV===)))Pidd}R@-&𑻎"RbawBoH,&@aXÇ& 1۲Y{~c2bccΝ;', j4]\\"񭮮M򢫫+Hr܂K̙,Y0 tqqQh4sN>{@@&l Hs0 9Ր؆ծ]up.ff&hjj?~̙xEh קpӧOO>|XPPs&`'͕0ElWv9䒡kpβV(tW P{|rAAAЖǏ|Xh4: H5* e)F)wP9X,ڢccc(&j%ۃz# >MLMMŃ{Po [@RX,I,$Ji4͛?c$--- F~8LC"o4asƈiRRڵkɷ$ijjVVC$zը3l4DXn? Hi26(=MuU@T|֭[-555_|ɓ'׬Y3}tt*Aaaa$-Ç;rJ2lggtv$uR67W!B"##kkk7m$i1߿ȑ˗O6H$t95%\ԨEVtNcۏڇyHn@:Z/5и+5MIII__}QfffvvZRg۷|. -޽{N~z`?_TjPm_ܹsԩܮ.%<%' E]3tMg'GbF7?FdV64̿^RPpmpXK5sQz>lcaOŊjG(>{ hii)60YWWyf=~l6ǃFFZԨdì6O{K&3g؏eO߬n|Qeis1]5ZLɩ'-ohp[T"K@<~8b^~=44T'??ud2Kc4믿FFFuX3PJtnh4XwVVVLLʮjkkUM`߂ W>/[v]>S֧x;g͜6 ;:z!s4g`×~IUtEKg#|Bmi˂#_|?m1`0PڔDŋD QQQAuU@&tZjEDEEAW=z4 tssS{Uww7,O?qz'o셳q..o\)Ve~xтvFcWkW˾AwDb1z"̐deb`ej`?h37PPg0m9YPT:dqDD oECBB`lramw <ƘtE@9[4==}F?ćcCܾ}{ɒ%PՍ&uutPH~ҟn`O{;Y﯏Ξ2ay~!|pbFR )_DDD@_9Fj^Y<==e(C[8J111VZhhhGFF" @&͛P`nnnF3::z۶ma)(j q1.O)I.tw~Sba>Յ1l)P jߞ Rill~:%--m,߶QR4NW^au\ヒvkkk`j|*w:!h!##da`h!%U-+[¡ 9;.?RbsV~jH5u`pW|Z+#/߆ü|^>[TDRf`Iky睱 [pppPxJhh(̮!axzz:,^y6MpYX<36o޼zj}`V/[L~)ʲhKbb"wwwWXO`+<00ƥ6ut/xۅwnɸv{eӃ{u ǮTeSIR;؋E3ö|*9Sh, lٲ*q,K.a}Sb0B0QPXXxh˗_~Њt///hR`jjKT,..UA̮]VXmuw Qk%K={0UY|,ᕯD"1''\)!!!%%%^OXlvv6B+}髻#t^ʞ~sW?ؙ}߯巪۸#V_"uŲWCH:+uDUƴW2 Mޞc644@ӣDEr|}}v܉ sXvZq1,|Mnnczzzmmӧ[_`ͫ"W 2|۷oIʧ鹹CCCJ)6o +lGHmrXtYҥ 3 ht9ddd<~ɓ+++aYD؜:u*//a-h˗/*-_vv;wĻRނf o3|BaFNjf;ppIv{κ%1gEY*gϞ{묫 y,²oڴ)44T*=,, :cmkkSU퓖ݭDaaaEEExvZiii{{;'Oy q.\HMM~~mTTٳgDp|~TTTYYtNjeeWWWWXXٳ ___@ `X %%%qqq'N`؍7]˷onnnf$g޼yd˗/߻woUU=|bT;üjt62AFx^NTT>"Ge:ydBBd.?B'M$&&///&&J~Mh)Sdee=zHRZZpeXI{ޓnrrrffIf'%%:taԩwuzVVV\\\ww74}zVV֭[Tꫯtʕ+asΝ;wS߿Z]]-gcǎRN9qDbb"D$ #""ŋ0?'''h}(}}}˖- 裏  ##C(=z*L&/]T\@)))5صܱk w3^ïw~SwY겘ìoϾ9ϟ"dճ˗/ eX 333% :;;kyqիJ k9|0w+E({ɢoҥc jT~ZJƁ|}}Ү]`O,Y͟?ڵl6ŋ qdq-OO|_kffl@><СC%Fs?̪M1M]k;w:thɒ%˖-SڶgϞ -ݾ}xT},UP9L&3((h7nܐsxx |ݺu%Kdٹegggq&pr׳XȹsB)J*y ~ pYTWWK:J&DHrrٳa._0_>}xQTnݺ+V@wW`<|Æ _\\r19ńV^/ڕRYX^^f͚'OӳsN:n:2ˈzXp\C *$+p\rx rww755`0,cll,N888(1+..͍Fڔ~(BˑzuD P|\ nnn3,`T/$ڦV%<ann.Nhaaasqtth vwwWUU@q\P(Dh   07oދ_Qa˥]=J;uH?!5%DAAQ#R:og u@-|yOPPPPxD y\ޫF ̫9ZJ7AzVL4Kt!gNC]kWCf@ftL_}i\PPPPDN@C4"I{I&(((OeyEvJNjzQj((((* 5haPl'PW ]AT y܁d <¼(_uPPP J(q &6xL:]wEI& ki' 2 @4Nq^{m2*ts?AAAHZf34FdD1QPPd g-P6>]q (GAA&M$3WU u6tā}T7zCG_`Y:iype}GAAc3"0X*h<caS֣9mn uB03tm`'K&H_d:EbHSHVΪ=Gnw+N#fu뇂:008V o4G1Q1d>IOǣB`#rx,ޫ#a2*@QPPPPPPPPPPPPPPPPPP endstream endobj 34 0 obj <> /Length 85>> stream hA 01DhH :сR}T_/՗KR}T_/՗KR}T_/՗KR}T_:. endstream endobj 35 0 obj <> /Length 8892>> stream xyxU?}擓dI$1ARP:QU*r~[*}?V V5CT@0$!dB$g>g ' *ɳ^kkw]*^:NQ+9y dp1UU}6pNN!8 *,SS8/!\w@ ,g[<1NPPv@{%qBW#\S4cu͏ `M/`0@|en(E!:')fӟirC |`+k7!GϿ2D2/l;fF>4troϝ VκD tuYY"b<)2:Ej vup`Jb4\Cs6CVX0vgiQk/~Cy0n7Y/]`9.&?AIH1˜B("ZP-<'?8K&̳{!#EA(>8clCۊy/OHB G~2BQ9|rotx+G UG0WF|\M-,:JWQDU!`1 islirKR"" `l)"4N70rD7X;vQ"}~~Hs4%-~'YܯtDaX{rxVE # 7qQ:Ɍ! x`QPL1v35I)g#j BQ(UN+`<1 a:z NKjL"S(Kƒ`L,v R8U3Z"0I֠ڮtN >bYUkÏ(6%)l:|aesY4~ Q'C,`dG#[` K=[@%َMOn$1 g1qD~{E\X9t4S i\?+rrfF@lbQ.F`@Ps7Qɘhɬ lC4uVjUm9ImHȎebviQŕgyP;,ゴS,aXFz}? t:E6p21qzsW[dQLѡAgSIwa<ǖLQ/g ',A$  Ԝ_&r;/f'qHć?B|g_}<8_/;n4,uǮ/;n4r 0O=xvJ{/'Gt֕_c>#Ѥ?2By TerNh+)q G|b]/'# x} aUYrU3ar;084YGy;p].XLȼȮ׉ \N-j o=ڈ>)~뜞aSZhHM5-=ҩ{ A>av qY "FVw @wt0iPcQz Hw<&޼h;cIX.$$jYԿ%#"ܴ Zt7SfS<= to(!BV,An⧋TeQĿKp~`8'8x{gJ:weE.A28 /H+p o5CL_刳q{.b~9`CxiьS k$0m w\<ƝQT Hpbd EG+z #T>TWyY}1oajU@/1.nA/9dtONN!*uظ4 I6&e: 'pcb0 TSяXt uI ;Y\twR/kˈ"X.lMz|D3铰Fƃ QǕt{0Aa%4BQ(21c1n3y23W0Ia4Y_,CIEb}.7AD5`8/a(f: GE1Ҿwlu_ Ns۶m'Ni=PsssTTTccf FQŊjIjkkO8ݭ+..z555,{^Yfs[[[WWWGGvKSSSRRRkkkqqb[RRg0]RR okZz8r$I&СCGimm5}}}hloo߿$Iv;SdYZ$ZVV|>_cc(TTTԘL&zVkYYYUUUkkᨯ(ֶ XVVC:::%Ieuuu@tt鬬Sɴy撒I\.WQQfǏ7ͽ?OFcwwɓM&S{{/Byyhhhظqc[[Nܳgϖ-[:tW>SNu8/N3 <$IȨ{7&L??6mZLLOW_ݱcGRR(7o.--=tЅ^K/UVVL&]ooo]],ˇ޷oٳgτ kF1c\~~2''gꫧLRWW 477̛7oر_+bXx>H^y뭷666n۶СC|wޙuVTT444yyy7tb)**x<㏯XB%@rBVrz{{;;;{zz:ɴa?; ?˲駟{w_~||ݻw===k׮={NEIt:]^^ݻ^dt~_Ek4fs\\nFEE fZu:((psEM8h0.\x{ PPP0}tڪ53fLEE6Z%$$h CRRn7Lz^'N$''dgg7559_+--3gNmm-<hJәfm2#Fp8@\\lhh$IhM-!==nK`2v"h3fLjjj PfY,rټ`j)GSlINNv\~_qƙLc-[l׮] Y֔hNd2L&5 IRbb_hϊ%%%AAY,I*++%Ir8fھ}믿 EQB BLUUYK PHUUo0>::zh:]eF^*IҘ1cʺSRRۃ/^xAxV+`XdYs߾}?яu:f9;;ȑ#~n~~_||I4Bкu~_|>wm_>55U1 iDQ<~O>qF˥^n?Nx6nXRRPRR# gΈۻe˖nK/ݺu /8N1cƜ8ql6?c˗/^3^/?SMGU@ p8<?sKLLTt666fff'O,HOOomm꫁{V(2 ߱c(Ph\s_>l@HPUUwƇuh32PHk6#PUU[ , v٬zFv{eeR___cc펋Obcc'Lt:eY6-1c6Wz~O?@> f0IM6m4P(f͚RYGaaaFFFlll__߀b v,(,'%%ZJUO>(((;wnUU n[喖dkz衸m۶iݬM6{dY>eDh@WXQTTd6UUl)))Ǐv{0/oذA7 `BBBaa6noo9hznZ{͆f̘i& (J\\nĉ&Mjooohh c,KZZ(566ت=WeyȑX-T|/^|뭷.ZѣZ EQLLLQ%&&ԭB 8`0(rAAAUUf{9 Bp B`P ;::n!\pAYYَ;Cme9!!!66ZksqA.bIzYfi 뵹;LP;~[~:''x<:.;;) %$$L4( FGGGEEٳغuԩSDѣG%IYֱcJX̙3wܮϚ5+99n2KII,)770Lw_mm̙3MC111 Z̜9 .Xr~UVY뮛3g}wn۶KOOzeVu͚5Ec ))i…?3n8AVZtҤ$mZ9rn/_Ғg~᜜b4w޽lٲիWgffjzW̞=СC[N hUkl6/Ydҥڻd4Ǐ'O-Kqq .c{oBBBAAA^^-駟Ν;wժUvk endstream endobj 2 0 obj <> /ExtGState << /GS1 9 0 R /GS2 10 0 R >> /XObject << /I1 32 0 R /I2 33 0 R /I3 34 0 R /I4 35 0 R >> >> endobj 36 0 obj << /Producer (mPDF 5.6) /CreationDate (20171128184140+00'00') /ModDate (20171128184140+00'00') >> endobj 37 0 obj << /Type /Catalog /Pages 1 0 R /OpenAction [3 0 R /Fit] /PageLayout /OneColumn >> endobj xref 0 38 0000000000 65535 f 0000008828 00000 n 0000077773 00000 n 0000000015 00000 n 0000000223 00000 n 0000003785 00000 n 0000003993 00000 n 0000007246 00000 n 0000007454 00000 n 0000008929 00000 n 0000008990 00000 n 0000009056 00000 n 0000009209 00000 n 0000010185 00000 n 0000010581 00000 n 0000010650 00000 n 0000010931 00000 n 0000011376 00000 n 0000024024 00000 n 0000024182 00000 n 0000025082 00000 n 0000025478 00000 n 0000025547 00000 n 0000025839 00000 n 0000026229 00000 n 0000038532 00000 n 0000038697 00000 n 0000039612 00000 n 0000040008 00000 n 0000040077 00000 n 0000040378 00000 n 0000040768 00000 n 0000053254 00000 n 0000057097 00000 n 0000068299 00000 n 0000068625 00000 n 0000077983 00000 n 0000078107 00000 n trailer << /Size 38 /Root 37 0 R /Info 36 0 R /ID [<8d0980f8d4a7d09630384ae58b9ea343> <8d0980f8d4a7d09630384ae58b9ea343>] >> startxref 78205 %%EOFrows-0.4.1/tests/data/colspan-table.html000066400000000000000000000004071343135453400202010ustar00rootroot00000000000000
huge title
field1 field2
row1field1 row1field2
row2field1 row2field2
rows-0.4.1/tests/data/ecuador-medios-radiodifusoras.csv000066400000000000000000000061411343135453400232210ustar00rootroot00000000000000url,name,address,phone,website,email /es/informate-y-participa/directorio-de-medios/28-ecos-de-cayambe-1470-am,Ecos de Cayambe (1470 AM),Terán 52-91 y 10 de Agosto,02 236 0047 / 236 3055,www.ecosdecayambe.es.tl,ecos@pi.pro.ec /es/informate-y-participa/directorio-de-medios/29-jm-radio-88-9-fm,JM Radio (88.9 FM),Cristóbal Colón y Panzaleo,02 231 6642,,www.jmradio.net /es/informate-y-participa/directorio-de-medios/191-activa-88-5fm,Activa (88.5FM),"Av. Miguel Cordero y Av. Paucarbamba Ed. Work Center Piso 5, Of. 508",07 281 4688 / 281 9992 / 288 1088,www.fm88radioactiva.com,radio@cadencactiva.com /es/informate-y-participa/directorio-de-medios/192-alfa-musical-1140am,Alfa Musical (1140AM),Hermano Miguel 1068 y Gran Colombia,07 283 8451,,radioalfa1140am@hotmail.com /es/informate-y-participa/directorio-de-medios/193-catolica-nacional-98-1fm-cuenca-paute-gualaceo,"Católica Nacional (98.1FM) (Cuenca, Paute, Gualaceo)",Bolívar 9-49 y Luis Cordero,07 282 5845 / 283 8292 / 283 2280,www.radiocatolicacuenca.com.ec,radiocatolicacuenca@gmail.com /es/informate-y-participa/directorio-de-medios/194-contacot-xg-1260am,Contacot XG (1260AM),Jesús Dávila y Cornelio Merchán,07 288 1240 / 281 4164,www.contactoxg.webpin.com,radionexo.fem@hotmail.com /es/informate-y-participa/directorio-de-medios/195-cosmos-fm-97-3fm-cuenca-paute,"Cosmos FM (97.3FM) (Cuenca, Paute)",Av. Abelardo J. Andrade 2-07 y Francisco Tamariz,07 283 1423 / 284 2837,www.cosmos.ec,cosmos@rcgrupo.com /es/informate-y-participa/directorio-de-medios/196-cuenca-la-voz-de-los-cuatro-rios-1180am,Cuenca La Voz de los Cuatro Ríos (1180AM),Bomboíza 1-83 entre Loja y Pastaza,07 288 4128,www.radiocuenca.com,kleberpinosabad@yahoo.es / radiocuenca1180@hotmail.com /es/informate-y-participa/directorio-de-medios/197-el-mercurio-1200am,El Mercurio (1200AM),Av. de las Américas y Francisco Azcásubi Ed. Mercurio,07 409 5684 / 409 5645,www.radioelmercurio.com.ec,radioinfo@radioelmercurio.com.ec /es/informate-y-participa/directorio-de-medios/198-la-voz-de-tomebamba-am-fm-1-070am-102-1fm-cuenca-giron-paute,"La Voz de Tomebamba AM / FM (1.070AM; 102.1FM) (Cuenca, Girón, Paute)","Benigno Malo 15-91 y Muñoz Vernaza, esquina",07 284 2000 / 282 5301 / 284 2222,www.lavozdeltomebamba.com,info@lavozdeltomebamba.com /es/informate-y-participa/directorio-de-medios/199-ondas-azuayas-1110-am,Ondas Azuayas (1110 AM),Héroes de Verdeloma 915 y Francisco Tamariz,07 282 3911 / 283 1975 / 283 1792 / 284 4485,www.ondasazuayas.ec,max_azuayas@hotmail.com /es/informate-y-participa/directorio-de-medios/200-infinito-fm-97-5-fm,Infinito FM (97.5 FM),Av. Sixto Durán Ballen y Vega Dávila,07 294 4899,www.radioinfinito.ser.ec,radioinfinito@hotmail.com /es/informate-y-participa/directorio-de-medios/201-radio-super-94-9fm-cuenca-giron-paute,"Radio Súper (94.9FM) (Cuenca, Girón, Paute)","Benigno Malo 1591 y Muñoz Vernaza, esquina",07 284 2949 / 284 2949 / 282 5301,www.super949.com,Súper949@gmail.com /es/informate-y-participa/directorio-de-medios/202-tropicana-1380am,Tropicana (1390AM),Av. Pumapungo 5-50 y Juan Benigno Vela,07 280 7970 / 280 9644,www.radiotropicana1390.com,radiotropicana1390@hotmail.com rows-0.4.1/tests/data/ecuador-medios-radiodifusoras.html000066400000000000000000002127631343135453400234030ustar00rootroot00000000000000 Directorio de medios - Radiodifusoras - Página 1 de 7

Radiodifusoras

D: Terán 52-91 y 10 de Agosto
T: 02 236 0047 / 236 3055
W: www.ecosdecayambe.es.tl
E: ecos@pi.pro.ec
D: Cristóbal Colón y Panzaleo
T: 02 231 6642
F: 02 231 0836
E: www.jmradio.net
D: Av. Miguel Cordero y Av. Paucarbamba Ed. Work Center Piso 5, Of. 508
T: 07 281 4688 / 281 9992 / 288 1088
F: 07 281 9992
W: www.fm88radioactiva.com
E: radio@cadencactiva.com
T: @fm88radioactiva
D: Hermano Miguel 1068 y Gran Colombia
T: 07 283 8451
F: 07 283 8714
E: radioalfa1140am@hotmail.com
T: @radioalfaec
D: Bolívar 9-49 y Luis Cordero
T: 07 282 5845 / 283 8292 / 283 2280
F: 07 282 5845 / 254 1557 / 284 4436
W: www.radiocatolicacuenca.com.ec
E: radiocatolicacuenca@gmail.com
T: @RadioCatolicaC
D: Jesús Dávila y Cornelio Merchán
T: 07 288 1240 / 281 4164
F: 07 288 0691
W: www.contactoxg.webpin.com
E: radionexo.fem@hotmail.com
D: Av. Abelardo J. Andrade 2-07 y Francisco Tamariz
T: 07 283 1423 / 284 2837
F: 07 284 3727
W: www.cosmos.ec
E: cosmos@rcgrupo.com
D: Bomboíza 1-83 entre Loja y Pastaza
T: 07 288 4128
F: 07 288 4128
W: www.radiocuenca.com
E: kleberpinosabad@yahoo.es / radiocuenca1180@hotmail.com
D: Av. de las Américas y Francisco Azcásubi Ed. Mercurio
T: 07 409 5684 / 409 5645
F: 07 409 5684
W: www.radioelmercurio.com.ec
E: radioinfo@radioelmercurio.com.ec
T: @radioelmercurio
D: Benigno Malo 15-91 y Muñoz Vernaza, esquina
T: 07 284 2000 / 282 5301 / 284 2222
F: 07 284 2222
W: www.lavozdeltomebamba.com
E: info@lavozdeltomebamba.com
T: @tomebamba
D: Héroes de Verdeloma 915 y Francisco Tamariz
T: 07 282 3911 / 283 1975 / 283 1792 / 284 4485
F: 07 283 9067
W: www.ondasazuayas.ec
E: max_azuayas@hotmail.com
T: @ondasazuayas
D: Av. Sixto Durán Ballen y Vega Dávila
T: 07 294 4899
W: www.radioinfinito.ser.ec
E: radioinfinito@hotmail.com
T: @RADIOINFINITOFM
D: Benigno Malo 1591 y Muñoz Vernaza, esquina
T: 07 284 2949 / 284 2949 / 282 5301
F: 07 284 2222
W: www.super949.com
E: Súper949@gmail.com
T: @super949
D: Av. Pumapungo 5-50 y Juan Benigno Vela
T: 07 280 7970 / 280 9644
F: 07 280 9644
W: www.radiotropicana1390.com
E: radiotropicana1390@hotmail.com
¡Haga clic para escuchar el texto resaltado!
rows-0.4.1/tests/data/eleicoes-tcesp-161-162.pdf000066400000000000000000007230031343135453400210050ustar00rootroot00000000000000%PDF-1.5 % 4 0 obj << /Length 5 0 R /Filter /FlateDecode >> stream x}ߗ7n+dϪ]MG8cKIL;s$;#;>VIx,@UE? Kij8tk'nJ-7ɥڹWK^rWti>p8-7!s9Tood9?7o_??|ӗ?/޾ag/oo^ÿ->gX[>Ͽ:w9?g7TP3}YnOCә8مΉ]E9ߤ.Sos[X LDnZi+zN.QɗJI^'xKaVIT\t7~-!ڗйn7e)FgWGDju7&pZc=ޚ{2ՙf-5?15.3fZ ~?)a]tTϒC40,1]b(LaF ʠ4z3,ݾz%ߤVWZ@SߔEzibT$%MyMR=sb 'شE}W >кܸd)i/37v+^>RK*Zg:f=w<sc:B_eY<^P!ѤN2 $tJXW*:][,}IE |3"XWFIlL]2M+o'aQwߤ7^>d W2s܇MD!D̏/ ҥL2Jb2,Ѫ ģp߉Ӥ {>h[~dpm1:Uh^]=z[[X9z99e"ot'3=^*^4!wOLw. -o[A:2;f|6:=NA!AFHXJI} 8Ɠ;V4FYZs2pOG旂"#ڋ [#c'y%9u˔%o8LI0 bh7׸z"E<C?7\s!o,3͠e 72&Yt_iGnpj O7NuQ4[RQ09%h%h``;m Ft@%wglD:V$ċ#7!VKc">_r M:^.NLw |#l3U0YT &7u6|oDɆ|k)ŒEfr(D XrHGBB( ,7rK>L+2u1 CUpŰWtp4M,!>1#Zz[zhWm#+ȓ1= e:6h:ANjsdi'$R /:?KQDӬ6Ω32K4 (՞HV@h!+XMJ! A4g--A"0&0Fġ6g0}/KHC ɾgU+ _Dt×7 Ͳ.ā+吤cFu*1d}-Y楾eO<+6{G~DBiI N,hrُ'eYHF(r(7#;bSES̢ ,a gXfLf\zLɕ`( :v>",lK#HXk˳EW$FLT|h|DGc'Vm*5E; ƼPJ0KCȬ)Tm*9q&&H/Q*Bi2A(pӚA8$@8_01#P/,ѲȠ-c8TQ ݮRGhi-Y9apEHDK+݄g,%,I 4\J8X{T5>zeԚ8&Wqi%ǯ?i-XRH,}1Ke:ViĒ ypR=y1 k>cB["OŤG])0&wGۘg ՗YH#c< ;-uD Ãh*rx6RZR.1@ L#_i#Bh/4-/d7D cU, ‹ޏy 9.HD5:Ěf簄qrD56V}aSAd=<bp7Aޗ.tUj I&W` #  ~+iJ`aJlu2}o])%>F2 س8*dGT%&t:CS|e;L ɳ;ٜ>y& ?eHF5ƫ~M5 }r% BM*b'UG&ͼztQ\GMW\rֆ*@p" GH[5 FE+ )B꣪孇X/ɲ"dD 4&XՒI8 VYEމ*BjĐVA7#Q4, q Bd8-vo|!o/y8$#‡Qa1& C-s{TuMpUQ7 KbH5XzŠp-SM]9wIVKfdZ7d#J1 "߄հ..;},jl_"GV*aJ\&͒e'5b%^ n$.)qu%.e:YrzI45Tx3:qI¯YXh?uAGI:qI,J\rVGp:o 6?"- U :i iKFs2idҖӔLM7q7-nLZteeBLҖ!H[vi'Yv`9K)gIF2.$r 4gJMƫz3E[vNײI8~b $O ۞׀hPcQ?b V]x C$LW`PFd\mp DvGB)ec+:b JKKI~%uU4刓9˼8!62+\g)mv"URj@I $^w" ŧ9<V(H]&3m:u]M]Mϑ1OLtJ=@δl^fkZ\KiRjcZA%Z}z$c۰dyw5*h["V.A4 2״;Y8kڡ^e|9# ?e& j۫YU[߈YL:ِ.fcyF;tGT'Li7]ŊDGLhÅ2i` FgNAѪ>!dk#~N DF1l,|`tA64A؉bá0Qz=z[ѡ*z|VD~u` ( YQ dB' +T-Gڹ$lz}RyC"t ZB h;$(ɬO̐ϔ-D#i1ċiď8&xv쑅;&6]d#k~nJIdn+&5Jfw&?”Z?́Â7H]<,:z#L$&lau0x@۪Z -Cս8oJTHB̦.Za-8X4WU+V%ab{ϩ Yf7ϩ 4>)V48y>@™ t'1@y+=E*8Q#&2@ E:gq>ZNg?:aͶjtZzWZ1!B-#hVnNMiVͣĠ!^RzwTyBWD쁉!e bͻvi1pFڍ2Tڏ*fj'i V21ʂf: ʧVmK\L;t<4֢E+qm iukYx˺5ް:eݚ׌$G$D&e>Ӗ cJnЬ8-gh(6#ւ$I,mZcI7VF΍bΕ[K6 #F ݚln:`Ȳ}KAY#r v. /A5 iLv^n08Hh9F2#i7,dݚfX֍uSS\8* TIehE!4v(vf (Z%ҭ\-\FI b1tli1*f&ObZzXgCY^$-8,A笋6;}h֋F4ڨ<3:ŧ+fN@N1VuRcő̅+nT/P-K&nł/P-F4!Z.^&LDm_Ġ #3-ef,fH$-VZVc-bҋ4Z̺+ 2 9Zu"=qD\'Ĉv:L/<50qdA+B%9Z<h1*,7w,&;%ȡH[G )os8`ǯ#6t8=5}-f-&ZC߬vv UKSX̦hm Jf!YR5|bo8qtѿb+I",vN!Ey8T,뙾:wEw`GW&oh0X}@BfEtGž8I1Z"ӨiX1V'y@$71 !G$hTE-BSgX ;q*QLxa1wv_%J?2*©OAt%Ju7n+")' bqΝiKl~ZIx6pZ=?ŋtR4~%s"UW bp) j?kV$;_Uj?3nL7_u#6DWgg0]H 9j?7i^#LGX;.~u9}Fusqpshj?@R1shƝiQ҆FpsbgF;j?w9_ڣO0j?HUNj?OڏHK>%&E"ޭދc,u18V$ˤ+ iRq8}=~+O 9ͭzZ?eDkKj?16cR۷: Nk @_#/DԿ uV#uXGkkuYU J0 f۵3"SGmD)'o-4I9kM$ſ'RM&k  W²:7'ѺHZv]~qF۱5= v&aaT ц#1 WɦDjđr."mħ7d]"ԝ-hl .8E {HkCb),q_YDb B1 (E44 q$%y֬jڠGVVɡaV6QvGXtG*XgS]dװU^sƒk3^zBb.8Xm$,p *GG$rkf ƹb6%OAjcła5 A6a ٘%bOCw`V!ZoH{tcl4)_48O Í+So R0%i,"B'F ƒ9Qϕp&[aU (y,n EX*Myӑb_!C]#E 9yvŜf vG6Gg=KD3hurhڋl1z9m$0wZ,bU 3V'f_t} 53} L5g=E]NQjAxhAf(rj )k ^e穚,d0f/Q0Pʃ{ܝfy*2)"MJL70iXq7ġK̜SPȉmd_ i ݴC;MsvƐ;#Cl;ݴqM2#u/0M[i+fn[Ś2"˰bS?`w[m )h.`w 4 8TԱΟ'ܝm5b8c=@™w vtb *4HVJ֔WSwr#M$2BZ)?tsоՃđlnZ$,XUЎ'Y%ktbYYZLp{h#qO͋(^aEkTA}l*_ hChJQW*¢ Mwv c dbj.'0|[TOO53[9AT%Fti0Pyl 5Z* yĹ U OzRL@۪+~@EOTE5+|5b|͂b[88'Xhvv4:d-6MP&iN +MJݼb1V<B ӭ[PQ`̌p{)f PTSvDV'1a59~Mq$#g; k~jJ=c&1*`c3 KȶzZMB&81h)38 ceԶC vm&?Hj сܬk d)ɟ4PS@*+X:ݰv W=6fgKX+fTQU0J֥VƛWA6ͶP7. b9su;×4yF,?=2h uMg3e!`e: UF`6gh9\*]G?,% "fd z% N!W e:  \ >2|Q*\فM]W7~B 'ӗ-\&I_JŠbGRZ:i" ^d &_F~cܿ p66uN.*tr8E#\PEOL9JTO'Oӣ7߲b,̠v1ʓh@ 9Cq*{^"B%wJLs0z Ty},M3لJH(4II.rpWRdMM:EW᝕8Ϭ$ȮHKh ]l?gFrs /]IРp*3MEvBm3&tWuq9.⼊UUZDKfRm9|,dR#_% y$-Wy<>MtLnQ]svۉOlu!F Ϋz4\E򰄢]XyqdFK7}iXJ@t'qҊ9?1+гsM״`$H]LEU,\i,%FpR&~pΘIbؑqv>fYJ^kC:=C#AWܶ)Ygfi$KWm.<99"(m@:"X_*4 |0e6YN7yo|0O2vruѢ8 siXv \mI a-Qrꡚ*4M!L2?3&&)g3+8), ARAM9W7` t9TCdXr bJ9ӠSv!}VÜZ^9IZιj%Ut6ZU&hO"FQ3}eӣנ3 N23Yeޙ-ź'kmn:b>2Go°6K*p)iŒYb4))+FpxCjpx۶! zW괁9Ke47D1]q6tQC Opzɰ=F:H` /Lb =r51"D\Q/]=8ζh/vz̢b PdT[c6FeB#BMG;F > y!)9$}bg/g5CdReh8>ݒi7do߱MW>\?=ݣK:6ԎkdHQhJg Z3L1Ei;ƘD.;@bX,@\3TCGC4)9!юI਀j+؅^-y~"mj$LD [%lް(aÅ{s0- ?S iiѨѠWшsļa趺8G<fIgNk"`|KK(hӑVDkvz7;iv(hx)WG OsKvڕV=%>`EH& p)--J1RUG$ZE/2]897I7b)$:@Q*6H5vcz^7d%mmuu~Z*C9 *j-GHjrGF0H e@(#:BDדw=$N4epPp"T~:+4Y@r uAlHzF'5O-9ih?#< #hب'tvUmX_9F\FtB{ۓ峂]V؂øfILTc_ UǴ8.0\YGmIԖV4̢1+t 8,ps.98&Uڀ^O$4@S]8VΤ8fIx}pG{ݝ69g`ܸa 9 !/ ·}ïS&[Qx/ ԑ> ;qXNwdC:?5ˉvRpQR&ӉKydGREtca¹`qDEn)ōhwTFH8qI'7W>'ʈJqåcUyr^]GkNئ%Z/ z%ΈO`n$ }dH{$ :+m&a9 &|Eңn G!m=.]kv$m׻$b+Nrf){4=n@$Y,iIH8q#B3F,Zp4:}ݧ6]oZ׫J88j~Dиt`KK n?=qO.Ny_~::tv%} q?)ۿo=lG>B7y?||O?t?s~O?_~ y̍P|o^}Ǿ6{_:?nO޶L2AKC"GRe'аitN=&+(gZM}=]3ֺiiFWPV9VۯUU HCZj\:>%/?>cV/2oZ 3nIr@h/j;l; /A.W^#(yRgKr0[`c$XG:/i;iQk B;o蔂ÀGʹ$:esNa팲m`d*9t\}S߇ ӼSӬJq.m~qIʐ+ Ǖ:+ R(BKl @=S|u,c:f.bb.kn%X~3=Sl)(q4C}yݫ_{gwۯ=ߝ|n;7wˏϩNDre\^ԁ!hrk%wjt2EtVcJkCCtPB(RtQK:ⳏvrI)sP:LVk ]}G8uw߼<{wo_XR^ (iq6j2M&sC+d*BV=E[H-%/Gxw.Z5[j%kCcc14MМh5}#Y -6s/h}x Y@!t;nvuQ9RRK_{׷o{Wyqno^=Ci i(E؄B Lr=ř\cB9@p $hӌR;TjrT+BUJ#P8$o'e-eN>]2'(x#?^|ݽow8@?Jl)Weݪ-N@jgJl(dnMj'lBj'4ti_A%wIЪR*M6>(Ďib>&Xh?͚>P5,8ybo^7x݋!}{WwoxC8LYMt)Gv]Rv7ܡUy\cm} J=L.n A;. S_se; }ƃA}vT +n$49nhro;X59:E<7,U/v~틷?O_xzӆC=`\V4- %L`2렒gGD_,m%C&vK6=ƺ,1Pdp u R5H ƺg?W.V׊/w) bMCfw_O(7_M|Ż7{}G"47}fΘmߖ9k{z3{=鎁:r?x`d2m`mt5*ߕQ*=xxd\kr79yq;XG9@I,|g: ;C&gC&k27:A:XG:c >F왶Bv7ܡU17\cm} =J.n vLnDB4J] YZtKk{?~s!3̑gC&Sj"/+uD`U {L5$W={?v7\өP<B6>} ڨ4r7jtMKI0ϑW`t萫b/vu ĥt7k5^?-myy~>zCZ c3=HYۓ5Z1sAA +kK&u(n)tG>z(o#U-Uܸ5aU-{(:Pf$4ΆvM)dnvD` .adw.Z5[jgM5ֆ@#1@Ma]))QӣCv 8אdܾ<훷=ў3MY DZ ՏmcRVUڞL`D ql26m6T:J ʃaDDZ~;$ה9n(sr*w*sDQo~s=z7ol 4ΆzMidn(w*t*wEȗ'H%5dw.Z5[j5ֆǠC1BM].▃1=:n >B-~{vAjA#<WWwkiqUJATrthdm`mt5*ߕQLG#\S搻ɏ: ֽlo/Vo^<{d1@=Rl(d~Mz'TBzg.9,5w^,;\];jr:Hk $cح1:@5}#`+UWtkr7;ZY|$TI`bUgGEuY,8ӛI5ZAd`q82Y6X[ 5Px=Ѓ#\SPUr@<Տ@zuCsr[Uh+ksNbk{ \б CLBMC:w1sA? ?v%!uCVuP$Lox,9 @frM$nvB*sv<ljszm%C&uKc64ƺ,zw\9ƥ ܘTc3+΅t89 ;їC)vHP ,e;qjVi(veG)4=H債=Z=x3EzxX4m6T:J ʃa=~;$ה9n(sE`U|U~29Cf#-5ΆMdnwD`U ~. 8dw.Z5[j'H5ֆǀ݃1FM]хv1BWtkr7Ghwÿ]myN9Deޜ_ϯ>˻sr~_>Qw}qIMiS=ԟ~O?7$Vn~~ϛ~U-?V~Ct?wxvٝp͡GԎ2Bw@?w?p__|~0ߟu?x/=}n9iiqɮw߿ȮwG|W^-b[I%:{V endstream endobj 5 0 obj 21095 endobj 3 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /XObject << /x7 7 0 R >> /Font << /f-0-0 8 0 R /f-1-0 9 0 R /f-2-0 10 0 R >> >> endobj 2 0 obj << /Type /Page % 1 /Parent 1 0 R /MediaBox [ 0 0 791.999983 611.999983 ] /Contents 4 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 3 0 R >> endobj 6 0 obj << /Length 11 0 R /Filter /FlateDecode /Type /XObject /Subtype /Image /Width 233 /Height 216 /ColorSpace /DeviceGray /Interpolate false /BitsPerComponent 1 >> stream x1 kX@xIkiZkZkZd endstream endobj 11 0 obj 43 endobj 7 0 obj << /Length 12 0 R /Filter /FlateDecode /Type /XObject /Subtype /Image /Width 233 /Height 216 /ColorSpace /DeviceRGB /Interpolate false /BitsPerComponent 8 /SMask 6 0 R >> stream x}XTIvs9dDD"P# *$IsI@Ls9+ J 83{zھުN9B,,1Yy3>XHLqDkig;*B8|Ĥ@&9@A lguQ kvRs?1!al)=e'-on^j:(a-_=iB}`.+/U- HOJlLx"˟?v}]dHB. Ǒ c@:rqؕ4;!l]rטL n\Hac! l6=m&L ' qb欜if*& $P"N 6qrXi-1+_5wLOUYk+K+EАna!3':M޶pN9la|13΢|# i${)_Xt8ND Y`?6(:Xs&> WEٟIcM-8@ ){ #; t,Qu`,0faD ߾053}@q6pf2@Uh{k㪝kICwMʪ 1k+Wwʌ83hʙvk/}V{kOYQDX@Ԅ̝ ١kv{k,q27Ӏ16j@_ؙn4,,y/#^# 1F! LJj~lYxRY Y]rJ,Fuc]@@5/# pBpnl~Jp[ѵrA4m[ mݭdx$03p_57$iIqIWkw]EX4X@X@GUBP@XB@OJAQVRx+Xo~U{hM_-jY-G5h눪8Y;k*5;:7 22Z̰[6Y@I(I]ر(,߆{a_腽Ȝd#> HoP HS"cĉ9ueJ0@ctX~@_8Z>vQD"@"pYHqlh31{ ߐw 0sAؖeY./9]P]-!>~Z:4MʛkY;9)zm*I8,_+F$DJTGg sHV@+0kץQ)R#/<W'pTv> DO`,Ostݰn񦐭.Gf_?tnrrk%tgMWlD:s.@}O).N56aXltoo?B/F!죣dı1⋌v|B,^} -mW}t}SlZ) ! /~%v f`vk˜6l J!8AvXVp@!]!H. P yj o.c A!,"+ jC/ē UW'/Wo 4-Jg(U\CUE/ߴE y&vA-w[Q ˄Dl$Dpc "&BOtET./ sž8ɰ!i CW\_JTjݚ5v UAJ9u$4Ŷe|q:P6TNP*jW}`B,b!EDѯ|LN[JBe&PM/m. lȝy!hgeZD̯JiWC k9u o$3r{;p>;W[Ȋ= ]. `8+υq, lmvSP_X ] ܅v0Ѭp Q/_;UE 9n w>/}TrRܙI1LXчe Af d}CT+5p"$pm)y^N?m{EfOk3;.xA^vbaE<%1NːN;txFBsju2zQRVL2ʆC5ī_Wt `3{-Gp-Sͣ?ho (XMY1 -+mA K7?7FUAX*mY뷺ۨ1ɮ퀨=^l֣\1U1?"^*8XO(1IJf2 {…ߑ%>B{pkf)u 7Ƒ-E K\m 4uy:OPbuxVy˒*(Q}] /j@n,aסJaAq_nW79{xhzYе8Ljd㈘5%Vz, 5=`{1+e+(RtP]^eݝTq0Y>kzjוs ǽ"|v3%~ }ADh)}a2 s΢r;26ӧ9Ff1<'wPF nݙj? c_h1pd VpOsRz/ydx\ X*9RGDA. b o^ lƎ[%>s"y>UΉZLʣ/FV;}$9F$}Sb@n<ٴܺr%!ýo/NhI};YoYrX\˰~ְ13~-д+$@0(KWu6;̜17=͝^cöC|{wnw'}^ ~qɥinRqόyt|;pHd9us' }&d&*9-Ԛ%^{kߏXS#ik@Jow)gVP?s떍Innl0%~\.9JMIu_H,rpJ֠E7Ә3*{mw'.8{З;zHDiLF4N?Gcb,ԉPPSCGwNs$ܮtW+YLnC_Ej_\//.|8woxr۟רU< iٴ6@<8m{N^>|X>=vY)%ٽkjUl n[DCH;ʕ^^e>dqy'tVO?e֕ڙ_f=JPܳ~Y tOhx~aK"l[1Ư.*|.贕mֶ<^\P ʶй<3 pArpC"LB2p6,7Ȼ-XY([hף$-={ttt@tC5h[5!!2HAC?}3DԢ[D4 %gSn #\͏/`az ++bVX8zY9G?iX:'D8`[eno-\ޜ׻<}VvXgnl2YW[pi26fjzU39&+yl3dEG:c0N An'j- WŌ)S/e\0N=jPmUy᫃=N\;MݹGuO?,O("~=~z#XIPzP|չ>į^墿\"jؿJ$ں-x*h5$tr<̡*E;k֝o 2T@iM]b[0 F^k2S,4ȇ{h"v7]"bɢQt2i= j/Jll͉$1-͛W}ln^;Et<8b:M2Ƶs'kdj.Fru nF5ov`0)YWbq*(U?`}{zRg5{Sd(pU_CvU885~U~ҢNY ]w"N/&\;Qi/E ^( (j(YYUvENEVe^ݰv͠Tg|Ϥ>H߁;yS >MkOp/JKT08q}y۴yqָ[Q/6S/RxlJ Qm;mܽk7ڮq.|W4, O^Zd|Z-[u}xZϨ~:snƆCOLX]>}}j@;/ܸ 4H"ӦU U D"%@J\ХE Cs 2t'zGߖ+tP.DUٌjee*:y K@ <#PAWE%.k'K(s= dypiP@=q\roa\oW;0?p.:8{t3Oo֨n=I)Qn6Ί]xc?nFzXpSCO)ʹ+c!3\xV舂t(Y$, GC!R;@78vtN80ݐ:iTN('!|=y"9`YUkic MւܥP V/:UV_Pt>flYW;ow̖[2J{os6rΘq'AxugٓA "̎o 0 9Wn )0K o٩Q)Q'RFcʠX j]&9g%Ջ;oF'ӻS6ێ5OOͿwƓxɖh ߦ_Ž۠ {,\q >hlwO8;9۲}l+^gQRg Y8:!nV i K\&Sy`>6^uq"1~ $lG&*,:_ eP?Hgu}e?QT-s/zyH5,Q hZqA}ZCN{Zo[wվo?(zuI[/ pYwk˄7@MCq&dhQ _x]779w@8uP o؝+Oa`#h.4[e:u_ّg|;BdžNڜ51lcd}>;np7wjAΔOWLSn-7Cb]ZYcPRD[E^.(_I@O]j[Y頛"#[ӽ'zyccJܶɿz4 `_''T$uKY N)w/PJdgjGz/X4K`"6pP!O>i1Ł#3g`Хd g4j%D\f>1`A{Wct |/"`A Rw [w̫K=+>7ąԹy3~|s ksB#TLyrwIM5Niɦq@.DSMˠ)=2:Q 5f jskʘ80۲gABWu~UV;7lrYxǤ$=?{otK*Gby Z㎀W_krk<Ma@(>C Y`-elZ= ;ص4umb&?܈ ёN/-(KGU~qŃ:ogY•? zn}mvRiNjw+sz8d# )8"YR,Y J|4f;^x`MP54{/MvFpnӬ,%偘045Нee`kecVėFi0ШC_#R6y.X`mg"iC9,׬1!?-leLM%^χ|lgg<~vʜ]"MQ]O0G7:޻orXkSFAe ᬈf9&t;iJ}}l:~c{.dYCVd,d;]xqr /k lRK]n 8 jSNdZueMusWGoNYQ ^=xyVyV/9ޱ,M<%h@6?t".y}]3 /@Vna Ms=]÷>.t;6z@q.3}Wg'[G9E Kg*;0] MQYܓ{b|VV, H;xjI×}IN\04i^ƭi'yJ.q?6VQE{ Gha*6$Gm0DNdLR.CQ. ~^cGU ;Pk6! :&v{ O!Mz6vKh|aD4b_y=uI@OMc x_ >>jPy;12.@1B@ep\(sl/oe+z%I~uC m7]] cWN&; /PG=q! ^H ܊)'AH2XU5 d}VSAc{lH>|P{+ e(l䀋+C2~{\E\N`L&Ec=/S{ rƔRzqm{^t+h0K.>Ibr;s?^73yo&dYdYվ?A5L1}544 ݿ^M2b`.LR(ͦ24| $Ȥ*ǝ;4HLlԖ7F p G"WG;jC4yͯœWͶ؆wI| pk,X Ky74}ㆵWJARqRٽܴB3 }#YKR=wU˸mT$E׬Pg,z&rQEN^m`:KawPD*}"9(!b&CбOԑq*bð(#C(<#eNraTWI?4g9EhQ2v FA{gczYk^ gD\bގ:VJDCAaqaAݜ[9z٪ Ov{`M|c 3?k[{B%/$\%tߞ-ᩮՓSRoI J|-|2>,-Tx\T1sV819}DVap̠!e0EѴ?_hYE52yƵB*QM۹@xAE"ZRQPd[U/VU^'<$uOc ZKWsxC;JrUܗLRU f̓.?Qi!!KHMY7|[Nr'`+=eƑfQX,D mx~BJX  D2V҄x9d,ӆG~#QdICHN$AlB 2[[q)fjR|,(ZZ] N2>kRUo]Si.ϘUG]J_@ˣֻo M .FgjnAӒ=ڎ 7i" p0Aǜel8 ,#<9B" n6A1U{ Dc=txthkcvyzA3mIYUR3+ƒw5xE nWz^ZOxp4hWFu5ӎy*SXlFs}v=G7zj±|idD[RW*4F}G7# -|%X8R飓Hl?a*~"E>LG'/.=1>lkMzlyUI] jywncabʇMhw ~{&$%),>j,]8w0FޡaT[A7'yz}IF¹u*2.4EUYLpVdJ͋O?&{E ,ܐ+fF(n^X~:"+ UK,1.OH{O/LΎ7)pfAT,tly5+k]eKD[xb!}?9CAb~mw8@I"avV9YMEP>HQɐ<ƉWc_,2&!՞vz>@L__甦w2Ap]ݖĵj3H|$'MgVjzl鵳<&&,eY%G ff:jݚ+ME-@(8E,3Y;N S@s#i׮#D`C:l}rŋ< *흂]K.7W Ԁ0ü,!vYo϶}:d0,aQOܦ ?s南[VenC]D3h)5Jڊ؈hভSha*IX=|enh@{XFGC ZXT4 9Unm#hD@=$ 8D> ĢoPDm=Qb3hcL@#Q]?"fsq׵+ogMckԞOxgb{ZW@OgY\a9f+O4ϏvVhX󌛙{{—/ɺ_MHH<4_O]\8RNEq` /Kp3z b5C]2pݎmmm]4է`LC=i=O#=HC7J([}\8|wۻ˫D7<d˵`ob9 WcD-k <Ռ sb\Jnly,X7~d ~gC 淛PIE$vp~m171xhLNlrAGT&hxμy|sv0Ht~hf"}UL !gP"R R>'DȢFkȄB4 An#DiTz-ie- _ә@w+jA:8R>;ӯ.N yTq3r ^Pe4~.|p05 ekd(z{.7زeGIaDl2Zv UpޥnҢZ*YSPcX]F+c3JFAm?8M)y*Ľ 6G ]_2f: r{@]R(v40Mr[n/#;:pxl?9WO%puw# 9liiA欘۷oEn 2Μ8F7AOƆ9UY0G6类[iIōgx;Īz^KF Uz$ Xd=@ӨHk^˻  ~*ʵX9Ͼۿ#h$tY<'`{|a)qK< ?ȉ,yJ*z{-H`(-ESɸJ;eWDiKw޾ēg"*FD!I7X')C7/Xq!Mj oG)+z5 )ᘥ91neee/^_[[訥܌piPX,)/Fo4U͛&CΜJqGLk>$ݕH{l\M2ntѩ+CO_\5ymDن(F=DƤq#8A~ܨNaBaM򪕙ًuz&(HHNȨ7)%`tcpRZE9OCPoYJaB"b[܎QPD40S _7bynsqWwJ^ $!O'M+ʚoU..Oi+=ܸ}dA]L9Wnpr ~gn@9rD&%%z DUUɓ?̔hqޑQ^;x&r.g' ٤3j(S;53boOQ%VIņm\\ e9ZHM?4ѢQlVq;@[ _)_VW851>)4Hx;{Cawl8&I 3|%x:¥ʺJ+J1M[,Q)h&+chOyP0?;%`zHīFRRnRj+dQ舽36?ͬ?\v.h4pyiuuuֽ|֭[ѣGϟ?ܽ{9SW6DIY,:n*d:O˼Ud^P`iIiBdqHvnL,D%UxSջgZ69lJ nH/?(SżaS>ܼ$:p}}A8tzTp },CC1ʂ8(QZX+PT'PJ4ͮ@i0-1%|ŢWob뉊aRid:]9=^Ns_rٳgRRRa%%%8i2(^y7f1Fً7Xflg tK_A<(A"o(+Ѻe~("UF/!x櫆*vU߽ڠ"r~-_ZS jo,d!ƿC~=]:1* ;{B{G[O~G5U\\:+>=/G£ JA*X I~OB;}C qؾDlh\4kl2DZ`<#9ӗU1P9"DǨ":$s@Gn+2@ZA*'HBb5S4@;pmO__ l߲ 3go@DEEP]ɓ\Sq>2aj%/wLhT]{>fv&/[ $P6So5W8;GSl^L08L".eI?@^ӡ ']t@.\000a޽CѺ۷o983f ILBS|XOnʊJF{9 쮙z Eiq&*\;[1R5GVA,*RA[H蔚_V]cL!cYt5Ǒ`*96b%pĞ[ZVBIω4brQrP+T GSԮ{G}ϊE' <I*| PэZ4);fe4,|;44w?C '!!͛7o"aÆ ś?(_2V!8>Qtn#mcɒD*/Q˅Oz4R9@ !2GPiHq/RmX(dX|8R.FJ2.=ef%ޝ~Ow)x$fZd kM ?Wjm )AP$ Ov8rh@E&p|(im$J*3 ԥ/UyhZps TNߖ'NwA,@!!!E.{ ppp)G7ū'5)SY5EB#Ʊp!*}(]-]$c]>h ?hDcO04#B~?CJՋ4AN+ ߑ6S4ƘlZf,ۊ7 JV|) J(&׏.aP>c,-P>XۧaSfcswh+hpE Y1'Bϡ _|p> Hq+R?}زBL.+N/TvykhW}Th)&B*(d8,n# VAC NP7:EC\ I˫OM<5$@>UR@/_4X3у.+zK;%-:na4[CoTQ%Nz/hs?g)nHy֍@WnuA$ա稲_?[E_@tAu*My~U8%'߱b Cb 0}t{Oz @/{.]E͋%tr7'bTUf !7I'PNABsCZR_$*C&d 32Źcopv!5VMT@DlH(RE" getI(RՐe!u7iMΖZG3Rv(p\V9kxBKh\e /djE[>t ䷠ A~a,(h};xĂ|(hc"fACN$q72e*/=&/onFt1@K2UTzp xPcR:>b H4v hd#`>{aԜHt7P > fxZJgq>2 $!{hYPL&1̛ž & F3g6WS]7L8}r%[̔6Eo;eʠ\ ]qk]m{Z_!# TK\}|,.u.KcvD7m،~W*JYhr12!9RHQ PbJeeQk] ` "Z@q+܊$H PD$=o͞pn*Qzog3|53kfΜk??ЈHp?#GXbĈNNN~6m:ݻwQsss*={?~r1/_4i"קO^zasۨO^~}֬Y}/C|9?~Ν;HιH¸WOrL߾}yyyXFdV I3Sq:9eeLM[u+Hqd9.jrj6#mʲӇo`O^ݬp>#?б>>{_k0N@KG? Mdogӯ,[piMEnѓl ns"p!06ҝ]-5Vڝn?ZtW|eoLfebYx3O8!c@w3m}$3;=ɪ"p,W#6qr֙3~;S{=c\w >޾Ylz-hTTt``q~Dy&::;9HH #ByR*lrʔ)͛s8L:8;;#DBhffΘ1Ɠ'OY(-^x cll,'ݻ)ZR!(-o߾l֬ĉ/_|ᑵװr*22*IeVȫnv04B7'WTQ1씗ZBeZ2jM49xl_i:nKߜL05Yfe{ m뚏//c_pìCVڻ 8[Zaztؘ:*'s04E5hH[7LTQW]F *⪙Z&"g:vXf&tĐ9p@Wa+Vq&eW__?nPP0nZ#H D")) Aܵkג%K6nܸaK.9^^^1>|nT/^ܼy̙nݺ}ݻw+ɻw*w! Xko2p\\˗/* ;JDXwiQrM<z\==U*XӋ,fyFk3ywmOБAlʙ\&=]NEJ:w^jly Yh`rƟ@حȝirD/R`0[]k1gMUIvc 1Y8X(cb6 MZ0yͦqvm@sl aU( :lrKI6GFb?2.c[ QޭMSj&m.᣿70;0ΓI׮޾y>~Hd'*#x>}vs{z w#D?^~$@l~xqB!1cN_z7 SRR8 x9UxDÇ:tzĉ7n9sh_?'/(9|ЕWiBkvcߎ?2l7L;5mz8NAN/"]wҕ"s٘9x&Y`{k]a~wM6;1mbu3gG\ Mi"9n[~l/dJ5iH.ϑh1j M.6Ӄ'g+^f<@Crho {rtijU+ݶlӘ2&56+BHy9C쳮 R y)L;Y   )o|򒒒K"t񄫏Cb*[؏>t&*ꠚJ?2wvF|.x'IVF |}׹-̝1=/?JL"(֯mgKY?#ٰ-?vjރG7ڍ:lZ8Wm|9WwNPn/y1ڿ;!~?r!.;;3L V+]3E/2iTQ9>mxfֱ]o2WO&ܖ3u5gOta@ֱ(]Ro Ũ]40.u 1/rA 3&%K-zsƳ7ݿ~ܘxRs+(F`c+\;FXE J]th.4i\Ę 5%]T hy!THHdWAoFU? h١@d"OZv3`<߾ڶ4n<'{6 d!⵮.yxFp~IurpNo]̥`&}(R#) );_o=yׯpr#^c (ncuu(qMhY4bĈӧϟ?ڴi&LprrBѯ 0*Ϟ={ZYYзo_fΜ9n8tuu񶆆GFiDH;4"TXMTGU{ Pbu^{N411C.D35u%Nj'sK2!]DQm0IwݤmTNhzvpMM];Է,%ܽhܶ;8vu;o]NX{'9'W"Zd(W4v58q _,ټ%nM}. _=1#Sڂf_ 3'؝tn2b|56eh9b^7@K$hNf8GpafA?NpnٷL܈`y_^t,{% B׀ԌghcܹMq~;&<ʨ[;/=p>&<HȍW r9T*lToΨ L<3TTA'Ԡ$.圜ٮx+; GEƺ)cdWx =8_;Wu 46xmW X0@3}(c D`>ďOP΍,yƬ>g;=ќ" #-3ߺ->2Ul&7h@D7+*!1n^(P]RCL xjWh&+ hY7y([F_- E2PN,qާ\&煎vl,ب:tbgDzgəC?Ƨ1ӎ"!?nY[j׬q .]`eY!2cvsͅsE:-g%9}p">PܝUef׽29y`ٕmžTH dXg^2t`".2 TnxB:M* JP(QK]777 ]]Қ5kL| h >tq62q'K(lEE"J)Gl2d] OF;+99aCj2&Ⱥ(EEE%*⍳7[Ү*]y,ĽpuֆBD`2&JuX[J2Bb-AЪrN1_Ůyl}';΍{Mb` Q7f~kW0g.?3Ļ.Nƪېm5t K4C \(?^ău+]"ю$qɩb`;H]L9Xˏ@wZ, s*P5'e+{dfO;3>oqd^:6cndqC5#M u)5טtXtg;[8il^YVKˎ#oJ*(>Eknu廜sg=^\[L^d~$cjzM3XYٸFV .zbl .lw>`-l6N H&v]8sT/i`bѰޙq Fjk7ݵ e-[:PEJOA|r7W]8)[w7cg&HՂV~^#sūP- R1W,Tіq׎֎"K$Ξ==n8SSS<6[uRlbķ@?=K1J%ENGѭz­-y~8=vqd~alcv#Ov.xaq[,pǺpϊ.5 k-3]+#~(@U'ӛjK eD9`k/ k!炒FrNp`I~7[+R vvf(9>,46.|nBgܷBjӣc8iAN\^ J }INV0"oˉ?(ǚUڙoy!=oxϰׄ| w@Jç M]z.xw%2"""T|6LlA>%yed.C&8D2D-gg)(f]=ʅGܘl,i'O>] G[xڬ"FX a/ǗyI.dJ_>)h2ˍpv?[w2fXî(/l!ufb\>sS.kg0",o{}Mg֠/kj +J>i; ~Loۺ} .Ï웹k#7_yFVphS羖@~ .mKj V};J;0HV<5X 0GHѲ0DP!}P{s͡)9iv?q=}S?b,i u+pyj V9\>ȿ$tJHGr sK+Z(X>z2g̘` !$ ~p^o6IѧԦz0^V:4QQ(ԽS}xc)۩9L.JX7S!+j 69sA ֌vQwn5lcxz#[0:>0):HZZk״C6h06RYYQWUXSAVN{V@ETƁ&&m>8} `U \ lɁϞ]zޚFi#8 Wz%XJ}]^6XyʋxIQTs~ckl&xlgǶ{6s 4v:wLQJuA $``'⁚<. .)kIEo(Cd|^U5A5YiCv2Y)(3!,:ۻ~ TNs O_vU xH \LN$ٙ|&O$ا~fjX=L *v酑vE `شfދ}V]-)H_~Q|9H@.M8ֺƎ!Hr(won.zǘUEyi؂便~ܟFm_?>]ɜuP[{++W0 7;UB}uqTaq(S5 ,ijB&1hXĵoIy5*s( }6iI`%%bx9 %; p9\0AW'XdO:ǕXM' j;<%B#AefbuOPx pD+o ޽~-nsq>pu}PJk=zsٴΜj7[ſ*6 PH4(,̘[A0̲lC;sCciW0RmMk'ܽ&/c5¡.kR[V'mV^ϓV,ᩚZ&wZ/*_Tհ\MLiA~dšrX^WWHg(2Ev\Ѕx@RuF+}0mdeI1 nŴijBS[Tp &B\zhdtmW6U Mt8ѡtX 偧He.6vN^N5٣G+_ (yW@eO;>t9  8|L~:6,R-m@UHjvڂ̷ 7-nNww6r?AcR==3ե7Ӂɐi!Ԯ{WHt};?)8~]<=Km?` Z[P/iűJwӂJp לINy;oѰd9ms+SyAw3a s KbvB145UxRn}Igh$rTS=Z3mW,t>"kX-OAe֎.]Ez:Ol9M{~^f-(tD]FfHF@)GW*yË@KlJP{_zW[&Fûhc-߷)XBVYy])!!W @!}iD m>7,7!؎ӌ ap/اwN{z]/9N0y-=k7fULB>,rYYZN5.׳讐<1דAwGHu?ytZy`2< d U4,W^X̔8Yb~*036lgfs nɱ @gIb.4 AJn;ٝNyNcY#KQp}-͌ 2vRVͻ@"C8)A M4bs\֞iaޣO7 BM;B!Q$V%L$5ܤIi22 gw.]6AMV3}}el>yVPA(9.yMW:+͝:VP(n=g@ ~w̴7OhCӌV_P>s,ұg -&f_u|V};Y1ƖRNvYf ;纮g9LeU 3EKmeXTlkD _{U0LY% j@mnUq<4ZEjkv313KUk,_MoCĢ%G .>J5ݞy ͅ7CE}džEͤ03?^ThafuhYYr@G&SDC#7뉋C ]>:yQY ;V#jAl:O/O ;livz a9Oeױk}stSlZNdT!Y9ݷF]6S\5ٷ7X`.916 4,=<KZf_9~Ƕf_s?<{^=[‘FU+yBɺ+$&u-**ontӺndÚ+xy,xplj5Ӄ2_d3DlȉJW}>(CX%:^5M50CA RKHNoqf2]v}Ϳ1рn]cb^?QV|pB!& NmaV1KI#"1!4A]3S#VFO< >J@Wru·뗊 28͟&rb5P \`{K:5*IiF)Sԧ><6}d'MJsʌ? 4 '\+=M,#AJ w/wn4KeSWzlF5-3C-A׳c+kXOCq \dždRDɑAC=?Y5%W".3Lc(Go{:G*5Cd,X?tVfT̩U*Xn,$+[Z]װfnUy*9? ȵl2 -{Sudͬc4Z/afoSo0e1N" xRԄPJFPL`<X-q[%.ƲmfZɈo8B> C=<<X)qk?iC"YW]g+.h|.\3c\Za,*c 8@[ 8[?4BRȏk5ա0|ťU1;)"[Pb7n?1zeSkss?B8.+%L1KوuuV}\$2g}÷y b6 ہUF8Sx43˙ 6}x釄48AEz+X)AG p<.OElDRIB\srHW*V-m/"ACie0ޓ{< PZcĈ!>v3["31 ?[Y[GE̶jRxFtI٦+Z= yn)L_nr{Jȓ_!ZIl²Z̑7I\F[ZV~Eg1ehwU%ts[Q| JvhU~N][bU6fSd~]f~SJtZX^]SXFSSSNUxjVT[c#ˮe i)U(##)@a bNO`|'+M#-PtHMrP_׊8xPbrHl(n<\m)Ѩ'd-E!X0iu.(IdH FtF"pC&c"?v1~|ؽ01O ?Ù ޿^jGY^ZA.rd DZSYʼn=fΠDCU7" : ِ$[q~y-b!hq>fuo+҉) X\d1ih7cs)gj;UWʀLm ֨,cw`Ҏo4$];7[QtBͿљ=zDήf@ݹsw=OOM' ?y5xjn겡&jo.<06o.p@lKbHA bPj@s!w/ e#"]=WIaAѷB}"]l:E:q\E˷A[WiBZ GC}f^oڴIOO믿L>r oNcǎzu<;w\``8-/ӥ ̇bmt}J$gJK:uLg( >tL;wy{(*z<)rQ S$u!2Sw4ii v>D%h蕚r#vtZ"i\]߀HU3J LI'3u@ߤ0ckQ?, 84,,eV7@a@ǰ!Jh\+w%wqO!/#nrmH$ׯߥK߿]Kt9~x||Dľ`DgH}R::7x LMj Y)T*͚5[: q)Z汾."$&,z4`,q̣oV2o3ݚ, )7Wix u kJ4 ztD vF Zx7t I |Hl JЦS燘d?T[|+M޽4VJII}6?.%, !11… Ϟ=;uTbX ΣLךυn१bxmMZ3NbNľ֭~|K |IOOO$dZ\/z?"4'1W7hC5Glx:H= ~~A">ZGfsd0Ol(5@XRgϞ}͛7Z[iXl\x6mhok TИڈaG}Z#(lVc9tdP%p%&}uBoN- a4Z%|Df_0qi;t d/D=gȐ~qaInDž*ÛbDydh(HVa%1`MS[hJ'U$;_||ZƌџRʕ+u3B=t۷o(h9ovvƍ)iW=S ⑜2Ç4H$Br\ХDa@|BPOO?//'So䓽i| /; Bx- z|>G ΔbѬm(O^>fd H#âCHoP-/7'0$dݪr 4fpMnˆڷAfTpd{C:i  RkjhΟ?|ov܉C'`]z kϞ=.WhQB@"t?`mwÌ3 ̵ǵ&襛!d;q.Xdc%pAQJ$G9XuQlo(H}85L,1kbDP𫈨G K]pñVn_nLfL$%YS+_ bw#1T %(b8eVA1ccc Y;r\__ӧ &^|9)) Y ~kk.'Q1W)"b`@ZХ u;W7j ]V|OnuG";쒝YޡSOT`!5 "Pb+o'Uc!C`~iIs?Dp/;;" iX;*ўCGCNܶz>}01cmr븧PPc7dF7XP@fJ b98 xqyliHLoN oRRRRgrtuuEEr`[ LG>2JCNN%%}:(*']|DdA c.ٻ+y˗ĘIx2X0=nWӌyӖy6~vgs6Y70eX#JWA~cW̛ع?!=OIL,huж` V$)W FSژw-c&-Fs('6Q:t(ݿzz:5GK|9^_LcZh'#v .$#DŽdOO *G]ػ,69;wlsKؼtMh} G)ۙMmC{^c"!Eq m*SXD\@sSk@-H=##fձ踚)^|sـk׮10hDs'|P Ern.YYfZ. Iĉׯ_qdFV6>_GG͛7 y @Ċ@, ZbBRШA8X¢!=mpY/UDxA/Dyl޸W1ѹkfϟd02;,Ɍ`% K7u%;IR軻{hO"}Ll4ujcK;7oF9j?q ]zCgM.MR1IKKCqF5$b_ ]-6oc( WDrhHO>Ŗ?xL]6ĸPP(ɔO!ۡES)A,(炕)adi˗  u;t\!Uz?agH h+h7"nUq@lm<##{ͨ{Wna3_+۵k]گtE1s=JTB###i\2JAeM .ȠJz ٤$p֭"{(C48jmC.L#q<7-*4((:zQ\<,84, ϊI8$X*Qkj U 1WUַjՖTV-M )ݻwSN3f?_~MXhh(uJZmD666Zaj蒠2\DDĹs3h!ijN:o>x%L-ԩ}_^ $U6.N;DPB+f%1/Bc#q*"О\Bb`Bƃ!n 6@a2#q ˹qnHpL#RCi+VrA`Μ9K.Ś*{T~3={ mذ dʴMOe.W# j`aMuttDI֣GZkh6ZW'-l``jgϞuF5 TZwVI,eOcbR2C wDsJR%* l[24@)v?"?/,&|ђd9-Xo@h+PPs0WX'"аj&}e֘ʵ֭)µ}4OŕJFG~РA(\`L===/]xuKK]XVRĭFbqL_ ] .pRԩ%KCb}ׯ)%%%Al^-)l[n9s50r^k֭iŜg_nظF"01WRgT TbͲt}}#TGWi֡ I&-X`ƍrʓ'OQ![o۶HD˗/1}wڅF7| 6~V\IKRj;id-pSemL\5tidѢEXvXjX^X@i4A`` GXdOF{![<CBB #|}D4|pI&!j/!+c@" XBF*ضЈ(׍t}o%2I:R^BDG3%% TAHS||<~D<={6g($᣻ΤQaqtUJ|;|}0=ŋobF3+6?x-/=V7obcf^Tbb",fÇ8*q]tzhkQũMFVիW9믿r8p9}/nЦGlΝa@M,S,D,S,#Hy ы16Q#y  dKJ#DQ!`Ɩ-%yzY^GDžuuS)%"ax@h go{d'ļ4Ų VKGđxR%Z_ٓC|޿Ml̜li;ɓwޥ|1hX-Q#?ccKٴ/NV3geʶ;.2Ä8?O&ZXXhm49z(tAē@  ZS-y4<-Sd,'hтPϓVFշJlձd-" s:#N)>UHH k:n,.{e $EtJRGyw{ձSS4XFsr8[ 5W&}HQjz&l}-0VX܋ [Zio1ZlYǎus[2v>j}~+!!t4EH6{Â`{;35S@ O뮵$z?v&3{fw=oC Z x׈SMiğ?yWpp]t)Vh_iFFܹs׮]% /߀?o3`孷²#?~װ6pBWJT{۾m-'% D}ԩ+Vؾ};z C_ǟx ,uo"oݺ߷ɓ'16wu=X!ŧh=F߳_oB_^_4i7gfXqׂ) tsRaN0a"5 BsCBBpf0t0|sd2~=&&&-- 래?-!,T~~'h/OD_L`)y| pr(?:UƹMa_o/b]̦Rh6~iMb,*۽e4oPԒq[W7uZpIXGW r,uboXr~iQ{1b?~ԨQb¿.̯Ѷ\՝ŋ_yvL0 <::4tMMI@Y~d%'}[_ G "o>11g{%e0oGSfMc{,u*=x(i_OsR!P#B8i!hӍuʓtE6x!D v-1,Wϒ^|bSZoufiC]z*fK /eiT`v")I7h}ݷr VL{?fP0qD}18ҷɓarW0Cқt/40G'%c@8kp,$1nv9 =<;y`Ƴ_u| y/^4nB#aR *f/TQoR B{P+C o/0,5U޹+ / PQmֲ& Eyn^z$+T `B:X"Bw/a-6Yӗ*WQ/_ ^\wq}4JK a>#TuwjjUams'w|UI)dJrN2Th+s4H[R7 tڂ]IaECkRw蒡aq JFBΗbW]rpa4kҤ0i< iq6>xhJTzwEB7.1u把wP0 qӢsy_r@ Ys߆ՓXLf2kf;Pdn:>)ӕV*@k*)3h`7 J+:}4*O-t,YRC|4[Sʽ|^ LEyҟ;hN+4IcYܿ"b9VjP%="ܽ3d$%|Ue]R]C<)2;W}j\VQb*/R4%E%& Oe^[՗˪ ҇1H٣9\O?_|AH:;v4D *L.\7 t_1th(>YQ<) +QTT"1UܱfET]d,і&Рk bT1ijS1xY[~r{liC(0?]ab󄨨(o%"ku몳 }|ΨM7Q‚"MMQ[>Q+K=[+I0Ub(td=*>\6F{S#t.KȎlO߲V,5^1t1^ڲTd薒)U.ABݲR)3N"g9 M&]M^ڬ3,.^=n*9kkR}^&^l2UW~ՑE )d 0AIdhN? ]u{h:Oe3=iкI:-fŏl#ՁGݲ|cR),T _yNs l㥚ZA_m)ԼÇ H$ 8rҮHA*հ<#5ZB%;#x ]$J aX޵CW%ڠYRb $4 w|R:1eAY|K,eZ6\)t&W922*r]:X*_*χ"NM4Q^N<}'3 \J._.jl/:oq-EFyr`IH-WA\"j-sDT*T#z]2 Y(]6T)BbL6=FnY"AyfwBnXV$vU oQxѻ!DB!0ts*>I%?!fȦwpAb\iyaMBźKWZ?ȡ^y0`/R9pL5@csp#99hE`K[:ns.l~ }4bt[>ӉՕ6Ҷګjy1)! Ǐɺfʭݰn⍛]1uVr\j,N$ crIqr/cŰV>=•l#P!tGצ(RWGqh ݯe4kt&.oj{].%K#er,?FEc&!RI6!{YpْpA) ]ؤjF GZx͍Wzۺ 7ZjނES3bY"B";1z @"$ 93Е߁.΃`T]_/5 X)ݽno6uРuPB>G#m{4.G$EJ 6ݰrVPE8poܸ0LTrG+!%c,H)`*6ɢr#[TDU)?/u|ρ:Q & s4b68<~kc MnEY5sȻ^_ٯX(H$, T!8D:Ig> a0D6d2 ~̈́BNCH"AEm]XX @^~+&3@! 0Hv\۳5Qm@qbF *q6 \ ! R6C =i2hujKZTAeJ!b*&%1tCf QL8'K0^ 4@ jtw E.WE20nyLx!A.0U*q%; W3\De|R ̉c ,YWXJ$RWP@`4t(H AkAڐw˗NrfM>}YoB~'a tS^9+|H@oṻz,&>w1 3p_s"jJ4( E6U~LDrbK@!|{I<- v]Xg@ZSLv4^\7^wmdg6븽fpAgZ6%1g'}7N^HO"S)AH ۔Q?e?:l!z8-6aڐT\F$so,-5> f*Խs猅!2æmK8ŠI0=2 U -CR=⑋, E7/3->{qRa,O faOѰ3f Us+ <(^drCrB2A*SlZ9MC@w}Х.! TSOOY\`)8[16k|lHC1@8-jY>RÉ;!D)Y`U͌3 cadmC,Vl[,}xT!CG2pbP}.pVkØتpRr ]bIucOJ aѰf8 WthW-Q:w ,?}ĵs!I SFן=P'^EPDaF& UEX5s?z:!^H]LJcOxnY\M,.%I.p;dbbk 1 "1]]]"~.Vs%Nl${C A)NI^iA,fo JY;CAF'ˋEӥGm+ \\YQvljCf?f; rœW,iP=h""$ t#J1Ć¡W.`\).h5hXEzbBeva񏧬] C`ݪ#_>>:VeB]TT3m`J^IoG,ݳ^ 0ZH)e `<bJU!cR;Sm'=5Dao@CYĤ>'=!tlR]ahE wd|h"RG\?PZI3L#yH!H쯇umc ûu=ڸ>]U4Fwj&tO,~zWLf:A/Ig\5bLT;#Btn~6WOt1exb"}-%3C>'_#qgs0gUo0adbxqW\~m!Q5ذvf o8ql;7ؾhk'k<2B)чdPɄlV4QѤQD-0zfSIV];нe !$y<TN;LSJ.aX|u3*Tւ Imn;L"%=حZ+^,G;T!3o@a>+]e۴W bb)E'"e az'V4_=WmkhRϼ=jؤHPKUYb \HN.1geZdǣ 3|E\Txj7%5'68RñÜˍJB.&jmJl 6'<-B6G(l%W 9^&/lOa^WZZg^SVViu{L,>1⒲մ we@J_$χEpnTi~uSluY^-.9gKU5W/NlBb aJGdp؄J,:;ZnT1V"9ȏL.6b! 1$ )cB1]fS[ZZwᴿ3씖s7ӍegosveٍQ h7r>-k|prl\P1hôaPzr4îfbG}L>4*5 bC qp74p Z>^@r||R%drd :CU6;믿kj]MMĸmv,IQDR[ais6j6Rܲ|6j"GQMg쨽ku<&DP}jĘ!ĵ|? pY "$jЉQ8b>AٖlŲo9r?裏)ʽ{8qw}ȑ5kY0"J¢b"`SUKI Y1]'r!# tERZuc$-ZR\QgX;T&Cp&Kr<&/'!Y ,ǧ@Sme|3~7e\҆V `^]&҆uG@ *-_]`!CAna bxm@j#p5r[]bo -CU}q:$̇du%މE+ѽ#5t߆?l_Hg)/9>gk!5+2F'MOo=;1gkJ,=1nB|OB>Q]Y<-Ï,HK<} 1̰Pa L>:;zosǎc|&'O=C"TGA%@ z1ucp8tuW^6'*$J)GbƐ>\]gL!X(++ʕ+9׻70Irv{}OpDz(\qjt>LL Q')Ѧ`86+Ǥ&0by$q`8k/~I{ͪ)8c2yȓw^@4V#B cέKIy׊ԓwX5{ Q|UӉ3GUT#/RUVVI9]QWpҨmٶ 8yx06AxmxM,X0Ԯn\yl..ǣe2,U${ 1l=K .NᓘkHŽH6P  HTIA1$bX"NJ8>>0B˴d#b8 cTLeY0DB(Q7p,X8Y6Jl-2u?u >H'kla7T_jmFuSĮ}5~E>@VCb,;]u+0sEL;RvT$:9 q-pu,_>gŊj|uUAo-0L$ؑTI(鋵E<خYY߹5w\S˗MRM . ]>w `8,'eޤ9sK \D!cReDdQxzyc^$%J<@r)Pu뭜[sè0qfX$ʈɦzԒU@ Y MFGa}}}*@;V10' gw9lN!c<4~rq뮎j~b(~P>7e 8a&OA5@â qc"1D5kJZl8۬;s'>ܿucGL˯_[c;יɡ7R)W7hTW^i>0tx#6ܴW~w_MVz .k]jTk bMqR;^6ӇljL"_aǏN1s̆ ZΩ*ӫ ڢ+[65s\Bx~:CHgߴƉ <%OW]Uc*-kNOUUkve .BA P?,xGg~`X8RE;XL|j^amvq1!CِLq [!P>2ᑭ%SqIv,)қG:œ#ӳ5CV'jy{ AZ+.YFN%uװ 5t@t%/@R1e'LVW7|uyݤDE0t /O?25%MącΞ6nTzba):&A).;SS]sL0٠79}ۏgNTTq%gY4jMɬϚ-*+ά[;c5[7/3Im.0 !eݸNcꁽbc-$r'H(f%`hhz 1ItkpaJp4ΐIKpUTd'Ͳ}翍FjqK9uy̸sW^)loH}5Cn%\I;A诋6AJ?'|K޽4:'.T cχFaCkzÏX0$)jƔcFYJ%z5F,-)Vk4͛fo6U`5 0//?fMkM|sE1tNηfM˚=亥]B00m:Juh^}PB=*m.$. J]SL a)PKɺxz%VCҙ/LH M7A qED~ymOg$yoʐKћVsWbKE( ~!SخΣ1kq/u'!u7uw4[ӧ5gjJr maaNjf3fZ]jʴyB (..Veݺuiii|sRSal֫ggKb:]!UAL\ԤG*e)Et_,9EˢTLaѬA_>0W"DX<+ ba9s,$ n?BÎU+Ԍ<-ɠ|:nSjX1o5VÍzALv? Dh߉zu丌|[`(cnS"dM24ןۼeٙBW !Je#"槹tE¯qKT[|W܏6˯zko+貂%vl$3ľ@C+tz m[GZS=7ICm tPBZxfpYtg=v0D@)O _y|~g5]iA4A$!@wgG޾nYs`Hrۭ.JQ퇮s:+0E}NlFփަB@)& "mI[VI6s t#$nA QEƅR}`OgMtԷ>|VcܾmHd1JTUtޏɇI Rt T=>؈yӏJ$D `mu1KMڰaÄ oߞ=rHPOf̘lُ j sϘ |{~П\^y',E0@ K6,..nĈ{YrVVP-񌤄7ot,;"b) \&7K'BXI%Ub 4g2)$+P[qxu *c2g.jݥF Bxv2~LluP"XzIWNrx=qtup _!M &@:5` :T٠MnDTw{N|q4旙B/]H] 2S9p,;@KSiw^7yTV/@f9ۭ-ҲbscUٱoޕ aM?TUQQ`TEJRp @н{d2ƭT*8qt#B9khh(2 !QiHqGNH=%-ЕR-%Gr:-^KR{˜*%6RS) tLX X6%R *bfpp!PӇ o2z}ю\XN`Qe b]~}.'v4jf D} Ї5d6!*|?s@NSVި~kjf;dG>R±M)J*եj\%0)e0v9"[/^my-,I Q! x;YOŏy혴X"X *,%#R)$tyz!  ][&+JĔa`:fjSI;`r Ð|c<\ V@Ѩ,;jD & G #0a1r]IUϝ܅;er$]rm׊t {ɣڗK%`/]v"7(G (I /.Tw;1jK<4ّ; 9:&.RzRƅ癴pOh2ImINgZg|./KnqtQ_J߂;o[2++-?̭᭙pAf50) EԥTdHs3=zntcҍB[:?*ɭe'߹~izL  @& Xd;ǧ xb>(eHdx˖M&vp:>W'&'1Ed %,Z_H,!DXp敭]6NEU#yT ;;i R%\Lv> 58Pц:Puv 3{P ː/(Nzu͜?w0G SoH?ة_L[Ee"t*?* [|6R%/@I9:]vtk=C?+/ jLC/CvRkz?JGM6^W NG!i/):z: A#"ݻxHl K7kC}ؖ#`pxs9v8 XFDltL/-趶9T(PLT9IGk+Vڊ[s7]3l<o@ ƌ H9V 3 l@kjO$ WFOkIsˉVlR%VcDX,&Su6[ t7j`JkA"^c}ܮIX"u\UeGJ/JrDSMDi  +)y"$3ԦqRvr'nQ"xp%9MOz:1:;z:="Ӌ0@MkWZΚLG,IL3?\f_i]&QU41%cVDBD"4 6g6N*RSP^8J kUX?U T|.MS W:-o<UC ޲֐P|&_`6" |dYU>OSt'Lad")B%& Q" o_rH-Y;5ۛz}]VWډ}y2ǟ; d3ZU%_rm nO7knm]vTVUVP{egw;%)ϗa0Sq{띨˨vuӢ6=YKPS8HR  [GZy'W_5) <6Pk! ̙! R$LCBϬ/A7$lu=Ͼ}zy0F9.P:] o-QK#.Nb˜!d&FPף--J=A\fЃa.N<~j٥^W*%"A# Xq1yTXX}P0>W҇{[G2m.Nrx^hV7L {xckcK݅ Jfn:|%}IvGEogh_tPPB %QnDw:b}wmqŤnS[ ?pso?{[OX^{ݬMa5g?FkG<{ތ:_iarcG'N:;:!WkJ| 5. `VcC2/hWpjH!՗ut^?3 ƝxVXqK%rzo#azlJS9*3 FO e`i.T犙l!J |vݠȞ0Q1̌/0 Ca<$a>|.VEU˺x(eFl7wtS7렴zJ[驫zsWϛ2[>v ;nz/?Vѻ-?_z^ˉ{ ٖO߰lzbrU#&6oW7ra/?$~AWTء t;=$rG(K?y沯lbo[<qO,f=3܍]##sϭ*q 7ǥB?yd'&ZOЦG}MϿS֧՞[ܮ.46c̩#.p~B[eanEԚMĤD>{C tݿq b(d1<+7kˊBQabY6%!V EtuL e=%^Cf&1?ҹi?Nl;6ĂȲ]j6dhNܴD3 fEk6*ׅx09]b9z[[?FQx|*<=^/΀G&oKջ \##}y,֍][h,{W~9LFcxrCv< y| *] ƒlX p_Qƶ5_g! )xAcC)sNPForpְa"Z(>4lxnrەԋ嬋fq m374J]WZ.:Ac "8}^9)kVSS;o[vd ֍w{+G#2Chfh (lCR r1HL$%/*̆ |eC9([ VDa<ͭ]zv7^%$%w/$h@[zI۝]nfSOI6T΍9wdq1` |xMaĂ8 `: SrG$REPgBV_g잫jaQv[bp<=ޡ`$3OFA~\NtH. ZQ|AhJ;"x pD8Ny噍}9wr,8sw}ekHTrP^f_W?dh(qOa޹NïO?հy k<\}!I;zzu2׃潁@~D!qn.;>mayTʹuqZi!AuOK]$IC2IY>BA R0e #'˻Ŗcו|<6"z(P2W^2RR#/, F\ETq`o8`=ӷLV 6Q) ~}uF5k?OG^Eb(\lQ{n.vr HH H HB7VE3rh_{>]+;8W#~~ؐ1-T'D'T #hd7KnKJ2@WeP{ݔÀ.cLz_~i,К7wđVKD> L4MH4a$E`>/iKeTB~ !c岆EK5 t8Xv<8dKҢ>;pE]Bx#\J. 0hg qxH @B>s{y"DG(lOxcr>_}ZO+w=&G$xu\ [6†0f!Ḅ,xz(97& Tme.]ro> %}CpPg*μ"QZ[XB*T E## H!0Ex'NEJ("&/,!;f+苻f M#߉D'#洝X^tm2½^ >3>A ?Z+pu3xN ݨ0|il6[ͩer]/僥8%jm]-4]~jчA\~$n 쎲2 >[5b n'z7gc#wiim[%OY|BUqD .6 h<1xHbN+,T넠d'AP? $;megP\8Y|lQS%j&w߂wB)HV Dcc Ig>~[:jCȾ)aL|'Aq)$ =ڐ<`bBdgۅNwG<Ilr|,28pKEoڤ!}UBWc=k \!5n<]$c/C7Ȝ W/Ls53gc*\)JqoIkZZVa26Y :,]G;eΙ^33HBޛ) JQi]`*"\AD" {IHBzL3~g?1p朵޵RU"ZU.I uobT˻ww'|J{Wn3;pα}TjLuTd0iȓ 1>=jd6O#W8Mf̀OCkz;m،B:Nkoڧp觛ZIth'P  ; 5Lj*ѧŞWO7/&=1RMrBP@GL"Rʰnz/E?!,,B;Q6q4QD"*C+uZ.| (e[,u _qpt4OǶ5HUͺYW3wB){+8KVw+|Kޛj_KA_):O0]d!!Bbbbw/Ga|p|ldp/nNm) :^xЕkySYW:bo :o)=?%g~xb˴gN{vf5)Y +)rY[D/b(&2eUfdy nRZq٢9Z~w垀Dl-&oÄ+e(l CrSM]>cw[%]c`^ b+Q!G͓?wTÃf#LDFԂ O@.JYL M*Q!򵀬]]VDu3ۗ=<R2?ۃ졬V)Uvx2z7bQM5FRjWV?IxBJ{EjpL:X=~^Rg1RxKJtM)T";Ž w84mfp1dx_푗Uq rh1V8dxEnIPjK | VlXRR[aB>rCXx 2*C8S :;Lg˅y>5"NT=h7{|*%z5^B5K:ሙ@G%l-h𶓹93Rmh5 ;l.#p<: SA԰ыXb: `W(b%&VDF[P)K_\q6IoO˷`K˖\:íPy}m&ZH|uc}*Uҿ|\(L7drOv-,h؄7w?ū'wﯔiWԄrU50_LBHS7zruX]Z~B fڬYt:Ј˚7WPA!BN!.#h(@D+,f?Vӌh7*gmqor60ބx4z&1搻IX4QCQ)<*":┆).l5{*b5(ȪEw>)i!ʂ*@߬p@JiϑEcP h(ы ,L/7#]i/Z8KT NK c G68Fĥ* ΢x>ZYZCTiEc$*H1A;[GUxiYEfecւִ,)5!;@B͌83c'NHLLQXiA^N$;𼥝?|^_QސjGJG֋ՏV VHvl١[Qv{m0La otmJ'7*NM$U80"TIFe״[?pF/;Co}M[g"jݩ%FG _{EtP|櫪SUN{}Gr`}3O͈޳G86р+EGt K@2 Z~C1^L>Dr-\AVDl -pFY\=[am`3Ii]qYO5鶔Kq[c Ub~ Inl{6С0VbM?xjp@ /e=B3+տ:yrn,tGُLWЁ*/Ȼ; :uJYΙ5*¾>-.tSTv>Wvo NgBM[ؼ}{JJɰaT;ɧ]a$VUJ0b%q`R<' ˀ͗v=gDh&dYPKO}ڱ֋'`U I4bwyDQ|~zs' !Ms`lq/iۤ_ nS׍X#WTO (!qQZ,T.M{q0ed5ei0'̐RJ0l m P^uIWlMs[*Xُ3Ŝ@61͑w|նo> ɯv`FJZrbrSդq tXA5%Lz>iޚUj*@TE'&5G<߯N+5uyrNp(0A@o󡉹%j}5v9shxWo9jaIZfD6Vw fbDܑP+R\-'3v~P]B fG W_P4 (Zb~'Bg8NǾO[g8Ac_NcIkyMi,5=U|(S` zڿoqٳg-[kL"d 2"8>BnoXD(^!Κ@~ޠ7y*^5Af#qGV4U KV4#Q&AWkdUB):?/_&6f[AL1!jݑ IlxV7# eY D^=cբ̭1R<> yoD6cc-5@Brǃs?JjqHM#qcюjmD~Mms3NYl?"[B]-Q {^qlMڥmr`H-N8Ɇ?ηE̡N_qvb7 OyUq:[.)wV"Ow7?y܈]rme*آ:`O۴ 7k`g6SD@s AjPII0ކQ,)@BC69Խ<8;giY+N73Z/j7AOeb|T8ԙNӌ35\wFܺhZ0YWx6ʖr5Ksfw:bۼ&@7[6NBD&=,ogЖT&\a f)GQ@wd nŋ7hC} GZw*RvV훇Ki(bG 67TRU\D?9uB;lƘA1e&=d߿jƢM,_w'Ř{ÕYKmC%x ʽ_|-aP;w(JQO=_4tU5%>y XEF{'Ni֟]| $?%R„ Z~ [F:,g%ji*dT]gḞ[c 0}6'Y5az?rR(li6T{`_|4-Q k1WWFcIԃߏNr2z$i_W5OXN.#_zt8;D+(2_|CYGLI  }=|ŠNTdWsT&1F<4 T$nphji`J.rܶ|#W 8\FK28+4h_c3l"J|Akկq"rS!o XkOaM(T5겪U؎Ӷե((+]TGBE%+95ֽ_ JDFc?}z"-|KZvb%S4KX# "* U4֑JVND%j;hȉտHXzS}gd8Ht'ͭx*uZ_]]Ԥ'5 (// Wh:6)y%Fj4R /__yمGY Сfs.}ռzͷPwS\N툻*TL!U5.R %R7ƳݱbFq%Us~[P;xCǝ682Cro$zXuQ[h`Y4 )+Z =\'yFb.d)$xJ%8"ھSgj0x>FT[ k;.Mk;Ս3 E$01p{mTȠ/pOV&c =#WBF[jz7M^&WU ŒƝnZ#M&b=&vʡ,9Mx(֪\Tm1et% Z ?'E&V/~>>+E [d_[{{Bx,ʕ+^DjB*TI/*yʭ*FeҵıCӧwm۲ԁ7^٠],7t'KMpEC(=)|*Yfpy;6Ȝe-wQsn籮AJ&)җc!^!;lg]J䠑j}V<۲GףS]WJ^L=pj|E Y8M ai i5*!QJwtPUA4)^o c@Yp< <?xTbP[Pi)73`bOtG>5b|jV k VnpѦ "I*L*(Ip G"e+)޸a^$Nq sSmՍtF#_ ݠħA*KQ" RAIbV(E,+"-v/3/빶Oq'8iPj9MFx E\8c VD7\Eﲎ&.Pry&wTL3ӢhYeSZ aNվ|I. [ AP$">#)8#*(8~B@Y#ЯYKHDqlnמ= =t=Aϣ"0NGnJGFؒXA=]Kjs~))rk}1@-*w>Q5tL%ZJDA1K|fdȘCoo3D7hD{xfٍ% E }&:um$b»CO;Ξqhߖ} ]= >6OA1Qϯ<17EjK»8ٍT e5*%ߣ~AOQ D PA(\hTL/a&)Z-rR}AwN7B0g$կw/;NC.٘ H(t 0@]߂+.!OY |jNтGp>5 &Ez:{FaBDp 98W+kH [цbPo ׏ix#/" E-_GE"Z#|(kn[6Z=l7~Uk d< PAfYз@ϚJy]7bv(ܘ6cqIfkAkf ێ-4$ \d +-Xkg̙ۦM'}F4oNv-ꨛo+ ~e!-sz(.@ԣ[ֻ 2 Zӽ+2\ҶQe$O z :âb0O!?Y)7NP-U"` H iTZ6Du:6ţ[[un615-aNDߑ ё_KDŜY 4T*N9BVi4bTU(!_&8nr'y^-`֩ouꗯGM~c S Զ}V8 7ol9޵7Nz _tKzI%`0HCӼE aB@OҋRDw jLSF![%cV6MOAT$m2F+@y-W#"7;uA` *Y:P.ҧ֐%(\T[ ׇo 'OߍlЃ$qφ,9% O ` EscT]`P {}1bupOJDh{V]6_'1Yߣ(t('d%:lJ_h5}F${mP)S*_7ZbQ`D+X_YF<2a@< snף['^y՝o{A' v=uv8-_0*w8òXNi*735 JK~aP7P5}nFTUFmws|§:x{0Ʌ}CVH4!nNGxۇnwVnj́=~|پo'Lt%US³@yC_箒M(8tZC^w]5=.7Z<3.)+p^jSҺaa}&"+ awPgδ/(*b1DP0VdH=+{F$#3faR8< : Nݰ_Д+野۽3coǮ{~yGCc tr܍s/wj}[:'K.;518E@[I8H5 *lǪ)ג5 A8y'+yDh54pn461+ - nD!CMޭ-v׆fMbT`8*e>xR91SXs5NHE 9aW Lì,XοpY:5uz{X~kka]طNo536HL7b`-t(&_}32aeF Z (ǧo4MzzPgn֣&+/wFğX;/X7r͢aX8M:g)V`USU\ca 'sFRXni#G 4޴.9Mک2杳k_6nξAO PIzw^x cZRxHC1O"À%~{a[ LZc y"xFJ0"az:<#,úiO&(p< X\JQteKx. 1WXN g$( w(ܸ˨DV]4%R5Zdrc^4N؜I&91YjŶ"Y5.i~+($ӥHGK6gULaaބ}]96gƞ˧iB7aeX7 ^{R2ma~5a6 7W|hQvfq\ܻM{'wԩMVL7PZڦgOͳp~lQ,L" U+ⲚI4WMonavݑWrj??2)%`HE r߬2dK:1_Ϧhw& =tʝn4_ $ŤZěmE$&fqN IV /WC:s@).eJ^QF+,h+"x_MVld ~qkw aa `vIA֯X`3[y!=japL$KkI"V&X9s0z(1=S^5KA ފ* T J1Dpӝ#<Չ 8kAJJuf&K@ 6FLڭZ+_H]EqWذˠV0^CG#}o'۴1у QO̗ Q`2*4U⬻„ SS}Wd]T'+P|/\bv/QN僝&5F"*B*heT$t1eQb$5V/lxآckFNVߙ/\h $b+.pߖD:I|`݈HXTb<5c|xfftBqZ8\M}b}KϩM(sݤ|JG_*j1!Wjc]mVm Əl% CS(:zbY;8g9N(kUC}=^hܐ1 ^9ZglEX eZjzb ꌈ(\3Lsv@u1vE Px$rӦ6tsc[!f 7슝yMf?0.ox҅{!T$X%{L17-9e5` &9ԛV'bQ\nvțzl >~H[xs|ƞsGdf)'QPr^6>/Yʥ7?7@NӸ-?L47hwhfuAv;ƅ5;ix}Xׁ$?ƅmD1-DFye#4ŅӧBVm9 U*J#C4~p׭$CyZ5&c g 2f2I~ie-uԄjSGVX(anaٙz}Ƶ hD"Lvc:n\џ l>P8Z%@ 7qI۬ps8LJiE+y #e$5F.b~{_fA6܀ gk:ʢQtfܝf"RKVr /1@#Wal$f<#-O|hG_㍆?V_c(2 Z&O#(BL 2c'chx%w s8BȕNjG$zp6Ī(şn! lޢ%VHڽ|ErB%ryZ&(|-Mߩo$^Xs%:)ʕ_#&"o.V F/[fU"{nvfa/;:}1Q8C,p&م Xpep(x/$@|%TO.t(ZGF5W T,t%B{ʑ0ӻZG#t֎aaŞ(+Ḓ MUXQwR))S#& `HP~Jw_?s\67"uKP8r sHbN(0YX ZS>f"P67S=ۉCָ ۣc*gqm Dkf0{Sd7 $ȌMsn9%aE&wcL*1~s@duHSZI~ pB/nSA{ ƼTԥ5(N],^4Z^U5~0zu4d:Y{Gg8#~J<sFILM ^:E;c:f&͎{5#~4s2iQ>G{Ӕ#Zêp{!m(fMh0cPo`p4־<\׾u C಑D:<V(?GTY1U:])Ws fi aG\O R]0=TGHE<5%J㌪B6 1U:#^ѐ{w `⌈ W*^+5dx>L%:(CP -?SEXUu!R|1*)G9q9E_B-k90K`'bz4@nMngBBp| 10$mWgegސYw- jſD PcPTٞ 莧^5ypS˩QX~Y_kI}P)ZN2 k岥@YEF>KyXNTtm1mo9v߁oZ0cbvjs{#>Gxp>[S]E#s[kO2-Y)?h>y{ФdAv4Cg 0JK8xI 5a!夔zQ&="x!4mrš=Xa6ĘEU4 %~; 6 JMp;V m-uf LrzϮBmڤXI}FuX4viyc'@F iIo$4?|LcL.*  qx#/@֩j5a1U +bm^{Ӥ5ng\FYyYqS"lM|&*q5_vd05}yK&SL*dMvz,ySka@⺣F&B3SքzXh<ȑ8do& .!y~ձ7F?N< ݸPVh6o, $Im[~m\zEc|Af` [:YOJIabO9]qpw[g?h,q  _.ANg WU{4Q/|گ:y4D)}ܻQ|Q)3qѝg-mG`a]m;p Fx9ҽ9,e-Λy{'vӏ]c/0o;E 10NćA^^B 2n\ǜ6o$/w('p䊁٣ʣ3(Үgw?yG}oW_4t=^|Fq 1UnO3U}}suQFDYy㹣'.LXպJC%hBguhY+~6uO#\ /DdQnpO7zQרF~btD& HmȻA7O Y97=V+ Nn]k^\3ädq #ٰ 9ü,qaL @!z1 b .ջN 2N̘:t)ru4zvMT =8M4mQJ)8 B$a&{⺴C ci%0ph_"ސ?7EźnDTX c@8b2c,?``ӹ^kVL]pGE)0 lW♜ue%ú%2EӚz.|˲.SXџK D@tP>4LBސK|Q*ZmN/ou IEszSۄ~|]{;gJW/p;^ԐIn٪/` kVүk>e}g`+hq)Bm3IIBDcEZD2vȁ=d H$$JȎW蒾b8bK}Tk@ܴ8N7F4CAhSaDq8HqI Zv}Cvzcb]8N4 `f7k__Mk1"?|;W ծǹc;,DX<@l 75UbdyKKjFPG_+6hOGi5+v|ޞi;O`u;쏆rQ,jgyBZ;0#|yc_zwNqeg:e'~Hz l7~+Qg-}Y^[|1Bޱ[[ 7lz:ꅘM oF3_kµ6cRf>9*sPG_2ր r+l -,Z̬E@sTͳQX) iDg½mG%C剧l #\D. ;w t%;}"8Y%Nq0YUl@<ݳ!u 42 B(HSGP1mL_ eY/ e86Ш k(mPb+oM_eマ[G'Lujm>nG3 2!eL.A<3bjt#; ` IR0/8bSY}up$n./?ws{VXX! ؔxH ."ss4M";MlG|mjY?ďGPȱI1d 6Fi6h jDB.aR)p&etbRA%9i/OR$R[!gN7~Bؐ6M[0$v#{污\:➔hI͐Iy8DNLo_RVXMzCP#Fy-I!wP/=Qo)~ d,. >΅^Ғ1n,6 EОtV )KH-{Glf۝tȼygqw=߈@f_)k6b;8p͓)4J7IV۪Z9TJI E{g\ėbLv]g:!MϬ#q0æhdO= l,L[US<%{O%jcn RUB05Oj)mc3.S; ^Fρ5RG ? ӖVɘL"M''KyB|jQ ӑ`T{3 %pqҷ$ ~$>-6uMx|m鯓c'!:Bi{XV@6 T@gE> 5k8}'ٿG_mGt'ݗn6 hF8cɒذocޯ&٪JBtY(bz,IH)|D,syb_n+R$7EjNJ5MBaEٖ+=ܗTʝsc/M9Ѓ;m];&A2ѽ.KL8"{57cB TЮ'I#lP ._~%z<^}pnqH=`Qz?2klK}T UP7U$"<On"ЀqQ†pǠS Su1ON!ƞq"G+Y3;aY{c'Ő(3 ;T;])_Xf"$09 kL~"ci3@CjcǾy|56g,QJ,/iW6"eVJ _*52miϛ6#yX :q!941h)`L$YMq<l$@'6 <-w.vj| }r47L(@EA AXd?7q(;֊H.9_xmovgt2a11ebXKC] N *تB0IrY]Ae%LN avI1M5iyzp3'9N6!@?D6ĶhЫ[ɿ}.apYf#u }gszk #ҟ]0Gw]:8͈@$umwwbGgRL&ǹC(E" ZN %*0 Kq`VȻo$h;_ Ν;eQH6V&:= SH[`6E7@HEτiX aa߰'phI w9p2])p r"TB/Нb__?ӄS>˜yF%< 蹽 @Jy,2S+t qrO'J$8%qRIN슙2F1H NVBPׯT*P2 ^5s$PNqDɼ܆ޚޚ*&A^ޯ5#!S/[\sHLR%|Gj5`Uy(Ұj$W%Su+/1v/.'h[T l5!;WE`u8_we4<dU >iz,6. v(O$"_ocf)T1<@U=CQnK4YCV׏{3ifc3:B0sj=?ɿ\i7˥UEU iy\yJNxh*LŕkMb=\TBG:2oOˇN$*N l}t˗V|rfx"#H , r-2rY5DZ4M÷ SX5T΅ X7-ypɸ%3\ٌƊRB \F^0z 5g;`X^/>owaNIIs! ؿ`xaVҚ mxIbQR` ]Çr ظVT³b[KHe9SdaذJR|Gj^L ˻H[lPQ-Uk ,yx,e/Ddp$Xh#Hr6)لbN>`+⨞xdPjBX9Lu4+ȳ/.C.Z̧5bL66י}LFy,e}J#^MU+|FKX Ao2?<=o E4c'gvo{|lmU^QM!(CUըFMƺv\hBjUM>nNÁB~\%r8<x;'*qytYG-)Z6Na<٩1.nrdJfNWH(pHOua/7(0)c~Wo_8rh۳@h׆y5ebG4Z~3C"TO Fw <>)ү&QC/SQU6@*Ε*8l`eY8.XWBx"P6!N' jPɛ=\ILRG׊`p3݉$*3#RThCo-yqn7&tmF2^1ZH y: 80g Fy#KjOXh}}+nYdGodP=Jr ]lEZ;2 6mQ vj5w-vx|dl!G*r A͝=;faRDMu=GӣƊU-ܚo;6NjҒilG)%'sSίMؚ=k ,Jd2}!)>/i iaAo밾+$\z\i?;Gf;pжww61-YLhrjJt}9]FL IjX>+)3%ob>I#f‹m1J[@!DܤZBBSH4FOS10S^M1Le:8/ ~C!H/PCQ9d:LQxC?sn.iC'w&-yë=tfDDvCzWT^-ht?]L8dF^0upDoh 90_0I2CȌv7ס'fq" 7*!/Fz$dRGDӹ'G mQe\]XdJBR# D|SH bjFȩHkt* ¹R&1C'G/>4!T>f&Eh 'QU]芝8]>q/sM&!z.6 %gno>=j˰ğF ev\:iʝv&C/W.щ*lZr%l赒KQ}8IA9vmũMUo=56uHuYqəO.?#q@϶m#~c]8˯&Z=uwƧK,4oÏlv1?^89yNzRbYԁ/&|`^ l `wkNi=hvngLg[,<UP *rq0duN b"^P+gVʘeR(¬ 0Ը"fX7U3;(JIe)[;Xܙ:itACjWSk.Yne]eYAƴfS ?6b嗓Y5JգkFr_fTTT/_YXXѿf{Nf6֖̞ܿI|ȐP1)1j.vL2cqz}'⻉/hsĢ;Hu\Ђ&0KҍYLX!{>w4*UFW/k 82ҀZ7+d]B`@A@X|7pdI$i$1kAW Z ҄V d@?N,P,#i֬.oؓ7~֩W]F;]Vw _S_n?skt/6SZeξ>Ϻֺ2LÄ*S*MPB8#F0b-$t@T@nsu@=s.6fGxF&=H[us>,~&F~YOפ5:%j֧&~G\e3\(󫧗V_oꋨ^-CۮY2=u{y=*f"]^wW6[rY]TM_2%Ĥnm| HyLQ.vdtqb_SE=|dR5;:P{ݺHJTa)T+l&lv"G9Eݹn!54g4 an*1("cbKf䮯o~9{sԋLa'*˂m nYeU,Y-'Nj3v}t"QN!jn0ރNcNz\v wxYju|$f_2#T=.RXکyLP#rpqYaǸY<9uʈduj1c a0G3F.:`ˊۖgr̮wo-T;AeAJ&i,r&wT[ 87Ʃ_;b'9f[^.V+E ~^߼pS endstream endobj 12 0 obj 84971 endobj 15 0 obj << /Length 16 0 R /Filter /FlateDecode >> stream x}]1sUM[G8҇#3kdte7Y==6@V+}tr+\uPApIw^?O\ٹϧRKQIr>p\kq蕧?~gX[>Ͽ??:w9?:Z,7i'LBĮzĜoRHuNYөY&q]Xj74ӕD]Ads'fWQɗJI^'xKaVIT\t7~-!ڗйn7e)F`W'Dju7&pZc=ޚ{ՙf-5?15.3fZ ~8)a]tTϒC40,1]b(LaF ʠ4z3,ݾz%ߤVWZ@SߔfĨH>1JlVOi*|(,uqLwS^"-gnHW|VTյ8t2z|zy%y"tbhwx CIdId_: Ut$_Y^!%V#W,ogD (?ؘ |eE VިO*,Io}d4:C"h3K!_KZddX2UG'PI}@GVNx'ڨc47tаzwr]sr2&EtNRt"9dwF 'lt2{*0{sCd*F q' vjhʉe,kQ3!/EF4%ͷhGOJrq)Kp|M7;>a,hnqDx"2هIoCXfA@odX'L `w龘y~yLp;a4}SZ^-yN뜌GipD}NF>&8\ _s+HBi$g) +1 AM_*Cs*/BF1AbڔW |j?XWL gtFpѵ+Li|ZTtM ^ㅚN`]o6 ZlU>i~\MVc:5/?GoQJ;p3L_^8䜙laUuڱ]ċ+'w\t5Me1#HhFYɉg_%eM;Au"XwA,P-?\Nv>c*_'EĢ+Ǔy$_*,Z UN!A~Lv-IR (Xɍj:E@c,x x@0/' 4L p>JhƝx)nrb>:ņΖ"h‰MzWj յ2#tA. K^D.8C?3I\MI]<,2ݕ*]0~`wDMuu+'!FC% g T4\Ǹ87̭,;-5-?L& Vs$X{w.v9:H+iMte.Cb "#XGVJ"S33>EIu]IGz=fgIY']*P#@܋U;('ȯe%ryvI}(1Ҟdy%6 'o* J*yta*WfTY"'SaNOSaIԎ0pY{Fb El(0OP2? h+E)Q frLCE` 9H%nbX~5 J&&G&2pDZ6Qs} f . [7ȸt= NH$y@8s4fY4H,h [dAwd{i9۰ӑ& KnωH%Zt6 wBt 4O#5:s& 0җe#cB'ڗkOEQ 3‹NY/`W#UM VMcXmgZfN5 0NKe `@QW3hA;%W`B䍺x4tL 2vba XJ^-hq完CZ~g\q4[ĊS$<-"kYH FWe4'+pB`RcZELT%1P?VoBim=2TtOKhoOQ\UF l:,,&%{%G=Y#ˡ5 oHx6 @4& 1Rn m_I#'H AkzG~br 'd=2 L!rX 1(E_* K%I*5(UP(WfL '@ ^'͞p Vnÿ9(/&pmd8Z E, <"2l'J'-n#΂ea.jP, ^ b7wt6+94ԝ*%ҚK6|و$tH,G3oB$@ F"(aE|hfur=+z'Zs]^G(4f`$fMol8 3SDv }S̅P"xIXn|$Vdb&AaO@]i[YC\}bG H$GtW'czx+4%u-m uMͣۏ?ONIp/.^t~: YmSgeiQƫ=<\C I Vlg> Ch,ZZ8mEaLHa8!CmqD,`ɟJu%&589e?Be!9ȑҢ"ÏNEL1*H/ m$k|S,-b1p2%W䃄:?hإlx-`# |Lb-ώG_Փt1QUh%X=ՊlD^/$jvB)E, ] SXBđs "QD5ܫDC4aLkV᠓H( X06ȂCt.24JF":;RE-tjKQp%~,g'f).B!-v&h81dx9u2fyH {!LAGKB,RD#L8-]@ CM7B|}$VVO,p]C;GCXhdu1F'cH*f?E<>0#jshK "a$ih NNGx2B?H'j_(Єr)`'RPk\ǥR(`9K"Š>h/qRZK 0(DuJeBC3 " ZK]THjV>4!?J[ &9abJE#W&d51f1Y2faĘbwݨX8&KNR%H4̹"ahȜ!?9Gg8tWQ>MfOH``D#C(yCabHE)nY|V a ǠԍShd2%6.BZ,DT紉 P'|9/MHj@x%BdDٟhsl2>fO̐ЋKe*8H4Dc y_iT2$_U*/SZSC#@7R,. XW\)qn'}+ͳ]vmKN~qd/dHb @h< G%BRMFfXϛ M1tBT3%$fsq6f@#5֐f/T] q6)GbYT Ǐ L/cL1 <*-.&)U#NW,Wℼʬ8op9<&ZىTIY:$5zb+hnX uTXꚶ.uqJ.Nj4u]ܪ7G1C>SL%"LĈ/?? bG`t}L+%(F)cߙ)L1+oXyYzuF8IM l`iUϵ@Z6{qŅM],Zph `VJyؠbwP?ɟχb{ϩ Yf7ϩ 4>)V48y>@™ t'1@y+=E*8Q#&2@ E:gq>ZNg?:aͶjtZzWZ1!B-#hVnNMiVͣĠ!^RzwTyBWD쁉!e bͻvi1pFڍ2Tڏ*fj'i V21ʂf: ʧVmK\L;t<4֢E+qm iukYx˺5ް:eݚ׌$G$D&e>Ӗ cJnЬ8-gh(6#ւ$I,mZcI7VF΍bΕ[K6 #F ݚln:`Ȳ}KAY#r v. /A5 iLv^n08Hh9F2#i7,dݚfX֍uSS\8* TIehE!4v(vf (Z%ҭ\-\FI b1tli1*f&ObZzXgCY^$-8,A笋6;}h֋F4ڨ<3:ŧ+fN@N1VuRcő̅+nT/P-K&nł/P-F4!Z.^&LDm_Ġ #3-ef,fH$-VZVc-bҋ4Z̺+ 2 9Zu"=qD\'Ĉv:L/<50qdA+B%9Z<h1*,7w,&;%ȡH[G )os8`ǯ#6t8=5}-f-&ZC߬vv UKSX̦hm Jf!YR5|bo8qtӼz%F7v]ps~K cЌ;Ӣ0 -uĚφw~r6̵GE+a~^e~-68- :|J>)Lb!ѧq8^II'4z/4VFG1LU7j1Ҥpz VN- :QAr[ S2j?P~GcmƤ\+ouhAZARGh_j?C %A6ZFZ밎.5`f`6ZAͶkgD;ڏS8O[if9ÉrךHOJMlMA/Meu2n N: Y-u[d"⌖ck{qL'4 h¨' F.cM/DkԈ#\HkEږOoɊ$E;[d\pZ2"R-q_YDb B1 (E44 q$%y֬jڠGVVɡaV6QvGXtG*XgS]dװU\9%/(M?tg$&]>7pd#HbY@|!T&n%H,@ 1syl%KЃVՠ8:klPī1K"C#9:C̑4"/z^i,Si"z;;"q.7t(#ڇW`JXD N4#%?0s+L5@PX䱸ƯXQ#ž$jCzFsbۓ9WvGͰZ5m;z2f=<:"Ѵ1L0bst)H݉aXG=4fx'gg2Nr! 4IA-)jfM5A kz?Ԃ*Nт)u7Q*R署,d0f/Q0Pʃ{ܝfy*2)"MJL70iXq7ġK̜SPȉmd_ i ݴC;MsvƐ;#Cl;ݴqM2#u/0M[i+fn[Ś2"˰bS?`w[m )h.`w 4 8TԱΟ'ܝm5b8c=@™w vtb *4HVJ֔WSwr#M$2BZ)?tsоՃđlnZ$,XUЎ'Y%ktbYYZLp{h#qO͋(^aEkTA}l*_ hChJQW*¢ Mwv c dbj.'0|[TOO53[9AT%Fti0Pyl 5Z* yĹ U OzRL@۪+~@EOTE5+|5b|͂b[88'Xhvv4:d-6MP&iN +MJݼb1V<B ӭ[PQ`̌p{)f PTSvDV'1a59~Mq$#g; k~jJ=c&1*`c3 KȶzZMB&81h)38 ceԶC vm&?Hj сܬk d)ɟ4PS@*+X:ݰv W=6fgKX+fTQU0J֥VƛWA6ͶP7. b9su;×4yF,?=2h uMg3e!`e: UF`6gh9\*]G?,% "fd z% N!W e:  \ >2|Q*\فM]W7~B 'ӗ-\&I_JŠbGRZ:i" ^d &_F~cܿ p66uN.*tr8E#\PEL9JTO'Oӣ7߲b,̠v1ʓh@ 9Cq*{^"B%wJLs0z Ty},M3لJH(4II.rpWRdMM:EW᝕8Ϭ$ȮHKh ]l?gFrs /]IРp*3MEvBm3&tWuq9.⼊UUZDKfRm9|,dR#_% y$-Wy<>MtLnQ]svۉOlu!F Ϋz4\E򰄢]XyqdFK7}iXJ@t'qҊ9?1+гsM״`$H]LEU,\i,%FpR&~pΘIbؑqv>fYJ^kC:=C#AWܶ)Ygfi$KWm.<99"(m@:"X_*4 |0e6YN7yo|02vruѢ8 siXv \mI a-Qrꡚ*4M!L2?3&&)g3+8), ARAM9W7` t9TCdXr bJ9ӠSv!}VÜZ^9IZιj%Ut6ZU&hO"FQ3}eӣנ3 N23Yeޙ-ź'kmn:b>1Go°6K*p)iŒYb4))+FpxCjpx۶! zW괁9Ke47D1]q6tQC Opzɰ=F:H` /Lb =r51"D\Q/]=8ζh/vz̢b PdT[c6FeB#BMG;F > y!)9$}bg/g5CdReh8>ݒi7do߱MW>\?=ݣK:6ԎkdHQhJg Z3L1Ei;ƘD.;@bX,@\3TCGC4)9!юI਀j+؅^-y~"mj$LD [%lް(aÅ{s0- ?S iiѨѠWшsļa趺8'<fIgNk"`|KK(hӑVDkvz7;iv(hx)WG OsKvڕV=%>`EH& p)--J1RUG$ZE/2]897I7b)$:@Q*6H5vcz^7d%mmuu~Z*C9 *j-'HjrGF0H e@(#:BDדw=$N4epPp"T~:+4Y@r uAlHzF'5O-9ih?#< #hب'tvUmX_9F\FtB{ۓ峂]V؂øfILTc_ UǴ8.0\YGmIԖV4̢1+t 8,ps.98&Uڀ^O$4@S]8VΤ8fIx}pG{ݝ69g`ܸa 9 !/ ·}ïS&[Qx/ ԑ> ;qXNwdC:?5ˉvRpQR&ӉKydGREtca¹`qDEn)ōhwTFH8qI'7W>'ʈJqåcUyr^]GkNئ%Z/ z%ΈO`n$ }dH{$ :+m&a9 &|Eңn G!m=.]kv$m׻$b+Nrf){4=n@$Y,iIH8q#B3F,Zp4:}ݧ6]oZ׫J88j%wo~ӿǹw'_rvׇ'dPZfP7${w'}=.?D[b]:Hpu,wOO=Yw<???g._!糫?O|"V|ėn]9VT y}~<;~>??/>>}ǟ~}>"Q_<j/}w7C_/ I~⟺#FL?{DaR|GWxRTh^U?B1dEŗcL)>~w|~Z7> *]j 9crU=H}>6] л|rM=C`^@'_mf<܁/ߛ٬?/d7+N ۝FM1FL!~C`EmR4sG.t]A1KvgKr?܍ uCEؗ 7 i`/L+!+%+CFЃWr@K6T  J],]Fo؜^B;`1tA/t\s+Z˞bK1ER7o~sݷon/}on_;?׷n_ϟIN?meǼ]!qgrk%wʂtB%Ң'm1}]i!T:ntWtyY RZN~;$ה9n(skD`U&ebyC|yooi{Wnoky$-5ΆMdnwGE`Up )\t s_f'( y-΀r!1rA&vk3,iiVx;V ʥg.>?uȲ8|L!wCg@xZGd*`==6=He3\emOp&XkzaC@8 Ef-ܡR]y]PA t췓 r2M2'GruXj*Q@0(G[-̡_ PcV dw.ȝr7:H-A$b1b7@=&VY_5@g]t5[?Bbn}BWiCk@J8* nc@肞o7x]H&{Ѧ ֆJ\S鐻]y ]PA t o'2 eN\Qe ڶ[2bwBAɀ=Rl(d~Mz'XBz' tVBͻ$whnuL&XrHv b5[ctkFt0X)"+ct5?BtQrKH tLS‡ϳgݙ8.5y%, zXaA< :G&kі e!vC zXGAq0H.5U| \R8Gj5] :=l+];tjb:fFkbC R4[s@j{t yx,^r!!T;nvuDڝˁdWnϿ~ss&Iچ?ϛ)m̮䂵=Z=V6atlF6X*rMCJwA J;*G0q0N.5e\yr;XG9_ޕ/^<}ݾoEd췜KrMCvOATo_he]o!g}=ufoql:,jy׫bpemϨ&wVr"5v>,G6X*rMCJwAJ;GqN.5e\t;XG9!/G+RpϿ{/w}uJZg LөP {:m=l AX.'mGQgw.Z5[j'H5ֆ1.MָAANtͅJNxFӣCv s9AAjg穓ݕkޙ債=Z=ָ_:O(hG1hde9ҴPk*r7T+K*ݓx o'2 e<ʜPP%T' :Hf#3ΆRMdnt D`U錂Qhdw.Z5[j'H5ֆ1.Mָ!]!QО,#czt5?qCA6F— _wC~/_޽z7o_}tfJi 4i 7Ufr{ ].δ+viR*5[*ߕp =췓 r2M2W>]2 t1A+t8o/n_޾=H X92ԺB"X:aK>ӤǁO:+0];iUnu&!WY[rJvNcSn@0XbӥAb 8$yLܡZsA(D.tG>s\yWOo~.Ѡgϩ*a'| %e[mf _݁<_/ 6, zP۴0 e}!vC zPj"MXM`ܜ\Xݳg{q8Đ8uj"M&rC: AC&vK+.ruXą..o}ޞ=?9(4S9[UC*rK+( BuX43M*&nNURruX`Ƈ sCCaP`S/zũkeiZQf.H5[??DX>~q36^xGg5%{_VtHG@[Vp+ks&bk{l=sW:31ge;Ҳ!:n(tw?[+aDWFrIrPʟ: lfk=p!^;}:wfD%3eHӨP r:a=lK71uWu-s#c1"Mֈ]Qytah>2Bt yDw!vCg؇Ɵs5yӳE:zC]:V[V-bܱ˪[Y܃ha-:#Y+`Xi!T:ntW;Tz N.5e\yp;XG9!_pf/~{+ܽxq v0Gi ݚLS :{wAЩRM>}QnǁoL0+]~ n"u{Kݻ#CH[bi6 ^:XG< >viݪ@ͻ$whnu#!XrGv b(5[t<kFtEpއ7l\HӣCvil?t߱t/9t]WU}s~?~:_C?ӏu͹;{[כ_>|?ooO3Mc7C?Ϯ>{s'#LiɇHL'Ͽ~?_3 tϥk(xÞ_|ZnZ\??Ȭu?|~7^*-WI%:G endstream endobj 16 0 obj 21100 endobj 14 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /XObject << /x18 18 0 R >> /Font << /f-0-0 8 0 R /f-1-0 9 0 R /f-2-0 10 0 R >> >> endobj 13 0 obj << /Type /Page % 2 /Parent 1 0 R /MediaBox [ 0 0 791.999983 611.999983 ] /Contents 15 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 14 0 R >> endobj 17 0 obj << /Length 19 0 R /Filter /FlateDecode /Type /XObject /Subtype /Image /Width 233 /Height 216 /ColorSpace /DeviceGray /Interpolate false /BitsPerComponent 1 >> stream x1 kX@xIkiZkZkZd endstream endobj 19 0 obj 43 endobj 18 0 obj << /Length 20 0 R /Filter /FlateDecode /Type /XObject /Subtype /Image /Width 233 /Height 216 /ColorSpace /DeviceRGB /Interpolate false /BitsPerComponent 8 /SMask 17 0 R >> stream x}XTIvs9dDD"P# *$IsI@Ls9+ J 83{zھުN9B,,1Yy3>XHLqDkig;*B8|Ĥ@&9@A lguQ kvRs?1!al)=e'-on^j:(a-_=iB}`.+/U- HOJlLx"˟?v}]dHB. Ǒ c@:rqؕ4;!l]rטL n\Hac! l6=m&L ' qb欜if*& $P"N 6qrXi-1+_5wLOUYk+K+EАna!3':M޶pN9la|13΢|# i${)_Xt8ND Y`?6(:Xs&> WEٟIcM-8@ ){ #; t,Qu`,0faD ߾053}@q6pf2@Uh{k㪝kICwMʪ 1k+Wwʌ83hʙvk/}V{kOYQDX@Ԅ̝ ١kv{k,q27Ӏ16j@_ؙn4,,y/#^# 1F! LJj~lYxRY Y]rJ,Fuc]@@5/# pBpnl~Jp[ѵrA4m[ mݭdx$03p_57$iIqIWkw]EX4X@X@GUBP@XB@OJAQVRx+Xo~U{hM_-jY-G5h눪8Y;k*5;:7 22Z̰[6Y@I(I]ر(,߆{a_腽Ȝd#> HoP HS"cĉ9ueJ0@ctX~@_8Z>vQD"@"pYHqlh31{ ߐw 0sAؖeY./9]P]-!>~Z:4MʛkY;9)zm*I8,_+F$DJTGg sHV@+0kץQ)R#/<W'pTv> DO`,Ostݰn񦐭.Gf_?tnrrk%tgMWlD:s.@}O).N56aXltoo?B/F!죣dı1⋌v|B,^} -mW}t}SlZ) ! /~%v f`vk˜6l J!8AvXVp@!]!H. P yj o.c A!,"+ jC/ē UW'/Wo 4-Jg(U\CUE/ߴE y&vA-w[Q ˄Dl$Dpc "&BOtET./ sž8ɰ!i CW\_JTjݚ5v UAJ9u$4Ŷe|q:P6TNP*jW}`B,b!EDѯ|LN[JBe&PM/m. lȝy!hgeZD̯JiWC k9u o$3r{;p>;W[Ȋ= ]. `8+υq, lmvSP_X ] ܅v0Ѭp Q/_;UE 9n w>/}TrRܙI1LXчe Af d}CT+5p"$pm)y^N?m{EfOk3;.xA^vbaE<%1NːN;txFBsju2zQRVL2ʆC5ī_Wt `3{-Gp-Sͣ?ho (XMY1 -+mA K7?7FUAX*mY뷺ۨ1ɮ퀨=^l֣\1U1?"^*8XO(1IJf2 {…ߑ%>B{pkf)u 7Ƒ-E K\m 4uy:OPbuxVy˒*(Q}] /j@n,aסJaAq_nW79{xhzYе8Ljd㈘5%Vz, 5=`{1+e+(RtP]^eݝTq0Y>kzjוs ǽ"|v3%~ }ADh)}a2 s΢r;26ӧ9Ff1<'wPF nݙj? c_h1pd VpOsRz/ydx\ X*9RGDA. b o^ lƎ[%>s"y>UΉZLʣ/FV;}$9F$}Sb@n<ٴܺr%!ýo/NhI};YoYrX\˰~ְ13~-д+$@0(KWu6;̜17=͝^cöC|{wnw'}^ ~qɥinRqόyt|;pHd9us' }&d&*9-Ԛ%^{kߏXS#ik@Jow)gVP?s떍Innl0%~\.9JMIu_H,rpJ֠E7Ә3*{mw'.8{З;zHDiLF4N?Gcb,ԉPPSCGwNs$ܮtW+YLnC_Ej_\//.|8woxr۟רU< iٴ6@<8m{N^>|X>=vY)%ٽkjUl n[DCH;ʕ^^e>dqy'tVO?e֕ڙ_f=JPܳ~Y tOhx~aK"l[1Ư.*|.贕mֶ<^\P ʶй<3 pArpC"LB2p6,7Ȼ-XY([hף$-={ttt@tC5h[5!!2HAC?}3DԢ[D4 %gSn #\͏/`az ++bVX8zY9G?iX:'D8`[eno-\ޜ׻<}VvXgnl2YW[pi26fjzU39&+yl3dEG:c0N An'j- WŌ)S/e\0N=jPmUy᫃=N\;MݹGuO?,O("~=~z#XIPzP|չ>į^墿\"jؿJ$ں-x*h5$tr<̡*E;k֝o 2T@iM]b[0 F^k2S,4ȇ{h"v7]"bɢQt2i= j/Jll͉$1-͛W}ln^;Et<8b:M2Ƶs'kdj.Fru nF5ov`0)YWbq*(U?`}{zRg5{Sd(pU_CvU885~U~ҢNY ]w"N/&\;Qi/E ^( (j(YYUvENEVe^ݰv͠Tg|Ϥ>H߁;yS >MkOp/JKT08q}y۴yqָ[Q/6S/RxlJ Qm;mܽk7ڮq.|W4, O^Zd|Z-[u}xZϨ~:snƆCOLX]>}}j@;/ܸ 4H"ӦU U D"%@J\ХE Cs 2t'zGߖ+tP.DUٌjee*:y K@ <#PAWE%.k'K(s= dypiP@=q\roa\oW;0?p.:8{t3Oo֨n=I)Qn6Ί]xc?nFzXpSCO)ʹ+c!3\xV舂t(Y$, GC!R;@78vtN80ݐ:iTN('!|=y"9`YUkic MւܥP V/:UV_Pt>flYW;ow̖[2J{os6rΘq'AxugٓA "̎o 0 9Wn )0K o٩Q)Q'RFcʠX j]&9g%Ջ;oF'ӻS6ێ5OOͿwƓxɖh ߦ_Ž۠ {,\q >hlwO8;9۲}l+^gQRg Y8:!nV i K\&Sy`>6^uq"1~ $lG&*,:_ eP?Hgu}e?QT-s/zyH5,Q hZqA}ZCN{Zo[wվo?(zuI[/ pYwk˄7@MCq&dhQ _x]779w@8uP o؝+Oa`#h.4[e:u_ّg|;BdžNڜ51lcd}>;np7wjAΔOWLSn-7Cb]ZYcPRD[E^.(_I@O]j[Y頛"#[ӽ'zyccJܶɿz4 `_''T$uKY N)w/PJdgjGz/X4K`"6pP!O>i1Ł#3g`Хd g4j%D\f>1`A{Wct |/"`A Rw [w̫K=+>7ąԹy3~|s ksB#TLyrwIM5Niɦq@.DSMˠ)=2:Q 5f jskʘ80۲gABWu~UV;7lrYxǤ$=?{otK*Gby Z㎀W_krk<Ma@(>C Y`-elZ= ;ص4umb&?܈ ёN/-(KGU~qŃ:ogY•? zn}mvRiNjw+sz8d# )8"YR,Y J|4f;^x`MP54{/MvFpnӬ,%偘045Нee`kecVėFi0ШC_#R6y.X`mg"iC9,׬1!?-leLM%^χ|lgg<~vʜ]"MQ]O0G7:޻orXkSFAe ᬈf9&t;iJ}}l:~c{.dYCVd,d;]xqr /k lRK]n 8 jSNdZueMusWGoNYQ ^=xyVyV/9ޱ,M<%h@6?t".y}]3 /@Vna Ms=]÷>.t;6z@q.3}Wg'[G9E Kg*;0] MQYܓ{b|VV, H;xjI×}IN\04i^ƭi'yJ.q?6VQE{ Gha*6$Gm0DNdLR.CQ. ~^cGU ;Pk6! :&v{ O!Mz6vKh|aD4b_y=uI@OMc x_ >>jPy;12.@1B@ep\(sl/oe+z%I~uC m7]] cWN&; /PG=q! ^H ܊)'AH2XU5 d}VSAc{lH>|P{+ e(l䀋+C2~{\E\N`L&Ec=/S{ rƔRzqm{^t+h0K.>Ibr;s?^73yo&dYdYվ?A5L1}544 ݿ^M2b`.LR(ͦ24| $Ȥ*ǝ;4HLlԖ7F p G"WG;jC4yͯœWͶ؆wI| pk,X Ky74}ㆵWJARqRٽܴB3 }#YKR=wU˸mT$E׬Pg,z&rQEN^m`:KawPD*}"9(!b&CбOԑq*bð(#C(<#eNraTWI?4g9EhQ2v FA{gczYk^ gD\bގ:VJDCAaqaAݜ[9z٪ Ov{`M|c 3?k[{B%/$\%tߞ-ᩮՓSRoI J|-|2>,-Tx\T1sV819}DVap̠!e0EѴ?_hYE52yƵB*QM۹@xAE"ZRQPd[U/VU^'<$uOc ZKWsxC;JrUܗLRU f̓.?Qi!!KHMY7|[Nr'`+=eƑfQX,D mx~BJX  D2V҄x9d,ӆG~#QdICHN$AlB 2[[q)fjR|,(ZZ] N2>kRUo]Si.ϘUG]J_@ˣֻo M .FgjnAӒ=ڎ 7i" p0Aǜel8 ,#<9B" n6A1U{ Dc=txthkcvyzA3mIYUR3+ƒw5xE nWz^ZOxp4hWFu5ӎy*SXlFs}v=G7zj±|idD[RW*4F}G7# -|%X8R飓Hl?a*~"E>LG'/.=1>lkMzlyUI] jywncabʇMhw ~{&$%),>j,]8w0FޡaT[A7'yz}IF¹u*2.4EUYLpVdJ͋O?&{E ,ܐ+fF(n^X~:"+ UK,1.OH{O/LΎ7)pfAT,tly5+k]eKD[xb!}?9CAb~mw8@I"avV9YMEP>HQɐ<ƉWc_,2&!՞vz>@L__甦w2Ap]ݖĵj3H|$'MgVjzl鵳<&&,eY%G ff:jݚ+ME-@(8E,3Y;N S@s#i׮#D`C:l}rŋ< *흂]K.7W Ԁ0ü,!vYo϶}:d0,aQOܦ ?s南[VenC]D3h)5Jڊ؈hভSha*IX=|enh@{XFGC ZXT4 9Unm#hD@=$ 8D> ĢoPDm=Qb3hcL@#Q]?"fsq׵+ogMckԞOxgb{ZW@OgY\a9f+O4ϏvVhX󌛙{{—/ɺ_MHH<4_O]\8RNEq` /Kp3z b5C]2pݎmmm]4է`LC=i=O#=HC7J([}\8|wۻ˫D7<d˵`ob9 WcD-k <Ռ sb\Jnly,X7~d ~gC 淛PIE$vp~m171xhLNlrAGT&hxμy|sv0Ht~hf"}UL !gP"R R>'DȢFkȄB4 An#DiTz-ie- _ә@w+jA:8R>;ӯ.N yTq3r ^Pe4~.|p05 ekd(z{.7زeGIaDl2Zv UpޥnҢZ*YSPcX]F+c3JFAm?8M)y*Ľ 6G ]_2f: r{@]R(v40Mr[n/#;:pxl?9WO%puw# 9liiA欘۷oEn 2Μ8F7AOƆ9UY0G6类[iIōgx;Īz^KF Uz$ Xd=@ӨHk^˻  ~*ʵX9Ͼۿ#h$tY<'`{|a)qK< ?ȉ,yJ*z{-H`(-ESɸJ;eWDiKw޾ēg"*FD!I7X')C7/Xq!Mj oG)+z5 )ᘥ91neee/^_[[訥܌piPX,)/Fo4U͛&CΜJqGLk>$ݕH{l\M2ntѩ+CO_\5ymDن(F=DƤq#8A~ܨNaBaM򪕙ًuz&(HHNȨ7)%`tcpRZE9OCPoYJaB"b[܎QPD40S _7bynsqWwJ^ $!O'M+ʚoU..Oi+=ܸ}dA]L9Wnpr ~gn@9rD&%%z DUUɓ?̔hqޑQ^;x&r.g' ٤3j(S;53boOQ%VIņm\\ e9ZHM?4ѢQlVq;@[ _)_VW851>)4Hx;{Cawl8&I 3|%x:¥ʺJ+J1M[,Q)h&+chOyP0?;%`zHīFRRnRj+dQ舽36?ͬ?\v.h4pyiuuuֽ|֭[ѣGϟ?ܽ{9SW6DIY,:n*d:O˼Ud^P`iIiBdqHvnL,D%UxSջgZ69lJ nH/?(SżaS>ܼ$:p}}A8tzTp },CC1ʂ8(QZX+PT'PJ4ͮ@i0-1%|ŢWob뉊aRid:]9=^Ns_rٳgRRRa%%%8i2(^y7f1Fً7Xflg tK_A<(A"o(+Ѻe~("UF/!x櫆*vU߽ڠ"r~-_ZS jo,d!ƿC~=]:1* ;{B{G[O~G5U\\:+>=/G£ JA*X I~OB;}C qؾDlh\4kl2DZ`<#9ӗU1P9"DǨ":$s@Gn+2@ZA*'HBb5S4@;pmO__ l߲ 3go@DEEP]ɓ\Sq>2aj%/wLhT]{>fv&/[ $P6So5W8;GSl^L08L".eI?@^ӡ ']t@.\000a޽CѺ۷o983f ILBS|XOnʊJF{9 쮙z Eiq&*\;[1R5GVA,*RA[H蔚_V]cL!cYt5Ǒ`*96b%pĞ[ZVBIω4brQrP+T GSԮ{G}ϊE' <I*| PэZ4);fe4,|;44w?C '!!͛7o"aÆ ś?(_2V!8>Qtn#mcɒD*/Q˅Oz4R9@ !2GPiHq/RmX(dX|8R.FJ2.=ef%ޝ~Ow)x$fZd kM ?Wjm )AP$ Ov8rh@E&p|(im$J*3 ԥ/UyhZps TNߖ'NwA,@!!!E.{ ppp)G7ū'5)SY5EB#Ʊp!*}(]-]$c]>h ?hDcO04#B~?CJՋ4AN+ ߑ6S4ƘlZf,ۊ7 JV|) J(&׏.aP>c,-P>XۧaSfcswh+hpE Y1'Bϡ _|p> Hq+R?}زBL.+N/TvykhW}Th)&B*(d8,n# VAC NP7:EC\ I˫OM<5$@>UR@/_4X3у.+zK;%-:na4[CoTQ%Nz/hs?g)nHy֍@WnuA$ա稲_?[E_@tAu*My~U8%'߱b Cb 0}t{Oz @/{.]E͋%tr7'bTUf !7I'PNABsCZR_$*C&d 32Źcopv!5VMT@DlH(RE" getI(RՐe!u7iMΖZG3Rv(p\V9kxBKh\e /djE[>t ䷠ A~a,(h};xĂ|(hc"fACN$q72e*/=&/onFt1@K2UTzp xPcR:>b H4v hd#`>{aԜHt7P > fxZJgq>2 $!{hYPL&1̛ž & F3g6WS]7L8}r%[̔6Eo;eʠ\ ]qk]m{Z_!# TK\}|,.u.KcvD7m،~W*JYhr12!9RHQ PbJeeQk] ` "Z@q+܊$H PD$=o͞pn*Qzog3|53kfΜk??ЈHp?#GXbĈNNN~6m:ݻwQsss*={?~r1/_4i"קO^zasۨO^~}֬Y}/C|9?~Ν;HιH¸WOrL߾}yyyXFdV I3Sq:9eeLM[u+Hqd9.jrj6#mʲӇo`O^ݬp>#?б>>{_k0N@KG? Mdogӯ,[piMEnѓl ns"p!06ҝ]-5Vڝn?ZtW|eoLfebYx3O8!c@w3m}$3;=ɪ"p,W#6qr֙3~;S{=c\w >޾Ylz-hTTt``q~Dy&::;9HH #ByR*lrʔ)͛s8L:8;;#DBhffΘ1Ɠ'OY(-^x cll,'ݻ)ZR!(-o߾l֬ĉ/_|ᑵװr*22*IeVȫnv04B7'WTQ1씗ZBeZ2jM49xl_i:nKߜL05Yfe{ m뚏//c_pìCVڻ 8[Zaztؘ:*'s04E5hH[7LTQW]F *⪙Z&"g:vXf&tĐ9p@Wa+Vq&eW__?nPP0nZ#H D")) Aܵkג%K6nܸaK.9^^^1>|nT/^ܼy̙nݺ}ݻw+ɻw*w! Xko2p\\˗/* ;JDXwiQrM<z\==U*XӋ,fyFk3ywmOБAlʙ\&=]NEJ:w^jly Yh`rƟ@حȝirD/R`0[]k1gMUIvc 1Y8X(cb6 MZ0yͦqvm@sl aU( :lrKI6GFb?2.c[ QޭMSj&m.᣿70;0ΓI׮޾y>~Hd'*#x>}vs{z w#D?^~$@l~xqB!1cN_z7 SRR8 x9UxDÇ:tzĉ7n9sh_?'/(9|ЕWiBkvcߎ?2l7L;5mz8NAN/"]wҕ"s٘9x&Y`{k]a~wM6;1mbu3gG\ Mi"9n[~l/dJ5iH.ϑh1j M.6Ӄ'g+^f<@Crho {rtijU+ݶlӘ2&56+BHy9C쳮 R y)L;Y   )o|򒒒K"t񄫏Cb*[؏>t&*ꠚJ?2wvF|.x'IVF |}׹-̝1=/?JL"(֯mgKY?#ٰ-?vjރG7ڍ:lZ8Wm|9WwNPn/y1ڿ;!~?r!.;;3L V+]3E/2iTQ9>mxfֱ]o2WO&ܖ3u5gOta@ֱ(]Ro Ũ]40.u 1/rA 3&%K-zsƳ7ݿ~ܘxRs+(F`c+\;FXE J]th.4i\Ę 5%]T hy!THHdWAoFU? h١@d"OZv3`<߾ڶ4n<'{6 d!⵮.yxFp~IurpNo]̥`&}(R#) );_o=yׯpr#^c (ncuu(qMhY4bĈӧϟ?ڴi&LprrBѯ 0*Ϟ={ZYYзo_fΜ9n8tuu񶆆GFiDH;4"TXMTGU{ Pbu^{N411C.D35u%Nj'sK2!]DQm0IwݤmTNhzvpMM];Է,%ܽhܶ;8vu;o]NX{'9'W"Zd(W4v58q _,ټ%nM}. _=1#Sڂf_ 3'؝tn2b|56eh9b^7@K$hNf8GpafA?NpnٷL܈`y_^t,{% B׀ԌghcܹMq~;&<ʨ[;/=p>&<HȍW r9T*lToΨ L<3TTA'Ԡ$.圜ٮx+; GEƺ)cdWx =8_;Wu 46xmW X0@3}(c D`>ďOP΍,yƬ>g;=ќ" #-3ߺ->2Ul&7h@D7+*!1n^(P]RCL xjWh&+ hY7y([F_- E2PN,qާ\&煎vl,ب:tbgDzgəC?Ƨ1ӎ"!?nY[j׬q .]`eY!2cvsͅsE:-g%9}p">PܝUef׽29y`ٕmžTH dXg^2t`".2 TnxB:M* JP(QK]777 ]]Қ5kL| h >tq62q'K(lEE"J)Gl2d] OF;+99aCj2&Ⱥ(EEE%*⍳7[Ү*]y,ĽpuֆBD`2&JuX[J2Bb-AЪrN1_Ůyl}';΍{Mb` Q7f~kW0g.?3Ļ.Nƪېm5t K4C \(?^ău+]"ю$qɩb`;H]L9Xˏ@wZ, s*P5'e+{dfO;3>oqd^:6cndqC5#M u)5טtXtg;[8il^YVKˎ#oJ*(>Eknu廜sg=^\[L^d~$cjzM3XYٸFV .zbl .lw>`-l6N H&v]8sT/i`bѰޙq Fjk7ݵ e-[:PEJOA|r7W]8)[w7cg&HՂV~^#sūP- R1W,Tіq׎֎"K$Ξ==n8SSS<6[uRlbķ@?=K1J%ENGѭz­-y~8=vqd~alcv#Ov.xaq[,pǺpϊ.5 k-3]+#~(@U'ӛjK eD9`k/ k!炒FrNp`I~7[+R vvf(9>,46.|nBgܷBjӣc8iAN\^ J }INV0"oˉ?(ǚUڙoy!=oxϰׄ| w@Jç M]z.xw%2"""T|6LlA>%yed.C&8D2D-gg)(f]=ʅGܘl,i'O>] G[xڬ"FX a/ǗyI.dJ_>)h2ˍpv?[w2fXî(/l!ufb\>sS.kg0",o{}Mg֠/kj +J>i; ~Loۺ} .Ï웹k#7_yFVphS羖@~ .mKj V};J;0HV<5X 0GHѲ0DP!}P{s͡)9iv?q=}S?b,i u+pyj V9\>ȿ$tJHGr sK+Z(X>z2g̘` !$ ~p^o6IѧԦz0^V:4QQ(ԽS}xc)۩9L.JX7S!+j 69sA ֌vQwn5lcxz#[0:>0):HZZk״C6h06RYYQWUXSAVN{V@ETƁ&&m>8} `U \ lɁϞ]zޚFi#8 Wz%XJ}]^6XyʋxIQTs~ckl&xlgǶ{6s 4v:wLQJuA $``'⁚<. .)kIEo(Cd|^U5A5YiCv2Y)(3!,:ۻ~ TNs O_vU xH \LN$ٙ|&O$ا~fjX=L *v酑vE `شfދ}V]-)H_~Q|9H@.M8ֺƎ!Hr(won.zǘUEyi؂便~ܟFm_?>]ɜuP[{++W0 7;UB}uqTaq(S5 ,ijB&1hXĵoIy5*s( }6iI`%%bx9 %; p9\0AW'XdO:ǕXM' j;<%B#AefbuOPx pD+o ޽~-nsq>pu}PJk=zsٴΜj7[ſ*6 PH4(,̘[A0̲lC;sCciW0RmMk'ܽ&/c5¡.kR[V'mV^ϓV,ᩚZ&wZ/*_Tհ\MLiA~dšrX^WWHg(2Ev\Ѕx@RuF+}0mdeI1 nŴijBS[Tp &B\zhdtmW6U Mt8ѡtX 偧He.6vN^N5٣G+_ (yW@eO;>t9  8|L~:6,R-m@UHjvڂ̷ 7-nNww6r?AcR==3ե7Ӂɐi!Ԯ{WHt};?)8~]<=Km?` Z[P/iűJwӂJp לINy;oѰd9ms+SyAw3a s KbvB145UxRn}Igh$rTS=Z3mW,t>"kX-OAe֎.]Ez:Ol9M{~^f-(tD]FfHF@)GW*yË@KlJP{_zW[&Fûhc-߷)XBVYy])!!W @!}iD m>7,7!؎ӌ ap/اwN{z]/9N0y-=k7fULB>,rYYZN5.׳讐<1דAwGHu?ytZy`2< d U4,W^X̔8Yb~*036lgfs nɱ @gIb.4 AJn;ٝNyNcY#KQp}-͌ 2vRVͻ@"C8)A M4bs\֞iaޣO7 BM;B!Q$V%L$5ܤIi22 gw.]6AMV3}}el>yVPA(9.yMW:+͝:VP(n=g@ ~w̴7OhCӌV_P>s,ұg -&f_u|V};Y1ƖRNvYf ;纮g9LeU 3EKmeXTlkD _{U0LY% j@mnUq<4ZEjkv313KUk,_MoCĢ%G .>J5ݞy ͅ7CE}džEͤ03?^ThafuhYYr@G&SDC#7뉋C ]>:yQY ;V#jAl:O/O ;livz a9Oeױk}stSlZNdT!Y9ݷF]6S\5ٷ7X`.916 4,=<KZf_9~Ƕf_s?<{^=[‘FU+yBɺ+$&u-**ontӺndÚ+xy,xplj5Ӄ2_d3DlȉJW}>(CX%:^5M50CA RKHNoqf2]v}Ϳ1рn]cb^?QV|pB!& NmaV1KI#"1!4A]3S#VFO< >J@Wru·뗊 28͟&rb5P \`{K:5*IiF)Sԧ><6}d'MJsʌ? 4 '\+=M,#AJ w/wn4KeSWzlF5-3C-A׳c+kXOCq \dždRDɑAC=?Y5%W".3Lc(Go{:G*5Cd,X?tVfT̩U*Xn,$+[Z]װfnUy*9? ȵl2 -{Sudͬc4Z/afoSo0e1N" xRԄPJFPL`<X-q[%.ƲmfZɈo8B> C=<<X)qk?iC"YW]g+.h|.\3c\Za,*c 8@[ 8[?4BRȏk5ա0|ťU1;)"[Pb7n?1zeSkss?B8.+%L1KوuuV}\$2g}÷y b6 ہUF8Sx43˙ 6}x釄48AEz+X)AG p<.OElDRIB\srHW*V-m/"ACie0ޓ{< PZcĈ!>v3["31 ?[Y[GE̶jRxFtI٦+Z= yn)L_nr{Jȓ_!ZIl²Z̑7I\F[ZV~Eg1ehwU%ts[Q| JvhU~N][bU6fSd~]f~SJtZX^]SXFSSSNUxjVT[c#ˮe i)U(##)@a bNO`|'+M#-PtHMrP_׊8xPbrHl(n<\m)Ѩ'd-E!X0iu.(IdH FtF"pC&c"?v1~|ؽ01O ?Ù ޿^jGY^ZA.rd DZSYʼn=fΠDCU7" : ِ$[q~y-b!hq>fuo+҉) X\d1ih7cs)gj;UWʀLm ֨,cw`Ҏo4$];7[QtBͿљ=zDήf@ݹsw=OOM' ?y5xjn겡&jo.<06o.p@lKbHA bPj@s!w/ e#"]=WIaAѷB}"]l:E:q\E˷A[WiBZ GC}f^oڴIOO믿L>r oNcǎzu<;w\``8-/ӥ ̇bmt}J$gJK:uLg( >tL;wy{(*z<)rQ S$u!2Sw4ii v>D%h蕚r#vtZ"i\]߀HU3J LI'3u@ߤ0ckQ?, 84,,eV7@a@ǰ!Jh\+w%wqO!/#nrmH$ׯߥK߿]Kt9~x||Dľ`DgH}R::7x LMj Y)T*͚5[: q)Z汾."$&,z4`,q̣oV2o3ݚ, )7Wix u kJ4 ztD vF Zx7t I |Hl JЦS燘d?T[|+M޽4VJII}6?.%, !11… Ϟ=;uTbX ΣLךυn१bxmMZ3NbNľ֭~|K |IOOO$dZ\/z?"4'1W7hC5Glx:H= ~~A">ZGfsd0Ol(5@XRgϞ}͛7Z[iXl\x6mhok TИڈaG}Z#(lVc9tdP%p%&}uBoN- a4Z%|Df_0qi;t d/D=gȐ~qaInDž*ÛbDydh(HVa%1`MS[hJ'U$;_||ZƌџRʕ+u3B=t۷o(h9ovvƍ)iW=S ⑜2Ç4H$Br\ХDa@|BPOO?//'So䓽i| /; Bx- z|>G ΔbѬm(O^>fd H#âCHoP-/7'0$dݪr 4fpMnˆڷAfTpd{C:i  RkjhΟ?|ov܉C'`]z kϞ=.WhQB@"t?`mwÌ3 ̵ǵ&襛!d;q.Xdc%pAQJ$G9XuQlo(H}85L,1kbDP𫈨G K]pñVn_nLfL$%YS+_ bw#1T %(b8eVA1ccc Y;r\__ӧ &^|9)) Y ~kk.'Q1W)"b`@ZХ u;W7j ]V|OnuG";쒝YޡSOT`!5 "Pb+o'Uc!C`~iIs?Dp/;;" iX;*ўCGCNܶz>}01cmr븧PPc7dF7XP@fJ b98 xqyliHLoN oRRRRgrtuuEEr`[ LG>2JCNN%%}:(*']|DdA c.ٻ+y˗ĘIx2X0=nWӌyӖy6~vgs6Y70eX#JWA~cW̛ع?!=OIL,huж` V$)W FSژw-c&-Fs('6Q:t(ݿzz:5GK|9^_LcZh'#v .$#DŽdOO *G]ػ,69;wlsKؼtMh} G)ۙMmC{^c"!Eq m*SXD\@sSk@-H=##fձ踚)^|sـk׮10hDs'|P Ern.YYfZ. Iĉׯ_qdFV6>_GG͛7 y @Ċ@, ZbBRШA8X¢!=mpY/UDxA/Dyl޸W1ѹkfϟd02;,Ɍ`% K7u%;IR軻{hO"}Ll4ujcK;7oF9j?q ]zCgM.MR1IKKCqF5$b_ ]-6oc( WDrhHO>Ŗ?xL]6ĸPP(ɔO!ۡES)A,(炕)adi˗  u;t\!Uz?agH h+h7"nUq@lm<##{ͨ{Wna3_+۵k]گtE1s=JTB###i\2JAeM .ȠJz ٤$p֭"{(C48jmC.L#q<7-*4((:zQ\<,84, ϊI8$X*Qkj U 1WUַjՖTV-M )ݻwSN3f?_~MXhh(uJZmD666Zaj蒠2\DDĹs3h!ijN:o>x%L-ԩ}_^ $U6.N;DPB+f%1/Bc#q*"О\Bb`Bƃ!n 6@a2#q ˹qnHpL#RCi+VrA`Μ9K.Ś*{T~3={ mذ dʴMOe.W# j`aMuttDI֣GZkh6ZW'-l``jgϞuF5 TZwVI,eOcbR2C wDsJR%* l[24@)v?"?/,&|ђd9-Xo@h+PPs0WX'"аj&}e֘ʵ֭)µ}4OŕJFG~РA(\`L===/]xuKK]XVRĭFbqL_ ] .pRԩ%KCb}ׯ)%%%Al^-)l[n9s50r^k֭iŜg_nظF"01WRgT TbͲt}}#TGWi֡ I&-X`ƍrʓ'OQ![o۶HD˗/1}wڅF7| 6~V\IKRj;id-pSemL\5tidѢEXvXjX^X@i4A`` GXdOF{![<CBB #|}D4|pI&!j/!+c@" XBF*ضЈ(׍t}o%2I:R^BDG3%% TAHS||<~D<={6g($᣻ΤQaqtUJ|;|}0=ŋobF3+6?x-/=V7obcf^Tbb",fÇ8*q]tzhkQũMFVիW9믿r8p9}/nЦGlΝa@M,S,D,S,#Hy ы16Q#y  dKJ#DQ!`Ɩ-%yzY^GDžuuS)%"ax@h go{d'ļ4Ų VKGđxR%Z_ٓC|޿Ml̜li;ɓwޥ|1hX-Q#?ccKٴ/NV3geʶ;.2Ä8?O&ZXXhm49z(tAē@  ZS-y4<-Sd,'hтPϓVFշJlձd-" s:#N)>UHH k:n,.{e $EtJRGyw{ձSS4XFsr8[ 5W&}HQjz&l}-0VX܋ [Zio1ZlYǎus[2v>j}~+!!t4EH6{Â`{;35S@ O뮵$z?v&3{fw=oC Z x׈SMiğ?yWpp]t)Vh_iFFܹs׮]% /߀?o3`孷²#?~װ6pBWJT{۾m-'% D}ԩ+Vؾ};z C_ǟx ,uo"oݺ߷ɓ'16wu=X!ŧh=F߳_oB_^_4i7gfXqׂ) tsRaN0a"5 BsCBBpf0t0|sd2~=&&&-- 래?-!,T~~'h/OD_L`)y| pr(?:UƹMa_o/b]̦Rh6~iMb,*۽e4oPԒq[W7uZpIXGW r,uboXr~iQ{1b?~ԨQb¿.̯Ѷ\՝ŋ_yvL0 <::4tMMI@Y~d%'}[_ G "o>11g{%e0oGSfMc{,u*=x(i_OsR!P#B8i!hӍuʓtE6x!D v-1,Wϒ^|bSZoufiC]z*fK /eiT`v")I7h}ݷr VL{?fP0qD}18ҷɓarW0Cқt/40G'%c@8kp,$1nv9 =<;y`Ƴ_u| y/^4nB#aR *f/TQoR B{P+C o/0,5U޹+ / PQmֲ& Eyn^z$+T `B:X"Bw/a-6Yӗ*WQ/_ ^\wq}4JK a>#TuwjjUams'w|UI)dJrN2Th+s4H[R7 tڂ]IaECkRw蒡aq JFBΗbW]rpa4kҤ0i< iq6>xhJTzwEB7.1u把wP0 qӢsy_r@ Ys߆ՓXLf2kf;Pdn:>)ӕV*@k*)3h`7 J+:}4*O-t,YRC|4[Sʽ|^ LEyҟ;hN+4IcYܿ"b9VjP%="ܽ3d$%|Ue]R]C<)2;W}j\VQb*/R4%E%& Oe^[՗˪ ҇1H٣9\O?_|AH:;v4D *L.\7 t_1th(>YQ<) +QTT"1UܱfET]d,і&Рk bT1ijS1xY[~r{liC(0?]ab󄨨(o%"ku몳 }|ΨM7Q‚"MMQ[>Q+K=[+I0Ub(td=*>\6F{S#t.KȎlO߲V,5^1t1^ڲTd薒)U.ABݲR)3N"g9 M&]M^ڬ3,.^=n*9kkR}^&^l2UW~ՑE )d 0AIdhN? ]u{h:Oe3=iкI:-fŏl#ՁGݲ|cR),T _yNs l㥚ZA_m)ԼÇ H$ 8rҮHA*հ<#5ZB%;#x ]$J aX޵CW%ڠYRb $4 w|R:1eAY|K,eZ6\)t&W922*r]:X*_*χ"NM4Q^N<}'3 \J._.jl/:oq-EFyr`IH-WA\"j-sDT*T#z]2 Y(]6T)BbL6=FnY"AyfwBnXV$vU oQxѻ!DB!0ts*>I%?!fȦwpAb\iyaMBźKWZ?ȡ^y0`/R9pL5@csp#99hE`K[:ns.l~ }4bt[>ӉՕ6Ҷګjy1)! Ǐɺfʭݰn⍛]1uVr\j,N$ crIqr/cŰV>=•l#P!tGצ(RWGqh ݯe4kt&.oj{].%K#er,?FEc&!RI6!{YpْpA) ]ؤjF GZx͍Wzۺ 7ZjނES3bY"B";1z @"$ 93Е߁.΃`T]_/5 X)ݽno6uРuPB>G#m{4.G$EJ 6ݰrVPE8poܸ0LTrG+!%c,H)`*6ɢr#[TDU)?/u|ρ:Q & s4b68<~kc MnEY5sȻ^_ٯX(H$, T!8D:Ig> a0D6d2 ~̈́BNCH"AEm]XX @^~+&3@! 0Hv\۳5Qm@qbF *q6 \ ! R6C =i2hujKZTAeJ!b*&%1tCf QL8'K0^ 4@ jtw E.WE20nyLx!A.0U*q%; W3\De|R ̉c ,YWXJ$RWP@`4t(H AkAڐw˗NrfM>}YoB~'a tS^9+|H@oṻz,&>w1 3p_s"jJ4( E6U~LDrbK@!|{I<- v]Xg@ZSLv4^\7^wmdg6븽fpAgZ6%1g'}7N^HO"S)AH ۔Q?e?:l!z8-6aڐT\F$so,-5> f*Խs猅!2æmK8ŠI0=2 U -CR=⑋, E7/3->{qRa,O faOѰ3f Us+ <(^drCrB2A*SlZ9MC@w}Х.! TSOOY\`)8[16k|lHC1@8-jY>RÉ;!D)Y`U͌3 cadmC,Vl[,}xT!CG2pbP}.pVkØتpRr ]bIucOJ aѰf8 WthW-Q:w ,?}ĵs!I SFן=P'^EPDaF& UEX5s?z:!^H]LJcOxnY\M,.%I.p;dbbk 1 "1]]]"~.Vs%Nl${C A)NI^iA,fo JY;CAF'ˋEӥGm+ \\YQvljCf?f; rœW,iP=h""$ t#J1Ć¡W.`\).h5hXEzbBeva񏧬] C`ݪ#_>>:VeB]TT3m`J^IoG,ݳ^ 0ZH)e `<bJU!cR;Sm'=5Dao@CYĤ>'=!tlR]ahE wd|h"RG\?PZI3L#yH!H쯇umc ûu=ڸ>]U4Fwj&tO,~zWLf:A/Ig\5bLT;#Btn~6WOt1exb"}-%3C>'_#qgs0gUo0adbxqW\~m!Q5ذvf o8ql;7ؾhk'k<2B)чdPɄlV4QѤQD-0zfSIV];нe !$y<TN;LSJ.aX|u3*Tւ Imn;L"%=حZ+^,G;T!3o@a>+]e۴W bb)E'"e az'V4_=WmkhRϼ=jؤHPKUYb \HN.1geZdǣ 3|E\Txj7%5'68RñÜˍJB.&jmJl 6'<-B6G(l%W 9^&/lOa^WZZg^SVViu{L,>1⒲մ we@J_$χEpnTi~uSluY^-.9gKU5W/NlBb aJGdp؄J,:;ZnT1V"9ȏL.6b! 1$ )cB1]fS[ZZwᴿ3씖s7ӍegosveٍQ h7r>-k|prl\P1hôaPzr4îfbG}L>4*5 bC qp74p Z>^@r||R%drd :CU6;믿kj]MMĸmv,IQDR[ais6j6Rܲ|6j"GQMg쨽ku<&DP}jĘ!ĵ|? pY "$jЉQ8b>AٖlŲo9r?裏)ʽ{8qw}ȑ5kY0"J¢b"`SUKI Y1]'r!# tERZuc$-ZR\QgX;T&Cp&Kr<&/'!Y ,ǧ@Sme|3~7e\҆V `^]&҆uG@ *-_]`!CAna bxm@j#p5r[]bo -CU}q:$̇du%މE+ѽ#5t߆?l_Hg)/9>gk!5+2F'MOo=;1gkJ,=1nB|OB>Q]Y<-Ï,HK<} 1̰Pa L>:;zosǎc|&'O=C"TGA%@ z1ucp8tuW^6'*$J)GbƐ>\]gL!X(++ʕ+9׻70Irv{}OpDz(\qjt>LL Q')Ѧ`86+Ǥ&0by$q`8k/~I{ͪ)8c2yȓw^@4V#B cέKIy׊ԓwX5{ Q|UӉ3GUT#/RUVVI9]QWpҨmٶ 8yx06AxmxM,X0Ԯn\yl..ǣe2,U${ 1l=K .NᓘkHŽH6P  HTIA1$bX"NJ8>>0B˴d#b8 cTLeY0DB(Q7p,X8Y6Jl-2u?u >H'kla7T_jmFuSĮ}5~E>@VCb,;]u+0sEL;RvT$:9 q-pu,_>gŊj|uUAo-0L$ؑTI(鋵E<خYY߹5w\S˗MRM . ]>w `8,'eޤ9sK \D!cReDdQxzyc^$%J<@r)Pu뭜[sè0qfX$ʈɦzԒU@ Y MFGa}}}*@;V10' gw9lN!c<4~rq뮎j~b(~P>7e 8a&OA5@â qc"1D5kJZl8۬;s'>ܿucGL˯_[c;יɡ7R)W7hTW^i>0tx#6ܴW~w_MVz .k]jTk bMqR;^6ӇljL"_aǏN1s̆ ZΩ*ӫ ڢ+[65s\Bx~:CHgߴƉ <%OW]Uc*-kNOUUkve .BA P?,xGg~`X8RE;XL|j^amvq1!CِLq [!P>2ᑭ%SqIv,)қG:œ#ӳ5CV'jy{ AZ+.YFN%uװ 5t@t%/@R1e'LVW7|uyݤDE0t /O?25%MącΞ6nTzba):&A).;SS]sL0٠79}ۏgNTTq%gY4jMɬϚ-*+ά[;c5[7/3Im.0 !eݸNcꁽbc-$r'H(f%`hhz 1ItkpaJp4ΐIKpUTd'Ͳ}翍FjqK9uy̸sW^)loH}5Cn%\I;A诋6AJ?'|K޽4:'.T cχFaCkzÏX0$)jƔcFYJ%z5F,-)Vk4͛fo6U`5 0//?fMkM|sE1tNηfM˚=亥]B00m:Juh^}PB=*m.$. J]SL a)PKɺxz%VCҙ/LH M7A qED~ymOg$yoʐKћVsWbKE( ~!SخΣ1kq/u'!u7uw4[ӧ5gjJr maaNjf3fZ]jʴyB (..Veݺuiii|sRSal֫ggKb:]!UAL\ԤG*e)Et_,9EˢTLaѬA_>0W"DX<+ ba9s,$ n?BÎU+Ԍ<-ɠ|:nSjX1o5VÍzALv? Dh߉zu丌|[`(cnS"dM24ןۼeٙBW !Je#"槹tE¯qKT[|W܏6˯zko+貂%vl$3ľ@C+tz m[GZS=7ICm tPBZxfpYtg=v0D@)O _y|~g5]iA4A$!@wgG޾nYs`Hrۭ.JQ퇮s:+0E}NlFփަB@)& "mI[VI6s t#$nA QEƅR}`OgMtԷ>|VcܾmHd1JTUtޏɇI Rt T=>؈yӏJ$D `mu1KMڰaÄ oߞ=rHPOf̘lُ j sϘ |{~П\^y',E0@ K6,..nĈ{YrVVP-񌤄7ot,;"b) \&7K'BXI%Ub 4g2)$+P[qxu *c2g.jݥF Bxv2~LluP"XzIWNrx=qtup _!M &@:5` :T٠MnDTw{N|q4旙B/]H] 2S9p,;@KSiw^7yTV/@f9ۭ-ҲbscUٱoޕ aM?TUQQ`TEJRp @н{d2ƭT*8qt#B9khh(2 !QiHqGNH=%-ЕR-%Gr:-^KR{˜*%6RS) tLX X6%R *bfpp!PӇ o2z}ю\XN`Qe b]~}.'v4jf D} Ї5d6!*|?s@NSVި~kjf;dG>R±M)J*եj\%0)e0v9"[/^my-,I Q! x;YOŏy혴X"X *,%#R)$tyz!  ][&+JĔa`:fjSI;`r Ð|c<\ V@Ѩ,;jD & G #0a1r]IUϝ܅;er$]rm׊t {ɣڗK%`/]v"7(G (I /.Tw;1jK<4ّ; 9:&.RzRƅ癴pOh2ImINgZg|./KnqtQ_J߂;o[2++-?̭᭙pAf50) EԥTdHs3=zntcҍB[:?*ɭe'߹~izL  @& Xd;ǧ xb>(eHdx˖M&vp:>W'&'1Ed %,Z_H,!DXp敭]6NEU#yT ;;i R%\Lv> 58Pц:Puv 3{P ː/(Nzu͜?w0G SoH?ة_L[Ee"t*?* [|6R%/@I9:]vtk=C?+/ jLC/CvRkz?JGM6^W NG!i/):z: A#"ݻxHl K7kC}ؖ#`pxs9v8 XFDltL/-趶9T(PLT9IGk+Vڊ[s7]3l<o@ ƌ H9V 3 l@kjO$ WFOkIsˉVlR%VcDX,&Su6[ t7j`JkA"^c}ܮIX"u\UeGJ/JrDSMDi  +)y"$3ԦqRvr'nQ"xp%9MOz:1:;z:="Ӌ0@MkWZΚLG,IL3?\f_i]&QU41%cVDBD"4 6g6N*RSP^8J kUX?U T|.MS W:-o<UC ޲֐P|&_`6" |dYU>OSt'Lad")B%& Q" o_rH-Y;5ۛz}]VWډ}y2ǟ; d3ZU%_rm nO7knm]vTVUVP{egw;%)ϗa0Sq{띨˨vuӢ6=YKPS8HR  [GZy'W_5) <6Pk! ̙! R$LCBϬ/A7$lu=Ͼ}zy0F9.P:] o-QK#.Nb˜!d&FPף--J=A\fЃa.N<~j٥^W*%"A# Xq1yTXX}P0>W҇{[G2m.Nrx^hV7L {xckcK݅ Jfn:|%}IvGEogh_tPPB %QnDw:b}wmqŤnS[ ?pso?{[OX^{ݬMa5g?FkG<{ތ:_iarcG'N:;:!WkJ| 5. `VcC2/hWpjH!՗ut^?3 ƝxVXqK%rzo#azlJS9*3 FO e`i.T犙l!J |vݠȞ0Q1̌/0 Ca<$a>|.VEU˺x(eFl7wtS7렴zJ[驫zsWϛ2[>v ;nz/?Vѻ-?_z^ˉ{ ٖO߰lzbrU#&6oW7ra/?$~AWTء t;=$rG(K?y沯lbo[<qO,f=3܍]##sϭ*q 7ǥB?yd'&ZOЦG}MϿS֧՞[ܮ.46c̩#.p~B[eanEԚMĤD>{C tݿq b(d1<+7kˊBQabY6%!V EtuL e=%^Cf&1?ҹi?Nl;6ĂȲ]j6dhNܴD3 fEk6*ׅx09]b9z[[?FQx|*<=^/΀G&oKջ \##}y,֍][h,{W~9LFcxrCv< y| *] ƒlX p_Qƶ5_g! )xAcC)sNPForpְa"Z(>4lxnrەԋ嬋fq m374J]WZ.:Ac "8}^9)kVSS;o[vd ֍w{+G#2Chfh (lCR r1HL$%/*̆ |eC9([ VDa<ͭ]zv7^%$%w/$h@[zI۝]nfSOI6T΍9wdq1` |xMaĂ8 `: SrG$REPgBV_g잫jaQv[bp<=ޡ`$3OFA~\NtH. ZQ|AhJ;"x pD8Ny噍}9wr,8sw}ekHTrP^f_W?dh(qOa޹NïO?հy k<\}!I;zzu2׃潁@~D!qn.;>mayTʹuqZi!AuOK]$IC2IY>BA R0e #'˻Ŗcו|<6"z(P2W^2RR#/, F\ETq`o8`=ӷLV 6Q) ~}uF5k?OG^Eb(\lQ{n.vr HH H HB7VE3rh_{>]+;8W#~~ؐ1-T'D'T #hd7KnKJ2@WeP{ݔÀ.cLz_~i,К7wđVKD> L4MH4a$E`>/iKeTB~ !c岆EK5 t8Xv<8dKҢ>;pE]Bx#\J. 0hg qxH @B>s{y"DG(lOxcr>_}ZO+w=&G$xu\ [6†0f!Ḅ,xz(97& Tme.]ro> %}CpPg*μ"QZ[XB*T E## H!0Ex'NEJ("&/,!;f+苻f M#߉D'#洝X^tm2½^ >3>A ?Z+pu3xN ݨ0|il6[ͩer]/僥8%jm]-4]~jчA\~$n 쎲2 >[5b n'z7gc#wiim[%OY|BUqD .6 h<1xHbN+,T넠d'AP? $;megP\8Y|lQS%j&w߂wB)HV Dcc Ig>~[:jCȾ)aL|'Aq)$ =ڐ<`bBdgۅNwG<Ilr|,28pKEoڤ!}UBWc=k \!5n<]$c/C7Ȝ W/Ls53gc*\)JqoIkZZVa26Y :,]G;eΙ^33HBޛ) JQi]`*"\AD" {IHBzL3~g?1p朵޵RU"ZU.I uobT˻ww'|J{Wn3;pα}TjLuTd0iȓ 1>=jd6O#W8Mf̀OCkz;m،B:Nkoڧp觛ZIth'P  ; 5Lj*ѧŞWO7/&=1RMrBP@GL"Rʰnz/E?!,,B;Q6q4QD"*C+uZ.| (e[,u _qpt4OǶ5HUͺYW3wB){+8KVw+|Kޛj_KA_):O0]d!!Bbbbw/Ga|p|ldp/nNm) :^xЕkySYW:bo :o)=?%g~xb˴gN{vf5)Y +)rY[D/b(&2eUfdy nRZq٢9Z~w垀Dl-&oÄ+e(l CrSM]>cw[%]c`^ b+Q!G͓?wTÃf#LDFԂ O@.JYL M*Q!򵀬]]VDu3ۗ=<R2?ۃ졬V)Uvx2z7bQM5FRjWV?IxBJ{EjpL:X=~^Rg1RxKJtM)T";Ž w84mfp1dx_푗Uq rh1V8dxEnIPjK | VlXRR[aB>rCXx 2*C8S :;Lg˅y>5"NT=h7{|*%z5^B5K:ሙ@G%l-h𶓹93Rmh5 ;l.#p<: SA԰ыXb: `W(b%&VDF[P)K_\q6IoO˷`K˖\:íPy}m&ZH|uc}*Uҿ|\(L7drOv-,h؄7w?ū'wﯔiWԄrU50_LBHS7zruX]Z~B fڬYt:Ј˚7WPA!BN!.#h(@D+,f?Vӌh7*gmqor60ބx4z&1搻IX4QCQ)<*":┆).l5{*b5(ȪEw>)i!ʂ*@߬p@JiϑEcP h(ы ,L/7#]i/Z8KT NK c G68Fĥ* ΢x>ZYZCTiEc$*H1A;[GUxiYEfecւִ,)5!;@B͌83c'NHLLQXiA^N$;𼥝?|^_QސjGJG֋ՏV VHvl١[Qv{m0La otmJ'7*NM$U80"TIFe״[?pF/;Co}M[g"jݩ%FG _{EtP|櫪SUN{}Gr`}3O͈޳G86р+EGt K@2 Z~C1^L>Dr-\AVDl -pFY\=[am`3Ii]qYO5鶔Kq[c Ub~ Inl{6С0VbM?xjp@ /e=B3+տ:yrn,tGُLWЁ*/Ȼ; :uJYΙ5*¾>-.tSTv>Wvo NgBM[ؼ}{JJɰaT;ɧ]a$VUJ0b%q`R<' ˀ͗v=gDh&dYPKO}ڱ֋'`U I4bwyDQ|~zs' !Ms`lq/iۤ_ nS׍X#WTO (!qQZ,T.M{q0ed5ei0'̐RJ0l m P^uIWlMs[*Xُ3Ŝ@61͑w|նo> ɯv`FJZrbrSդq tXA5%Lz>iޚUj*@TE'&5G<߯N+5uyrNp(0A@o󡉹%j}5v9shxWo9jaIZfD6Vw fbDܑP+R\-'3v~P]B fG W_P4 (Zb~'Bg8NǾO[g8Ac_NcIkyMi,5=U|(S` zڿoqٳg-[kL"d 2"8>BnoXD(^!Κ@~ޠ7y*^5Af#qGV4U KV4#Q&AWkdUB):?/_&6f[AL1!jݑ IlxV7# eY D^=cբ̭1R<> yoD6cc-5@Brǃs?JjqHM#qcюjmD~Mms3NYl?"[B]-Q {^qlMڥmr`H-N8Ɇ?ηE̡N_qvb7 OyUq:[.)wV"Ow7?y܈]rme*آ:`O۴ 7k`g6SD@s AjPII0ކQ,)@BC69Խ<8;giY+N73Z/j7AOeb|T8ԙNӌ35\wFܺhZ0YWx6ʖr5Ksfw:bۼ&@7[6NBD&=,ogЖT&\a f)GQ@wd nŋ7hC} GZw*RvV훇Ki(bG 67TRU\D?9uB;lƘA1e&=d߿jƢM,_w'Ř{ÕYKmC%x ʽ_|-aP;w(JQO=_4tU5%>y XEF{'Ni֟]| $?%R„ Z~ [F:,g%ji*dT]gḞ[c 0}6'Y5az?rR(li6T{`_|4-Q k1WWFcIԃߏNr2z$i_W5OXN.#_zt8;D+(2_|CYGLI  }=|ŠNTdWsT&1F<4 T$nphji`J.rܶ|#W 8\FK28+4h_c3l"J|Akկq"rS!o XkOaM(T5겪U؎Ӷե((+]TGBE%+95ֽ_ JDFc?}z"-|KZvb%S4KX# "* U4֑JVND%j;hȉտHXzS}gd8Ht'ͭx*uZ_]]Ԥ'5 (// Wh:6)y%Fj4R /__yمGY Сfs.}ռzͷPwS\N툻*TL!U5.R %R7ƳݱbFq%Us~[P;xCǝ682Cro$zXuQ[h`Y4 )+Z =\'yFb.d)$xJ%8"ھSgj0x>FT[ k;.Mk;Ս3 E$01p{mTȠ/pOV&c =#WBF[jz7M^&WU ŒƝnZ#M&b=&vʡ,9Mx(֪\Tm1et% Z ?'E&V/~>>+E [d_[{{Bx,ʕ+^DjB*TI/*yʭ*FeҵıCӧwm۲ԁ7^٠],7t'KMpEC(=)|*Yfpy;6Ȝe-wQsn籮AJ&)җc!^!;lg]J䠑j}V<۲GףS]WJ^L=pj|E Y8M ai i5*!QJwtPUA4)^o c@Yp< <?xTbP[Pi)73`bOtG>5b|jV k VnpѦ "I*L*(Ip G"e+)޸a^$Nq sSmՍtF#_ ݠħA*KQ" RAIbV(E,+"-v/3/빶Oq'8iPj9MFx E\8c VD7\Eﲎ&.Pry&wTL3ӢhYeSZ aNվ|I. [ AP$">#)8#*(8~B@Y#ЯYKHDqlnמ= =t=Aϣ"0NGnJGFؒXA=]Kjs~))rk}1@-*w>Q5tL%ZJDA1K|fdȘCoo3D7hD{xfٍ% E }&:um$b»CO;Ξqhߖ} ]= >6OA1Qϯ<17EjK»8ٍT e5*%ߣ~AOQ D PA(\hTL/a&)Z-rR}AwN7B0g$կw/;NC.٘ H(t 0@]߂+.!OY |jNтGp>5 &Ez:{FaBDp 98W+kH [цbPo ׏ix#/" E-_GE"Z#|(kn[6Z=l7~Uk d< PAfYз@ϚJy]7bv(ܘ6cqIfkAkf ێ-4$ \d +-Xkg̙ۦM'}F4oNv-ꨛo+ ~e!-sz(.@ԣ[ֻ 2 Zӽ+2\ҶQe$O z :âb0O!?Y)7NP-U"` H iTZ6Du:6ţ[[un615-aNDߑ ё_KDŜY 4T*N9BVi4bTU(!_&8nr'y^-`֩ouꗯGM~c S Զ}V8 7ol9޵7Nz _tKzI%`0HCӼE aB@OҋRDw jLSF![%cV6MOAT$m2F+@y-W#"7;uA` *Y:P.ҧ֐%(\T[ ׇo 'OߍlЃ$qφ,9% O ` EscT]`P {}1bupOJDh{V]6_'1Yߣ(t('d%:lJ_h5}F${mP)S*_7ZbQ`D+X_YF<2a@< snף['^y՝o{A' v=uv8-_0*w8òXNi*735 JK~aP7P5}nFTUFmws|§:x{0Ʌ}CVH4!nNGxۇnwVnj́=~|پo'Lt%US³@yC_箒M(8tZC^w]5=.7Z<3.)+p^jSҺaa}&"+ awPgδ/(*b1DP0VdH=+{F$#3faR8< : Nݰ_Д+野۽3coǮ{~yGCc tr܍s/wj}[:'K.;518E@[I8H5 *lǪ)ג5 A8y'+yDh54pn461+ - nD!CMޭ-v׆fMbT`8*e>xR91SXs5NHE 9aW Lì,XοpY:5uz{X~kka]طNo536HL7b`-t(&_}32aeF Z (ǧo4MzzPgn֣&+/wFğX;/X7r͢aX8M:g)V`USU\ca 'sFRXni#G 4޴.9Mک2杳k_6nξAO PIzw^x cZRxHC1O"À%~{a[ LZc y"xFJ0"az:<#,úiO&(p< X\JQteKx. 1WXN g$( w(ܸ˨DV]4%R5Zdrc^4N؜I&91YjŶ"Y5.i~+($ӥHGK6gULaaބ}]96gƞ˧iB7aeX7 ^{R2ma~5a6 7W|hQvfq\ܻM{'wԩMVL7PZڦgOͳp~lQ,L" U+ⲚI4WMonavݑWrj??2)%`HE r߬2dK:1_Ϧhw& =tʝn4_ $ŤZěmE$&fqN IV /WC:s@).eJ^QF+,h+"x_MVld ~qkw aa `vIA֯X`3[y!=japL$KkI"V&X9s0z(1=S^5KA ފ* T J1Dpӝ#<Չ 8kAJJuf&K@ 6FLڭZ+_H]EqWذˠV0^CG#}o'۴1у QO̗ Q`2*4U⬻„ SS}Wd]T'+P|/\bv/QN僝&5F"*B*heT$t1eQb$5V/lxآckFNVߙ/\h $b+.pߖD:I|`݈HXTb<5c|xfftBqZ8\M}b}KϩM(sݤ|JG_*j1!Wjc]mVm Əl% CS(:zbY;8g9N(kUC}=^hܐ1 ^9ZglEX eZjzb ꌈ(\3Lsv@u1vE Px$rӦ6tsc[!f 7슝yMf?0.ox҅{!T$X%{L17-9e5` &9ԛV'bQ\nvțzl >~H[xs|ƞsGdf)'QPr^6>/Yʥ7?7@NӸ-?L47hwhfuAv;ƅ5;ix}Xׁ$?ƅmD1-DFye#4ŅӧBVm9 U*J#C4~p׭$CyZ5&c g 2f2I~ie-uԄjSGVX(anaٙz}Ƶ hD"Lvc:n\џ l>P8Z%@ 7qI۬ps8LJiE+y #e$5F.b~{_fA6܀ gk:ʢQtfܝf"RKVr /1@#Wal$f<#-O|hG_㍆?V_c(2 Z&O#(BL 2c'chx%w s8BȕNjG$zp6Ī(şn! lޢ%VHڽ|ErB%ryZ&(|-Mߩo$^Xs%:)ʕ_#&"o.V F/[fU"{nvfa/;:}1Q8C,p&م Xpep(x/$@|%TO.t(ZGF5W T,t%B{ʑ0ӻZG#t֎aaŞ(+Ḓ MUXQwR))S#& `HP~Jw_?s\67"uKP8r sHbN(0YX ZS>f"P67S=ۉCָ ۣc*gqm Dkf0{Sd7 $ȌMsn9%aE&wcL*1~s@duHSZI~ pB/nSA{ ƼTԥ5(N],^4Z^U5~0zu4d:Y{Gg8#~J<sFILM ^:E;c:f&͎{5#~4s2iQ>G{Ӕ#Zêp{!m(fMh0cPo`p4־<\׾u C಑D:<V(?GTY1U:])Ws fi aG\O R]0=TGHE<5%J㌪B6 1U:#^ѐ{w `⌈ W*^+5dx>L%:(CP -?SEXUu!R|1*)G9q9E_B-k90K`'bz4@nMngBBp| 10$mWgegސYw- jſD PcPTٞ 莧^5ypS˩QX~Y_kI}P)ZN2 k岥@YEF>KyXNTtm1mo9v߁oZ0cbvjs{#>Gxp>[S]E#s[kO2-Y)?h>y{ФdAv4Cg 0JK8xI 5a!夔zQ&="x!4mrš=Xa6ĘEU4 %~; 6 JMp;V m-uf LrzϮBmڤXI}FuX4viyc'@F iIo$4?|LcL.*  qx#/@֩j5a1U +bm^{Ӥ5ng\FYyYqS"lM|&*q5_vd05}yK&SL*dMvz,ySka@⺣F&B3SքzXh<ȑ8do& .!y~ձ7F?N< ݸPVh6o, $Im[~m\zEc|Af` [:YOJIabO9]qpw[g?h,q  _.ANg WU{4Q/|گ:y4D)}ܻQ|Q)3qѝg-mG`a]m;p Fx9ҽ9,e-Λy{'vӏ]c/0o;E 10NćA^^B 2n\ǜ6o$/w('p䊁٣ʣ3(Үgw?yG}oW_4t=^|Fq 1UnO3U}}suQFDYy㹣'.LXպJC%hBguhY+~6uO#\ /DdQnpO7zQרF~btD& HmȻA7O Y97=V+ Nn]k^\3ädq #ٰ 9ü,qaL @!z1 b .ջN 2N̘:t)ru4zvMT =8M4mQJ)8 B$a&{⺴C ci%0ph_"ސ?7EźnDTX c@8b2c,?``ӹ^kVL]pGE)0 lW♜ue%ú%2EӚz.|˲.SXџK D@tP>4LBސK|Q*ZmN/ou IEszSۄ~|]{;gJW/p;^ԐIn٪/` kVүk>e}g`+hq)Bm3IIBDcEZD2vȁ=d H$$JȎW蒾b8bK}Tk@ܴ8N7F4CAhSaDq8HqI Zv}Cvzcb]8N4 `f7k__Mk1"?|;W ծǹc;,DX<@l 75UbdyKKjFPG_+6hOGi5+v|ޞi;O`u;쏆rQ,jgyBZ;0#|yc_zwNqeg:e'~Hz l7~+Qg-}Y^[|1Bޱ[[ 7lz:ꅘM oF3_kµ6cRf>9*sPG_2ր r+l -,Z̬E@sTͳQX) iDg½mG%C剧l #\D. ;w t%;}"8Y%Nq0YUl@<ݳ!u 42 B(HSGP1mL_ eY/ e86Ш k(mPb+oM_eマ[G'Lujm>nG3 2!eL.A<3bjt#; ` IR0/8bSY}up$n./?ws{VXX! ؔxH ."ss4M";MlG|mjY?ďGPȱI1d 6Fi6h jDB.aR)p&etbRA%9i/OR$R[!gN7~Bؐ6M[0$v#{污\:➔hI͐Iy8DNLo_RVXMzCP#Fy-I!wP/=Qo)~ d,. >΅^Ғ1n,6 EОtV )KH-{Glf۝tȼygqw=߈@f_)k6b;8p͓)4J7IV۪Z9TJI E{g\ėbLv]g:!MϬ#q0æhdO= l,L[US<%{O%jcn RUB05Oj)mc3.S; ^Fρ5RG ? ӖVɘL"M''KyB|jQ ӑ`T{3 %pqҷ$ ~$>-6uMx|m鯓c'!:Bi{XV@6 T@gE> 5k8}'ٿG_mGt'ݗn6 hF8cɒذocޯ&٪JBtY(bz,IH)|D,syb_n+R$7EjNJ5MBaEٖ+=ܗTʝsc/M9Ѓ;m];&A2ѽ.KL8"{57cB TЮ'I#lP ._~%z<^}pnqH=`Qz?2klK}T UP7U$"<On"ЀqQ†pǠS Su1ON!ƞq"G+Y3;aY{c'Ő(3 ;T;])_Xf"$09 kL~"ci3@CjcǾy|56g,QJ,/iW6"eVJ _*52miϛ6#yX :q!941h)`L$YMq<l$@'6 <-w.vj| }r47L(@EA AXd?7q(;֊H.9_xmovgt2a11ebXKC] N *تB0IrY]Ae%LN avI1M5iyzp3'9N6!@?D6ĶhЫ[ɿ}.apYf#u }gszk #ҟ]0Gw]:8͈@$umwwbGgRL&ǹC(E" ZN %*0 Kq`VȻo$h;_ Ν;eQH6V&:= SH[`6E7@HEτiX aa߰'phI w9p2])p r"TB/Нb__?ӄS>˜yF%< 蹽 @Jy,2S+t qrO'J$8%qRIN슙2F1H NVBPׯT*P2 ^5s$PNqDɼ܆ޚޚ*&A^ޯ5#!S/[\sHLR%|Gj5`Uy(Ұj$W%Su+/1v/.'h[T l5!;WE`u8_we4<dU >iz,6. v(O$"_ocf)T1<@U=CQnK4YCV׏{3ifc3:B0sj=?ɿ\i7˥UEU iy\yJNxh*LŕkMb=\TBG:2oOˇN$*N l}t˗V|rfx"#H , r-2rY5DZ4M÷ SX5T΅ X7-ypɸ%3\ٌƊRB \F^0z 5g;`X^/>owaNIIs! ؿ`xaVҚ mxIbQR` ]Çr ظVT³b[KHe9SdaذJR|Gj^L ˻H[lPQ-Uk ,yx,e/Ddp$Xh#Hr6)لbN>`+⨞xdPjBX9Lu4+ȳ/.C.Z̧5bL66י}LFy,e}J#^MU+|FKX Ao2?<=o E4c'gvo{|lmU^QM!(CUըFMƺv\hBjUM>nNÁB~\%r8<x;'*qytYG-)Z6Na<٩1.nrdJfNWH(pHOua/7(0)c~Wo_8rh۳@h׆y5ebG4Z~3C"TO Fw <>)ү&QC/SQU6@*Ε*8l`eY8.XWBx"P6!N' jPɛ=\ILRG׊`p3݉$*3#RThCo-yqn7&tmF2^1ZH y: 80g Fy#KjOXh}}+nYdGodP=Jr ]lEZ;2 6mQ vj5w-vx|dl!G*r A͝=;faRDMu=GӣƊU-ܚo;6NjҒilG)%'sSίMؚ=k ,Jd2}!)>/i iaAo밾+$\z\i?;Gf;pжww61-YLhrjJt}9]FL IjX>+)3%ob>I#f‹m1J[@!DܤZBBSH4FOS10S^M1Le:8/ ~C!H/PCQ9d:LQxC?sn.iC'w&-yë=tfDDvCzWT^-ht?]L8dF^0upDoh 90_0I2CȌv7ס'fq" 7*!/Fz$dRGDӹ'G mQe\]XdJBR# D|SH bjFȩHkt* ¹R&1C'G/>4!T>f&Eh 'QU]芝8]>q/sM&!z.6 %gno>=j˰ğF ev\:iʝv&C/W.щ*lZr%l赒KQ}8IA9vmũMUo=56uHuYqəO.?#q@϶m#~c]8˯&Z=uwƧK,4oÏlv1?^89yNzRbYԁ/&|`^ l `wkNi=hvngLg[,<UP *rq0duN b"^P+gVʘeR(¬ 0Ը"fX7U3;(JIe)[;Xܙ:itACjWSk.Yne]eYAƴfS ?6b嗓Y5JգkFr_fTTT/_YXXѿf{Nf6֖̞ܿI|ȐP1)1j.vL2cqz}'⻉/hsĢ;Hu\Ђ&0KҍYLX!{>w4*UFW/k 82ҀZ7+d]B`@A@X|7pdI$i$1kAW Z ҄V d@?N,P,#i֬.oؓ7~֩W]F;]Vw _S_n?skt/6SZeξ>Ϻֺ2LÄ*S*MPB8#F0b-$t@T@nsu@=s.6fGxF&=H[us>,~&F~YOפ5:%j֧&~G\e3\(󫧗V_oꋨ^-CۮY2=u{y=*f"]^wW6[rY]TM_2%Ĥnm| HyLQ.vdtqb_SE=|dR5;:P{ݺHJTa)T+l&lv"G9Eݹn!54g4 an*1("cbKf䮯o~9{sԋLa'*˂m nYeU,Y-'Nj3v}t"QN!jn0ރNcNz\v wxYju|$f_2#T=.RXکyLP#rpqYaǸY<9uʈduj1c a0G3F.:`ˊۖgr̮wo-T;AeAJ&i,r&wT[ 87Ʃ_;b'9f[^.V+E ~^߼pS endstream endobj 20 0 obj 84971 endobj 21 0 obj << /Length 22 0 R /Filter /FlateDecode /Length1 4680 >> stream xW T_ݷ޾~L=0=tLh3̃7#Oe)00 €!DD.A%A]-*Ƭd6R f3kRtϞ vvkR[|4B u-)B rG3+Wv=MMB8Yy?㣄u._r?Wq'u₴gq퟿?8xeKߔ㻌58w-\[mf~H*@x ~c/+ p?;ZDassʍwY=@1ZŦش&D# %4`d3||JC# yl|ۘ? *Ѻ4yK F-/ҳ`{=z}ynJdgfk[p-bx1 #ofF-*{-~ )+$b(qIIF% C"GHT58r)TEktH#%jIJ%rO (} m;_) _Gɫj^أe V8#a@b O3M _zW3|"~Ҙ n{N8t lp*MRE}2gՎ:Uz!^00M)udB"ZbB#|ߛ- _|"T?z3߬]ogP{uܮ|0P_0Bb/c_S4~E$N e¸e*.!15`=P2@( Hl{WS'7Xk.Aա'.te=t߼u|,yg9 7|ǿg٢4_eN̵d 7}YI('T Q>dEkee$be2R2sگ43SsM4#b%kO<6V&mIUڍ+W 3x,EƽGrZ6zRjZ, Ÿ9QxV`nun7ɹÏC-}L\$wȄQW2qa¿ܚi]NvG&4 ޷8dEhKI(FA{)Gb@H;s h5t4rkͻM`2HEW+orl$* PSJT<Kh/f2$ F2lVTm5B,f~! L]i>|eG4q = FHօMl9_س' [- 0 \/7YɱYL<ɞr~T!M-rW3gH0?ֻOJ,QL'mUN4uvQڲ0 V_(*emxt(b 0̂%a/}~ j|23a1?pc߄ #s'p3Xow`.h1.x-=r/-_^8qXpX` KM5 )`I 3%n![+u0|>|Kamvsi~|M~ &${f 9B.N҃_BҨ ֖ѣ̜qxα,z U")5qb%Gg&f++l<[vrx?$Q})+W(D=s`䲲4(WaRe)bex>#=0.;(eRJ6itEY2ZxK4,΍%vA4f[ vx`jل736hKI@›0G̚{lT ][f ]S`O" endstream endobj 22 0 obj 3427 endobj 23 0 obj << /Length 24 0 R /Filter /FlateDecode >> stream x]Kk0 :vG Jwa8!~U:!"8 σw XMuFpƋ8n|y q[΃:(>ȹh3>((ޢv_Qr/Sxf"'C~=E|nJ~I/0iʲ^7|գچB˒ q-\7Oް޶Ӳ#s%\1kaM\ɐ.H&9^kaJM2> endobj 8 0 obj << /Type /Font /Subtype /TrueType /BaseFont /OPFXAH+CairoFont-0-0 /FirstChar 32 /LastChar 195 /FontDescriptor 25 0 R /Encoding /WinAnsiEncoding /Widths [ 250 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 752 653 648 754 610 0 0 0 293 0 0 537 0 786 834 588 0 630 512 576 750 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 752 ] /ToUnicode 23 0 R >> endobj 26 0 obj << /Length 27 0 R /Filter /FlateDecode /Subtype /Type1C >> stream xXW?>230v! (EEQQ ;DQABlшKb%"0bEthLlIΐ~[~= wgv{|9(J%Dƍ L KNJq6+:RYS"R)"=p+:BZ:G˸PKT?rw0onPoTj{nd&ψ٭[˴d*\%HJLq%,:ffRF.щ.aC]"2.]M5%iZi)q).IisR:DN⒑<Ӆ'͊NuIKKvI`<_.=a?RXm\y|wxg8[G_ʟ?>`G$/ۯ1-\}+#/qHyxe0.aGtk@U`j 8vݱ7PX_u- һ;/~J4Yfewv<ŋA #ot+%W‹{?͍]M{w-<ό)Y 4 w^#B^;$z5yaْCwN Y)U3V%ԪL\fHI3++]iOgӲg>])Km[pxuB#dNJ >>jG̝g d< pށSq2'$<{e)&7b({3Wx2.t9;v9߬u~oˤҗEX ܮލO<{Z͑6:2kL!r`]Ʌcdy߾@"JH /$fpc4l0~q;[rXɏGcJ8/C{.;=!IVATG4)ņLoDŽ4.>q0|_?pzִZpR- W>;l;*#pZb,Ht-4_M.ؽu[6 _kqpk%dc~b駸05ڏED'Vî{RR\*Z%<)+uX^X+$?6&2@p)fR?_'oKN޻_BW?OW DzI8@j Y4GM0ZwM)$ԎS*#r]֘m^X-(YSwUN ($dL”!>ssJ}/ r ֽr\@o}O:L g\&fzGKveE翽eӓ#z (Յ]l0nvGeN' Ed0 =Ф:Ѩltc ؄xf2˖E+3wKB C_*8-fa؞ĭ(szr%(D绸=}lO5aYL'"9[YcdLd!Ѥ }ڄɣ+ݭRV!|u9憛K ~0(~-k tqDθ{/aYz?/MT9ur\wg V] EX4LTz ..5:7th,|]1ff Z ,rY4Sf]w{ 墲KhƩ茄TSJ9EBD@e"^B~̄q&J} 5,_jyzZg{'z9?^Gsbma8T3š9E00J0zQ!p)'6xAT3x.CBc @\ qoR;HPjs2\nh}Ɏ:ã`@̪ïmPzMS.q $L6݄g5ȜG BDE3O@,DYDep/sCڅ(עgrs 5U$WFsHf5̭0CWqjR#\˻L`[߈a!o cV۝yvmiVvL̜EDboZ;tyBTo}{ϼOLba0S B2ܕa҅6waN3vҖN1z͓!@'1:mc#pupf?"ś!?Mu4=E;~6pH0֣TJ>rhACޮd~Qb͞/m+"0^A:4x *ܖ:7uެOI\۵̡Kr**c!n5&#qZ(CG'|,hgđ򐈢4LMҫkB$ūƫcCG _#3`i Cp6=.Գav5]3MxٽXOTPeQ}h#\܈sQ臄uo5t b ip5H+.@-0%XXsy6*\#JGK#lؿe{w-J 9׌⏵uǶ_zmܱ=a6SǏ` >LyEtWm#藡BUވ`s1jqF}3x~%F9d$w$ wحX2;P7Ms,&ӄ'&8cR+EENArA / -)/Qu7 7Ĥs (Q,`hЌ ADc(b}D{W~15*BQ^jAF< SSSS wY2C񅉇P5td0.Ta5ԩ)h(Etl8ÞظX.7>uCse&CQc-"+6`{2ҕ<8V-p?i~P|Ȍբ"7 j(_T85{M*8z@NATEMYPœӼ©9!NQ#jZT'WE|ۨDF4Q7DpQՖ|'N6*&u'N˩DF,0zԨNtZҕu*e[5]${ C}b‡8ZY}Ҽfjޚ[b cK" dt|Ma4DhQsޚS4KE'5DJH?Լ`>Z+xi劽<<\q'vl+үg.Uѷx=GpK!yޝ8:cJXBdC><̃ebk8e~n$hsyFI_' r,޵ e2| <ͩabKQn0iëu5c 3J}?z޹F}y pQ>X+i 9($+nHoP9s2a඀/7^\xN[^%PR$pw=,aogW14ޗ(̻:ؚmd0f<_xCњ{|cɮE3tGzO/'Fd0ifs|d}X6]aie֦MM6I6l^ZjmG~i;[!N \"[G|fwuDn(-Dr F/TvͶgWgξ}Wۯh%rXi<4^IMf&Kf渦XS8p<F8LsX~t(u(shpxCcDiI+;~q9tXX?j-[GЎЎhgjS󴫴kj{EKڟ:AFtCt#ut1d]nn.WUOwDwBW+Օ*tuu;Gޖxߎ{=^u'G|,g/\~襹ɳ./36}ŰKv6P 2Zϱ ^8THwU] :<,@[U̫}hćHW֮r ?_hi --ƔDk{ k!ٳik5v4n.m& SI4`$=B젖($%R&Tǔ; eNAZ SS/|v{r^CؘF޷,g:MXƘb9a9yvϕ؎N5|(lh[*82rwhr 5#UU"H9F ?`%G˓Uݭ_%4Y%?H`sOu!Gn;W t^yq* r~Ƚ֎uΖ74X:)י?=\Dξb4:-1F11 bN1Lst< qj /ű׎z k\I~l2&skR̹#3Y)9?{PzaQ|B6dİl,W;Άaf +a-#SPulX9)ۍ/mC9xcV'ĥKa<`0gx!qJ7lm޶`us JXWmj՛ k^ܔݟ*S1 Y0w+{gtҍ=OWAXm._4g g&M[Yg'% hg/@/k0Nt^0e[ "V%ܨ[-8ֿ ߓؕv{Nm29 #猉?5\;Ls}oaʀ6]!i^6Bkh,)K7<l|!+x83iGŎ:϶!G#:^BO/:?a(JeoaQ#xSYI1 27fHK\OP(V$dCc ߡq nYq)qIZ-+0i! Ê2jʎ1VHdܱyiF( @[GҦe-X)C¶ 4ōu[.gJ/0l_pz6\?vJFB5;n~دtrڜ͆%G*eT M$X lcVJՅ <,<%VA PzOY0Ѐ[y^x`+svo)6 ~x_/*ЀjK=Qly@_#`݈P] N2 )0ċ*"~0TPkYl7uj3qa,ÇP}Ka8۵77Cn a C|WoCZb_b]11YԂ;6] µ,/Y0.g bbIBmBۯ_C}V%pH; d|,ml8M (Xj(/|s}4@kFlT]kZ1s&-m:qغ^_5VcevYZlnw۷V]p^:h` آ-E|+it'k)w$|:TG/KE0CtDGeȈŝ;Ytn ԃ8a$WAъYyOG ;q%x C!eFh (CiAU&IUX6nJt6 7ٟ/xvz[%'$ny')vw4-h_`Kp.s$<Մ2fc}QC_J"EUAU,vopt s+LMC]Hl+&l|<%glXxO_Ѵj3dMwYt[S$|(s# ݪPz νa7މO7W9oAFVjes&COF8{^r5^jϚ=) Z^-()^hizY^ qse?Xf3+ϼӣheF࿙ вJi{mHRVZz@=~F0!<^;wk̪32\pݏo.bЏ{ &=`^XU18/y#<{V>*,Oe\5${R r5B<%<$g'5Te -OuX1?[39b9D|IWZ| *wccd>c/Ka M$ϒ-wrz a^#$/'Dlӏҕ RF!2C33aJ(OF@lw},y-c2y%d!,de!JPt?hDt9h É1[e*\D)Զ4Ԯ7N:M6#e&RuPJ]M}6Ļ>r!ݼ?\Y!5c[YΩ!!%lׄbbez˧ @i\<5QjC[ݾ瀾"XߔhߕR)s)?؞iS>&X!XYc Fw>r8Vtap^O@=q[g/k$zbw}Y|1KezڙkD7=8u?zl#jKfKP[, ;gXriFN45u@$TdT2ae\Wje4pGQER`pP/{%,01R&2"K+IYѥha&jR7  ΈZI SO Xq0ZqSKBG0]e3ׇ6"J,C-YSUn2ٷd-O 5T^Ur4jNܩ2Zj?vɒ -ElMLnآ֚F5"nСR wuĆEBZKb<YڈzKp*xcT71i <ƍ\y>Ut {.UrG%‘ʬ /#7.N)-dTk٨vY{S;>|7PO"` ci endstream endobj 27 0 obj 11286 endobj 28 0 obj << /Length 29 0 R /Filter /FlateDecode >> stream x]Tn0+xLdrI&`H.>Z9bIN=-g+a7S\t ԯzokRs8U;k1mO?]s~rj[Sʛm|/1YvZrrgXW(uћ7K-9eϵ`X>ct[<#Pf7NXY  endstream endobj 29 0 obj 516 endobj 30 0 obj << /Type /FontDescriptor /FontName /RNWVEJ+TeXGyreHeros-Regular /FontFamily (TeXGyreHeros) /Flags 4 /FontBBox [ -529 -284 1353 1148 ] /ItalicAngle 0 /Ascent 1148 /Descent -284 /CapHeight 1148 /StemV 80 /StemH 80 /FontFile3 26 0 R >> endobj 9 0 obj << /Type /Font /Subtype /Type1 /BaseFont /RNWVEJ+TeXGyreHeros-Regular /FirstChar 32 /LastChar 227 /FontDescriptor 30 0 R /Encoding /WinAnsiEncoding /Widths [ 278 0 0 0 0 0 0 0 333 333 389 0 278 333 278 278 556 556 556 556 556 556 556 556 556 556 278 0 0 0 0 0 0 667 667 722 722 667 611 778 722 278 500 0 556 833 722 778 667 778 722 667 611 722 667 944 667 0 611 0 0 0 0 0 0 556 556 500 556 556 278 556 0 222 0 0 222 833 556 556 556 0 333 500 278 556 500 722 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 556 556 0 556 ] /ToUnicode 28 0 R >> endobj 31 0 obj << /Length 32 0 R /Filter /FlateDecode /Subtype /Type1C >> stream xWw\ڞs.Xuvǂ bERD)Q1!"E"V*bFbo Ɩ(\{Ăz.`wv1Foϙ9>ay%44,?ϔex9sdz_O6㭲`5 k#\V5#%2566rf3חMN.]qv9ޱolov_kR 4!۔aI1gX1J?>!՜gIMQ3>>J9m( e)9>mbkWc1eY,sNGIN(yTY4SŔd$d6dpjV"RY(II޾}vN9+X&ciV/dio{;4:*;"oH 8M)i|S[^Ln*7+JBn[mjsd>Y|.gxo?B(a'1B(\(l*C9#EPq5/?s5~Zq%I"%iEIk҆xOE:.$&}HK"I!#HKƐHRII'L2xE,$\G&|2L"2L#d:)$3L2&E!s((AZN1ld8KI/hC3괂Y% 7VpGFu:{EnEZgoW:vtg^%NFYHno^CIz;_?jTWJ(Ⱦ*%RolW]e ?XuAnh;ɞzM0PnU$6Nuh ^tpra#, OkDuwv:%s}t~$O]ǙkDO폽KRPyuΣY{<H9<"4x7/*_r,3ҁwWhJ[5$!JPb˜/'h4}'u`,G0Q@\cG%7?{J~A!=1;Yw|-6h#Ӣ{%̚:0sYb}gK2YdAI/AeJWhg \En/8U9 FG[$c)-#ex=.'};ЭkL辡!qP. .qq2l b\4wsCg\?w;EqΜsr.\[ΗkŵZsøm00g`^\gn.Wmvro{|+#'Xȗ;5 VBOD/ !~o"Vd$jSYcf e"R=ygɒ}ĺ^oIѾj8.sc9Mmo& AԞUdï eE~E }a%k蔲7F^,j bY+-)skx8T;J祢cަ*MxYuSвLPZ;\7e咼nh]/Wi6'W/Lc-fȃFF։Cdh"5b"Kt.\bfd^c_9<Y~PPFh3~+e-'z@%ruk났=5Z_V"k{;ƒFP 6_t//,]v`K,rtGF.mpG\%wsuQ}BGYg|$"؀IHe.df!S/d ![,Lg=C)f EB0G+, _c[mvaP! pH8,"!hg:dD jZ)BEyh*@Q!fb4EP Zh*G&A{atE{FqEet]E=t=@#3z^1wl;a n?kq#/{ǾĽpo8P<GH<1x(x$Nq3q')x,‹1x=ފx'I| o|Ws\—W$͝7AۣXھK)Zn~\6x_xz1x mb /`w/KSp[gЕ +lfE:QOڦKg5kތWݬ8r=Ҩ/E?6b&m`e{֭?ݧibWPƎƄw1:M4ƍaDe4B<;/$OWv%Lڥi }Kff!lWP5(^3,c+ !D]ۮ^$(gv 6glR:'m'E|x >wFڼz[EQ_](+lP爨X䅶k3[ DS@ӂb1v2k t}w t=K}f~ endstream endobj 32 0 obj 4303 endobj 33 0 obj << /Length 34 0 R /Filter /FlateDecode >> stream x]RN0+|C4vlJ.=HMDM{v=HgwfO4.zSѢ1L=Ƥ֍cX~V.4LtΗ%_Ctkt7;vy#Ejёn/tUWrYq2ny Kat@OR]]ot7 E)5JCچuAu-׌́6aѮ yeEˣK>O$ށsν`qh^˗߉RV T N|#|NN0g+>-|ZiJeQkb+x?8dZVAvw9g^edD?OTH endstream endobj 34 0 obj 410 endobj 35 0 obj << /Type /FontDescriptor /FontName /YOQLKF+TeXGyreHeros-Bold /FontFamily (TeXGyreHeros) /Flags 4 /FontBBox [ -531 -307 1359 1125 ] /ItalicAngle 0 /Ascent 1125 /Descent -307 /CapHeight 1125 /StemV 80 /StemH 80 /FontFile3 31 0 R >> endobj 10 0 obj << /Type /Font /Subtype /Type1 /BaseFont /YOQLKF+TeXGyreHeros-Bold /FirstChar 32 /LastChar 245 /FontDescriptor 35 0 R /Encoding /WinAnsiEncoding /Widths [ 278 0 0 0 0 0 0 0 0 0 0 0 0 0 0 278 556 556 556 0 0 0 556 0 556 0 0 0 0 0 0 0 0 0 0 722 0 667 611 0 0 0 556 0 0 0 0 778 667 0 722 0 611 0 0 0 0 0 0 0 0 0 0 0 0 556 611 556 611 556 0 611 0 278 278 0 278 889 611 611 611 0 389 556 333 611 556 0 556 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 556 556 556 0 0 0 556 0 0 0 0 0 278 0 0 0 0 0 0 0 611 ] /ToUnicode 33 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 2 0 R 13 0 R ] /Count 2 >> endobj 36 0 obj << /Producer (cairo 1.15.10 (http://cairographics.org)) /CreationDate (D:20180611132507-03'00) >> endobj 37 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 38 0000000000 65535 f 0000238017 00000 n 0000021387 00000 n 0000021211 00000 n 0000000015 00000 n 0000021187 00000 n 0000021619 00000 n 0000021900 00000 n 0000218443 00000 n 0000231330 00000 n 0000237290 00000 n 0000021878 00000 n 0000107102 00000 n 0000128510 00000 n 0000128331 00000 n 0000107127 00000 n 0000128306 00000 n 0000128745 00000 n 0000129027 00000 n 0000129005 00000 n 0000214231 00000 n 0000214256 00000 n 0000217779 00000 n 0000217803 00000 n 0000218177 00000 n 0000218200 00000 n 0000219020 00000 n 0000230405 00000 n 0000230430 00000 n 0000231025 00000 n 0000231048 00000 n 0000232073 00000 n 0000236475 00000 n 0000236499 00000 n 0000236988 00000 n 0000237011 00000 n 0000238089 00000 n 0000238206 00000 n trailer << /Size 38 /Root 37 0 R /Info 36 0 R >> startxref 238259 %%EOF rows-0.4.1/tests/data/expected-eleicoes-tcesp-161-pdfminer.six.csv000066400000000000000000000045621343135453400247260ustar00rootroot00000000000000responsavel,cpf,processo_tc,transito_em_julgado,origem,exercicio_observacoes MARCELO PEDRONI NETO,715.241.698-20,3331/026/05,20/12/2011,SUPERINTENDENCIA DE AGUA E ESGOTO DA CIDADE DE LEME,2005 MARCELO RODRIGUES DE LIMA,112.645.308-03,77/014/10,11/12/2013,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2008 MARCELO RODRIGUES DE LIMA,112.645.308-03,301/014/10,15/04/2014,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2009 MARCELO RODRIGUES DE LIMA,112.645.308-03,302/014/10,15/04/2014,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2009 MARCELO RODRIGUES DE LIMA,112.645.308-03,867/014/12,28/03/2014,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2011 MARCELO RODRIGUES DE LIMA,112.645.308-03,981/014/11,28/04/2014,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2010 MARCELO SOARES DA SILVA,083.179.488-70,2863/026/09,07/11/2014,CONSORCIO DE DESENVOLVIMENTO REGIAO DE GOVERNO ITAPETININGA,2009 MARCIA ADRIANA STORTO TEIXEIRA,000.000.000-00,783/006/10,27/02/2012,PREFEITURA MUNICIPAL DE SERTAOZINHO,2009 MARCIA APARECIDA DOS SANTOS CONCEICAO,152.555.428-06,1972/007/08,03/11/2010,PREFEITURA MUNICIPAL DE CACAPAVA,2007 MARCIA APARECIDA SILVA,043.963.258-78,15361/026/12,03/06/2013,COORDENADORIA DE ESPORTES E LAZER,2008 MARCIA CRISTINA DA SILVA,190.342.798-39,9798/026/12,03/02/2015,COORDENADORIA DE ESPORTES E LAZER,2009 MARCIA CRISTINA DA SILVA,190.342.798-39,9799/026/12,03/02/2015,COORDENADORIA DE ESPORTES E LAZER,2009 MARCIA FARIA WESTPHAL,618.385.518-91,16155/026/11,16/06/2016,PREFEITURA MUNICIPAL DE OSASCO,2008 MARCIA HELENA MELLO,137.169.438-94,1923/004/08,03/11/2010,PREFEITURA MUNICIPAL DE RIBEIRAO DO SUL,2007 "MARCIA MARIA ALVES CARDOSO MARCIA MARIA ALVES CARDOSO","102.840.638-00 102.840.638-00","2804/026/08 5678/026/07","17/10/2013 17/11/2008","CONSORCIO PUBLICO INTERM.DE SAUDE DA REGIAO DOS GRANDES LAGO CONSORCIO INTERMUNICIPAL DE SAUDE DA ALTA ARARAQUARENSE","2008 2007" MARCIA REGINA PINESI NASSER,172.827.508-31,1545/010/08,16/08/2013,PREFEITURA MUNICIPAL DE SAO SEBASTIAO DA GRAMA,2007 MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,20110/026/11,27/07/2015,PREFEITURA MUNICIPAL DE CUBATAO,2008 MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,13568/026/11,02/06/2015,PREFEITURA MUNICIPAL DE CUBATAO,2009 MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,39951/026/11,13/07/2016,PREFEITURA MUNICIPAL DE CUBATAO,2010 MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,41395/026/11,02/02/2015,PREFEITURA MUNICIPAL DE CUBATAO,2010 rows-0.4.1/tests/data/expected-eleicoes-tcesp-161-pymupdf.csv000066400000000000000000000046061343135453400240030ustar00rootroot00000000000000responsavel,cpf,processo_tc,transito_em_julgado,origem,exercicio,observacoes MARCELO PEDRONI NETO,715.241.698-20,3331/026/05,20/12/2011,SUPERINTENDENCIA DE AGUA E ESGOTO DA CIDADE DE LEME,2005, MARCELO RODRIGUES DE LIMA,112.645.308-03,77/014/10,11/12/2013,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2008, MARCELO RODRIGUES DE LIMA,112.645.308-03,301/014/10,15/04/2014,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2009, MARCELO RODRIGUES DE LIMA,112.645.308-03,302/014/10,15/04/2014,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2009, MARCELO RODRIGUES DE LIMA,112.645.308-03,867/014/12,28/03/2014,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2011, MARCELO RODRIGUES DE LIMA,112.645.308-03,981/014/11,28/04/2014,PREFEITURA MUNICIPAL DE NATIVIDADE DA SERRA,2010, MARCELO SOARES DA SILVA,083.179.488-70,2863/026/09,07/11/2014,CONSORCIO DE DESENVOLVIMENTO REGIAO DE GOVERNO ITAPETININGA,2009, MARCIA ADRIANA STORTO TEIXEIRA,000.000.000-00,783/006/10,27/02/2012,PREFEITURA MUNICIPAL DE SERTAOZINHO,2009, MARCIA APARECIDA DOS SANTOS CONCEICAO,152.555.428-06,1972/007/08,03/11/2010,PREFEITURA MUNICIPAL DE CACAPAVA,2007, MARCIA APARECIDA SILVA,043.963.258-78,15361/026/12,03/06/2013,COORDENADORIA DE ESPORTES E LAZER,2008, MARCIA CRISTINA DA SILVA,190.342.798-39,9798/026/12,03/02/2015,COORDENADORIA DE ESPORTES E LAZER,2009, MARCIA CRISTINA DA SILVA,190.342.798-39,9799/026/12,03/02/2015,COORDENADORIA DE ESPORTES E LAZER,2009, MARCIA FARIA WESTPHAL,618.385.518-91,16155/026/11,16/06/2016,PREFEITURA MUNICIPAL DE OSASCO,2008, MARCIA HELENA MELLO,137.169.438-94,1923/004/08,03/11/2010,PREFEITURA MUNICIPAL DE RIBEIRAO DO SUL,2007, "MARCIA MARIA ALVES CARDOSO MARCIA MARIA ALVES CARDOSO","102.840.638-00 102.840.638-00","2804/026/08 5678/026/07","17/10/2013 17/11/2008","CONSORCIO PUBLICO INTERM.DE SAUDE DA REGIAO DOS GRANDES LAGO CONSORCIO INTERMUNICIPAL DE SAUDE DA ALTA ARARAQUARENSE","2008 2007", MARCIA REGINA PINESI NASSER,172.827.508-31,1545/010/08,16/08/2013,PREFEITURA MUNICIPAL DE SAO SEBASTIAO DA GRAMA,2007, MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,20110/026/11,27/07/2015,PREFEITURA MUNICIPAL DE CUBATAO,2008, MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,13568/026/11,02/06/2015,PREFEITURA MUNICIPAL DE CUBATAO,2009, MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,39951/026/11,13/07/2016,PREFEITURA MUNICIPAL DE CUBATAO,2010, MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,41395/026/11,02/02/2015,PREFEITURA MUNICIPAL DE CUBATAO,2010, rows-0.4.1/tests/data/expected-eleicoes-tcesp-162-pdfminer.six.csv000066400000000000000000000044121343135453400247210ustar00rootroot00000000000000responsavel,cpf,processo_tc,transito_em_julgado,origem,exercicio_observacoes MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,20113/026/11,17/04/2015,PREFEITURA MUNICIPAL DE CUBATAO,2008 MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,20109/026/11,28/05/2015,PREFEITURA MUNICIPAL DE CUBATAO,2008 MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,20111/026/11,10/09/2014,PREFEITURA MUNICIPAL DE CUBATAO,2008 MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,34002/026/10,30/05/2014,PREFEITURA MUNICIPAL DE CUBATAO,2008 MARCILIO PEREIRA CAMPOS FILHO,036.483.468-49,800160/573/07,10/06/2014,PREFEITURA MUNICIPAL DE SANTA BRANCA,"Apartado (*) 2007" MARCIO BENVENUTTI,002.289.188-98,3765/026/06,11/10/2011,EMPRESA MUNICIPAL DE HABITACAO DE ITAPIRA,2006 MARCIO BENVENUTTI,002.289.188-98,3320/026/05,20/12/2010,EMPRESA MUNICIPAL DE HABITACAO DE ITAPIRA,2005 MARCIO CARUCCIO LAMAS,069.930.818-65,547/026/11,03/08/2015,PROGRESSO E DESENVOLVIMENTO DE PRAIA GRANDE,2011 MARCIO CECCHETTINI,077.909.008-03,20991/026/12,11/08/2015,DIRETORIA DE ENSINO - REGIAO DE CAIEIRAS,2010 MARCIO CECCHETTINI,077.909.008-03,27520/026/10,29/10/2014,PREFEITURA MUNICIPAL DE FRANCO DA ROCHA,2009 MARCIO DUARTE DE MELO,072.094.438-43,1164/009/10,03/08/2015,PREFEITURA MUNICIPAL DE ITAPETININGA,2009 MARCIO EDUARDO SILVA ROCHA,155.283.138-80,22042/026/09,26/05/2011,PREFEITURA MUNICIPAL DE SAO BERNARDO DO CAMPO,2007 MARCIO EDUARDO SILVA ROCHA,155.283.138-80,22044/026/09,16/01/2012,PREFEITURA MUNICIPAL DE SAO BERNARDO DO CAMPO,2006 MARCIO GUSTAVO BERNARDES REIS,165.052.578-88,800148/505/10,13/08/2014,PREFEITURA MUNICIPAL DE JAGUARIUNA,"Apartado (*) 2010" MARCIO JOSE RAMOS,044.453.658-23,3348/026/05,17/11/2008,PROGRESSO E DESENVOLVIMENTO MUNICIPAL OLIMPIA,2005 MARCIO LASILHA SANTAELLA,058.409.938-08,3239/026/07,29/05/2012,CAMARA MUNICIPAL DE PROMISSAO,2007 MARCIO LASILHA SANTAELLA,058.409.938-08,146/026/08,08/06/2010,CAMARA MUNICIPAL DE PROMISSAO,2008 MARCIO MAZZA DE LIMA,084.827.028-23,1073/006/11,30/04/2014,PREFEITURA MUNICIPAL DE SERTAOZINHO,2010 MARCIO MICHELAN,850.896.998-87,20310/026/07,05/01/2016,GABINETE DO SECRETARIO E ASSESSORIAS,2005 MARCIO MICHELAN,850.896.998-87,21083/026/07,05/01/2016,GABINETE DO SECRETARIO E ASSESSORIAS,2006 MARCIO NAZARENO FERREIRA MATTOS,113.124.478-85,2223/026/10,10/09/2013,CAMARA MUNICIPAL DE MIGUELOPOLIS,2010 rows-0.4.1/tests/data/expected-eleicoes-tcesp-162-pymupdf.csv000066400000000000000000000044311343135453400240000ustar00rootroot00000000000000responsavel,cpf,processo_tc,transito_em_julgado,origem,exercicio,observacoes MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,20113/026/11,17/04/2015,PREFEITURA MUNICIPAL DE CUBATAO,2008, MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,20109/026/11,28/05/2015,PREFEITURA MUNICIPAL DE CUBATAO,2008, MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,20111/026/11,10/09/2014,PREFEITURA MUNICIPAL DE CUBATAO,2008, MARCIA ROSA DE MENDONCA SILVA,066.086.978-05,34002/026/10,30/05/2014,PREFEITURA MUNICIPAL DE CUBATAO,2008, MARCILIO PEREIRA CAMPOS FILHO,036.483.468-49,800160/573/07,10/06/2014,PREFEITURA MUNICIPAL DE SANTA BRANCA,2007,Apartado (*) MARCIO BENVENUTTI,002.289.188-98,3765/026/06,11/10/2011,EMPRESA MUNICIPAL DE HABITACAO DE ITAPIRA,2006, MARCIO BENVENUTTI,002.289.188-98,3320/026/05,20/12/2010,EMPRESA MUNICIPAL DE HABITACAO DE ITAPIRA,2005, MARCIO CARUCCIO LAMAS,069.930.818-65,547/026/11,03/08/2015,PROGRESSO E DESENVOLVIMENTO DE PRAIA GRANDE,2011, MARCIO CECCHETTINI,077.909.008-03,20991/026/12,11/08/2015,DIRETORIA DE ENSINO - REGIAO DE CAIEIRAS,2010, MARCIO CECCHETTINI,077.909.008-03,27520/026/10,29/10/2014,PREFEITURA MUNICIPAL DE FRANCO DA ROCHA,2009, MARCIO DUARTE DE MELO,072.094.438-43,1164/009/10,03/08/2015,PREFEITURA MUNICIPAL DE ITAPETININGA,2009, MARCIO EDUARDO SILVA ROCHA,155.283.138-80,22042/026/09,26/05/2011,PREFEITURA MUNICIPAL DE SAO BERNARDO DO CAMPO,2007, MARCIO EDUARDO SILVA ROCHA,155.283.138-80,22044/026/09,16/01/2012,PREFEITURA MUNICIPAL DE SAO BERNARDO DO CAMPO,2006, MARCIO GUSTAVO BERNARDES REIS,165.052.578-88,800148/505/10,13/08/2014,PREFEITURA MUNICIPAL DE JAGUARIUNA,2010,Apartado (*) MARCIO JOSE RAMOS,044.453.658-23,3348/026/05,17/11/2008,PROGRESSO E DESENVOLVIMENTO MUNICIPAL OLIMPIA,2005, MARCIO LASILHA SANTAELLA,058.409.938-08,3239/026/07,29/05/2012,CAMARA MUNICIPAL DE PROMISSAO,2007, MARCIO LASILHA SANTAELLA,058.409.938-08,146/026/08,08/06/2010,CAMARA MUNICIPAL DE PROMISSAO,2008, MARCIO MAZZA DE LIMA,084.827.028-23,1073/006/11,30/04/2014,PREFEITURA MUNICIPAL DE SERTAOZINHO,2010, MARCIO MICHELAN,850.896.998-87,20310/026/07,05/01/2016,GABINETE DO SECRETARIO E ASSESSORIAS,2005, MARCIO MICHELAN,850.896.998-87,21083/026/07,05/01/2016,GABINETE DO SECRETARIO E ASSESSORIAS,2006, MARCIO NAZARENO FERREIRA MATTOS,113.124.478-85,2223/026/10,10/09/2013,CAMARA MUNICIPAL DE MIGUELOPOLIS,2010, rows-0.4.1/tests/data/ibama-autuacao-amazonas-2010-pag2.csv000066400000000000000000000045241343135453400231770ustar00rootroot00000000000000no,infracao,datainfracao,estado,municipio,cnpjcpf,nome_autuado,no_ai,valor_multa,no_processo,status_debito,sancoes_aplicadas 11,Flora,18/03/2010,AMAZONAS,MANACAPURU,63.637.540/0001-09,JOAO BARRETO FEITOSA,679198,"18.300,00",02005.000290/2010-57,Ajuizado,"3° 47 Decreto, 6514/2008, 1° PU 2° Instrução Normativa, Ibama 112/2006, 70 §1º 3º 72 Lei, 9605/98" 12,"Controle ambiental",18/03/2010,AMAZONAS,MANACAPURU,63.637.540/0001-09,JOAO BARRETO FEITOSA,679200,"500,00",02005.000295/2010-80,"Quitado. Baixa automática","70 §1º; §3º 72 Lei, 9605/98, 3° 66 Decreto, 6514/2008, 2 Resolução, CONAMA 237/1997" 13,Flora,18/03/2010,AMAZONAS,MANACAPURU,63.637.540/0001-09,JOAO BARRETO FEITOSA,679199,"39.000,00",02005.000300/2010-54,Ajuizado,"70 §1º; §3º 72 Lei, 9605/98, 3° 47 Decreto, 6514/2008, 1° PU 2° Instrução Normativa, Ibama 112/2006" 14,Flora,19/03/2010,AMAZONAS,MANAUS,02.694.303/0001-87,IGUAÇU AGROINDUSTRIAL LTDA,678501,"4.500,00",02005.000319/2010-09,"Para homologação/praz o de defesa","70 § 1º 3º 72 Lei, 9605/98, 3º 47 Decreto, 6514/2008, 1] UNICO 2º Instrução Normativa, Ibama 112/2006" 15,Flora,08/03/2010,AMAZONAS,MANACAPURU,01.242.525/0001-04,W G DE ALMEIDA NETO,679184,"10.500,00",02005.000304/2010-32,Ajuizado,"70 §1º; §3º 72 Lei, 9605/98, 3° 47 Decreto, 6514/2008, 1° P.U. 2° Instrução Normativa, Ibama 112/2006" 16,Flora,24/03/2010,AMAZONAS,LABREA,251.082.202-00,OSCAR OLIVEIRA DA SILVA,632860,"15.000,00",02002.000171/2010-24,"Inscrito na dívida ativa","70 72 Lei, 9605/98, 3° 50 Decreto, 6514/2008" 17,Flora,08/09/2010,AMAZONAS,BENJAMIN CONSTANT,233.753.702-10,FLÁVIO SOUZA DA MATA,27479,"18.000,00",02005.000801/2010-31,"Cancelado por falecimento ocorrido antes da const. do créd.","70 1º, 3º 72 Lei, 9605/98, 3º 47 Decreto, 6514/2008, 1º único 2º Instrução Normativa, Ibama 112/2006" 18,Flora,28/10/2010,AMAZONAS,ITACOATIARA,23.031.107/0001-00,JUMA PARTICIPAÇÕES S.A.,678003,"10.000,00",02005.001048/2010-09,"Homologado, por pagamento (AI sem defesa)","70 § 1º 3º 72 Lei, 9605/98, 3º 50 Decreto, 6514/2008, 225 , Constituição Federal" 19,Flora,09/11/2010,AMAZONAS,PARINTINS,05.804.922/0001-75,J.J.B.GUIMARAES,677883,"63.668,00",02005.001069/2010-16,Ajuizado,"70 § 1º 3º 72 Lei, 9605/98, 3º 47 Decreto, 6514/2008, 2º I- a 1º Instrução Normativa, Ibama 112/2006" rows-0.4.1/tests/data/ibama-autuacao-amazonas-2010-pag2.pdf000066400000000000000000000546031343135453400231600ustar00rootroot00000000000000%PDF-1.7 % 1 0 obj <> endobj 2 0 obj <> endobj 3 0 obj <> endobj 4 0 obj <> endobj 5 0 obj <>stream x\n$+ G=UIqj uhK=6$r^kgdaA^dxYd7y$,Ø.Ω[<.Y yIpr>'g)(cd'\'_&L Rʒ2Nr(9k9ܐO#cPav+ $Q1qK 2#nli;!r=R6h<W̿ThxW\Ҳ yVR!+ټ/r>mfըF9R?b1bڴ'ߎ__A-J{Nld6rDn?͕N4 ]\`$s*Y6]/w߮7nEHz[8)DhYej AJMŒn2?ۮȹ j69#RUElw*(DzC84{4 lh@>v6QI%`8X#fWsR,JKzRUMdEN Q.T@xQQ&nM!*Ϭ, BꟑoKˏ'e2D9BVSR,?g@gꝸ;1uC v.4auA3= E4Sc3i{cvs:vY+[^NT}{Mc+`tt8 41½Byǽ!1 q8gZZBnBBUm,W}[zêK!yf/\vEÂ4L'r1O\/#⎐pnnH&XUx(q8'G ƽ Htڎջ'ջvšv9wڎe+ǵReKUL;r7m?)Efݝ~߼ǂ>l߄V>')*}aQT>Ew(nn!=V+0/p?`H] !=ː5!֛oYۋ / L'!~ ǖ*fC*< c6h$FԆ PֆL  C; (pGmJ!#Ԇ 8jCPZ#tÀ@VRƬQZ,"toElݣVΰ!+Ri|ρ7Wף!|x 0 .̓xt1b$5a&AMZ6T`Br ̘)=H-.OJ4k0ձj!gAFβD\ tA[ 5"|Lh1`Y>l1e{2QՔLqzm7aP8 w4Ҵшk{v 0mwLDj;FJO%jck{iۣ!  =BWtԄst$W6n3륒zixkqXP$WvYx&fJ&WQ;mY{6-` lc;RTExsq8qC'M׽(6DXJi]1^r"8@,hmdBd7p vm.|{2:L[QG WÞU2c{i|[U~ 9;}BxI7qāyj$5_2P8N& 0aZǡzƘp&^cA<=>Bij1:a 8Xx:4X͛<8n`nԯeJn^h\G;Jd/n3NqmWz}vBfgl_7j#NQāޠjgq?meVmcq&I%BE;D+x<&ؗhK6VVtEh8yu{6N>l²rk^Wvz|<^wnЋpJ[Ia["ĶxΨH<ԨRic[@#8Q@Y2p0:852&n9DF- 4P|P;o.&ͼmao-9$%9M$Jsm[螃b Wo ii~hFfN0?,ʊ1X:6;X:iV[fu%oI%ocdgY~@k8h(tns}^ WۜFNcegujп$7g<Ua"V ϻ_]*әZcG; i=Cy=3!?v>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI]/XObject<>>> endobj 7 0 obj <> endobj 8 0 obj <> endobj 9 0 obj <>stream JFIFC    $.' ",#(7),01444'9=82<.342C  2!!22222222222222222222222222222222222222222222222222"C!1A"Qaq2B#R3b$Cr%4Sc8!1AQaq"2#BR$3Cb ?袊T)R(J(Rd+bJ=)Y UK\ff7iKb%~m!' ^;[yJF[!C呑ECQx':|r=h9!u$T^IzbsKVRIE,TԴRRRO]Qlt)L)QERER)A#&RD)R\ p1=j6&u(JqaH%JQH˯ʼ`Of<% u_k*F ݱ2Z>ppYA#qEz-f@ZuM]PrFbA)8)ljg$Ԇdƒ :"R3ҟZr:X U@xCtZ,n,ru>(a!1xdzhjQ>&5U[WIQl+6>l2R=;~ut賭1D󥂱 e'BJT ȫ^ַ;KT+w7%o%uӴ6c<<ۈ ۵Cado"K2ېai|m)$z:aD`~YV5=B\&1XN›_A)##GnjY_~TO8S_4HF:Td߾CrɷZPէ1Z 4Uzz$ǟC=}JRI=EZ%с=\X }ʏMSX O!=㠩֞muHR]-mx#!(%*.3ꡌZ,O‘;_.IA9V: Q.*uu%ӏjnU|<3Qܦ}Z=#uKFPpO$2OiɩRd gs--j[pmRR1ڼEBPa-k*̤R 8 8'5! 2fHeyyk̰08J} 㿷XTc4l:(g}5ie&%m8R i-'+?,ܶQ~uN**ucS[\.6զ8qw\!с<#עIUVߊ$53ii: 8B[Xa!#^%I{*@q<_ϷYF쨸O) 2JIIHNw<'4~SMț}j[h:p@#8L}!31to@zE=}TܤsUA2^Ó8h`pCeJWc5XZ7Jiw9~L ĜJ d~p.CmOqqD7q$=?ek0̫_𒄆-èr?2M_y?X*~]N_ &rbB&1liϕ)#e;TA{MBl#~9܅RO^2sǴ⤶<[-)Zq˓ǥq;FAf @(IΧW Q#``3By#Nޅ@`TK0^A<}W}dxP9Cbx I·'xsJ~>0$$( Iv{TQXO0G5tIL3U_ܿhʔɇ(q9N}j?cg j6ݖWb< !y{v >b:Z@Jkr#_@VgVTFI>V jT % ,ieAHWZQE+Z(EFmnY@Iڶ:)*GxS T>~L Z w)89J ^-Qف#}cLmOxZ(gQum%ͻ=8C<|V#njm2߷%jiLc'j%q7 QJ<5u&>8ʙSHmHyo"eMlݲUP?Jjek5GA3'TNoPݝK1a̴<'<3v}G婚ֹvK-)Sfd)R ?$bz[owI~팩 n=_AӇ_cjf#p8ujȲL U|hmD<[@7NZ+wCnH@HIzjxZr:Vm MEouE!Qⴶ);F5j떰&QpeT2[HZX8RV0qB2ȭYxk:"x90q?:zUԴ2Vl/c yGq<b@i#QRKTNG@R@-+j^G?f+N|OJJ'߼:Ê ( >-m#'P*V<)h)zn‡9:Wb3!% (v^o:y8?cᬻ Ϊ2ʛq>WV)ۭ׉;/2wn2ҕ-H2;jXwwxztuch)wΓcƤtdMM⊎3\|`ʫ1`-$Bv4NṲh8<30v!S4kC28)q\(Xwuހ `Nk y."@QIǡGsU>8˹HxP8鞼U5an-2Y֙iTڊw`qSnZZmոzyqQI)T$̈ #/b.U4]SxwdDk"FVYi1$%6yI#z gO ?Kxœ-G WT(uHK.#v[g)EFJwS[C\KkPhRzzM45;Lu(Oh 3+HiΛj~\qɻ`<*"kb%ICPcEvdRy0=y K'sw٘M)Oqַ>Z (OS>J՚S6XgI\d*BszꯗVhzMMBk1.6mVo*C T2OaPM2Z R߄ R<#y֤ۻapgiv ~ym'ҦW띦-lCGm$Qڍ z ޞ}jW(灞yUha尩SH2K8@gɢd<>fXRӇ >3ZWٵ۶)_}N)oy'RqU[":t#sP:OO14Kckuqr}I5>8=*I֝diEEk)4S*`W=EdzجO-{g zW{**Wy9->Ђ:VUIWDWb?Ϋ5q?"H\ُ 9i$ 3_I}Qs?er#m T O(WJPjIYVdF9UV0wWe⛅ $dʥ֋xK2υ|PR '):+]٤iW4@)xRCR7(5Sy1 -2HmyB**pj1l!eUy8Υsum7ES&=\a^/:Ҽ3v`-@d<' ?SQW[M-0Y!tAK{oBr:r ugȝHXR!(_]@}ȩ\4RNq'@v4Kf9v? !.7|Z]Bٌ+3br0 +jCIYq)bGWW!.23\ȒbJKI£IO?#2+Ua{R}GwYݘ'}n#SֆX=S sʕn,Fq [N8*{>$bHC쐗rc84-N *Gۥ]q+BF kSeΕ.L-Ɣ݀J+mO$iϩU+"x #&w}RNi-S>"eMLfaV8 *8+H5mP?hNM68AbF[U* V|Dn#x6^>e6!)R6c*"}ǏQDal[Q9')AzsDfU;l$s8#+I:{jޡԡ9231 a{t@'X]LTKyQqR7-lO(>F>u ̰7@OyC:Z ޖ‰<aUԟבoJ2KoCqom R`i'75--$Ԍ$]#Qc}:ER.Oː\̆YI G95hy_hMăh8RYEIO۽Bd٬79i *Rqی+ kGjnқOxF^E XεѢ+3IX#,׭BjG J7Y6 ?u#='R81Xθj;~U|2A܆YugoV\xSXnC)_uX#Oҥ>#o7Ss"a*i l1lэ&SPKf,u?Jj+@I պ]~έ -ԕJB_ʼoVoqIy9`}֟pqOhշE%6wY/POZב9KM+{;7n?ȭ֕~iWHux @Si#\ښO(c>9^Z՞0|ߌT}Q%36&= %GboHnTl'qZ"rZ?^_6J])w8\5({Av⠃#N@F<2TmX1n>ʰ뒟%8T%) ա/M=-nhmǢ:HV=E7d[^%@#?Zw#ĕU0ʼn=UFy|C1" z1N?̕g#"mPYdpmOSKiX@P5hm@-RbJa O!pV}0~NEW6xł~WH ʎz%i''Ogش;u(%C#I\iˢ-e/>K(D!(c':uW*M=i(+:[zQ*+n`:iKkvGFW}dz "DO :θ:DԖGL+L+8ۄHJzm#eMucD|V6-A$'|?\Ui>l^0Ђ8o5BVCjv+ 'p)dԀs-z@z`RN잪|YzKi֛DqwstW=H9 nXHN<'j!,g:?.ouԧsGcLYKia2rdTNGQVu*Shם,QP 2}@[-,;rOR&JcKBjP!@8#8ȭT4.R\N~\)PVRmrGL$Vm?VEc^} 3G)KHIHkZٵ8-NsY {ąf&>cuq==y]NG?YhJlK+nHz| qtFY_+Dz_~UKCݱPR*u a6GOف 6EJ*ZWlL:~ܪXۄsC iSfgtaEJl;'+n%27# %q֯*2~ᰐ" ӘVK]igaďˏ!Vn@F v5 %&@65yAQ8֛k͵;XB%E9fh:EC )8Ϫ jsh[4vƸ -1JYJJq[rBP2Ts$ *5қIQO j֊zj(di򜹁$:ϥ:R4bJ {C4l 7RnMolz׍1cޭB< [P?S۾Ys^7 wuN^\aXPR Alc4j鶞cfo;kpP1[8kkt`'W?S1n"pXDğQtj%ڵ |4ӧ;R 2>GP`8'BhKdLH Q.iW^u3c f6KR5ժݸDcR%PԌ[{{Рq0J]~5ş n%P 6G1pvcLʷY'2 l:T9AdC@p. k'up0% 9rÍ6SB#۟1؂wKiIЃׯR%#q\m$*!lS}Tϰ&o<<0RyS;ղ2D%5[m¢{8{״8)oMXү<Қ]TwLMaDГd+gC#ؚf,“24Qk'-2-n-0IN` ^տUk-ѥ&ט 1}U I\[}8m*p`T,OBPi+Y<բu fm-EE)J(niIш0<Ě!ضLe貓s9g8* >UcBQCG1#>zm7Ue\!o-̟' .$0AI'ӦxVv]6rAXƀzm) `*~o/@CJP07LsTv`}(Ox-6=3>Z^62M Hί:ݨD$:LeI%&K >V i[7~k.82j-(o*Ц~NIG ЗU*Ю֬dRP"֥g0ԑGOaWM:ki2JIp6s>dXWmoe#L9V6%@$g~l2P#]|ܡs'NCy5b.C 3sH[-e'ާeYKΩ;@u DO֡/OWolc]2mJ+{2<tJ[$͹!VA##Fp pT(!Hj<rkO|Ya* !tB}s邷 $@(NaۂT̞fmИfdle uzz){@kHiM+smE`}ܦ,}Oqk9PR{{C׏5~җDY}yG<JKnfG*ͨt%  䀖Foq#8ҥ"&RcnTeyr8LjA)W=/q146c)gU=y=Ez+݁yE %'!8I'3V Q'.)ȍA#3>懥p MvV%#S>dwUqi>tR@ u9UM-J;BpBTt<;jAWW)GbCj7R3uD}jt"Џ̼<I/&Z/ig_:b l(Rv|nqgPi[ðt<ݩyT? #`9Fu ĥT)c#o ^L{応'E Ar=OF8vMD {!<81GJ)$P2p8=JH ʀPH^ۚxV֯&TGlמj԰uURwU6F| 4~*M+pPTx锣ҥm-,ZZ,LluEjQ'g~cM'YKJ[*^/{{\Eia;#''=M-`ְ@0#ׅ۬^wjBݔ}]}Hf]ZKHhWB$1kYzzae4rۏpϜg5hQRC$JN)k̈xk[iǃڜ🨫=Èm< 3;@$xЧXOݟ1fҒLX.aԾ'?>ۡPRJ@oʻrwK1YikuO˧*RO =:J@^mvۺ[WK;h<#6gtأ$- pH*.Lɘ z:oH '8\1dݑ!Ä2*W=OFFIVV:iK N[c=B8Ou}08wWڣ5(-vǍxi=.jE?drmԴu:Vim!) ==EtiM% IQWTgJ&ApC̯!i5tDq2,\$aʣ‡5R[FPj'mX.[EH}ζ߅`V30iZn1 ys$X]Zj}HSC%P|U o<[L-v:CRCQYĂZ_T]y'Ρz92n$ !ch픤HP c;js#ZS aX8RH9\cVG j5;ДLgpn8=}.72 cJd('D-St 1<ӆ@oօ]$(L}wmW>NmuR7(oN J'ŧUh;$ ;2dW'FqnJ"EAi IV*q5j+DD) 3.;}{QNFA8=ZR[1FSڠ$a4{@%sZVΚ AloR<'䚬ᨲpt0¼82TԴM& rl6+pӠ*WmGN=qsVja~g~8Er\ue[1p%hs0`*N*IW@J*9ފފ))"3JWXݝ)qKF)qH"&(-)W4ۭhP’~b~4)h+qL$lHRsIbm/—nppNKL~;3-(F*QSvZk<"xtF#5 ?!N1]QPfE,)1]bW%S\(+ɥ4E(Bp) endstream endobj 10 0 obj <>stream JFIF``C     C   ?A" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz ?S^_x<7gWM֘jW_iI$$ LA-|s'ī ͤx?IAps=eYLHib>nu}+ukT:T'V.6mwtW\s+'P4I5Ah.-ob&E!G'* M5 (+ 9d&g@%dBH 5گ~3<%ii a~ͨ]3|y&e Uy,?Qo&5I#.4:mV̯*ZcjIZݽ+]#ϥFIʼKm絮}/|!$q&Vі7]=k&g% |4QﴋxK; jd$lw*B2v# z{RY, yl/74{҇8a\<߷ikK?-aoilnDWHU$ Sk|ǯS(itokx|?aV>OIY+VѧG5O kž>ևu%YJX݉I*&2=~V\#JṢ^pQEqoN#{IbӼKa"hܯg:2#8[i(iYG[GWZOE R$ċ'N@UE~OC'ÿk^QU1]5u$w[VeYhIv kA>KQ԰8E-ynmj:sާ^cec2rI';`0liW,ЏZA_>BOvWBC?:%C4mGԫ+=ΫZNNͫYV)C(V(-7Ieti-~$zqbEE؉{o>z~/ ,].H;Y[]{RNpx`jIJ;qFw 4K{xJSYn5}Z٠-EvAub`Aȯ֟į |9>S[*Y,1-SE nVյ[8&^tV!';c~'6IҭWdVɵG=ى$I&<̩Φ"5򼚷7K%}"]ӕmE:8YaѨ)攭M>[$袊8?okO$jI|mM{{!j0sߴ~xcY@o^El˷LfͅVc'RhQfٶM_|ۭTuu W0g$kIkj7/b ԫ p\r'|_qcox{U֧h-"Ӭk!Dd"$ldESig]]iZG%0Ej420@Fqp+i˝'}˳]{3/ x&6Y>5c0XP/? 7yɹώ<׾sd15 M#k3;<#3gͳ985 gli7OzۭI.x6ÌTR4*]Ͳ#w~MYv}/Og=ڷ Zzu*dt)I l0]ŏYi?R}i,͹]m]!T !Ehk΅kxn/dQiy̌Gpљ| ԤCk8CV\_^ϯd/]\3m7_> Gm垳[xOԵ[t+ 2Ol"m1垿J+gQylэ&zQ\ endstream endobj xref 0 11 0000000000 65535 f 0000000017 00000 n 0000000066 00000 n 0000000122 00000 n 0000000209 00000 n 0000000318 00000 n 0000004538 00000 n 0000004663 00000 n 0000004759 00000 n 0000004850 00000 n 0000019124 00000 n trailer << /Root 1 0 R /Info 3 0 R /Size 11/ID[<0D0494BDDECDD961834F8822DC29B017><0D0494BDDECDD961834F8822DC29B017>]>> startxref 22534 %%EOF rows-0.4.1/tests/data/ibge-censo.html000066400000000000000000000672161343135453400175030ustar00rootroot00000000000000
RJ Angra dos Reis 169.511
RJ Aperibé 10.213
RJ Araruama 112.008
RJ Areal 11.423
RJ Armação dos Búzios 27.560
RJ Arraial do Cabo 27.715
RJ Barra do Piraí 94.778
RJ Barra Mansa 177.813
RJ Belford Roxo 469.332
RJ Bom Jardim 25.333
RJ Bom Jesus do Itabapoana 35.411
RJ Cabo Frio 186.227
RJ Cachoeiras de Macacu 54.273
RJ Cambuci 14.827
RJ Campos dos Goytacazes 463.731
RJ Cantagalo 19.830
RJ Carapebus 13.359
RJ Cardoso Moreira 12.600
RJ Carmo 17.434
RJ Casimiro de Abreu 35.347
RJ Comendador Levy Gasparian 8.180
RJ Conceição de Macabu 21.211
RJ Cordeiro 20.430
RJ Duas Barras 10.930
RJ Duque de Caxias 855.048
RJ Engenheiro Paulo de Frontin 13.237
RJ Guapimirim 51.483
RJ Iguaba Grande 22.851
RJ Itaboraí 218.008
RJ Itaguaí 109.091
RJ Italva 14.063
RJ Itaocara 22.899
RJ Itaperuna 95.841
RJ Itatiaia 28.783
RJ Japeri 95.492
RJ Laje do Muriaé 7.487
RJ Macaé 206.728
RJ Macuco 5.269
RJ Magé 227.322
RJ Mangaratiba 36.456
RJ Maricá 127.461
RJ Mendes 17.935
RJ Mesquita 168.376
RJ Miguel Pereira 24.642
RJ Miracema 26.843
RJ Natividade 15.082
RJ Nilópolis 157.425
RJ Niterói 487.562
RJ Nova Friburgo 182.082
RJ Nova Iguaçu 796.257
RJ Paracambi 47.124
RJ Paraíba do Sul 41.084
RJ Paraty 37.533
RJ Paty do Alferes 26.359
RJ Petrópolis 295.917
RJ Pinheiral 22.719
RJ Piraí 26.314
RJ Porciúncula 17.760
RJ Porto Real 16.592
RJ Quatis 12.793
RJ Queimados 137.962
RJ Quissamã 20.242
RJ Resende 119.769
RJ Rio Bonito 55.551
RJ Rio Claro 17.425
RJ Rio das Flores 8.561
RJ Rio das Ostras 105.676
RJ Rio de Janeiro 6.320.446
RJ Santa Maria Madalena 10.321
RJ Santo Antônio de Pádua 40.589
RJ São Fidélis 37.543
RJ São Francisco de Itabapoana 41.354
RJ São Gonçalo 999.728
RJ São João da Barra 32.747
RJ São João de Meriti 458.673
RJ São José de Ubá 7.003
RJ São José do Vale do Rio Preto 20.251
RJ São Pedro da Aldeia 87.875
RJ São Sebastião do Alto 8.895
RJ Sapucaia 17.525
RJ Saquarema 74.234
RJ Seropédica 78.186
RJ Silva Jardim 21.349
RJ Sumidouro 14.900
RJ Tanguá 30.732
RJ Teresópolis 163.746
RJ Trajano de Moraes 10.289
RJ Três Rios 77.432
RJ Valença 71.843
RJ Varre-Sai 9.475
RJ Vassouras 34.410
RJ Volta Redonda 257.803
rows-0.4.1/tests/data/merged.csv000066400000000000000000000001421343135453400165430ustar00rootroot00000000000000id,username,birthday,gender 1,turicas,1987-04-29,M 2,abc,,F 3,def,2000-01-01,F 4,qwe,1999-12-31,F rows-0.4.1/tests/data/milho-safra-2017.csv000066400000000000000000000031521343135453400200750ustar00rootroot00000000000000Regiões do IMEA,Centro-Sul,Médio-Norte,Nordeste,Noroeste,Norte,Oeste,Sudeste,Mato Grosso Área 16/17 (ha),352.094,2.025.045,472.015,300.110,165.776,533.113,890.798,4.738.950 "19-mai-17","0,29%","0,45%","0,26%","0,00%","0,00%","0,00%","0,00%","0,24%" "26-mai-17","0,93%","1,97%","0,58%","0,00%","0,00%","0,17%","0,20%","1,03%" "2-jun-17","2,17%","5,31%","0,87%","0,38%","0,37%","1,43%","1,01%","2,90%" "9-jun-17","5,47%","7,87%","2,33%","1,31%","4,50%","5,02%","3,16%","5,40%" "16-jun-17","8,28%","18,18%","7,18%","4,69%","9,33%","11,74%","5,04%","11,99%" "23-jun-17","11,44%","31,15%","10,45%","9,69%","11,66%","15,74%","9,01%","19,69%" "30-jun-17","16,88%","45,11%","16,77%","14,04%","16,23%","25,24%","14,65%","29,25%" "7-jul-17","28,39%","61,41%","27,68%","18,61%","24,23%","43,02%","21,35%","41,99%" "14-jul-17","48,85%","81,01%","50,22%","45,36%","56,13%","64,14%","35,76%","62,02%" "21-jul-17","60,70%","91,07%","65,38%","72,26%","80,16%","73,54%","51,08%","75,19%" "28-jul-17","79,29%","96,86%","83,31%","89,52%","92,53%","86,45%","73,05%","87,94%" "4-ago-17","92,01%","99,28%","94,05%","97,07%","97,22%","95,46%","88,23%","95,50%" "11-ago-17","97,51%","99,86%","97,21%","98,69%","98,91%","98,27%","96,07%","98,42%" "18-ago-17","100,00%","100,00%","99,63%","100,00%","100,00%","99,75%","98,34%","99,62%" "∆ Semanal*","2,49 p.p.","0,14 p.p.","2,42 p.p.","1,31 p.p.","1,09 p.p.","1,48 p.p.","2,27 p.p.","1,20 p.p." "18-ago-16","98,79%","97,80%","82,00%","92,90%","91,96%","99,00%","96,72%","95,81%" "∆ Safra 15/16 para Safra 16/17*","1,21 p.p.","2,20 p.p.","17,63 p.p.","7,10 p.p.","8,04 p.p.","0,75 p.p.","1,62 p.p.","3,81 p.p." rows-0.4.1/tests/data/milho-safra-2017.pdf000066400000000000000000013246661343135453400200740ustar00rootroot00000000000000%PDF-1.7 % 1 0 obj <>/Metadata 430 0 R/ViewerPreferences 431 0 R>> endobj 2 0 obj <> endobj 3 0 obj <> endobj 4 0 obj <>/Font<>/XObject<>/ProcSet[/PDF/Text/ImageB/ImageC/ImageI] >>/MediaBox[ 0 0 595.32 841.92] /Contents 5 0 R/Group<>/Tabs/S/StructParents 0>> endobj 5 0 obj <> stream xoؗ:Cr"]h>⺈NJ}YIrv50-;$?3_=z2'?Q6:F)!/Go?m  ^ `P~zp|#whpvJie8sΏؕqP!GQFsVQ#HJ%գ㭢ԚK2[vlB8/l3 K)?,5\^_ ^W+;ׯ?UC~^&Q[E /?%_}s|דoɋ=;x};'Ow>*ڝM?ȅ/^\|~|s'SpY_vgNAi3җ/_@Wn8Bln Lӂ]4Zwtns.Ff\T.jbȼ[e˰&*S=Q+jg2d.BvEm5Q)baTDY28Dm5Q>/b#S`EM)ڰ]l/H6i>H5пԺlXzxuQ E(6#R, b<"uAuL>rb<"uA)>.TKE /!URzi!(DEG6/), MNMYjb#<´)j'6#Kۚ ˚Hvb;I|GҶ&Il9m$F"-'wt{qnc} ;}iCTXC-FB҆( Gdž҆(8>O,mSFc|RGe+M 1#Ƕt?tPڂlN8='3ձ SJGlPznREϩ c=r.,`\pպy6[ K*{@\|/l Ov֩|[Z `5l9*lKlEKm^>օa$v:'cQdU<uaq hAu'f l>/P`Ќuv ¶m%16OP`u3>f;lۊ¶<ϫ.Vutz5Bܺ0wS;$nbGGl[6x\A!nbvq\. Oxj#+(m[l^n@-e-zM! {?F"n]ő:RօؼUdݺ0<ÓBlnb =yݶJ]x NmgiSz~Exy;(6> eQ֏O[l-Ο_ڞm^9߭gɶRmRWܘ\/xӒ9_\ +e2"\TLon̎Ѥ[hdG+e]*k]X9MɘPR%_f]̭ot"G7&Y/Ե v)cVuYu9|cRƗZy]ٸ[ɧ)c̮E6Ӹ@RƘ`]EϑV1o+Z1պDmes^)ݘkͫxj_J~k;Fi)8-``1/rKas$pgwoOTw+Y!*J%i繪YYX͕ G+`н_ɼ+*+yyK"k*QJJ{]T+pusk0#g3aX*|7'Z,T37m E˛O;s͇䷴u?w4j7J2-Xvy8/$}zɛu؉cIҙ*fzL%F|b\0]~$@ L [xF7n.`Ga.TP{ _CRA FOKžOǙAn(`6g] AA8TfPN[g1v8)k% 54oF 7[("޿y{cux endstream endobj 6 0 obj <> endobj 7 0 obj <> endobj 8 0 obj <> endobj 9 0 obj <> endobj 10 0 obj <> endobj 11 0 obj <> endobj 12 0 obj <> endobj 13 0 obj [ 14 0 R] endobj 14 0 obj <> endobj 15 0 obj <> endobj 16 0 obj <> endobj 17 0 obj <> stream xZXX^4F_TQA&PTG(M@X Qi*(&Kԅ-ﰣ;,w?es33DGGݯe23 %w=~/),8 unIcUfMPW1{Ks+_ϙL!ҨytVJηPxC5ย9s^V7%=,uVj^ yMTEk}G_WrEи6\(QSx!?83 pg}him/IQSFn>0Q|G_wD+~B!W7 D y5 LV5@xɽn8>@D~Ä 2<&47D"~;F8yu%nKCT5r5GyGOcC/RY1{75( yQCi$ߥIC:CUp!ƟH"ҮC>2 :'`^SИN dMm5֧Id<+Iрꑈ#)n;QqMBqqاC e$XNo[:@5Em5]#|GTߡJ`=aZvM-(2wIAKPPPYǾ>v:; 5b-GZBiA`u{#upWZr 1PuP0 uԒ+Zn Cj+}31i"ˍ3 ӫ ~:5ȗJc9#6]6.T"obTaíQQCOCg hɳC P/D vymhmʗZmLEY-#{ݎmB Ke g]4@NaLj?*1M[da(ByVtZF PDs?#.tNl@Af,1J U3/U[oG=MOz|0`*#K9#UR%  A06Brw#d]l~9GgD|lf7oDg)@;߻(p@1&=I~+捁d /?5醲a:컣|S;|c^lpXߡS#oaSR#f IFWȹ+Pkjc P̳X7]O.@WG%ܗ~ 384‹8*jt&[Xn+MՓr:z؂i2! 0󀿤TK:岀lb!j ]2%1F`AO}=X215:7= d`:{#-!8G-yzW PS(,`Q\*曫¢nz5UUm.AòeMGv339DֺjbV]oD(g+BpfqncykA(׺5Ubwwo8&5 dnuswG`}l w+. k7z'vlq sCH/J3vOhf({=b ci RAzJ*` >VZJ5]sc6@}gƥcӨ/XB +&{uG㑛39Y y tE@/ ωl#SWa0` iY׭yg=^8 Zu2'fR *8M  j1w^g2Z zbSLq |{RDP b6Uj^<I8vM>`A{TŤݢHX>8r1.;#y^I>ЗNBȷB]VXG&@ B6<ܣ)2N{dfI9*@,=X9T[3jIBy2 k 0D֑Ooq{ևos(N5w} ©Nflc,zJ R3%jh!Ph_L- +xY{'sB=p0}.fYŤS_&ḃ \r ](-AAx;c>Vq"LfiёG\pg%dqVUJ_p2LF dܷ˪o| *[X`M(~ { y;A ;qrwϷYkڱ!q=jaq߃EB#nQ!n}cU %6Oa3w{DQ5xLPEJkGlw \$?;]\ϩlP+9NHSu<Ȍl10g\JJϘ 4H-r[HRcT=uJk$ALpedx'e5bHoQpt(Ҥv{X%hm^wsx7^rEɁj*AF+I+ ~jek@oBZYhCd&(N.e` 7W5:0 Ķ֢‚W/"#"#_6xz7oZr"ßu^ݘ(lõ559UXRwpp?چ0o9ek-#.OsS5eQA~Q! 1YI"BKet }ՙg}⸪r‚r d`u*txM0?*middX 5ƽpak}rV` wiow9YOOmuCGxh(M`ߙ?ŗ/䤯^\^VV*W$/DssA1tݥ oZ>Srb" ?XT/[xFյvA`laIKE|K@ A=::~,,+lREJr2BܿGb*p8[vMYY)3j5e%IP\O .YӥE|0ݳa:=-U^Jv >IU}v} +<ϻFz{9G tn1*έl2BOPBX(6: 5Dr/ 6^` +AL0y@ꪪKP5Q{nG488e:WQ^{!U,x^L`',+?f%-a0mIVlM+)K"2Ғbyi tv |"˖5س!Q?z-jЖpiY2 C{FO=?,*(@S+A-.,e:ػS+9) jp"j6%OQo٠P-0h(~ؠ D6#!9-|YԐ!'~.fC-zz&Ogܢ.//[ZZ'[z&f_>CErŅj*/W^& -] .pq:=:Iz[7O*԰5'8գג *PS}Sw7x_-j  ̗=rEkW.3>М2޲kd:DAt<@Q> stream x\ˎ%q ?^]|¤`&ApxAh$ÁİiHsfF`<^H<'N͊\(q##(HK92?U| åQi>e\𩦣=h^ǜh9أSF۸r\H%מvJЊkoB{P#GD>^kw="IMtbJsW0ms"9H2 #m8N~-7 vqJhcR"!HIHzs$H.]9w7D2"HFcڕH ɫn0o)^Du.]ԥ M] s]&0 li N& 8QEBbFBJ\ 78D@ic j t-KіONIRC֝I'؆qG$tp^h;:+mcJIpCAh;wڎq읶72N2'H$O/% A$8 NWE D(tjLɸh;mGGAi;ktNW}cǤ̐fgNN2$HN6̘؄ΗhPaVF42 @ l7mg9h;>mI9/lI\OF% \ )SŸA 6JF# F M1cz1q0wU(c܁)/FH1F5K*GbS03Y.lC`OFS/0&s1=Ւ/9"nedVjN2gJycg:\g S{Љ؃N, hA /t:a?AaHcRQ1K {0ϵ`{%}d.WFu({0 At`ctF}bLz͵``R#Z"ӧZ|Q49u/Rߵtl"}etC }+O`5lOw*npKӝLb%䊆]U= ֘\P+#ޱqKO?2A?~.r|,⎡U؊ RWhkqq{@= >=H[ɐ%'{@ߟZ@L_>ZsZsH *u w\]`s99z1c\9zHd|*}$`.c;b *] b\\ҿ WڱQi`<*+rZ# FsFa";F,F"U F,w&eҀ1ƱT*7`\Uy1|1:sEE-DcR1X[ B{+RR-G\f\}Nc )gŀUB+YͿэWc1Aw-bi,0󌳌l<{uǻݫw?ۇx}Oon_O؏|  ?'<67jpd]Rnz\K]2,#[)%[M&VKU=jnb kZXˍrcrcr#/3׷nݼSgcԶAUjXXՒ jUM₩bUrT9kS*3K UMt `E`&AOU"0 '*YI@En>qf֞={ unL˪F[g/xT Նղ*Yֆֲ*Y9gÍkY̌k'*YIDl>ATɊLL dE`&A'[{|6ֹq-9oצ׊*Yeh|C™oJ=8UB8sփS+3zptp&SكS+K6UOUB8*ɧ*`!I@TnSF,<Ȑ-n+to%b1^U,f OϞo*i1͗򥪚|)>_TQ ͗EUK3[M}B7L [ê j*5ԎōUW UWҕ-ij篺ҕ-ijO˥+[g|te lT/le+[g%R:gZ&4astKݘxzR;&[ᗮl!/7&^xvLlnLte=͍MW^C5/MWKste l4՚l!͗u/=M}B7L [ĦkjρvL˛l!l!lްo1k|._l!͗|._l޴/nB61GuM)Ԏzו-ijctcbו-ijctcbו-3珩l^>x|ѕ-ijN/CW~ s|xve| f4n*# dzdgthxN{rcP-Ƴl13nLxَ狪l1͗EUKv2Ueld/SOlvSqMh #&^T55g?0{kxvL|y11q9ĩ+[gD?{YO랺x6_+[gON.m!Kxo7G7O `]/jo?鑈{;!kIdAo+_{E'I oR&(*.y\%R Ȇqvq׾ԯ_>7~pO?{|ǻ~z矾xxVfE*B޷[jc×OOw_>}cm|qq/?Ӈi7p9w_^=||=7g=7O7s?}N>}v>?~s> _{͂m?9H}|?Ey}wvW k7w. =;g<t.՟~7t/wU>HONL89MNQ19K˒s|)9Ics"9WHNݑp9=FNuV9DN 35 9BΆ3,9@3b^*٥\*"[*Y*WaNUGS-RnPMФ:LJHZGjYDj?&Cj%Aj $_R%O]%[%ZR%cXy%VO%'TR5%R%P%;O$L$JҢ$[I$GRn$FD${C*$AR$3@^{ty-~啬)^Q^[8y9&䅎gVB^lˎlD.lʞl5l~lclȎlT_#X~OF=%_m  endstream endobj 424 0 obj <> stream x}Mn elpL\ɲ9ETp`Z1·/ IfQ$>͛LTZ3^(ikeaV=©ׄm|!<ffjݍ$ilک$z lOtQ61_0iL*\<ֵr~^.G3rT0Vm HUмr ]\c'?[ĩDž&X̑Cb!&R$H T❗zۦ^ƶ"uwX-ZcY{JD ^!e!H LR1/K$cݙ٤W9ܚ'ֺ`|z q2Y~ԹC endstream endobj 425 0 obj <> stream x \~3,000æ0" $Z-feھX9f-VW=ge%e-ߙApm{8{Ͻ4|bDڊHP nWiqI/+(-5|7˿b%ec:{+գkg8ƽ@8fD36;9663g+444il<+a wd'WLo1环lK3@nsiSGwy3ѠnݘO9goG)N{ޛ6㳛W=SeJri\bso>^io([3ZwJ ^e_m0'cAC?půpQGƺ SB7]G;r*FKB%,A&4~~E,XXE:<.?A~(I ~t ྫjF0h ~;|& { ӻL" '+԰w^oT">Pq?ͱBuRSNVٍL1 HR1?ҥ%(:~iN32?]o5 -5N~_=)"&A4>k&;n%4=x\_*}s(/KzwXtӍ]x_13bO_BB3[x "PS\.':e>yݧQ0sb b_w ׏p x6xg>u.܇O&p>Ń0\jF{S)٠ #.@ՁjGAm@CAZ,0ĊF\U Il'YP&n QQkQ3P@zJ~N6>1 P B' Ap;X1?yc^*(db[Y|l :A?}^ O|_{g3ϔAo5}3}'>O|F#O|r*ak#O|'>O|'>O٨u6dJ<3{:M!uHL!%<}J>O|'>O|Y=K߀+) $[l-$A! :hsZa#< [a;<~8̲qqq ⻌Fb\hxMQ--z6HGaASa6̕=N9+.7.=yqq3syyf{$<.u@X ܹnKo9Pٙٔ}vgW|ӑ=ucK7qr?E o/׈vqgN[;xH<,\X@2F,B B%L,X fD65Vv[̮b+ٵV+A%RI%%Y,UGߎy]^<7Y"O?"7cs)!ol0L{sxʒK?ړWixzLSLzycxW!P*3)n756vc0= ۍxV[g<=\_T.do` 6Mt^6l6)j&RT<lkDR/s2Pp LPj*ӴbTE^Dv%` ϤSgEv9v;p: Hó_aFpm@ҦF>;x[?sES=n[CR @XLn÷#6jµ/Ōsi>wU/ogMSaeַrk/y9G jdc; ipM&,jj0b%hŭNgJOix$JiBj`Ej+$fz[}jW(= 0:XTǂq3MM1-ZNjF)؟j]d3`Es]>„^n71A76ԷopNHH?ުmmړ7xk{Fp $׫,XOb&LEU3~bib?W~|Әu+މFSrv9k,$|qBd29E&K\*2 B[ uB LL}":e>7A=CODGmXۈш-JW!.CeHC2#~"&){Awof'!|MO% }.C3}J늋F|D|D}%C7m[=$AxUn+ nA5_"HxpE"wE!#Kx\69{!&9{ڽNx*a7h/^wE#vQ稣giϐ O$BmGunF}aԇP7>ދzݨwމzF U3oE]z ͨkPoBQC6`fjkPWD &/E΄x/K\!|k's5p>L800p.` P@' $r 9 O YB0AG".\! *BV& ED= רq9?FCPG}]\wPꓨO@}v\P;R"W0 )8 . )6Ba(aM9F.(;v"X."Ҫ g B9aPJ(!$hFB<@#B4!ICvz꯸aC?=ꗨ_~3OQ?ݍ ˨DK/<.P;P߆(V- ]ń \(ffPXSd$Dل z00`'2 V u!FGH% &`"(A$FW$DQQ먯ގL4_!Z/gʗ/ݴԾ|MŃW.ՋoZbmZd-T //t]}!\PjkzU kk:=hukVýҚ?li뵭B ʂ9UX6|}ަvin Cs޹LȚ˪6֖I)e1eYsms˛-4oh~YyuS9@[v^sfS},YhVGm+6۫kT̋;Űp Z№qĖ؃= Y:F ^WGo%di4X 7XB AA rI_ GNly^eAxmDV-Ҏ֊̦ٴIeEњq4ԲTn`Saw@dFƀ?V_&>?*k.=_=V8͵V3ީ\ vƮog:gۺ_jW:j.qƸJR䴛ԧM:oyi:iZ d<*i^cG\i^Vh'yn\T>)gCm|Wf}:/j$ ~oIK6 ?242x>8׭ g,| }bhŝ>xes<}CwGJ5:ed#Bϻx^Xr~7wo3 "%.+a90K0}5Up k:n&X7-nua=lzY#;^xBw=p܏1#h# FFvp؊kFyov6x Ws<OS;qem͟&>yx^3^W`7 {,<:o^ooxއc ;pB;X='Xs؏5;&գ:ȥ_`۽a&QpcyWrzl<_zaø<ӷzVێQ{ճ:'/ŋ~i\=XDiob9|!GGǢk: ӡ-XC)O6Rv [m53/lWp0[g.$NP1g 9=Ol5h1*iHt߭J1RbNTtSz:ƪNE.jKllP$Bȣp[2np䭉CwLê}pHߘQ]t*|XÌY~p|׵um3J4_*_Ұ °0kT#ú 1a~uCWKl.Wi _3aI5I=TyȭH$2I_d1Ir\v&oa<1483GG7C.z[uM{ mޤDkȱ$ic5ZC%9Ǡaqɖ6IS)"{ TMSLMpHtch,3[>ݤ'>(9밷Ƅ ?vHݼw۩=!~ ihPgHkh;ߩS)4 ڬL5**C5)[Qa1Hʫ֨v \jVIS:Hnx{*oy*'vSp I?vgL?3917˟H*Web5BURH|VbbV|ؽFP2xZe԰(%jS^wt6X%*i{yA~u_aRP ܓE - ےT֠05 cRyB,¨kFS2-~ y~a *+ .(HLvuS`-dSDD 5^U͡-ܴb #IRI&kj`*KԤ"R,!L5֤DkH`ZxYuqFNaGydGκmpXŢ&϶Dc!.<$)w7@0  ,mQ*5H,m-F}ܐo|xP;M?HxŒ˞\:sjSXvuݝYeU-bes7Y9t)%~!vϼpyPh[uT\߁:WjQ K;t]3o"x C:X6[BM$NNEn  n~4)')G{A2ğވ ?} \U>CM眚y>5]]=wuwU]c:cw:!錄$$!0( W]pE tV"DS'\\Kb^I}NM=&ej:{>a,etPdMBJ>P#2y. KTX~8-*=G6h&x!mosC1ӛ'DRNdBِ66s8]On]D4NS`ÝP}䃓Q&Xf<P]`e yYRkI,ve}Cb}⢈?piECt.x%Z#"cj i6@FRdx 3} +G.YpavJP8#(1 3 #?jV'j W`BXךI4Yh]*0ZDcB#h 0<SnV@dY uABM)bWJTm"x g\/0'&W6_wd+H3{ 0@gVvs|,љ9lh/)[Ǿs:_\b2ܱh(6%$[-^=',5Pf9wVX@r< B\ć$_$w: ?#i3ݠssWr(v8oZL 40pd|9{DJI9:M[#uOm3Z8eb79ac2y4h:$OGjU)Kԑ{ ʝs/yd%"U_wbcl)/G8 ȋg)^(s=:iDyypZF9f)4J er;i]^ {iT51PY;ݥ꟰܉6x5=m88G^pﭭ-{uLHKiDw.-ť02T{& MS=фD]ZK$ -m]\kOc3,2{n:0~z*Oٔml@pJUB?V*mn~G31,v>3) VRv]_Vboj lVV 1:n72~Z?D}쭭|uߡw%Ls1# 7OvzH4:̠Wǎclu> '8ٸ<,ƫ Წt `çƃ^Z6L\m|~>; |HDQ֥ԕKK\uYܿ,l*$9RL EJKo/PcIz< +}HzG|~Lޱ"XF)ߚ]R|4I?e~=5-Q\j񈐿\5AΣ>du.KSQ1fvsn,?tu1 @R`\0L"s _i!u;w_m6L%Z՗KMX9ƚXxl8kd HT% c#Iݸ_(Ɨ¶mw};QXnh}["e0pAM;vΦRSm| 狹iF=S>cL6KHXǗV0?L&˜cz]!%!*~0bZ%7k}q6 $#>}tq;o= l5w4 2B]}] ؕswB)VDHDH$2FsIlez8xxv|f'ܡDʶ<'LawpA~"0Y(͏gMԌ b\$r | Fx2C_N$AJbbs[{|bqN*o3!pҊ-q:(h Qꨶ-O5R%ҋuWBh%D~O 7qAO\:-IH@):CGy!CF8ʵm:Dz䏼)TjOy=ILHa58 -ӹMq)@ 8nwGnE8͆'79y* {>AB|kL{"&M7'}G3r=Thmzk$;7Y*ԙ 2N̙ 3N(2 JlYNM3.PDژ&"1]VPolY^:Ll(|*nk$k[~ `0@, 9l͌quɊ/жA"N'Gu"Z$ ZAHq)iΪ_?ex˜4x`>4~"~%E?kkWéG?rwjt?XYӅ(٦h>u1,˕ ~-..rR5]gE`"֢iL >{~+WW"LmhFB--fbzyYx*n[ @y^޽Hz. F{àgnwRHF7r,ͩ@Q3箆Ƽb9v#AiO8f\1+"ll8nJ118>Gofn@ݕoAO qw  uU]F 6>)U/ %_wpHk ."s}HC,8f~i05| ލ)drYjͧ--%(wr'KH"JwU +&JkK-#LLY4:h+}^_iHHknʻwWȓy_[V+@ _yq&DH뱄*9-dn~#,b? >9aDs 2ILݍpJHMwH|o9؅ ɹLfygASeFT@'7AZ:R5H.N?oؗEXg,s+2yeԗ>oQׇ+x Nh/Fi+]5*xzyΚ,5Ϛ:Y[He0Zjh o[p|e0#H: @yZyHMT`0fk1UhqV{[>w_A7}_S[^:0ͨ _N=\ut?vk.j7Nvc.Ѩ--Oj9ܱywLCƐ;`щwM^pm[gMvpzy0!ţvmc!LYlm>>nJ;2I~(ֺ羡oxKC5C`H"٠gc\Ej«A; $msY ^T`/1l#]E~RWڣsWsAͫ  ٫yn`E{w-`>W9Tu7ol;V21tO˺FkR=zjI0Ou Ӏ|& ٳ^75Mh_3j%!{Hz>x}$Z]6X9epB#"\4`0u 'N9!Q}42ԕTā[+aJRq,a:a1e"yQtrgҋW1Zb_E?Ja@= &+BC gFʀާAf*+e 9u3ܾ}Jg\i,km% {}ӂrl j1h &궡#^<#^\Sy|V&Z @4 Zن m$v^' u@bK=Ȗʲ]HfPlUJ3(u &IظVD#W0)]DՐSS g6M*Łߦ*2Ȥ/}fvCmPOݶbA $e rMNX^L{{*D* i=1v ϷvWOEgqny2e%& 42<q$N̝<ѦNmr Fĺu&7_s{>>wh40t0#W{lYc\*y'yƈ)\8)ZJjL;IA–6>P3ftڶ:<҉+-<*|y0PjosTF708 {@OcF[z[CbmX&'ެ^{@S<}x_%Gv<_fn*󈕣LrN(BJPhF獱, rOlbĥ2k~|Q;9pHk66YDwGSEjsht'B-|9bHB1Zw۵Ny5Y`(}+] 2Ev!wJIކSf[RNƢSw*NCG&1ɐ':^CiXrK>cH1#CFWڧG{a$#qWҡ`;F:XV8: H֊67B+ܙ_2*\.HkLn=8M.h£`Lk@8zp Q~kK-)PXѮ ebOݢZFi:i@: |Ft^8-NJ]NrEKQX,LD.51vG3fԲ(@aw[ 'Q)cUũKe+ʉڿ`DOB?=sS>TǺ&N:=[o:y=h3Kݗ;?"sE(ܹ"7Y3|tO!NQk^.׭ΥOIѓSZ:3oӛe-?P'JYN{Mb!Z NԄ^6E"gSfy"HTlke3!/Oorr0"NRy e;BG>oj 37O"i O6ȏ q\\a/[jke٠f5y97\]}"'^I>~ߎOԑ>-0L`*dKkRU:щ(7v=SwW ܿ Qre|Z=A#~@FZgDږ9웳uodrìT,F??y:J\7˕fJQZ[RfCsS{te(M{Nt.iW. #.Fވ֬k)V61=)Ҩ,44H/`CF٬Bi?`'EyN<Qd{ۚf33ZVV'nhg175F≑GFCN'E ~k 6m()/(dfa_Y&>Ͼ,k<8,ypvLV%(.8ɨrl.=+w7])'%Pf7#8.$ÉmְM.a(c1'džCf}S4AY`MfU* LVd]/or}EAAU0dcUCNrS=; . NR/neIkzn|el&J ^Jc`Y-Q4J'-8d)wchET b Ў=DH"O8:(lA#h|yƑ}T%͝VpˇV7.i6?Ec2MLLYM&lڌ6oDƚ]e6 iκo0)"3fy44b%-WhzV-vz{LLjkڣPh/ZnњK;u:ZF=sw?#d@f*Dc ȁEfB]GG0nܿd,o[[3V ȔXXEXիc)vĚ=W+ gG"05>mO:.gt=٨8-%wxl-=zJѮl$ ׈)A(!N\]Wփpc:`T,<+t[ʥܵ9 ªh>m Z" f$L4-0ZRE$+$Y>)4r[ ̲XP5,j)3dg݈6%JsY %:i>H _B*J5*R)"xߘW Ust~%m# ʚ55ţɠ=e= !Ȟ*[VqvYe!wԥm|V:]\MG:`4$ix}w_P(w( NسhoK9}_ut\{?]h=E˳N;H8̕&堔 ">_$KZYXYˮT| ?,B_@k,X9,|%x&:%1]t]N] \|"olr*JV)UeᤷeM^H`kO9AOmK̯LZAbJ@Rom|H[T'Y\5jj!l ibhQj< #1z5mB3A#(iec_"yϝy8~K\APD;Tkw+[Iez4Q]zB|lLvO] Q(H<1ү]N I8zTA:JmB!$|;4; x])撑t5\/2}+,!{TkUFVyϯKsp]L4Ȁn$充VT>8y#,:+ʫɛv VQ$,KWöpB /`tn䠘J{T*S=xnJ./79Ltq>c}#Vڢp$Pfx#h7rT$g%?n${ ݇9>>r ecqWJ).ROΓ\OM !Qj*,x?@ ͈,,@}X ‹z2,(nq3jO6q:T6#_nѪMr\l5CK0zNq 98~~'Y5XbqY0FaJp^# 8`qc[bxF MDl7!9W?V̶-f3#uvr0"ZS3\olV7"FĵiFƵAN9x0owvYd!͍)jkz!^u͵PȲssBI3չj>G ݽ0/8 e-VYypvbLPs(!2~g|Uq9xiA;ew?wf{{kj{{3ţd-lO߁1o\{̐kV{plYm3@x7Xʸ"#Av9[N[\Z S:aMB?ᦑ`$t*qRR-uH{.=z fOo=#0x HY}L+/|Ft㕩ε{E LzAO4>Ԋ9sWm,: Sw.C _Y+ tկĒz-RRjKrGи#iI|@|._~Kiڞ3vpOmچ,'\۾yFa6ai Gjݳ"jkz1Ts]:T%`B(E1(S¦IKծҩ(]7iG 3`)+w(#\DR,.\hQy\kכ 0j#n ކyLoΈAY?셇 &| SΊ]'Iyw0񟖡H$Uvg4L5Nq-g?YdEyxN N؄.LG C_ŗOXꞁc9wOC{KCY/)`UxCVG礌:XN u":|o+wpw\y;AV,#Of [X&1  |wi| 14PrN#[ siV[\cYxH"5H&F,n6'vAy dn$a_3}|ό">Ç{m' PаQhXU9\C+)65z 4M ɞ.ΰ ^(?`4Ɵ՝;^MOe,lZl syBMfј|0֋J B^)Ͼ2WTD5qY'zm}!X|IB.W/&6n;_6"&'^%8񂔕/lW,=DӰ1sqQm+z@umi<T A=^+@ӧ'O0,Г'gFGgN>1KUHɵq촚fILz֋JBc&@ca,Dڐъ`c@ ޅwr, ;}?Nz %(Aoq6?"DZK"|mP0&pJ+Y^' %;Pr癹\, |hX4S)>˥RȓɤF]ҕJ^#d&HzH{&\;JdїgGGѷzىT2-qފS6:# 6 j qn($,r]2jP Cj D-BCo@З 1xTl;{ 0/w9yD8M: i\27[tܵ)*KW+w᫃_wϏn]vd8Zqx`٭#r^^q˔F\qc0*ՀR[L,#Xo蚉]M_pU9*Wyi2ooHMc2, =S—6wr v;1-ȵ%da&XQg4 [uoMj3A_wpP)A|qcCCrjp 2 Ysԍ’>)Q md}㌶ŋw?, ?kNk<4?FC(Bzݹ۹nq熳q/4&,!:8 Ec?&#/B2Њ!] rKy8Ha{ͦ4+ %~uSz4ַAڤ~!ZT6syCg'ݕZ63"QOcKF^qHK6Q| 'bkGlb ҉űV,7DT'QIIgO+W6@CeŻ;r+򯊵./6|x{ok)Ehy=Nɶ'*P|_ljR1.4ki|kok۝|[B6ݶTfTIǷ{"NK\ȼi ;I?f(J#/`R^f5t~m@ @c(oq'Z0Ddr-'N22goKQ<c'SkvLyyvU%JCŕǿwJT%֭l*T\9RbU*¢ɇO@jML*0U&?ڵ{Gl<-7i LjO R[/rn;IA{o. ?$pL@F뱸#ZP@긱n[vI߭@C\P]JC>ij׸v7wPQ&iЖ'͢r y9-kU )FS*HkT ꗌȯ;&o;v'KX&Ր1մFt;kG[M rCغsX {ݝwww ꮿ@i|;ͲI/A/V+>ΕZlc"G0ɿ`,=RڸgeVaDΉ`hԶC@Kv 4G۫$p U?o% Pn TM%nZ[܄|%^kP6w~ZB(k ]Bv vZf,.3O.U;r-˓r!#x4լnt5l(KŽ5lzdW\)tn=(3iս^ .[}AV٥[rhgi [ SQxPd7ЄrȠQN4JˇgC +Oګ]-eXjX`4O\V^Xԧj<}\2:}}0 -3w=ڷ LSwpWE1bLVGH[$ւ뾽e1b e )4,>HHjvTu% &tP_y5e(7Ԑ~=I,Qr._CZ˓3osVYZٵ+U[@E`9N^_5gHLg\5+4.(<X?wJȑϾ(BnJbH]ePV& l PmjT TP @U @ ( @ N9%¸Sًܟ+?+{ŗr{O莴a-sj뷶L޷.bIC\\( h{YM[Ú ւ;Wǂk4}x,-cΑ2F3\2SѺW8flƚ8f^ -\J69 p ߿1xڏeŁ{9&sh@`Yk\#KHE, ^wlW÷mp%HsU#d<~X ՚щh9HM]F;M޽i!߲PEO?4ϸ a ZqX{?wjG򱎶;/ܵ~[pőnE1ÍȂ:ƅKi4VϿ -sXgѼ սXOZևR.tpZ qKQDZ/4GJGeĕI۶B>B[pBm%FG3+Xi۔dG6 Fɣ_=ܚ&qΓ8-S>(U`[1 ᰒX`Vr +/_?4Zdߒot!Tb#4@mry61ڥƣk0;N@SZnwZ<@ :֧*Z͞.Jʈz7ku:8˄j."B6x&*>D):Ar2xw.AJ4a)Z 8 B@(l?UZ#~0/s"o&,YxJo1Ӕڝߺn}zE64On=XgUc}q7D:5@{2UnɁ}W[ogKAw'uP^Wzvh6n~kZlI+ðVH7S`O)aO j_~,P]-;L{۶?_Gr{}X:+P&j+nYN7{H[~ >>L>`7R[l.FkH߅aC}'軒䠊Y#5ƜΨQ\R !T]W[ ZT5A%q@dcaP,Fج[鲸洿 {to3v|!x!vihH8M'Z<+{KZsvM[@r/΄Q'cQ3*&ˎ+o[S%U(Ōi|Z x<~ );Et_Ʋ<M]Av̈́ä^KzNRLtH\E0?2kJ-[1^QJ{uqƀ; v+w]6-nXatu7HDWX=jH, u'Ygp`wƥҫ98mP/JygیuܢAS:Uy9!GQSiZ-W{b$&ti8 pX k@Ώ/̋RQ_,TVoTϛt5;=/xWKp[T_w|ͺ/ FW'⟛ۚZg_S0W!4mwD}GyKݺvPHi/k1iWZڋCQ+ި1Szb,)2,7B J @LQ>YYUw.f>s(Ĉ۲ow}H!BW34ҼLc^gjU7gY_˺T7z~hR( ;'*F׽HNwܪelδW*q( a _jz08M@\6E.8\}@?e><)8}+&:C(֍ C!P&$qHY7ec[ mL|z@t".W:m٠초 knjВ~l7W ~C(%CJ V_uF^җ6sxfЗcQqڃԼkyЌ8tb!eTɅ@ᶘϙn_D7Z2i1h 9 V>ƍ|c1TCo]iqY*Ik0\DoZFrx.RJ+7Tre^Yva%Pfle:/\jSFV (Xp EQQ,rp8rR F GI92;(H$t ,y( uB;:pyA|)%ΧSǹh##4nxjSGw2,+u^k }u:yDA?Pnߞ@/.5o:u_U{jzBGE@k4_oیCzșY&UA( 7m9rMi1ʾ^L Ý Ao{yt~`m #ĭEkyOPfqs9: PЃ+^- H":rYWL>QA J` <9`s(don4NH+9%QT̬puxhh-:pC;pHX?hh8]LmJ[p( I À/?j&ZAEWM3%4s] Igaq(X,Gy}yRgv:,HrhtIJ~U5 j,>n8+ YGgvu"kn{pdiSi@U~n5jߛu ea7uGh<gYkB"DV 2DܝIa_EJ8~D@2֫qri,i1jllss KkDcZuc`nT{;yTƐPvΎEM.,VR#_[r6@JPiJ0zd%DBOơ?dXmvIO !ީI3 n'A8Bւ0B8I%f[]o_^BsCcw!(K7?5cwJ-Wm+oUTؓ i<=A1Zodj $KTP9*4PgsE(<)yBH2--,o,Txb44m1Cb% S";4tϦmfAhؖR|64{&=>y<&Z0Vx &GV1$JnF>65] ,%7 ~>O9[ B=L3h KE@qDA yB?j>`g;ag9;Spǣ$()9f SFΙ#T+fyHN^oՇ\15HϼFkف,bs7)6Ɉut4QM7,y_mFc)FD;7<7}M.5),+XZQ6+RP݅rip7Q9.SF"?|~zrN=s9"9(g+FI_]!=9虃3SD9H93k`2sP)ǀsu·RɃFxk)ͯqě~ٓsy ռ9v|卫FoXDnXS :c543Gvj9#MQgwbB{RPIF+"sv5\V!RQE qH#H›)\ˑl[5%G$}Z8aYx%Qџ;uJ dxdO1Vm,@i]6a^/gPRoҫ^k4_pd_Gk.vނjXƲ׿0_$o5w}P3Z o žrQ=;uCXf\#ۜS8gBL6%kpKapKaUDgZG] c`:ﭴ՜ֽ,ջ38Kٜ.h౺O&ĠUꜢ&(s@L+j]aEа \+Յut~ nZİ%]>%09}טCR3PR%\ R ㌶x`hoKTRzw8p+SZwsAx9"|scD l]|n}}ȖJ318V+c+oMŬIw0T@HevA(`'Ի?M.9-l]dG֠Q`. n1+K{G$jD@49X4)tQM_~Y_ݛb:_ol{qϹz5w/5m'ޘ~nw]s+[8{[|PeK*,g[Z W~APg9vN/:gnuD1B w!'lW3;?kE* ()[͘I%7N^:䴙SX^86ۨt׉8~qMD ilӦEI8pxɴgp+Z+[ErZޜ\:>끶U]{/'U%8^ݑ 1m&#IKN$WYx{n[Ypw"*V`4OzV-[]SO~2VtqzIFj|B >;ppGBz tIs>֗Acv x`;76D"V칡^aBbQC&1g 6MPf`3;/燌-z:ES(~c {Tw%(g N ??Kp3Fn?z@Hn}by[XgXKnE#upkw^3TIp{IBgN7WgqOXnrnj9Ӡjs"ФYP2cu QE0ڭ:& RJ":I0L :j4m~0 I's+?RslSIj[;hV*TBcЀ66XZ(t^f2q#~J6ۭZgcΞh #%?jATeU) vEH)Rl%44Q FHnX<n7U}?/( [JVglΌg*eRsGh{fx_)Zp*i뜾Zw,]j2J&ֽ45Zu|9_5~;y>9-liP`pt[ WkLzV"lĥVX9O߃mCTŰD$L-ê4{g`߃T9gkEUhi@fiï|uoi䲘8PP!Ip44d4-D BU@k1B|\ s` "rt yF?\%ʰJX-66sjT)iksyt=nQ0wImx_95ó.75Jg4&hpij 19-\ w-~lGsSa(jP kiȷ#/DHzm!>Q~ wje|Ua,+4b,н&̙U R3ƻ+ZI3c򘰑y)Yr⑛'*a SQX WIMUC:h6߃Mlg'8eL؊lEښ*4ߑ-g$u_+x4ث"ȫ4xv׀1#LBߌ8q"&Dc*mg#K [H&D:~*0g{sG%MQ=#a2Ãߞ .Tj? dN^SsCɅY)^OXSѐsRpofiesy춠Q3hb5&zƇ%ӎ3C5 Dh-}q5pP?IC0ِMp rB/oMbO l;@{EByE62j߷hk/U9_kq mjU\]ˊVW+`{[KɫllEw2ٽbR6\ R=-~ XB> g|6U]%ν{erjCL&)΀7L?{j:@&πgk]\L\.wm;EC84Am`Ds0O`C,izI͓x!ޚ#pk\-}Q*2͍zШK02 <4`8w8 @>J+ շMsyWG ǚnsՅ+rzZ@zo H(A4~Jv<\FN؉ߪqF&܃SEӲޛ8̶*Y̆pیNǼ #詻;}P(ib$4n ˴V]j)%;}d{gfኅEe{g>R(@ūv+d3C+2.jhc9cCz*Uj^Q:1he}qxdcI\xVZAj be2` CǹecH: W>7=; R,bt[aosO#)3 ÷(*\Ej2h$HrgBJ 6zIkVh{ߙlʲ]a&l.5 ['+;=cԻFFiͨPq.p4+Z4I!/-Zx2EoLb=ZAx 9 `IWۤНJxt'.KbݞG#)1¬5C9O[7ԙtVx^4fSIf`Q *Rl k_tnlk/"JpTRt:D?g3^ota-Gp DO %&`fʸH Y~$xS@$T / ֛sJ"IQ}_oCCR~$6O7zӤZj5Gxx+*W <*zUɄmT^o3*=tRr+%`ʅГp. g+{H불O{OzVpmψKc9x73~,>"}O[S?bb։[^A-?f <)j FhvP!oŝ#f&HMK-˪Ÿq1ۭ6z,_nMkk Ta='[69-9j !ᥦ{:k˟E(;~qݣ6u TN>=TW*6лFҶ=EHGUǐrǕJl1{*i6gt zǖm_\TicX}JzwՋF~-55bNtn\ 5B u4#z5S "6^=hq׉o /?HA0pSWobȾf.5,&\ٌ F7g\EW\;uh;PikTU3*P{nO:m򗎎LڂXMPTcXetD(ؗ & 䩓QpA(P?;SHE $Ϩ +Gu55ID2%<>ڲ1bUw=(FD1pjh٬b|cTb~ruB { /:Sd$l͍T=CVWD+7 ;lZ~^?ݾ^1UP,'\k~vT<)=!:q n2Z=~ T/g!SAd4֫RI}H*& ʅٔ:RsiکZF^HK&zPvCv2Z&hw:#{lDvh7M:U.>m}zЁ} "$5Sazqv fސgO UzIa[b@wڃtV٣ye4д/vsaZ6F/bu:RmZ݂^)W-^n4 suڠcƇ/2*4X *F/{2Xd lGQTq-]% )$Xh"/,Mq J> Py - iITDp (FlT!x !kguk2}s%p{ĦR4RL[|i9e.`sb f8rvcj}hKu Jp;(A#prcu¬I8cko`]|+9XK@SջC+YHV'!KNNvcQ6A+ynfS7<,6nXٹ'aox}f$:&ptjQp}%kY&7 \k=*̓ʍ3upZ2-esƤ`Zvy6&['6hĂ],_"e1N;=DH1ך=]e!b?])2ynфZ6]!2sgl'JRVBWUyFrPݙ&TC测.tq1'`ZJetR٢n5T֠:-6Z.[V]SaqX"ɖ@u{[*.miJb 9oY%o ;"U я6 h)X)6aVIqnӌӶɕ-K-MY&%N|KoYc*-*V-<{Ҭ烠8obXXrvzU4:fwKيZ/GtZ53O$#QԴ4I$"K3{.KS_mue䂢Am jeJkS=Q#pIOޝ/6(/Vvy+;gQ(TzLj*й8nl\4*~ O eeTLKuu8ҫ lCg@CU{ErAInK!+Ca?!+96deZͫ+#52|sݕ{njr\>WK'z)YW]p5a s˼@JYpۚ{`um)~mKR5yj[Bg6-_pa=נBfzNRaqVx2KE5&5OM¤͡7$M^Д?4S{ys~q|EgM[/iO]OXtIS[ AO]<)Oy_%}0OyS<)Oyڏ>'U uxx]]CLǙyS<)OyS<1DVBPl3BĽBS_6^_e<0|BS7IqYv,ogy;;YNw,dy'Ok૘[LU'EЌۙ;D1f>2/ۙ;O5fx|񊘡،Ӹ2x\g^b< >y 5W1Β' |H{̧̝‹Y,hvAO%qpv< %րo0ncX/ƾxQ,σe^Ƽy UOE@~X3w2U*{\Lyx[Cazx+py Ns2y 5W1Β5^OR L/qXHUϱ3a+4Eq9*6sTl̫6C("Lw2!p::@u|i4n.̷.ydFԆK_`Iņ3}|.2kb2e^Ƽy U+:K,Β:K,d t)1py1x|+J ||q8|Mg.z沞g.z沞g.z沞Fy|_3ub vmE#9xXT*d+#H2d+#HdKdKdKd/Kd/Kd/Kd/KnGǕ]wݼj7ͫvݼj7p8Uk3xM5v|/qr#Iً_˼y櫘ܞ m<~] ǍLp;s'ZZb51 |*n@_e0X>>y 5W1o$ ܇S׌/ۙ;ûGg2י7og^a5lح2M? ~2 WI O~E kVZA㚓j.Zi桵j,SqP˘W0a Uwk7xHO=_5^? OhjC3KoAfqB} KRuO0Nc>8ll>8o coWIc>c\ >>~ʸal;wm6n=bjO yBSsPt˪rx6N :m`[(nk dAVwMpwNu##/6FZ"̜%mՊvS2$3"v׈Y3u:U&J'X\!'~HGƇs0IF)-3YY2NOgli3-9%3timJD+g.mSy杝kwwN {SN뺜 O/ ªHY=UDM.Y|"dcTVy2YVr4ޅLr >XO㣺O퀰&ֹP2.No姌>cP'#97yÄ9l>!X19 :,%hc򜬨O,aFpVTkdN"EKyXY+%2RF{4rϊfKͳlb?&#L,[)hȱDGgkbͯ5Ot\*Ƅj|6͔]oy\oƕAL աx4HKX4O2*֛B]ɄL@Y`[-]KBmP?;B&MD'ܭxPq=W׶u`$NOz-zX`={G ‰p-=zw ?xCwdiKz<}CNd+#26wɞx0ǃ"R=ڴ' Š2C2LGqd-ѭzC1{RE$a ˰>FWCo ubQ2ؗЖ`n9;w#C B4{b 4vb$ēQ8K.u@܋axY&Z{K:@C/(_a_IO`?|cDg'Pp{DBDY]O{?1'ģdW2[8o֭[˺uevmړH2a{  fX,B\1Զ=$, 8dT14ȧ0=Teaa#N ~ V!LTQ ;3a pXGP"gO$!?G ڎIeM L ilVE(ǵs­ 7ޚrov$"K: C.NXnMР%pOLGc!YUjLxl)D~) zd((rc=iX6'Bho0ҍD24bN%Nh \q>D0pD|[Q77,oY_T6M jU/gW5?T__۲amzr~~Y^YohkW7Vc~ie5o^LҖ64UV7MKW[ect.*vں&qmScCs5_˛K\ӫס7㭪&oiCƦښ-e\R ˪U˭ҺեU5ռZX̴nj~UҖچzrciC}K%t}msu^TL,ojz+X WK->D BY[UWAW3-.+<+y`O/eD/Eˋ?//_bcb51 /3_fe|bq8G 0mo? ѴFW\ I^mL&_&J)s\ L**` L:I/0L>o80y](mS]Kݶ+I%0YLZI0^`L&_&_&?&'M`VSgsu!0&I'0&&&cRxK&ct0%&_&Gq̾.!.KI0i&$Ln&L&&?&G00Q6% ˕J`I00||;09L~LkdTu:UݩRowW`r0sD`r Juo^Z6).&䟀ɷ!`2 LNEJ0T-0"{ 0090y^)ٕF[ 6j`&;ɝ 0909LFUZd+ՙZѾ'^z`&[00``~ umv{*MmVl&0;vttx,fJPNa|Gyf4R:) z~`AV9m©̏S=t s~1 G=,O?72444Bitppthpp2xx_fcc/vHqP S >Wp'E{Npj}nFx=C.p)!=KO~R\;})HkXi>16뀐=gFnUa׆M bXC34Q:lI[<`Nx%Tæev M;'뺾x"ݪpjo1_ӹ;>SvX7WWUYp=#`C ə}>3fЉ['UQaþb+F%}옜).:sXfZ}XS@G8x}^l5CIK{dӨW3mf1ӭ|L|Ke{q+RN45g朩S&חͼhVy V-P@.U4@  -vţ,3 ggYgp;p;SJSqG(8$mwVkIxgSGzn"B >!yՁ*.G.yv5ГG@G@xbsMzn`'^\Ձwp: }cF{NvF0,8ˎO/ꕹ1Ŝ] eq ZE^>6u8wЁp!KU1.O@<6œmSFnb l};wgHSruh4'oGR[i_+d^&>3Dmt{K=9xJž}W)KxոFy92)."VRYDPPjٝ<.I C$A!҂GbBٓNssY^/v сX,,m({\А LolSx& W W+#wtߑ#Ŭ5[Ω-~6ŶPd@pslC-}3xxb>XPR~OyN5sGzp/5㰹Bփ9| BH3| K/NxAdG(pr  fCT 8g~ g搉d&ir.ڔqp\^5ib9\ftg+;*yo*=/KsFYGb86Si?g_|U&B,@T ml" T.%^ek(r=SXҦ)vd sW̩CV!*tG⠑^gF 0 րYXhV5|jqߚ3wP=z)a)v9Ex"ߑ5 ӨI [5I&x6Gnj};X *9sG:l/vUV/5R]RΟkw۾3w9CN 9.)CN{yU.ۨ _s2]ܼ B VP+V"Uǃx~Pt"*u=xA\cJj!%'V'!M_`B1[b]6b>jKZlQ+6S^'] ba[ЬMKhCk.Q&EAl qKR<-Q_$<9C1X ~T|N|hOg7eS~pr`V_G|E?!+-#c 0rucK(oULtىa1," " F#?ΜuսzzfuhI0]p%6YjW&4@`f%Q\AEzj@ ԯ !`A9x]Gއx]?_ |)a }­ ;0y WR<7% gWa"X `+\-+ Vh;4cԭ`/ 66uNp&K0p5& ] O^%CB?YbV %#оasDބ#f@0ˏ$a Z$m-#L&L%H8pa:tZ$uh/. pj[ wjL$A/P`΃ ;9Zw1YM'zp e]1z31鉹o/#(z:*1+_%Oυ9k:TfwX  kK Cf 1 nkƸfESBvHuAw\{gߩZVWL pUxpmr$X}6ئ"C?./c{r\ #UP?aGFb2\s?,>Ua87kKdAvSexU^7yo>Osy&DeDUQO4l]ʈ01Fuub73Q.nNtpqOtq^+7'ӳsMzy7yO{0Yތ3-d9ʜn7W[ͣU˰¬Vy+͚n-6Z']N'ڙn__'_,e(U^Ujڨ*;S0OB  2,$=$#dqƐ!CCUhҡuCCBSCf.?|hv +V:,6nX|XRXjذaWKdž O O 8|c*`D؈''q|o Gh,#8tlȋ<~?;?v>3@)8znĹ_szXa\sg~frVԶڕ{h_N lÉȫV_uJ /tw<.8x#^|qqcmRܬ!6t7 0l !V& $\xB b,:8/kbqm+h[K׵mm'mh;Bv=[UԶմmSm;j[[}j6* Tr(u68׊qߪ)8T⑸ Eb~q*a0a&dX +a#lp6ҶݴMVfkHۍ7rlc=X1V׶MV_xaMd'0\+?.9GYMlff|~m#WZm'ߤAM.~ow-ȏYa)(U S 0o;T$MgjghS}Mk{V۬Lk," {o&".pZdmuNH:_@Lmk۩GmEFt5'jZ-^,UyM9x_rqT\NoX<16P9=zܛG9z$+U۽ϱI&IףS%Ynv¹~0m1Ok{=vv]q8`7U{z=v4V{hw{jD6==VSU꩏SY=cyڻN6 (\r=[[k[k[kmr٣CX_3~ڃKwMg!v nvg ! jL%^R)\9z kiZ4qc` v|u~3PF?|^'McL kkkm~\=r+4U3gW#y5ǎα:;g؍=Fzt6靨kWÔ>4溆{:떦arcmu{4ESt94UTS3={5gsV|FP`!,/xjpvSrF;@Y\>[-MUW|Q xyIi^b`%;x@#_z|۞UsqyWs_3"prw;xV`{%󓏸"w@8)/)8w,Ҭ@lN _v@S@_SSt!|mr . w]'~;x su2^8$’6ZFs|p ra!kv BeeαsX".5v z^=zA#xQ🏇pU(>ܳ4eeLĵAba*~]îX~ ^*$m:Ev:6+vv"TWc_͜PĶfb!+aː!2Wͣ1]yB׼:ޗwJR+TR/wZK-\O-H-SJ6SV*h-nA<U GU]~ UT6-C?d"L(q>ث>H<ЋEЛgš/kC?@ցu4.K,GlK`u~g(Ѐ0܆<8#c/x ^&2Lɼ ,]yWywX{TX򾰂'>|, tV (٢ۢɸ$&1a$SJp%riWkWkVՖ=b\]\]سk{ hf6ccv"~N_Z+^!P'U?OV@JSi#(Fi5J5F<Ʃq"FWųj(*t.*)ji*j)f"V}>ϩ9j2Tx^}kF}#Է[N}'%X-5RTTrQK~*JQkZ։j ^Q?E=E-ڦC4P.wGxCSDCuQ]oh-u]*mF s1ŲЋf{ g.gngG(¼0xyq^L(lW W ZZrqsS_/E5Wͅj/j>P PK-RQuL. ΕPOFZ޶Z5m feP1f4T;1fvch:BcuA]&꒺ꊺok4ϋWCbMXܒf|k| }} '~ ̄=h g[{ؿi1W;.VՍ?R;CH0g02X a7p3{`nf{v&{#۝l}]=&T^dx{M~@C]^_~dxM ;m K#LwP]Ml)ޑdz{>]SEFS;{hejeie>ʌLЊLԊLҊL֊LъLՊLӊ|T+VdV HVK(go:)2/*VdVk|7Zo" XN+H+V̏ZZ%ZZeZZZ"""k"k""sI4R2IQ ZZ""hEhEjEiEkEvhEviEvkE豲W+O+_+s@+sP+sH+rD+rT+rL+r\+rB+L중r/*rJ+rZ+rF+rV+rN+rA+rQ+rI+rY+rE+rM+r]+VV7ZZ[Zl=Vn(cB2&Q9ʘB+s9O\%E#c6󷛞Jj%f>YVh'EU'zbH1WqT qRqVqQ\qU\qMl Emࢮ B-mD[pNxDHH) t=DE|bbag|@qG3QTn98s3XH|/~?bK2Aט>Sģs<3pKC~ffrt~р|B?+x`0 0 >A0 F c`"Lt S`*zO`:̀0 fç>9d0B5̇o`| ;#,% Ks,U֠Yala^e lmvN؅>f}A8cpNI8 sp.щ \kp~dpnA6a>77xC&oyMy3ޜ'%o[6-oቼ=OxG.w]|7}|??C0?c8?OS4? /̯7ocq8h2G1q8i2Ng9qh\2.W5q2~7nl j'_K)kڲ|Y֕zUY_&ȿ|S6oƲo˦l._VD^&|Wve"SdWM'㫇)Se/[~ e_O@9Hr*ɏp9Bh9Fr 'I2]NST9M~"̐_ʹ+9O~-o\(\,LT. \)Wr\+r(/r"mr!w]r#}r< C<"ciOg9}h_/W5}βoڷl|}g\>O<>Y>)__/ }H#(_>_~__Aߣh_!_a__Qcb ItdT4'Y O{=(9*ja|&^C4o./ކ=M#Lj#8"Ɖqp"1[)nu)@| )B5*x2]&+ rt8;)lOBmEu(iɆ#1'6|ȨGÏ1ދknø8w7#Yah8{{{={==Uv/pdqM,޽˽>cnptܠB7[))ɖzJyb=e=e*O'TTdkl[6xjzj::gOO yۓxzڲ^\]fs36[m^)f ;qv;qv q˶V;ofO^|y-͠O\Vz˂\[T#iO3 f5>|y=4+|6 S6+ʕϕUU+UUUUqWqW WIR']]Oʸʺʹf[Vmg;Nf{^gAvfGQvg'IvfgYC.~7o"K.n["[+ NA&L~"%L Tqi)?a[RT< LŗEkaCm|)x_y{AKPh0p㨏 ȇs4 cZq6BnaEX"Kc Kb'sz%`6Jl8< fl)/R_¸tjcTESӟقz4GV=t/6m5`5ݭ3ZTUP "]c&]$!]6Աۤ||S!FeN[Eͅ5/7%=f á;fso`4]0Ba?h|O#?XL#` L`)FK #-X 1lj˔㘕Xp`1> W q. wUs["l} FqГ+! wEơ*uE:xLs5,ŭiO!jI}vg3 '?!A~ ?"?&?$yy,C6!E~( C!B '?#2Ux >d!¬$+*jkڱ,s>l FYll[66{Q<ʲCx$/ȋ{_'Eu}nLOwOS}b@``a}_daXAdGFDA@"*&;" c//1B Q, ]YL-}o_us=[uXf"vhEklG6)b;Rvhh^yf;Ftv IQv٦W67U l[Y؞ D=Հ@i(lWg˖yW_+@А;D!E#nG"n)8kkhь19cE'љۻDWn'q`Uv'3DonW>ܮܮkznM 3ۓ7ͯ^ $@{[t?-b+VC7-:ϙ'X\\֥BLam!BuZڍ0;^l}؎b;Rvx;lG؎l-_؞ `{*P۫^ z lka^6@r!MfHn d2{Rmv@r;<w8]p28 Wǫ*~탭 tQɼF }"3p(g#JzppaQ(8$4,<"2*:&6.>!1)9%5dE"Q:r9R%R5bA!#dz~zOhMb}7gtak.gtYs[%de6jQsH?TmP*\~HuMݭ8g?L9VKw={0|5ڷm#SϏ8ʛ FAVaO2uNT-UR>&t{BS⃄!Y !()!OyjӁ#(短&wze"+},<*n}YM*D h"KRI%f-5+4l ;%uR`/miw{1v1)ݟ}};9p{8Ko?Ŝę!m# 1K3~쳶-)G}L;*iVUV D؟X6PA֧LqDzzXE)VX}^dUM9ڌ1/ۗ˻qZc ١3f7#S_qq.>ef Ba;viy^zra-{f˙~5ǚ2f7k #sP)Bk2C& SgXMĩ]9YʶrR1#[e! h]x_SncJc7 ̼"o$Q[co^?>]nt8ǥ9Ɗuc_r?L3iLzMsh>R*t399z.rV]k"K[DU0фsYGeb m-;<6mv|nmζzFsl5-,l9.9ٜsE6\vcs. sbιf6\vs-ܶdm٭ܶٓmX.[vܶs۞ew;r|dsƞ ]l?fsfw9w{؜ٷ$-'ekSodZj)嶔=~b(Cy Y0^Cܚ*)OX*jڊ̆1wpL=f5҃Kj<*vҲ9baf[zz׺Ӿ_HU}iEiƦO[v(ʹgOs[H$}s?ff1#ciƶ2:v^\\ʔ3eȜ2s_ke^ Ƃ=<|#xPPqh|hah-G? }N W㈳$<)81|| z@y@Ai)R4HCRHiFH"Hc =H M4 M4ԃ4 H3Hs =H - -҃ H+Hk =H m mփH;H{ =H t tуt H'Hg =H ]t ]tՃt H7Hw =H = -'@zz^@zAz )R ؃)RAd H)Rr;ہA ddad8@{@FAF ddQd4@F{1@Add<d @&x@&A&d d2d*@zi@Ad d&dY@fy2 e@<=@r d6^ ׃> y@Ady@ ,,B" ,g5vԃJh(i4ѣ#<{|{{{`u>CU{MB&!J)4f$繙z~F"}F9_/Xm  *kM!IYP6P6hUmQ3e=coE*zb@C)}FG*8ZqԱ籗лY!|Iكy{C*z{ufy68Dַ [moa,ފ:mcB.Kh"y!IIPNb* ;"[bjad~,G[PYZ[krjĊZ̟tFi|UqCo&Qn>=HU~ToPEϦR{b桛D_1I;/;f~#Z/2F{0 !by%A ռ?TͼԢ "-fks݉G2nog˞ ddٹjK%[v+-ջ~X :Q;;iszC|;Vx;)}֌=+**eGAf=+K)!fe;VV0ɪc% )|s0sЎG<^g8q7~tr%t|: XCK~Q C{+ھh튶 Z_(`Z^FK^A_W2.9*I`PL -)ǔ2"r;(pVFXxj1$UK|Pn0}99WH>95fŘ+q2V7qd6+g5`d3aKx.ټVNI$HoJ0c+ZAeYDxGtF> FE!==xFDN7 HLOa)d@I G1gju=SAg# 3y3z19 z1gFtfhY9EG+~K3cQ~QԧL^H/CK9UN 9S-g2y-qP#uU]S9\.ԍtn覺n[薺n趺n;莺MO]z=_/ z^uzޠ7M>#NtBNr"N%vtlj9qu*;M*#ρjCfEY Ȭ d2+Cf Tl&dv?d3XÍyT>&rBOUI|ZkZNFIn(/?!?)??H5JVcX5NWDuV?PK#QAmTfuPRuTSϫmK:~Ϋߨ Z]Vߨ+o:GW5tMk<][uu@#(=Zc8=^ߧs<^z~BOzs>/ekCp,9~'pNcw$Nw$Ϝ5ό?품f d9SB9iP$)k9NKq=*DY f[{HٲB-aӝqWO;xg-n__MʵMɺFu&QYD&"G qnűdsĚCxz7 H!hWVtjtEvt~t>3mܡ0rRdƊQd #eفrdw$[ȩ/oMJ؉wPߣLN)9n+Q%),%c{X*ꆼ! S㫘Y%YTJ|gٔ%o2b]}#eS&T]ml'S-T[,R=Y¹DīZ"~'sBl}y͗%줦ekنn8mQ.IgNK)kH^c˩̵ttnHTr܈P{T8rc"VvoUܛpkn]6t:nc6w[n+hsۋFn'hvu-mmY:ֆ-'kEه{+z,!mљcX+!' 8Z<|c'.î'9v}fpL̿92,#͙G,p6O/a7,ns+D+])nw+j[] w 1-rĝnSt[nvp;nvw{ n{Y;wN}2)Nw Y w$κso;٦wUBbymOMV#Qs70_g8G9cਭ-er<ߙ(l7m U21<"w6ox8ƮOqg g; ZGy{OYkX/Ȟ#=H/LhXŪ-f9V;6BY&rc}R>=+*s9%}rM_㙱QYN:/8n?aj)WX׹?,sf3̚~Mtؖ>C ׿8~r8N!\P-I+ʃL9A!c!؈uTđ&jl"ijfhjζgp;M]bscs86?6n--EE/80=Fcb؊ؓT[[Csl ͛K2Xj]9Wd_/ºĖU gl>l!l%X:r?z"Zb XeԖ}*jWԑ=8+<8pm]2ibΖ9ؗ˾27t?m3K3%gYijbOהȬK<(W7rhrod*&V9'Mz** UU9+S뻢𺫦QMs5XjsT[c5<G|*OMOj3ә:v?^M߈8F) L'#&q܇k~b$D˄XK|ޯ_yq~ǔY3/|8GqPbGcUqDlESDB"r0o9 ,?Gy ;| }QS0c]Yƙ`<&`[XrUj!Wq0#]t'GfgiGftGGg*􀺪-Xm-fN},2:1bz5i2G,g6=垡+#< luMXڇuu**p̗:dVjuZcoN#YH(P oj̛#=V:}=EsDe^!bj,U"4tf_}s?gÚgA*,UEsd<:ο{߶3u:1|,p#y\+cgye"MҢD,|2Ȟʩ<əp^v~Dub yޯWsx8vvSelb}#6jqSjڲm6qS5Mj V:8ϰl/.Nc@Eݱ&_51cPSg75Ź)RJjlTA*7z%)R9dGڠ!5sשyeG-9>Zŧ&Nrط qmWZKl!>rg/Xf3יG1vAV5 ćO|j'>3/ ğ e?qO\5 XX?+ YV 7ߵdekh6te)Ļ&FDOa;)4hf;#ڤi]TNd;] GԝNT1aUmU2PjBՄl؃z"q88: >g NLzLAfN/8U0)S=3N@\dvArQ%-9#W% ,r% $"k@D%s@E?N0>=[55UէN>,#ݖGG{=|iߌ^mL4H3F {ۃEl%3Ǵ{ sxy5>By,JOgI*-B'[SI*HaT!*J!uJRBeTuS*j%UQOgz^jzjMj SwzOm&j^jt:Guګ'Ni޼*χ]Yvͦxxvy_CR>ܶmav V m+AʡmжUmuj-iZR-Ju QTG:tꮺS}[m֦hm+=ZvAkoF&mh TKZ@PT.8bb|t'} 2Lέέ8r\av60a*9.t^7i}:{ƜuH"ڄ/TSrJ1|g!_2AZ}> VBhYy,;ëx5늹5x׭AY\%7F Ң:QEOOqN>'*OF]Ul2w;-ysrD/]Un|u"x0xL/Q+ 5LwG{('f4\:dʼ tt>2o۰c5z h ΂Vp\41-+Vɩ˙>8uAxGY RxGY(5y)T!9/s#kCkymYZk*JW2NggU|TskUjZ.KfFBxs 5g!#co< zSz j. tsyAYҥkSkJ[toݛ3[LmIjW^o>SG::c銾7|1TWʎh'“9 ݅wk؋ȕDݕw4&O$/g]D|4w7w7h~sT -M6ʯ# nL53 -lhQz&ūW{w></;@QF Q2\,|RC3\yfk0o4 PA.p8A+*ʈh+zż'#ݺK|ʫ#m̎x9௼Kǿ\ s"Ԃp |$ލ\mD4DQ*0~ m'@r{9d}{{<YL"s< B 1^'<5.-qx+}4ʘ{qm7Ҭ@&VsG88FV pRvvuQObI x,Jͨ MaqS!5h'qbX [N8e[V=n Zֳ5{\QCMznP&`{i)9!gH9R=yev"lj\9"ǿC)W7m9Nʱ 9ATLv!瘔H9)gbR"lL> RŤGʯ1)r!&#RŤBI )/I)q1)_!%OLʷHN4pI4uƾƎ )0Jb)^p>ƾqK(_2bg;xk9#; _1f<]Σq9KuKY嬢xJps;S^g{idVkF.\:jUĽND kFL&=w{M= y %H| @[lowȌA⛭k$=ݽ)ڇaW6&ChPT^9 %w仿Wn+wdv=!X}AE>d> ~Gx3 ԜhpQ121)pD(i:1i.Er|QP8'9+׌8Woy3!Y;o8/3ڪ]W32d 2_ o2B&C.ZrR\!huEkB.Z[UWPcH54VjOT'ՉP=UOQ~R PHBRNw$Ž ;id!; NotdCvz'dƲq~}@_ }Q}Q}Q }Q}Q2rWzȈ+C"S2⪐Wtd5!#vC;o@;fhhg+- f3 K{UCl/7H{6,D,|my7E<~Gґ< 98ǓUϲgg)h,xEAA:'؇0h{8x >ib uQuR<hB۴"f۪5xeaA1&`Lǘh1c D Ak-.^ޠ1&bL ǘ11cbh1cyL ;5&j4M&]jJ45V3L}{;U=c~LCs0z 8hk9hkhy:&f77<`99˻3s!̼F(6댰Gڣ{}=mSTt/r|QnM/o|O/?ʏnW~"1%1\8)NgY_c,yV^YIVYVPUbV1JJ[)Ӝ}ׇe2oo$%FF772Y&SP($Kd˒$)YZ&-ʲB??/SP.r\Lc1A.˨|B>A Ij(OQ#J 5jJkj*uLn|A@-K%Qn"Mu:,ʭJnۨ|KEȷF+ߥrN˝r'uɏ%wQ'GrG]y#uGQ.C'VyR$0|/(;ySI<iqǜvwi<833Zb@'^~Vx]}AJ􆼶Wy\;--*neSӘ#)7Z)4cKD?[V шZYXV75)q<OC9?\JOK?s~4Ix,rOr~9?/LRO|.ӟ?~O9?r}¹>r~EOƣ_!p ܋|\,umm.40k)lg '[3̬ ͯڔ)5>۫)8"?vVw=Pvca/ k)3o`l/[I%ӷSUՀՁ55um] s2wʋr߱_W#`K=Vz<;9y'&)b% RB |8p)pqr+O> |

{b۟' ){~~~~^m?o{~lbjo?_3eOڧms okbDJ(KyWT9P *Q5zJ*(RARRxO_:)GUAUHVר"ZHSU u*JMUVSͻ,*Jj!z=1Uwٓ)=T^>{=ݾ~~ОaϴgٙlR9\a{OQ{^h/Kl%TE]0>xq|:N~A{a]P҅V'뢺.KtI{w;{%"Mз;z'I.=Y߭R.rSt+ue]EWtu]CԵwwխVnv;NF+XwtwCߪ{^:hȆ̛8k:ouP7ҍufno-Mf}iz_?3L=KgٺNtzl0=\#(=Zca |ax=WOr:0>GOb'}JUWS7P5RUfjnT-Mf5H VCP5L 7zIuV]TWأ[UOKުMWԃF+KRjzHQs5Oh,@-TbD=eqZT+SjZe4Գ9hjZ6zImR/ڢ^S7VڦRVowԻFHvH}vjګO~:CL}P_N}:,NSguZQg/F[kuު[m~Wީ?.[{>ޯ?A}%ҧ<'t5vۖƶ3<)ixc7'F֠(@Hzv̩/k'C`l@؟lKms[\f1+Bh;}DHDbKUD[2gI%b aH,{-w3cSǯ%qǐk[bn"$*3͗+RHz%pse AHp1܈+#ͿܴE1wsQ|o)olCxe$>-rI} FgӝAƎ.+A#{2EHPTld% qK+% qK!(aJbJeotn+殝G6Oq.w{i<ƙ>pS;Kie312121212121'_cKK!5B>\ƻE`]-Ʉ鎸LLLq sád"Gܸw(.,Ĉ3Yrl`֚:@1wvc95 ei:/xFw=u=u2CxZ0BkY³:Oc2_Dr?p{ ~m HY7Hy;IYΌikpgD\T$sg+T̮Gėb3/f1p^w\p.tEnhLE)n| p4p~L9\8 h9սs{TΩ;Jr<9@LDDL4)Ddx8uxEBjl/#+x6\G}0sz]e7'fzj se#eNu)Gc8F+ỷV^ǂJX!ۖc k qUŬ/aqV]r9[9^QϷCX 8ZiJܜq{}}M8kJ_3_/p|oGn897շ^U#0d  'aHH_%y_&ǷkH"J,y< XII$HjKvI8ntL}[1eٷv/~[BBxV̄8ux帱8ݜ~ofd%&\P'ɜ e,,,,,,,,,,,,,,,,,dKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKKV,hXI}DtGbA$:-H %bA$-4Kb%FłI4^,$@łI_,$@ƂI4a,$8q0`NVKj8Y-'%dDwJsNsZ"WKj8_-%|DKcs_gt6H< u0+pd&~ϮcXَ[XI֜J#od#XD -ڲ2۱;]:Ir}a|Ra{ʡ 1lw֙0UN0dnkfg.4̳̋k͟W-QKEfYZYOzFۦ؊mKll^v[튽^j_fԾ~~tYΎ^R{mqq&͌[d۬-a{m{x}KBx1mχͩ{i+v)fmYoڄ{Q_a k2uNQڢFzk;=(>bԯs]ZEڶ҆Fҗܦ=QL3__jX/oT>qRԊzt7i$i'K;K.v{WB6.I6vsK+ cs7y+Zh|gS1z6AS=v_H[!Iie|6KI+#?^iҖH2TڽAګ!  ݥ(i](Jiߓv{gc5|F'~i㌙ٵYgJ)3@ڱJ]fJ[*%#_I߸-28s_EOHEieS)ϔiVF3EWKZ.RJ[[wvѺ( 8.PS)ΤMLͿ 6M0M1M%쓫)ͦ]3&I5]1G̊9ki70c.UλT9R N+8UWAH9ٿ"VHDSvH+יR+m0dr~;8{I+pqqqqqYY#!&ki23 V49cd&JYϥ=,'Mftt^VftWzK]+|tҏF"̼zAD}Jxz]x/~Wf*S̑/mh7_rsOY^iJ+O֞֟)CZ9nv]x7 MG5-n'5_ DN.nQ/oQ|]^{xmai1Sfpyb"g\a!C{6 ew_( G}xY"lѭS]ˤ]+lGtW}x|Q\{"dCvD /;[l>jQ3gDyEz|޻o+s~,dyn ݸs͓v`ػ2 ]&lϜ#+B^s@?c~yr_>?[f_³8ʬ|IgcʝgqC2Cȳc\xi~X%#|4XZ9gBv\( a2 cL?o4c-/QQQQ_Q_o܈?J+V}xx#f<׌'[i# ~J*i!;J((ɭ{UnoiBvtmF_r=68Z΄[23ן^55>8^^:^1>/.ΊD$b|aٚzi^D}yg gIcX7rw5nCxvBDGyT5vZh/l^_Q_Qz}sx}RTD}|D}kxeOo 46>1=>"O?QQ">%-^QBq?G}cc7FE;Fs"#]##wG{E#}#"""C" uWD}JD}ZD(>+>'^QwGK"##/F}&&\(;7-oQQ#֥&}O/9L=qIq{$-'~J /3J0'Ě'|Z+[jK9E9#VJ}BvsiGIKb%~oº-I+dYۢxl$G%4%˛KC连Pr(]Asy+T;E٬l.{(r.},(RV22@VKQH^˿z/r>ѐ9sJ[{J?fȢ^/dZs]KXY&ROБv嵟v}qǏ.geJ,JNN~uy+wOR5nڝ2;VȲY=|J{vOQzY}WJn/w)(u}]%[}ꨯ[eԪW/~/,Sj zvڱ` iuyHƐvU^RCC VKᣇR+ʴs/.%pQ\3UXR|a V?5∋#FE\wF~_X2j򨢇dXߣ+]矨6fZ5;m㴍ӻ\>Ybvٮ3f~~;g]?99ʜsz?pNܤΚp_=UܫxtStwsOu׺?gL+yd{S'3г³w~O?oYفϮyv۳KSJ-輠xYsጅK)/ڰvqI3׋QK^B䟬5RBss-(Y乞׋2M_"#[E4aɼdڕ Vv=Mnh?V6~kY4%t_#Ze'=GF{A'et+Gr}8?m#\aF(\F?<З+B ";2P(IɊ@]du7rٴ2ޘOeҘG Zs:WRQxwΫ̰ZAiqCr繶9^}BQΫz ޙѯ{W{)i{ӌ,#}Q3_Q[ڼVU7=R q#Y28y,cc SCbʽЊc1][=.*C R-Vw-)i{Q|u ͯ-1DhW؉Z}W hƞ?,>ڢ][60v]wR"#c7q7KdtsIrcJY=⬟W }'WF)-=pjOJ^t>TQ?4Vg"Ty|nKnuB~f1jdz˹g~8n ћ˸B\78adg\IYF-od={=M'2f6f=O'8Z꧱y}#1b(Bgg oԳ,PMXM_h-C͖EeHZ>;UG,c#!6iK \adc"4E1=)r-'YraoDmiۆi(l"]?wp }u~WoKVfYx{XNkZΈ.oDwYӪ,@hkDnUF(IK*z\(fT @ x;_-RX )Ex '|UDI EiQ0/E:OGDeExhoyAz[ Ef8  UEvGCDEy}hqz7H>3iXX~([ȋ,cnI"3a6,ŬmxWl^zhF1} 0Oۄȳ)"ݘk/ڽڽ̶̶̶mmm̶A2m#aRz(|f}#Zϖsb1ҪV$7"E.?Lz{Ow{P$N/Lzɧ|&agi=N/g?2O'1M>|N?g< 4MWi{Rg#JKvYXYs劍obkw[ɓ'XyC7PYsW7 _,,9,e88 P'$NP qWIZp |UXA WUƢi~k--`uiÚ?JWPp iy÷p@u4lVO`}30#0Q3IǸ`2<ɽ9PgfYc5>fYc5> d%CEP͇j> T|C QL/D2QLTDODD4@DDGDG}Dg"X汒oT[--R,6F|~mej?v?-b,>O* >O* >N]++X'Y'Y'Y'XXXXXOYլjl5k5d1"uzug^`-E1(S>z ީw* {ީw* {ީw* {Zf-VY'Y{Xs'Ys'Ysq {)k kMaobT)KKjI%|5{) J.YۜҖf$K]Ϝn9kV}L+ZUzgyV:Ex 乁J\@Z3S>s6==?}}e}ri=i7Yp<S4̂0qH6}auRwCw3R5sOٿΊ:9NVNf<i,3D>x]BD .rY#ad9,0FrxD$1xRI'TxRI'TxRI'}<ٝ'3LDLDLDLDLD`~Fy&jD0 F '8'8'8'8*/XXmq5:#rӦNn\]wBw=.<1ސ}/ >xƒ0 C`( V1[6{oa=`3la+|w~5Sv´`7|j>(s8v<3wv$j%@9}C/:YVNHtȀL5xC[ߺ^k۹. g=B{-րv:f& mV)ptd䐷:AgwBw=כ&;Rk5kc-`q I ) V4H Ȅ, 2Xcov:mБ1u~ zs a7;{OnL0>BXiCiA;pk6VcW[ͩmmC%&3Q VXIYS2@*(4H Ȅ,fXh B;h6?rM' C.tpt;;p @zAoȇ>~p? ` <!(009 `4<c'0|Y`1< ~/KaZMJ_j%g[6{oa=`74m_>lv\^;/ v_a^zH=Y,RO~,4@&?} O*EƋ"ExQd(2^/EƋ"Eh>lv=>O3N "1 h'A:d@H..X_% ռ^!V4 [X=leVl%K[?Y2^\ckpS`VkeVI6{~8TDޛOxV>Y}\J )gy33[ T|N;]I$H - YpdnБS :\=ɼ@o>&_$ƚEB `;8 !! !RAX'A:d@&dM 3q2X{+nLKF]yݍy'kd؞<=3G/0L;;iIm*i0ftc9W> x߅eޏ5O]]C_oZ5OoM\Siwg/kALn%sCTH6+ҹ[dH[^fU?7__oP3V6XL q/M5{{懴0T*N(jc ^:_*%_x¯a o oݮ # iXoƯ2~Kb~W[mYmi>J10Ӿπ" ^T|SM7T|SM7T|SM7T|SM7T|SM7T|SM7T|SM7T|S׎000BU4G ̆gw&d~D<rbPM 7&|/r|/r|/r{s33UθW9*g\匫q3rq QjQ̠FfP#"> bSAF?# Q8*G`qT0 t"..ұ'fj7rr"mYQ `!,</FAntFAntFA^tEyA^tEy1vpLƷ_Ux5k5^Ma[ڭfVvUƮ2v]e*cWUƮ2v]e*cWUƮ2v]e*cWUƮ2v]e*cWjljv9goDjP#@cF|VOsX)va~ P/xK/^zҋ^/xK/^zҋ^/xK/^zҋ^/xK/^zҋ^/.<9`.< Nɫx'$O:r<يv@hwp/? x0i0fPOLxfls|mb|MOse<;Q **-fR%;N=;N=-ꉲjOii00000D_%*WJU}D_%*WJU}D_%*WJU}D_3q !0p(0>L0"x f0 f!*UJtU]DWD3o^^"]CkvEˈq3u-5g.@4*C24*C24*C24*C24*C24*C24*C24*C24*C24*C24*C24*C24*CZ4EZ4EZ4EZ4e4BX! VH+ i`4BX! VH+ i@}h>4އxCJ4DJ4DJ4DJ4DJ4DJ4DJ4DJ4DJ4DJ4DJ4DJ1(GA?CE厣ܷ(׈r(׈r Qo9^E״-(x/ (<9uX~TGE?*Qя~TGE?*Qя~TGE?*Qя~TGE?*QQQQQQQQQɏJ~TG%?*QPPPPPPPPPPPNtC*ոD$B#*4B P@Dtnm"MDnnnnnnnnnnnnNi":MD4&DtDg2C$ a?ˌyp -Ȋ € +.Ȋ € +.H:H:H:H:H:H:H:H:H:H{=۝avvhx1%&MmOKScIMiL`\;x/xQ@D֪5bnEV!a̎;"cs9}<Σ7;ߥ~F?u7͸lUE^PŚiNp:PW?ϭ~tۏnm?G~tۏnm?G~tۏnm?Ge )X`e )X` ;TC;TC;TC;TC;TC;TC;TC;TC;TC;TC;TCZJ7G}qԒ/' ?Xsk,[cXƲ5lek,x\ެle5eV|wUO%?25dv|{Jt3Rr}|&jqVf5;ulO~]a]a]aQ}|(G>Q}|(G>Q}|(G> 뫰 뫰 뫰 ؙ~;ogL3vۙ~;ogL3vۙ~;ogL3v֭8J]z/:WKwzuB4t2C4[{o(ѵ=tk]{P:INvҵtk'];INvҵtk'];INvҵtk'];TMUTMUTMUTMUCн=t{{^{%^{%^{%^{%^{%^{U{iNb6S*OR1bJ#cZGǩS)]W_ XeD%RaN)RaEOY'>OYW\[OC3Ø}HUEa/hi:ѶًѶ{ܭb>t:3j޻kw2ߑޝ=ݼuNw(Qsm+TN/'dlaEyf:|߻r}EU% Vˊvw+*("v*XK` ;X `%;Vc5 DGKW-2 w4 " _hBw1x՝7Wyo8g]56hw^x}߈FuKx>;wSb'%vzqQ NB;.fAkˬ!aኄSrjbD8x~,NP_b߿a|l C=|@=M?5*5*Lh a*%Rb*+P{'wR{g4=l}B&5*^C[¤(<<(4Ѐ#Cqxl|6K8qL͢G'qB8NTyͮ-k"~8ٚ85ß_&>x:_p"4L'3u<fW%ΏK\aM3amEq4 !xPV2g%㛮 Sp+njS13Yƽ3 4Ckz8̕7-_h^5ZfEdMO_6-3[\[8?ο}lijkȢݽ^Zt~ksoD}7;Z{g7mu 6aSJ&v`Mغwº=x؜05DXלt\ h|ln o47c|?p~X(Eq;5q>CIOcs"h4ڰo9:^7f{|naQa._$RFTEE9s<|<&*^VItϊSxxv }.ow_mr=o|> / yC<6KKV󂥬ξ8=OÚūf@ZU82j7sBZ//{v\Z.~-4ũg̬6fofK΋}`X,woi^ƽ0,`dy~Exh_. dUMqn+DZp,g^}pl BvM䓼|g9%V Bv].dmckVm6vݎTI~ᖳ2*cX> c[4^X)KdÔ+DѼ_jkM<%rwQ]l;eF\u:xFD vMdmYuQtȹND\)"zjWD"JpR\IيR[)ҭVbD *QMkV``VVD i&:NmQi&*FDU:.t,v.]K%Zt!2t vjZk֊ ]"@Zk;W<~%_W< }ox^򵼼w^ޡQWkS»ѩl.f|^۾. R}Ş|P|+g؋|+V3X ּ^-tjaͻiB>Zcɻ鳀> 賀>}y7kM4Z@ֻ`y57؝Vg3<6s!VVluYYqheX+fW^1W̮bvUŌ*feF]fe6Mt+fQ1p# ]FۍwU䀘Zhl5`tmhլ4@bF` F@}F0=¦p5FX~!q׋iv}X:!1:ˋ.Wl}w.+e%Y^^-k%Y"(ZMJVJvY.PEPEPw]I~Ȑej˚ȚXߚ;Y3=xW$IU:MUuZ=g ke~ع]&vn;q]njvvDM8:={euVW.jңh7W^ҥ.ҦC9,6N;M>&GHϏr>3)GuVG7}yn9mz9m17kym1-ؽƮѸxX[Scct?bVP[W_ܽ6*۟b>bGøXGrƳ]l 6>mHվ{y; FwΕ+ZVR0UTZFV*U,ZQij=m=uŬʲvjO"+/ѬbEkYcsv5B, jnt+[Y}.grToUWYw.g@k Sݚ_u?gU͸ό̮n7>3r;۩Nv*SFp;u۩Nv.ڬGD9NP+Vt԰):³O-GqG/%*>e앇{~yߧOĽ>i{|/^р=%GࣵOm"qb9P|pkS/ſâ{l^sWTfE5=zQ4%W^6[q}vѧT4x;]u|"c4nFOne;MtMiOo|HOd:hj5܀n1g([WQU\_K^?-Ŕ_,J=uQ-VQU5WEUQsU\5VEU1gWՀ9=Ωs*꜊5SFQghˣ^LM7[+ѯD鷻9 7p7 wӰD 7p7 7Fna%hhXaɜ7pna%E.uQ*Oyo4(Hyj䩑Fyj)DTS!O|tuXc58ѝOGd yRW< tOM BjWh8*p4n.fa>=ߌ-g h[xm´ј8)_կw8MhPd 7rz|xt|8^: ѷYq>q q8g+8_p |11}#eW8k~5?Κg͏'o m"9p'.܍{c3ż]lĻхn Eo;~ȦRPW#1QC)98 c,1yrnMYozY㍗Vݸ30ꭨZo=XW5dxkсux9l ńbb1#D;`#}ĉe2qb8LX B/3%MCˤ{&3"emqXz_o oqwͷpAX#a<.7f5ШF 4O~8 6С th_[|m1_k `M|n[@~3QR52)41OnEi AhP1a9l|.l|nte?;'|2;_-Q+vtiG[R;Ԏ.Kh?:ծNST:{ןPaNpntpذpCpqAvI[u%8ɌۣLhXoȲaaYboF$ _ux˱7$E%2\+p%ո0~Gkx1Y_->OeSŚ9f1F/sė9AE`[qF09uI89:AL9c1G#~?sSl)u҇[Wհi3yUY[#yopu~3s ۣPq?4;]ϾwߣO3֕nH_ ~7i~7i~7;0p'7}=ٸ?܇蓞yh\ kq ɸ7f܂)v܁;q=ٸ7:hCZ\ ~dMO\4ˈf,#eDh2YF4ˈf,ȫ* ȫ* ȫ*7B(*J(*J(*(9N'J~#\% ~+ÈzU%MGDQiZ4Mi4-EӴhMӢiZ4M˻y ݂[w nA-Ȼy D"7c7c7c7c7c7c7EQ1zQv?<%/ٗKD%"Q}>IT$O'sj>ϩsj>ϩsj>ϩsj>ϩsj>ϩsj>ϩsj>ϩsj>ϩsj>ϩsj>՝ ;_7yDY(+eeL2QV&DY(+eeL2QV&DY(+eeL2QV&DYD^b^b^b^b^b^U/ѪhK%Z^u!5QZbCeL~G&id%lL-}+`lѬVϵ_w%]$٪DgD,4jN,7'ք'e,劲\:>lZva߹|h,2\FrY.#ed,2\F˨*J.*J.*J.Y!Nƽ9>܏`%s9GZ]VY4-eѴ,EӲhZMˢiY4-eѴ,VgX3cufΌՙ:3VgX3cuf]؍w=|Bf$3OYy/r/򺄼.Km N!Sdqi9d!?h!ĩuG"E^GQuy?#gtyE>uk?} 2*Q*L^?h#BB<:#<:aaaa\JM)q43pj 1T?SIdUQ:D4<$W/:VUF6e/T9?*#[­zeze*~x~('nX_ b}AAD_ bto0I7\1j{֥w(bCwU8iNZ![][ cUOZ?'bDUATAiU}EJ-bE"[**** M 7TMO&&z^?sz^?sz^?sz^?sz^?ʪ+ʪ+ʪ+ʪ+ʪ#d|>6= pi(в* -xLs>5.xv(F7BGZ_&F1*1PN_JzoQFŗQeT|_FŗQeT|_FŗQeT|_FŗQeT|_FŗQeT|_FŗQeT|_FŗQeT|_FŗXe]w`45Uu+1^sDc:o&Fs54Il jamx DI/~7Z¶腰ݿiq3}$[FnQhQF3JY, s"Qc"vs'֚{Gx9ιNq+mvwάWWS]cRtD%vlDzeKQ")̫QS?:L|bx&kwiY+XeVh:֋fe錻gE2-2uz_O8Q/ JEɰ ЈHW&4c(2:0%qn-[qn\$<? $1)44c'KĒ:N,KĒ:N;Ď:N;Ďp4 uu|n&Z\q&a2nMrt܅qf`&f?ԟ/\7%do3KJbcOډ'vaw{v$ݻ(\w{$C{{A{O°d|qoGrE\r%ɦ0$P׍ q.Wj\ kMmjۤ6Mmjۤ6Mmjۤ6Mmjۤ6Mmjb[H.A+kCq:sIS\1b^xqwxth<^gy{} .{l9Gc~k0,GEii;0p'7}=ٸ?܇1= EҢ'iC!b=BDc}@YPn8.h@|VU[gY5pVcoտ7VXcoտ7VXcjYZVj/tա՛z̫)M–6li7p8>My]hNNEO,L<BWG&VDG'Y>ׯg뢑t/W?Vlqvktzg3T-|=H/&F \GhH9Q\|bl4RֽU;.-N5a D3ɖEg-[dˎZ=7VP):bڑP f'~UZUݤsqYΆewtXFW?|.lg{sagkB hnCЄf 5y~K p1B;uS{r\429r\+q56ggGG'6\[tmz6lN,Eυ FlSYx6W-XhuGwi&w||׾ӵ:qvftlbZCNJ㒧Gַ18'qvgյklmw8(a>]~:AޕR]9ܕ]bgtPUUCEo~}/s'K(_UuWuf'!r} +-뱮.(xr(ʡ *OAtUDCEh pRo !󩚚W^WV7.7vu P"4+b7Q†/ _6r!lBȅ} QM['znJnf4:oNu/D/BbԷ +>zñz-WQ(co?"X9 Gn$VxnRaRBɧ&C-i-:T!!G8@n8n}%{9/zr$4}F,V=holm?sIrCGCG;#h&RKBGF!LDX߿!d#@N y] Y!$j iZb# Ѵِ6Z&Ih\HB栎[2;!k>dcՖZ`}][}#z )9^w{ZO>ϲ@BN8!.tya#_| Wa΢T IFBȳ,uA֊ù@Dy-e :CآseW Box,W W~ћp{WwC"2ɐ/CQ:"l/(ٍ6խ1!_>ˇ|/KsO1rAߏ!E88G50*CoR`(T6v=k6$Yi0O6,dKdKtޠ8Mv|{ vj,{]||^θ1qm_ow&AcT1TȝV6Rt-~G`e9Jo-4,An!r pZBPz?8َ:!]`=lP6el&r2] 'L2qlY6X u%rj)Uy*Pr1J"X8\B} oo詮c\ \̶h.Bb+{TdWȐ-v{/ؕgz8,َTpB'|Epz8LϛY8Mb9^ P>K8Kezf Zz"O(}_@{S2۞9f;SI"ϩ)rL:WGUF.B=s ̪ҩ p&6p 6 4{6@~ k!]R>$-uRԶP"rq6ϩdʀ| 5&BH7@pɅ ]'q1s!Bשsuq.ZڥB-JS7 Tzȏ-j|]7u* V۝ds8+2VG^hj &hj64AHg)޶k %r$A䈷m[T Cm=7Vjaқ뎖U"u|Eu' W7PFi*.*ȩONUWӣ/艉+3 ܃lab`ac Tk&U}.E@-^ ll 4rj͜Y@jNxB'(.a IChI)Tu*Z;Ptu@Wt@Wt 5qac_U}VXU47X2:Rk։ڱ;9v'B)C7o߱E8TX4ׇ%8 `7&Vt1R:LG?nkC^MY%>;ZN#(6}VU,5v-rc7F֞Jv`i'IY/6W`c^ǧ&{j/BV˛x ޒ[V_Z-o۱~-]7+'ށw`Wޙͮ]xvw>u>d7l/gcx&aMvw|//bs .'w"^$"I$eHcE-Q"z,K4jqh̲ESєDsN\JfD[qKVqmW+vq-lh/b{EW .+x\<}1R⁘"x'ib$>sxX V"[l Dм:Ir+N[ g3q~t7%:s7bw}MpRw3_Ynóznp7fw;_pw<ͷ{ݽ|[|td LU#Sdd-yeKGQG^'o-m)Z>-w9YCE9\ʗ~9Jqr '-!'ɏEO!i9W|H K21\%R#*ZrxE5EwR5dU_sU+V,SKrGR]"VbQ(֩z^.6TgUݧDžVO'G=sF;zUTUcX'YpR5ISN;bivn{;Y]^sk]rxm?yW9zx8ysuqyrz}>Ny{{y v8Pg7<8yx &y)%oy+ Qo3:`9Θ8q8*Uqťƥ:ť;22C7;::9::; '@Ѓ: =|L q> w> Mvf srBB:yUuήp?\'<w? rdžwoOqgWCR2{GVd?Afa?IGd#MMŸO??/L?۟-ɫ%''_gJo}d\>8OqA rpaAZ.A]RP?/rld0@ oÃ/#~0&#IG"Hb$Y΍TT?DvGɅŒB 3ՠrUz55eEz1| #u[OSʶgu.bG6gsu>>%E J:?~g{SCGH3HgdH;ÆT1[dX ,]Usm<ֿcgNT.[A+ɪܭ'x)RkYTz j/ɴZաKrڲڤʨu=\}~RDjuͥEhU+ >=|.if| cZXev=z讲W-Roқo96z#qQHi{iW[YzTzqP豥e'ՌMgQҾ_5zbt^gQg~;aGAAni塑Kq28@XSzce}O@l<\i>Z ~{[&[o8=4:L·m09Nf,[.`׮D9j/f2Q y@w2KU`DC] YfRy ds~X:UAGt X|9L\:#ڳ3gK=+FҥfvT̬rOˬuAY|bG!vԫN߰ P M&4jH_vonmil3la3ubQOc7Soc1~ = 7_*zgz vzoii /i0/EiXa&Oŭ6):;h) ;R7MrJər}/ҏRKM DD_O(OOOk'`'*4~΄zQfXQ7V "Ɗb ƊbƊbu2Vn[dlY㳟j,ˬg5>blYf;k8 sI2'AoYaVo8f$VUoD伅=hJZЪG3[![!|G?Q\r|hۯ,ˁդ\ǨفO-I6T^[V/}>+%DhgU嫠ٔsլYMMZjj\sMA_1UH)EAAoz6q}q'6׷ڜm"rw;$ V ;ۜ]T)p ) Vӭާ[Oz +Օv .FUȹZ]k5ԵZ_0Bjce8$qr3-TSݪnxQuvJvTa7)iE//pO7>[\N+GY,Ugžd})R-Y<'gwe@:֍uGܓD,{w=EFAƐEF"c`Ѱ2# V/P%tH(A :Puz2DGۑo0"`SEi%KطPo?YKxWb|^.#aqMѮFlLX,s-%u1X& 7@iPE8b@Nw;ߥn]TєET= Z,SQE>7hbSHDSфE85D A 1,c6B a1NYK&k5õqjxM&( tNXS@HtSj 9jbn ikIתX\VZŵkjډRݪXtKC@1aQ,1QУ =_B>Г'3 4mMsSBSw,fGv|kX!I;1-TTf`]0-s4:5FvJE"f[jq~v00Vr C%f4WQ-ޑؙ+L}Z~~}Xm{U@nzn\oHu@IXc{c1  1Ix-hʟtʊ]=я <bvt.(fY#{kHsONo1qK?3a5u+1gJ cQǷ,T@β?m~Z{t6ѷ+3y'HʭB\_?E9 V/?z̮a,;KX-'س9nw1XfyǮzf@N4NTlZ@izn9d0|\zN_uNIhΉYѝ*eMQݶX8X}V)FHG7AO:gga; ϮX[j3_6_2ךؙh3[c7h9{`emWѽs$)?OXyZXק>Iʨ!zʬ#K;{?gf8hpZݎS}3Mؽ/*{5rjO5UMb{'oQHH!_RKJ,=S]u׉g,V }xدmwRFSj>5Zhߎ@v? ۔ uQ]RBƒ ߅8X_k[a5Xx;]0<43zzyr6ʽK۔4;Bη_SI5~~.}w_<*Ӏ^ZbJmw{:va︲e_ѝx=>4f:7w] |ͷODUebi&%o;6f'}<\4Pl_{Zv[tbi Se}jDE4yT=t7W6_Z;8aٹa~rNsKvߋGrGEmƕXLܑZ}^(}?gU~F*Ӿ󩄐Hl.u~,?PCQ6B.F_.F5x}O%w.ySE~}VQ3B@ukKwv[}oCj o~OϷDkI)on94~ h>د鸦R1bO4J;R4Uwh__fn1Z뚮/*t}PN9Dv[ʴO|Q/e 7ϣO:7NIʏ]k{7;ISǙMTj z8BOGOA֧г=}&2ŞlESO!n| Ƨ0>h&-\ތ|Zd| R~1~2>h3VGW>HD>;| O4?>Ϣk |>?/⋩/Ki_Η^ዀH_ /Y@+2eyPX/g/Dh/:$QΪw/XҌęb퍯v6y=n[uqTvú/=3^zX_㥇3^zX㥇 p "Þ7yH㙇n<7g6xa9|kƟ3^tE?oޥޥ|G_:eK4thKƗG':|7xCxCa]QO2udQdӌ/?7tƗa|Ɨe|/REkyƋ_d+m2^qg∄Ds&xWʏ78Vx?#^0ppG0pXGL0pDGcሏ$#0pWko?1?o6"xk7xی7x"<≂Hƃ##7#P Q<%ZfdP5 o:C~}|5ɣshq(s_j9"oͷnF ;](9wQO|ROz*ӣ$QzPaUO,`JocrԘ5bg!lv67-n6n^@KGdv0ЦCE~?6AlxURs6L籑@&aXmb1]6~CWl.d߲Bo/h"Xma](OSM>\"r-53.W\r:pG 5΋/FW]btm`OuEgE@j$RE*%D]lPi65j7@P4B)`7mZV[8VE|4qq.]n߭̾[w}R`zj< $dF3IpF9:ጧ*ΛLqR 55ڨ3PjVr+Q3PS?p˨]N]AL.f_f!g]M]CAa3sfݍFJnA][mTn*fFA]nUu BT{=d}>"jpsTI P+J!EqQX2,$/}*Ѕ2"#\E*l)2i%tY3d֒ז.Ɇo$Qy< g˳ɑsȗeo*\pk&l(dERD([F^BT^ v)GG|7y&;;䝨.ytPkUv6!5|X7䣲%c|_>>O')8<-r>#͔jfjy85#:ДzΎ)E"19.cX89x&55@Y&I'Kh"CDG?S&!39_-ٯ?_w;߃4"HT.T?es\Nir\ALRX+~\nj\ 7~ |YBY-wC=r?%DtDUf*QUTJQtjR6jDWRANcՔ.Tsj9hSl T+ZemU[b!efBMͪ1V-jAU bZcՂXPYPY ƪ̪iZjdj=V-HcՂKݗ9Ue3%Iթ$T3P"P ""*P"CJ+"27" **""J "ڴͣmU}m^*~jegiu_NVZ@ZB-es]&H$414 u`| Ԝ~ff#?'4|H46hL#i< =c0ǥB9| =|uu:S7>iy+(^cH[tȋťuʹ*ɫWCj̫|ZH "?(<×#?8|Df# oB y 'rBo5Y')IV'~~ҾRjCr}}aR۾ھZR׾ƾF {}Q?:c1s}==:쉨SaWt=E&MMΰgjϔm,c϶9׾#γg=aE"Yf/+J@U*D(]cFZ{4QO="GG~ hoDӠgނg(]/"8@@/ۻu^_oߠ^{/Fg ߱}u޵C` m/>?F e@sf\#-qΓ_D|7r/8GIb颐(8@\!Q*$*B@B$*QD,`D݉Dub4Lc|1Ĕ0ĔqbJ#LXz`QLbQŒz`1Ũ،z`1ͨ`ԃz0A/F=ͨ1AF=(h$-ƽhRK=W_oMRm#P/G=XvJ:n}N{Nh۴(U?kWnįe$~5\ \M W\! %_c\tĚe(`\(2^Lt{ n餢˥ĸhOOKS};m}bنIJ77\ ׺PS}`@U܇fDX38bx_ B4 HN?Q[SrD14CG^Eh`XQ1#F>8-1#bk4jpsK>ps+psK泌fF3[𤀡I[`s`tƹ@-[[]._!ЗC#sweˁ=b$r{s{O1wEf$ ,C_@ ݙQ-zIw n1#Abe8/(|_qT!DAN@Ab o;mЇkIJ݉bDQlw ז(Ƭ݉V%lh v'ZMc h|b |Bb81K &5@bėee+|Ys`u`YYY&,;6H˧QɎh0e 1%y $e{"ˎW{ëWQ7ߠBI"D,/;__v$59/N#˥H`0i4DsMKˁtPCsxhOjOG [[վ h^ '4~o)}}R{JOh ?M 'ԥPBK!!ЇAЎB_ x!lI)9Bkzm!,8[礅ކσV -PY 7kn}[+{|v 7xp=1Ohe_X11Z3spT:ȫ(H  5c:TqΰAUDfvc\z@bT1`P$Ԁ1ZHA|5A$b'5bڑDJUfԀ1RJUTqE#Gl+r4G4|REVYFVFV"15`<Ԍ1St6ѬCgW[+AC1;@ǘ{}4W/32V-\}S̹tbsδGTWr6gImNmvn']: ba~-c)&L, E&}t7cji)Ի4Aҝ1ar]poSuȣHǒ@:tiTқI&KzKH.#}c&}uOn Ht+;Iw)*8#Rj#rf0FKxBcI|DR{ v4zd?uXO6~v̜r6N]?#gI~{b!le !:MЖώRhX;7}Z}GUI}{ߒ9Uu>3}<1NVnSqQ-:t$T1^Dp]m:ӜhfcRB 93qjƩ gNGGO8vER/BڝrJFI@tOVSR]pK0Z)vYcX>z>)QҶhoit_"b]/V#Ӝ} @Whi9V#Obט/X=e59%30V GQ9^XG)!UiZgd8鵨c5r;8͜Xg9go\Tt3 k2hs5}g(97gjOv&ѹ'--g:I]̕s&qEo TQ9O:/˹?'|ߎ2T}{@hGǹ\ff3J?Nf~1Ye s6gYY:YzeY֪#˸NJNCt9ݪʗK1!X8][q;p֡m,Ga)^;y|rFA8~x s.C=? BB N?ƿߖ*Hk6j Ӥ'Rڱ_[Hfi#$ZZv~?׊U, ~/zȻH5hZ$qlRV99=BS{19iIN$Nrz؀H=4i~/.iYaSJ28>N?W3g ZSښU.>\8oI{Hjխ`)pgUϷ3: tsw>^}U|=lZ]?X`}ea ߪ*޵gFxeد}mvv[-KǧK[YwV[oP>Pk5ߚkMETFUTXdvL|Yv H>+ Zm(Rko&,T<1+ꇒ:F-̷̷Y|l߬[PV~n}U-f4עD렯Js {XMxMS(nͰZ0XV(][5b5UDŽghNEie Z`Y-ۚ͏ E&ܓO!S=ry#x?W#GI{]pl q=^iݪU!Yj.7gzRb^&U)~@Ch8,22ru7ظ`1 Xa1Uӧ辢"׋!zX=6(6H_-,׹Fw;DnrGe;ֽ^nsgOȜ 7d,cE꩝V!s'ꀤ2;#vx$}==Wt/U %Se92f%/RU~I&" T$TTOkʡǏ?(T~6WH *ffNSUw ľoG՘ݶmپojuҜ_c.Rf9|ȝ}絒I2Aj}vi=RI1G3\gRgI})["~7!\/W:S۩6n=RZf]%dG43}rT}S軲Iti7>?#=I_;z6uwʅH#FNt a ׺õ^kz[R.rInk&68cgΖ@}#b!9wuC {11taq ccbdaX$^P7^Lަ6hhv㢱E͊n(R E8n-yV4hh׮+:* ):.:NLq㑿9zE..]]6!D?~) !}XM3bu@Μq[@l)={7bĎ}#cbǃq[ #eɋA~Z|&]Y_qH$S͠[[@_Uz,%QƿER$6wKиNg3hW+hkpqmܞnOnoЋ݋A/s/D wD .;IЇ FqMvݗȿA\^ zedzxĻ2? r!Ы@]@T(,n\\*n%#@}xkP$/BUjUн'^ݟ/ȿx;AKx_lj%/I+_KMI'UBKI@u}ިC yjJwH=';HT]}XeyyLGdSùeTT#U4fe>R2=ek1#UVb6@aQUB,q6Ia(ׯ; kvtt VXїYPgbt"O!FoSNx:394s<]vw unq Wh6L jôNv]6_e {_le2ϖPV:5ewn$s LO7ͩ7=7]~c?jEEU3:q3_Η_tD=z)Rn?|'X^"nX/;Ƚf0EOH@X/zK(ݙ؉+ /s%Vr ]]  EE/j0KKY GG#et%D@~UtFD~ut5ZG 3P!E!)|&__ab0P"M㋁;__rWMgπCAHƏ]WJ+jkk!pqA7|;$:a0E"L|&_/0`B1";Ý8.ruMu!=|dhW(i.A6/A6/y!<^>^BAȋ"bǽbJu;;^gX^W+8݀Y y\ |'x ޅzEE(tcy{#׻ ~^?cy"?>7  7 pkk]H8B~WNo'*kk{MMy{ΪϪ"UE< PPPP[ +|k|$c-5W5r&&5UçxC4g4gqDJxxMiJib0MZo"Y%gѳh ob?ZxQw›(ΎTΉtOQ#y2n t$=ߑݿX lb-]HT߇HbZp_`Ur\xR26X.Akdpcŏɐ]Ż{J"<]]^x0y pĻwe$l}.aCM[C[K+,ՌG([Iz:[Rʔ%L!ozяã`)c2DKpk8:5BvqVFnKm+zbN7'װI|$m[$K|'J)LjI]VҒX2 I<N拗$#H& %%cR#&],N&VfN?>rE^ q?1J&ԈnqF bgΔ@n.A1NbZZH$* BXk 98u~}TccRaY|8%u*O%XOx8!UG8BbYesYiE9tʡSrhà gդEjHi"5ZdEiyH"]h2vi.b$aaZguFh1Xg8l6i]-UtuՊHqOXu_$yA-JjBҘX1O"9Dk&3ݢs%GOaϨG|qucl*5^[VncZ~lZկ3Zwt|4OH|=ME@T4&fHߤu_j*3}I{<L =} T#h6Uyń}eU2,`j&mOoO0q?%F'mM+W:U}p5 WդS5nR.rWP^PB^s蔲>))O5؏~-s7/\2AWW7Wy?EgP{}uܶ"LgBj{Ur[4eCKK! ni5\뺆1CcGa 1553x<<.g23̾}u߭[V}߭{tZϿ7;pgEB#?'J(iY_7;Z6%l: ӏS\$ pt77m1rW|]+GoE(?fv~ ryw\ϭr]WR_ տ>'1o,zez,QXc>>G IPeegpհzpyy[ߒIR J /!m2Iƻ`9 sb^~\KXD޴ -5EK FV"aZaXT!4(!4,C:ž<`5jZɚiA!g?Vq8Nk-EA@Vu==AӾ}]<؅$Lwp.h  K KIT%Wt1A$TEA RT(u+Y-p$`PdC2a jh :h 9j(6DtP)Cs4͝ȼŜiP$dRP2 JFѡd(JfJ& %J&%m~ǙBKވE8_zM#>1tNbZXqֆ>X},~>+In |[ÇT'#z5GZn}/+habA{|%"\+k/XܜO賬C~`d1$@+ +Akb C` Chn w5gS d3ϐ"CI26XGR >$J gkCJͶ*f7{h#,gq0m7!Έ\x u=5οtAfNCu3PG}̛]u۴G@ o׶C]yi#жg3T +ԕ+'SթH~'S7M+ eT+oD7|/k?|vMWvO$r|R!bP8L::&v6W.,&i_Үr&c9P?-Ga X IOMMՑT?˟m&V% Q{oMh邺*b0rGIyys u6Љbqv2쀔vF(?&DWQbH>^E;AYUo{YRޛIK-HLlB$LʧQpsӼϜd:0D/GLASIM=|Adj^|ѼQ_fzzo4O5|iyyB>m~L1Odvv1̊gV8荶^[Ox\ hJWDDNA6qhqikGj{u=/zėLPA]/H-zgQ׫m5u_}R}}Aפk"辬;@vZt-d&`j__ABks 䋸bI }SO/77zB䫸B#_W7[[HszM+Wȷ,lgz(sa΅BǹUV-#KW=WcqN8g p/{Ǚ0g<1I-O Ju8s-I 'Q$}bFJ3~J38 NY DVQ2&1di˷Q>W[ 4]B 53OMb,=]E眓^;eީ/fBi_+_>6nGWb ^<2KiPR塽pZxwV>9Kk 88ߍ;z."_p}Rk$>"y|c~ߍ |B_cNBF%0ذtS2'!zÿ7MVa= fa\[6nmЯ} =Ӿ} `~)Я$k>kqMӧpMAg }\qR+m"nqŸ%t`~͌}K=YqݓMwCwl~GD$9*~ʉk\ʭEM5PU?M* ՆjSCr]_*9'<⪴%m)>^Oʯ5o@W1vE@:ߠ?Lu~[/һT's42D84SڜriEcIڿN!B& /j%ƓS<5Źe^FJw}Q |tEMg~DMi~QZ(1笜q EqjWOԒ-?ma3QK_Zzpa7v%ŧc%ŧcG*}^2/xgJx?*[ BJx?*ߧ㝋*aC$f'QetR~:O+1nx?nxOR=m3{#{yɽ8ԍ~Hn:tهYǮyVoAPAJfDipugleףw7*{q#Z(=*|Ghb="_䐃1r,EЇNcȻ8> y D if#"ex=wO$u{bZ'i{B։wO8VZsJSSN7BQrZD )G y?&wO-KsLsYɿ~q Cf~9/bM_^o\GS/YFSy!4Os%aQ[H6jx*PaLJԈ*]EАD ڄD5RG!aDƄk¸0!Œ'&)NLĵb/%EDJxMlw{&xHl)xFωxE.oDx)Ib$!I RT*KDnU.vK{RӨp;,:R-JgAtQ,]n[O#ʞ`OkiJyzNegڳ9y{\jn;d){Vd2G2:{}}湒ڏڏOڻ<%{>HQQp>"LT%xt?} bx|Svp&N7=[Qy?wY|g4hp7Jc[q19Ltt{22AJl׀, mn[|a*謵m.mcz`m!sx S3~9\^^ބ6bf6ؒleְ:!zsm/"VjlFފ3|f|V_[^o]d-rV 35͵fqH>xM$bߪ߼S7čp@#{k8ÿZK7/90qE\)q|4.m|vc+r{\ wؘ1;ur\/wVqAT\i4.erƔһ\vr%ΗbJ9+\W4&r>;Xr9O |c$wԒOou7{;myJv}2*!9Jײ'ٮ=lXء ;`NaGR/;&kKduQ8%r)K粸\.vQȳ}\!pʞ )yJʽ,gț's9"h<&eT D6mx_䀖4 v ]n`X,Yĺ7]>6danv/D+))qxKvYKؖ~KKcȑ`#eX K*ʝ׍{ٳyof [~iHI=&p{,xhI^THI\zϚ" -%&҂?N* p)8>4ϘMgo|T#~Bs\[n޶Ykޜmn0*]4176.=n>`>dn8Uxdci|ܸ|Jq̧g6777ml:6k͗Y+ooo^, nnM)wcX7:) ZMӢ@]EsS I-7+w:t1r1^U%-;@@D-d -PP&P6PPQ Ie2@u@@;MnJL[?:t gαhhHGFƀG*R.Fd!*sⷁ?QS)s\$9/9ns-6]@`M;;C@m@@`OVΟQ K@7n4-˝> =:?r}ez oiYJ9 i UoVl@;v#m[о-{|`f-@-qrM*a?!׍K#s8Xj䌒эC n5na ׸x^ui6W7D#ʔ`J6iMi_LS)ǔg*@,^7l0` ֙U;M}fAS$`4HÇL#QӘipt4ezh1ǙACL25K-fiZ`,<wgs0]n|ƷaΙovNl$ c1X2"ƲWpRj,E)w[|!V-{#b9l9b鰜tZ-A@rr*" aec<0#D4bdV :23l6yl[̚@=l0 A1[lMm[µǨ![Ƙ`kN-Vb]PE҈m +|y WmCl@k<lsOsgU9n]p׹mwY5" Ya]c>e]g](e`-RwPt/!;ZҫZGNkzpz2 jaec>|dSl6-͖iFA,l6@lF>[vvvcC Fh  8e{hD>\B3AZ~` px?ƷSi ϟ %@rAr;B I#h ( ku_/lR0A)J[ۅ@zmpB^0(.F2U0hn[;¤@xDQT1Y1M̌`'ŢI!:"~ utۊqSf ΰ<gćR"EKtt6OZkjr|(HN@URK#5ICR.<%<#/H +ץmt_{o(go'쏤tʑdVJ4G#ۑbaCtDu}͎c.G1r8? 15i1@ !Rǔc,,q>2]YzYVڲ\[ZY~YaYIL~'ZZm r.)lOYCmeNA?\H+;So,;W6\vRٕ^GYv^i'q6LBdfipfqswnpEJN K[۝; {M9a#yw㠳쵔::-t$rw^t^*[8o98'8#./.+,vҺ\fW+0U 1W1_es:syT+쪣2qKrI];]\Rq\]\c.k8g\155Kh?r=}kF(=;ΝN&:w0I[ԝ^kq݅-n/kW d.w9qS)iq>v_p_2u_q_g{7ݷ<<|)7g)_W|C90\T.ʷo/!/o)?\~5彴ϖ#{򋀗˯(U~|A#ʓIh=iLO'Ǔ){L:90ɺxž:Oi:i3ml9izzyNz<=>21G<3Lxyf*\EbE)BWUdEn% KZz+*]{*T2]h3UW8Uq  sr[+.T\>W*sq7+nWܫ_1%xazwwtսWe C !nENSѻۻm11y;LNoEYu4 +U W]Wb%]U8{fmyć^iy`|01d yC\S"U_0WFjǃiLp/a,[.;J'e^MBgdAQ::Z[VYm>USGToUWնjG:PYX`ukc'{Gl}գcתǫ'VOU?KgBqPJHCYu FC% ݐr`mY Al<:j NN΄CBá K+롛ۡ{"[̚x[l6EMR Sc5jX_o) Քp5R;tW [{1֚5;jv/Y\mM 9\st ZZsZ vjzkΦ^3Xs)j.\懕\xNͤuʚGaU8!֚ipX[9<$\.&æ-{pXupcx_9QC< r2g0dBã֎X;<T⟡%K[eIRuK jK-k?1K6Cm---AbR e?eϖY6e\,?vDK5E DĈ ޛ@"}C)""*E RP FEj""E@* H)EDԃ(E%9N=;3,3ff͚5kfg+W?\N/+_i gm+%)Q\&p+|w^s4?)?bvfY~TD ]" 647;H!U2.`!""#"!R)v426\IE&-22-23u C?Ida*4tdy0;*.Ȇp[فȞ>O *2*2+L=]Ʉ +ZWTt^ѧ"b@Њ`Š1*by7WL285pŌS*V]]]}%)5#;7Q)񢕪ҫlX٤yea*WvVٻߠVm32ƌʂʒʱ %X S X9ŏ܊ZUNYyʅUK+Իrk+WUPr[L=*T~:ragbV,#ˊeZrbcK ccbbͱIYbcƞ=[[{9ZlKl{lWرXu܉777wwD|P|X</#D|B|rCKOŗW_oo$~$~<~ꦉ񢄊G^aIyU}S[w_D^ 2-QMMD S34xӉUuCg%6$6'%v&$%$>MMHIZɌd YddNs:=٧rC279 94L?ג%oNNJNMHLrn''HH?|9AOOnOJgp!Uꔓjjjjj86Z2*wT$HMHMN%K^RRSJg?IT|kvpnPw2]z8$eOm?l!;6Jjvܝڛ7!}zùJᯥ>ّNI3Bum3}TK)>,}JV,9ֹVCujγ.l"jkSXKԅ##=U;ѽUt7UL3[gW3cT^捙љ 2_Ufn<^]u_z{}i_xy, gO87<`'`0<&<.ޱG7'~ GcG/6~X}QFɼ" f^yGJYZ*m=g=,kɽZ\k)oYo mm3_ޱvkjh}`}`f͇և*:`}lfRMlmkeԅk{nMn"QȾZ~k6vVF^oxUӣ77"P$MMA9=^hBĿT{8/;sSpp9cg? /}|)'|uOUx){<䔏=|u'+pDP9#:>Fr> MJ}( A~orԏ:'.5Oks= TyU@MjyWŪD5RfyԭjPݡfjyZ'x"ЀPC1qXФЌ,wn։šGCO1ˡ %=+^hyNCa' DQiemy{^\&RYYƿ·|kk/ڛbͱl"% kkoaX{c<`|7l2l6ؼzkG;rWH2ZVWaXX{c}Q]_b烌߭z;߀ͼk7kX{fPoը#UQFIF*Έg7\7~5{SL&56VsIMkGđ}O<;﫻v3)3EIt3& &LIȝܨ=`ܨݪ$ѱr]dQ y7I?}j=i{[6? =,Ke~JOiI~WoToOUve]Ho6&!6naZ] ?׋$Rn#ߛԋ%֋#'3njm0~hG!# oNGFҤS喒bzň?(rIёZ{94ԿE?e䪑O1rͲ&~,]\{,2X0ec +ɅpVo_S—VEֱGk\pFeYwjoQLIkJz-v(r'-7ҵL~3v2rG-7r?-f 'YeݮЧI̺~/t~^tuQON\noҿRo[MPHNtPuEd6mcvfgɷ %/bwWg 0qGqvqk ӊs;Kژ|P<@Aud,5 s?P'ć(xIbtːwC]Үڸ.>%0Cd!e+/FĂFXIx7S]X?XPVՏjs1]:FOYq76ث~%1W:VeJYɓI*qJGJbUw$oIJw$"d^}{盒w%2J}˒4d͗&hvw&u|oR͉I2=G|g2zb3jӤ=g_||m"kOmR>oOϣW7'7!_ȚRD9}B6{MkگL$.gL~oBlkҤ1SE;7'DDb@79I > Jo?&+R6lfb/-{[(>ʖ7_eʜE6źOP=p6B/p>z?$p> S? g4mM6J8/ߣ9O ׁ%dff{o_X8!njl'(jBUz>DJ>^9.-uMVmۏ#&Ќ> -4Ѱ}3AzaZ 0q!DV,t`38 @ htAa5WXEcsfDh_؞>~IÔ˷n} ;á=f+ھ%G; b-֣ z)kK@暍ا%S q_z:T 3ƆfC/AfmÈ;~쪦5:[w_) ͛/(d*V- WW2j5M kV֜1N_8h^uS9ӺPzrЯB= z~w@@=CA 2 E?B~:}=O//=]w~OivMO pVoЭi4 g o]ڟˌڟݡ?@_΁XWC F@rZ<za(:Jێ6u ]癞n9`[Qt/D&u7sDr>uMc٤DB"p=>p}l[6\,*hf`;_=;Ze]mٿ$'kwR Ӿ>J+%lZV둍ҢfiC`Gnjbiv'iس@_ʋOY+@dfhcۿtwnHp##Qm|ˉoǣ6-Y.oi[FzT; v#e/KTh eSGKסD!-<<Ꮷ=h?ܟB}591|Sn}K[=鍜tw/p/g.wޗGr_x'ƌ{1}|[M&1A?+1$XK7F1Ƈ VisӣQwz]Yݑxx*#C(hh)(Qh{:ĖSv SFTR#rH[*}7a)rV5g"*O%*O%*ѕ;]zy:|#7$ސ,Z`-6iX6lÈ 6Z}@Zk?C=74oOOs:x Aq%UȤ*4@&Bh {~ TL7A èURj|KF{pp67BpR= =issNϡ;E_C<-bv/c>sl豂V;$F*h{i] JNН@7..^@B7c-/748-H/ ZaCӆп{5O5#mҺK~m6rO%`1 Fme;C˨fЪiM3h~_N]e{PPPS]J_,޾e_Os=nA=6znNR9GCKixF$Oӯ[[F;\kЂy@AB/$+m!먫zQF' g>shý=x)9]+Ѫ*uy{BkӆقՂN3AmZC?} Nj# l/K;~kя>o?!&zvPinp!/!hh -GP!v,hiF疦/ԻTՙ>GBSZ^|f>s_DoI)3[u:P"XuعGkЯ0sAz:`'gG9' + `!h2 @9H@9X<19"NVN_l꽑\4i|\ίdcYb[S/v.FËy;Qz݇!OX`dž1{jzxM_#MJp⣯a4t@3]L4Ӆ; :?H[+ht74c={=ݻL~"p4A;=]qҶ )1~넠^DEH'"{hc~Jǎ ®P8HlhY· ݓ\pNDxRox)o8,jm{;ɳ$o Ƈ uZ` ·Z _w$;y,d3Ÿ!|^n<Ҏwr !bCt-yJF%z4)*o[wAj!V1+@eq}bކ-riA7̔Ɠ}<ʾC/Y%h_$Bk}i AЍnrn"g63nuR u;} NZz=O;B:A{{Iۼ\z݋wGڟfS}ծJurӔ{5Q'ٝ}~6r ! 9S2^/23J  9\!^@6J'y$g_n׻sH;ӊsW Y˪wy=K xjK'ӆ 0dv+x~A ֞fg!gg9EU='= =ӑh|A񇁇SĜJfb߬uR- 9訅ru̻!g-DL W)U72HDm=A<^aڹS;\iYBN#{`G)rv4<>ľ#oi? }V|~p}GS$IK%hbdWuD4lv4Ț}48GS"Ovv+ٜn )3"'I|bڳRva㱺~}Vbr11<Əe3^h\`t[ӑAvgFcCdV \G)iؗM g˅ godvKjnS\TtbF4yk=Xh/I&o"}vVٍv4KnzԄr6!j]ON59[QvM٠G*%?^tӶhuNbp6D~yDZ$VH#3t[DLb[T].'/9Dښ t*t}9'5 gpZG⺌Ĩ9|3$Gf@#gJN\}{rvr7)}.%u59o0jhv꺉6&K6JۦӶ<~8齝sAOkMNxxHn4֧R^{QK-R})ʩz1&O搧'^Ck<ۿeM`21Z%e-`6{9&J(0XHX  e MKdeopN7ymmvaMρ'hb09;)1&gcqiWt#~XFB+iEb>MT0N/"gO:7{u3!dr$K 10 ̜s̄K11c5ZRkxkz^5ZKZKkPc);g`$_߻{Yk[ku ^ɠ3}9OV/?#= q}ơwbЀo~7c_JRb NޡS@ߋkxkDdy d-j}q/ ;w^W-N vA1|uӂ fV ߼{xΦ A3\+EaN G%/<%y{V<|%>!iՐ{'Quvy. 1I;bky..4;sdw,4"!~r*$KCw"W %.\㹰tG=ow;9K]⹰Y/Dt䅐ّ"VJw=]>pߥ>2ې_*|%Ժ1$K'y.V#߁'0 ُy<l)ׅ 48n5f&g /Y|r!ay;w e r(->ZA"+$S_D[Gp_Y9q '5|!5<s )N 2]|'}B3.JNG;J vuD{< c<%$!93Y(@?iDlPV/fs=$߄{OrHi /14K=] p_9˳>0 a }OR8;g@#~'6'I } Y@U%7sf ]98-ʡ]q8;PNӡQ7TpRyG8G洐={lݭ[gY<,rZiaLshnp84.g!dCf9 84ӅtW]\ 8W J #}r^眤2N'94ݹh%tCCې6俍U(S_B[_B>j}W4VA*+X e6Fo~&ce91V*W!ȿ4HIXV3[UЯs'ʜFӠ}ʠ"/%KaC~37//Pw(?&ʛ(1?: tpp#q8x~iqW_D-Xp,Bo%!yttt:ߏ!$d)92c@c̃Ћ> ȁ'I8cq>  :=L3LE]/JU/W263@C͎V:6o#9ˠ z"9zS)u<X;i/\\ "8d6̆Z'a}0y9bN 93 gA?2;s:4g2.0wڽ~~}$I|!ƋZ^Ԓ"@&jE(s=9V&D =>+SEE,sJCXr;Z]O~rvCn{J=}13w;"/9Q7(*KXOaN>/+0vtw'89jC[m ˒`Y4G _O e Ύ]I|rhw 1CXK}?hKb~Z֣%m 4q)/Kt_@/ aaz}ma|$Xkc!;⼰8PWڌ2ACUDx9̤8OS_9h1|A¬Jά.L>| m6 8}}uS^4=W+}: :#V"V~ @n0fR.psӞ( 34&} ~ 4 {a#?N=ȩO~eወ}|bbǡQC> ^߇xۇx[ AcE["a]/B΋(VDd+At. YςB@\d~rA6а,ɉ>(8ً8Y| 2_ %bMۄD_7aN̉"Ka"JtꓨIȄW:^屾"7 qWno5W>߇EZID&Ma4+a%̳`tO ggأ#/<_==hKD[b$ɉ0xOr=< $y 16|B1=g3eJcXgl06[)a2i8hvQq8kƠLjL33 "S5˝d\Bmָά7 Bsl45z6[mN:̽yd85̓iyM+Us%/wxsv&0 gQJcp>Cg 3 l.` 㔆ᔆpJҐS) qJCNi4܋SrqJ}8!4܏S&gݔ8!0Ni(bbfJAՠ g5⬆2aP*|VY qVC-Ni;au<'<84̒/WVGzt=#JfJIW'_p`SF8Einx~xQxixE)^n oS w=3H} ܐ7d& &=jR:$ЪKĵGJĵI"ݙFZOkИTq iͳ[\?dҎWHøf^l% :1}ЉtIwt8.҅ ,iJgcu'b]sh]=X{iE\bZx/=Ⱒdud=B-ҘPPkhS-Mi7ҾPWMC=gt: SyX 'ʼ/pv8Mn*D$]'Upu<Ӱ)\7ipFxVQ@isxkxGxWxHÇ)?JDD3JRzeJ ,{ _6p> g#XylxY$wpZMK<-,v)\~~2ZlrOGNH{tFyi}&k{|ifKSv9gOP3]tqqp:8,qpŠss48BuCȻ2\Q]( #_s QXrXNh$4ָXb b a_NB b/ဋC.8^ON.\WnWB ?a(Fa߀ɄA:\q0170˥ __w ca bC%2kk+a-Dlw7݄}wBuM8N!q??>Bu0@z? iR|wu}e܏1ү:v鳑}mIn׵-#jXk1 AB`9Q1&#Zg/1ffU9w%aUgg];qI# &3uʑϽN޷Nhx?Vgo4h3h3G|٤Τ}Ϥ=LsJ`|:_ck|{ɺk9U#{`~gsҤv1 \%aR_LսW^}v֬q쉏ˤ6MjϜE~|1:mk] ׶ #迹pvLڷMڷMjä\$>kgnxhKi(r`G&Cq s@>欱Iɤ<2)0y<;^֌I7 fvcb߼9+ Q b a[]I}" KF(Y?XS~ã|p a. 2(ְE7s3|lVkLFb.1\Ft1M6 T ]eܺSG$n[WY8jш]YSY\dgݷf.gX-껵Ec_:ٖEq~}{ѢXƢX"FNqmϢ:^Fl";ά#:"98>+";{O<~G21G(f:HGh>?Rc|`͑N\?B{?b[Ze.V:s.Bq@b lvvL^;nvt5>.شyy ?ߕyW&I7aMYE BXKh%l" .B78pyB0@bL)iKK'd PpՄZ<$&#, ,N.#$"&so l&l% "!'$&% "%..4})=>^sF9WOs>/ TB9!DG|~B]µ0'@X8r}֗ ە5W}>/_3 zWo!ls[IH%`TU:Vf8u:U ZjLPԹ|uT]6QjQת&Mݮ}jڥv|>W~`@$-E54Y٦kZ6kZP+*5KjNm6[-k˴*mN۠mֶmGۯkG)֫]$\FA4FOqz@@H JMK)N/їRX_C:77[(mS! wz.ye ӈ*)b?{Fp^L12]R*ElS*+&EY*6eҮV)JҭWz3yOW!URSTYMW3l5W͏W ՠZVZ֪3Vg铴}>UvB7>=>^ }Qs6}Q| IoOVo;|ݤ5n}ީw _5U?w~^}T*MR44C?<5fRZXTKHE$GM]k r=,i1 ϑu//[F>d%%v;ȧYy%Snه&yːHV3A NHa! drD5+%ȏq/fT=#p? ɣU(WՐɻŽA|$>OWNTk-q* J$$VeS64<H=ޱ,fz<ׁϽJU$ y?@ƣJaI$)7(PT*Ye&avSR( ˈZRV+w#jCVe3akneGeœN)QrJ9IqWH<^PUFWSqj&!`O8d܄`"UU'ԐjPewsB\mT kZ_<_ݨn!l#jgqwfAC1Ns%`_ɸ4\t28#د068Z[fr,,$>ZMVkڊZ@ZR@~JX%LP$bD͠gis qm3ߣ5i-ZSvm턘̧Ik#l|5BH >mSv:yw˵֥ukǵne֓ȵG;C8ϵGqڐRy.钞zKO3ls|PWVL+ ]-ZgKbuVU_ (Y"3mkl'}:ow=w_V#,++C?O\[?%BFe lRV:8\ZV^*WffjK3u4BtR Si(vi z7PW:$s\ JJ.)]tyM-*m,m+]gM=r.yS|⡀T<H 3 $w_z =mO!C\N­5=lC{xz~y3ZN̿_~k\/q:]zCݬMk;]o6-E=}|z>7>k '( dpUY{3Z˯=Ym,\_ߐw7(gN'S7k=L}(n tS|߫o^Ǜcg؏Aqs,>xm|l|B|r|j!>#>['Xr W 7ŷw 6s}Hm_ů NGE^*ZI;N kZov; qcI>-8,8',vQE^}~_?Xx/'>5hO~?WW/@XLҎpįWo7*q;`Hx8މ[3P+ )S;>P0$&&|*Hkc+ ɉ A0C0!1[0/DP*rW '6q%Mؙ  ׊)섳^H%&J- /]K-A!ť}$ T*Z:t``h8z\7ΔpNEKE$XUtCiE]t`a&~C9Ee˜ eeeE^8a2a F 㹞T6Eie4f-[ X\L eeENQYUlw>A!er:-rB-grF)bZYp3z={ ~H$9W߄c'&amT ToLP>|`^.,,hJɷFpS v V)_~^ _*b]aX,#/иA%!J Q1oN(b&s*.,]cESŪ ›+Z^p+W Q*H|h8YqdE{1uI NEc7KWPTќ dMrtN0^PB8-9=9K07 ;cMt++ FO!i<(8<.|jr`5y&|!4b+ y|j[٥]ُp`J_ Ψn`$rlɕS[Θ7!V.Ȃyk)W.$$6Ձ{nx@PBN\HR]SJy;(545B0*5Fqz‡SLx`a#|>}yx^?PuJ * -bU0_,⢄+V+Vt~@¢t/7= =XBI#pt.=>I)7M0=u.=Kioe!Wl fVn0~8nM\ Hf\AA7+aOAoA?."TegF jc>rS332C0Df&xc2lU/9w%҄M-FZHÙmr|uRpZYgv8=ɸg~^qAB]lgNIAYKۛ-}i6*+꽖/̑8Ҭ̗_]bVk/}jY-~rlU}ZiXv{TXYr^_D,WG!/j/QӔ{%*UC\¢~4ﯹ"gISA(uK"Oj|)n׭[W;i߄\:B_q(XY_-:޹XbK~-9۷6EZ/b=N WTFj; IطI9VeАS?s`GN-պ9ۃ~% 'J#MB_ Dl 1R&)פ;hU:s ma'W f?8!&j؊RcE' 4}dN=qΝ_i5kJWT>y.DщjlCtt*#=&֩}r7Wr/aYw9ғW 043Ex2h'v>w,%tESn| f1o#J|?/n(&j`QO֢oOJ}ZmLoyhװ"V٭ibFi.Mjg7;ev-V"ilY˷FvHBhmޝHlRe-!fX)몶]zcARj[F/ _䎃ZΣWɫݨ!;=I޳|7d̈́{W ۻILuF'+YO%TТ/s ]ZK0?CPp2J٩i"F8ifg}қy.xT[i~vfM wf3KRH+XiwfG(ai60S P60 ?kh"emPi/e&jQKUic, 7> O*vݔ~y^58O^w9SrtOwie]ٓu?l<XkKH.9F-)zF,}ԴrU/OsTewIJ*u%Ifk`MMz_Ye%\VN;ٱP<[ԧF1#m` S'%b3KX6OB/'m6>M.1^KB%0Q'/ V/1Gϑm7FoЫ'b<`]x1~CB! $ O  =x0>LJ_x0;x=t`x} a|p}07 e#afo؛ZaX7ꍥzcX7vC={?Zg捝yzؖ7V=ycIؐ7b!f2 4a u+Eʉ7x ܇p~;Jet.|xIx%|*=k!ai9K@Z%>]&})WmP/$# =eRY%I^dr:%/:UϠYFlL"DzYA!uEL3 ~.;f #ǴM#bu|yZ!h| %4.1w`i9E1aQ^α cyt)xVy0/RXڊۑWT*zO):ӢL3m󺐲zs?iz+GJ~4wRfq拔=zwtϐWIZVE>N&+R?Vꃤ>qSr[. [Q_#ȕ1ڛ!Zw-j¼bQb]'14Ezy@)s۩3c{Z=~瞂P{2}ɪ͂Y| oMߢ- w#e:w*Iqhjʎ[4]+A9="ȵ\?1!%0:\"Irɨ9kD~í;ߧ}p^:!_i~yhxi+7smH3=# t/F>eEnvhݯu)$_ rΙ|)%DnG Ҽ>h~]Eۭoހ,PecoK,hrHލۼ; LWف~hK{IlzXkDGEЂHXB29F|,lOb/9Ԫ νGa4|;~| Ҍ##h1ND>ܣn_Rljk sc9rֽf^;<4_j]وg4\yo"GèY|¿I/1 6e>Iv"+s&2~=߶d|+z֬Wḧ!0dXGPsfʎ~lF2K-ż} NYO"dr9-֚ELf{ rƈ̗;ʷyf/2fdyCe:z3gCSC'f$6ÎRl%W\f>]a|4c:ηH/Ri@ h0{_Υ-;| #fM2_cm֣8:|uٽ#fcGO>J߀? p tرm9r;REدϠ1yY.͞MaG ~ѥ-Q%Wˡ?ɮ7qgAkhg~>ήw3moVcTsLӌ gѳN"܍kvVGԍzƓEMhR&:#]U'k ͎oD<@JTz*f<ێa9t牶ph5gL4xFY3ˈ-y>E (mvvi9+^$+.JiLFaߋ0l3OA֡gșÜ JH3UhӇoY)9za7sxEث]C0ªtȚ\<\bpC"=9bau!b惹w6g;ŽLwXw989v]̮Usj Cf.<]:؟zFu:xu <;ـ3Fu81ṿ>ɱh]g`.p`vls/g3$qNq^1߰,h8Y:vL\蠜s1WM}*_.'7~ JlVr>.or\Mc_9W"4shod,wV}%;X]t|5nJBsbS{ I !~C@瑲'8sGZw1џu^*Ibgnӱ~OiMCы]r\FCJx;r>mX 8-EJM݈eTc8cE> \Ψ-_=M=kP4EB{"3RNQr "nu(BJ";OꕳC6qbۉ~

H}*\5A*i%I] | ]|DD|-5^Cʿ,iWmy9Kp-X|l~`eu n4vPؓ Ԫ'eB|rE9(g2HjYտ~\]joWjxO9xVۇZԪ?%_CEۉ )F~C~ɳ=2~~>u_I3O/?T놃̬¾͕L)3\Y}^v2Bړ''K8ό|^ה=s@앹Z iA`{Qy^vCy7%%oJTY*F HiLיi]G]?w2_:cyI=}+s{CYbrU%m2'#etE hY p=#3pK3,/1cs_槖c2crȾhN‡_߆,b>d>MQJN;st/m(з"DY'4%a˜ >o{Ve2i̱w)f+Co4cQ)GٕMh9O Ms/f'4MqWؗ;6]"R|֡O/׳:k<_fF}>?N FYA&8jx gfFDc/̬_O2ߤ'.uXlq| ;l,w[;9sC7盩Y .Vկ]XY-+-8~yoޏqC?~],aA%/IJ O N0M܈ p=JPN˖t1۸$|ti:&ޙ^7)*VZ@kIo⶧wI(X^ZtZ; 0'%|L>v dz ^m(+/d j$#hK>>}I)R^Q)CftΒsQ5i=Ȼ@V(+ԅ 2[;E/s~$sUpFҮʫզgdI%p+|eHlLA7iIwEʹ"e^%0tּ!ru6P uޕ5Mnl~Q~O۳#3SR+ZA0QI{T95^_~<C]0;<],aALLNn{:%* ҇q#dnJRɧףӲI@s=.B仦ˬN lLϔp``Qf3&7e. hv%`қYW\EeK>P]ӥٱ ٩نlfT&UD76P$\W 7en M_f'{BHyCRމʂ)ʂl0ק5T4*(*>U J}JRCðӌzT1NV5 fJ?NC8# jizW…j` j0Divr}}yG)omAsvFMt{ag4\Ū˂vj:@P$U>PW0zpu\0 \I&aMPhAIOdsu]:&X].ȤIX^%*u!2~)KL&z5jяLԅ.)xfuu X=Mš% /$ Ѹ꺬kzz,\b2 us +&~sջV>^*8S}RA0eh=b`O[ni{uw}9Wo^ +ϲl} 篻vZʶ*ۋŽ9;{-QC=KXNvߚOy! \ξs_)ͦm=L}(nMNe Zuy?vƍxOYsη NQD{=^b;!}ھ`_q r|'pjNr;KZgw8'%=t GFFF"#S##s" #""###{"#""#]}܁nM8w;͝sMwt{5F ~hU&:&:!:%]]]]mnFEۢA,ߒf5=r+^w󻈮_}^%wneI|_(_.E\KR?eELG,׻}\"I_ju}1jO -VS&Fysy5o8_/-5a#ZFzXˉy%zgʽ7L(-JX=YԲi M^M$Q4_ il9Rn#.;_/_vKVk%~u&*lMkl k"poa _?v{JOKsĪVyOG@I}k#:vBjmbkG9azm^x}>$yO}/;_{+=PkÒOySiO^xFX{?!.]>IX$~7 5%a鵼_;O{07%͟&k0QOV6kqº%zJ^yJ\W;\XHkI2B5.Sׯ7+Wf%LHu&$'y H x L I1a% #Zy;fw[:{&oHbo|戴Bmg97%OL {>OU vzuB隹${FO7NfFIoWb|n~[ࢪ̀g<ܘD7DBE$33*y ԌԴz̬G>f]k }=kY>k38݆1$JyeJp#DB͚J8paK8{RyС] ΚnUǚ;KLv2 ^lȮs-O+y@%}wۖ.w^cro>>xw]_?{˾F|ǵ*l".ՎjNltxǯ6>137>굴&[1^n6م0Ic^%̹@z5vi'ϤYNiXk].tvtmt̢KeVA̮{9utKn)sVVXụ۠>~TK]]bt?nz^/N}Kwwg]gB}!멟RX/__z77twٽ,CGH1?ןןgWX*5ZؠPk ځ=ڟ w lhhalT裡1SCg3CٸšKX^Ѝ,ՊV+XAUVBDcc]ÓƼ.l<"[zVv ;N3"njܰ99 䇱\מ 9b: p9\.ETK"n+s6r!qsmww*zKV oVlogEPKb\4䀧JB.8@ Vj%Ǜ典 5x EoYduCܦZxS)) fGS =T&ΛBM񺨖FRCP}?|"W +TJ T@}SjIVKrJJr,J jy(@RU+H151k!旅WsCY6cA/2Pc'C>\#n\F1}gr7w{a=n9Zods0\62>.#.# x=p9}<7.).)5I}8 Rt٤¸ 8?|sЧ`={2~:pvlCn?5) Cbb7#Ż 84#%N`.ߣ+K r޳7oʭ.^\9Ϯo kM+A,%,VL9sZ¸qg/] W=12EiAc{ ljKm??;g#)V}kq;>㾌ߓjll'.=O%{4m4Hσc8~–΀7 ΖNNS}]7ԅ<1$J 8;8;V(P>A0Ol]"oa;0g3hw8\7 wo/Mcer!G n;= ~=|c c'N-*]" B(٥`61w_*'n^ESAwĎf7x ^ĴpDccil4ֲ ߫=sECv]|*?/K|%G$![(,6 b{uWO;#ϻ7wPaNN #9!ppvu#Ocio8!L?ZF3M 7A=S'fܩW:nIZ sR:==ͽc:v Z^;vwcꖻfunFt?QIލ͎a~uE{0_stԵB[utrO˨4j&8~qh[j?o?2Kob?`?.F.tO"-~qBЯA]CNqg@ c݃I;b_uBB$יn[B&FWس|CPc,Z.{cODr>VvȤw=m!ErJ;d w\K]+aMAc;'wk5vmu͵2n6ȼnס/c yrB{~7x Ȝ~au>pfu.SpmŇn?/7At !M KrpFe1AG3Vx^k $MffffKeGòβ.*=**'9[TeeATߨl<TKJQ~Rf:OQ,)*E%5 uG<JQVfe,E(J*fxum1~ yyҼ[%5fw/gL%yfngg<̜8?6<۝UrnkYfN6';ˁx:fsJT$g9\JwƚLU"\T08hy(U Gisi,*'h*ufJAGxUg7S HJq5Ly wZLY*95~gRxG?<M't/:/kF} Xߠg[96ob ƕƕ%xT z0Þ(1ixT $;sU5c.x:C}U5c_t6U+~͘k-\%h-Oiold*AOQl ߫dw51ح*]~߮6do ΖaXfXf,x XkQS ct\sB^K Za=d=Bo5 uWɫR&CXղi}V|?il}kytc]VW\wcn|clo9`9l9jyrlU;Hf "H= -2 I})D0 m4RKIbM$ʚJpL2Jd,LrJN&KqHmIj'g=R2I.!XtR?#eI9,Yr39%-YoY'{ֲͲeVr_lFNdlQ;](ܝ{ʩl[~ KFXm$'Ҙ˜TdjEeՐi7o325WO3|Zٝ%2bX-.BzO''IFH UwH %II,P u9Is EJaRo)fBk!eJY4B$9Xfz܂ʒ,0,VI62L?.{dfI(w;0,'3"werɽ4f2'G⺓q( E0Ğd,c'pgqg߱G#[z#=O>i91._wl#x Ǯ{1f4@. 8;S ch. %H3'ws @[b\n,\_ w+iy ծ2ހ>Z:3i`$A^I14%E3&ML&k&ۂL17aN΅O]1b)bRPV(bwJ;"vE(bO؃)b?D;"Hأ)bRG;"vw+shbc]Wl]|Ohq{G+Eh#dp)P""gG }tD^D)Ps: @"poCJYkPAA:ZBi jEkLkPkZi 2d5Dk AVZli rEkPkbSߨT[<*V(~|)EФaׯ{uM?>\(+@Bj/9 [R-NT:"9v\B<ͽqA 䘫."pkNEzeo$a|MܷkM_|3^l\BW_|Gr $_ %I"!=Kr Z1Ȉ{q (Ѐ#Z^yӏSj0J{ѤWC_x׬ZmP">4KYJv@׫WZģjG%_Œ>vծʯRMJp,f~l-V4ZiYqU?߀C@'|Bʯ =d0ɑtHH2$;LG;Wq+pU.BʯS[ i&IH]+ + t;!3?߀d3k@8Kg gI|ݍp!k@8#t_A8{cP2 dkZ|÷$y=J;VC#u6# ai ٢Ovj_d$Gj>}CW~DΐޏdIɂX#`1|g\ŀy5oZ&t^'И# XyBdI'< lU鄂N$˳$;']4'*XT\zVh4b>' CE4Ci5MkYh=ߊQ@K|ܻ=og4=D_A T!~i=C§ Qjnę| uj jBg=Z|u%/=e՟#og"S$ܸU"ϒjB@#;P~&ٿH1xb8޵ZhAcP{SIc3Z) ;.2F n %P@ I w(D87u>9Zx݇mAsܕw3sPpO 1-K%ZXUf;QmܣP\⡊h`l!Ўg3"_*qSMkgcp)<6-ep B^iR+K{xjDX4pwa.1 f>BWr,ڇ(J蛁ز0Fq@D<}7AK؋PZ i]tk#be]Cvi⃸&ÉQT>T'!Y:bvzN؂f>EKYyBT%8&Z1Wp=q[i>eSXDk<"Do-aB-,'BLpA#݁:8|Z(ٶnHMe JBEBi gqg!+;wF {)],,/@+OxE g_^p iǿU)p47j"ifq88i/Hn<"k \C$3WF{\߃kwgVF23hUyxU&`L>%#LE8Z:W1? i9M+GD_I+4Ѣɤq#X}VX_MMu.1ՄP !Pɹd`g7ߐ1:+p|=Ēq$GRS-h)vq]<uD:/V@O!Oȩ6ܣ#yi2 '݇:ܮ+"V31w^Z†${:7JgΜ O }UIe[H;xp^3UIef:WYtJ{p"^ouב(Nk)>8-;eY\rEsG]5ϫ%pnDZjohdjkոHЛ:"Z^WF݉ O _3˲ *Oaҿ?jiO:!{|g9;v՝O H:ǻ27;Zk a YFqhe g ӽ_imŧ2/D/[!Z1dm\IJXH,X|p 3x5鸿1SRw4?1;_Uz}1z;<;m.&|D@am8?;Hަco2甯?*`'Pig_MޖQ^>L>,"K!LHGsd*Eb2:ۺ0c$|RwGa=/#]9{!EVw)buT⽈)|NH3FLմ];%(8D<~3yׅ{(]Ot^GphKȦW"9 &/W h1MFA-Pn>qX TV'з'All|ߤU4b|.o;݅T 0?g G[ȯõQ7hI\}eJĉ}pAǬSMS.a>&Vӓhs!`#z~˻țknQ֤kGV*M <*혍X?BJE押0Dɑx}8-aZ7prh!)ʅ u⌦$~Bn^6{dz-Faxh.Гnfe?"8/ju3}"4b?sMO"rir6 4_+,QK1mXp܄@o]zh8|- @wW>=Boߎˆ!b> Fd_=M !Jy;K\];,yB:Bg=d22]sWBrɽ$/Rt~@-|L$EJ;q0KZz"(0*]dwP < eH't4}@ d9z gRl\m.I+9J&k2\DωJl#aiR?~,{cXpg$%Qc?M>Y1:Ҭ+}V7SZ@]? 4Gc. k #&ŀ ]h#&;~oR GzE<,cUo}"L\_= RǞ͵+˖n(O)gO&zp([$z>z~ýO.}:x(.;'7)}} sNl3 *'E?yi% ܝ -ǭ<ޖޅ_v'o&Jn7~9~a uXUK=ν{Et63"s[էBru8dң)ܯsn=2~P?_zY|'ۇD}1Gz|Ul`'K^#_~dΈ{tL syJ)@a wG(,5Y|zؑ\0&Ĩϱ9*( n#t}(`آ0J)D7mD/}l\@򚼷:m{xOSG/eyEfbiy)/^mѦeC 6_~:ŏeמ>h}Ϊ7b?7x\W|hp;=?orx[˻kz-o\Kuz6/+ 9?<3[G80!kkυ/^[FE^♿kgi-{<}SjXzݓԝ_~4w3k;foWb3wzovO}{-MfJ |tW'j"ϯ\#-D_m[C90e252\|va)^~umS…vkϷLyǀӅ侫5V9C-{t@xO铼b&6Xة ݷ{/V>GmcҮ'v{ȀUʁSF yko %.OO?%y;d؞ԜkWD'j'|4\|bw~}ٟ.??msRNߵeKy1/wXաݤ_F#fd8i_x/:бQEږU"(0*{:=fktYTP3/!os+>*i$)~&;Fl9ՓqIS6l)]hEstZ>8߱G/}w?r nD؊X3jld'[!c˄̘d";ە%!Y*ЂI(ZVQR,;t.?~|s9s}3a~jut!"=K ւ^|9A(X\RwDD?Iu ap/)ꊲtG] (Ů&,SŁ+Ilk&Hl'-?h?ArSIċvD~ۦb9fNBe"ƽk|[e!_Yr-ތ/l^wD@!N}|MB{1WB0v v&noQLOBJEv ^,%8/^٦"RG`8k=𘬿k-_%tI# o̬rNO]¯iH)%d¼qk7L%*d]mGC+X+AdLԭs>z h"DHRX]Ĕػ?v]T:qX6נ[Ōj3Fܘ# iofZ-'8ajfl/ldȘ_%%v(Ŗ}/OƄjG uz-%  Iͦ&tp(s&c&e h >H.-ᗈA(1ancbu-CV!7N 8">7|ք( H6HCUFޣ7 nSdqrgɱ0Tg9-{fOBPżR?n/Nu,ߗϳ7t&Wb* ߣ "jq%n'I7*`RAK!jb_GVxy\?•:_Nro OќZs#ڠχn2gbb{R䓃eOJ9~P)Rk*kT=y.\~&I ݬC 9}'ɨ۳5 c)GkIB;2hUt,̿, )fHڹKDˢV[ > Q䰟3aP"d2Nz(P^|hm24ESȀTaFugo? 5E05ޚχus &)F==FnF''n<ºavŀ` JQkH%Df({= 8Lx,y(fjzSX=}OmL1{A0#]Xfo8Nd-֌ns̼jvJ˲'b TqdJgeoCyzW401AeZtm>{c9qn0!LW KYלfg fQrZtәw2ws:&)Îbb~\(_O&WL!7Г]ߤ3N@u3i&F<bgr 6:ϟ X2J+\!>Ue6:0X+ rI_y&NzS41n+ވe+^xs#>S#F3>Ҳ꼽ibvKGte DqIm$;e%&bV8cF x,v,UCۑ~V C|ғ6uXW9@]ׅkXeˆ!U]le/ t' tsϓ#%\O=ۊO)~f aRq\:K~hJU9Ur7&cU'Ւqڋ3'GϷ˕_ܛ-t}cl1/-^ +8`1d=IvIžjC|EHF2?bfŒq0{n=,wzg#Lr=7xK5mHB?qITuSEijOd-~iPAV:6bi*004H}{GX:L> stream x||T{w&oݰ$HH4=&0`Q4W>l`;ʳ?sDUߓ|3gΝ{gDCHQ">LTS:b佉&MgWV\:ŻiU$FxlI{I1ȼldiYԟHk(>rɋ>('m6Ҿ-|~^2 ~hW>RH|ͫmq4eO$݉QdkK[οhl=o`jǖ;va e'5n|9DP[!Q? ] PM-yCD|Sg߰`H,|B]c?\|KOW%ϞF{׼Eݔ6/ϯsݹICpQVy2eACKSqHuXͺ㩝 I)VvW˟uڅ>G۳{L],"=)J22i j:juS5VHL5c4ͤkwVqظ.y>XӲ]$:e-GJ/;+F3v?jaosFxPvѼL tAփ˿ooljsF7.<|^ҟ8GqjJ[Z*;6iGMZ9TRVHݵckr޸17w(W3NB6#K)-n=k;|S_! YB,d! YB}g~ YB,d! YB,d! g-,d! YB,d! YB?cZ , rK/]3 A*R`$;oW,d! YB,d! /߶%} D 1-S2Ma&j 7E"MQ&)n#߮C0Z/ktdz$iz|a+.sr-ɓ*'N?nF ?fC40oX{-2"j 3tMP^ϮݣFew-]5~\]5FH"pgѰ>y2B!Uז\ghSQR_ԸKjJ=2]'#"!#9v3\B)ڮ&/׳j+}e*G%F.Xbr͑}s\y[Ӭܨzw} _E6m?6]u'r?]Zu#٘I/ ,նy=aY$iB҄_f9erȓ[jdVU5fwUe5%MY>y}; ߨwYuMkܥPa2sT<(Y%d.rdVeK`Y~k\v8]h١^.<(9`Ss0ZX,\41\.&4s7x<}rlr;f{L4qOɔJ\_%?eZ<u[H8 Ujko'=K>ʎvas9U Un\wgv+EeN)Z-v.uڎYmO[KYMP6wE}{o$ q4FRTYqi-v"YS|Mh%5U=P j+i V#ޱCjԚ Qd'Cc/m\ȃw]k<*}.k#HȨdk'9 Di6 S*]xCl Q&9pwpciR0׺߇˰.p=|ODKS܄g2W|W5T݃[{85p8,n(GH&<"QfMƍ+GkM)]S|/8vTeb-xF#nD #uɶ*K!pdf@DF74óV6$:ZU9Uzi{?,sk="kp&@IDunTո/{盲 D8$gE"}RG{9RUŝ7Jkew`*d_]44ɽ [촑ɂj-o7n P5M02qZȣ0::osŰwȷ|ȱ uɳ6 xl8:Vx\eUݮ5X6ڍ7%骯QDc/ %HmUIK|3.6/K0՗kudyG\m.{[~GJ&_xEZÎ5mmZWsJu! uUef:FSݵU03qqTm8R[bjmpg ;Ͼ)lnHeW! -yn'm9k̎(sc-7m%&[,Q&5ض6א6lx{U%H.V:P$TRq`x % {3/ڒuc|V#+z6矨B$ʼn~-rb4ڧtY]r.6=F ԡn7xzp`NՏ>b$~sjO}*!6M[A~:x5W?~dޥ)_73L"^P("؇Pw32 rigl OqCW)q)ѪĩJTb˕8E8IeJ,UbXB%NTE<%8^JQIJ4*ѠDuJRV%SbJPbӔR§ıJLU«%&+1IJ%&*1AJSbcDDeJ*QD#(Qp%QbG+1T!J*1XAJ T(%(_~J(D_%(DD=V"KJD.%Jd(D%RHQ"Y$%HP"^nJ)]%)DJ+aU¢Df%LJJhJ%((DثJQbWbS'%v*?(W%W;%U%v(_)%T %>W3%>U%>VJlW#%>T%W=%U%V-%T %^Wb)(/)/()(O)O()(UxHx@O-Jt(Y{ؤF%6(P] (qw)q땸C?)q)q(q7)q7(N땸NkFJ+B˕LKDH @8OJ9J)qg)FJ:u#ԱGcP=B{:u#ԱGcP=B{:u P?B:u#GP?B:u#GP?B:u#GP?B:u#GP=B{:u#iGӎPN;Bv:툒 Rthg2;qfd$NiV.ʴ2ZL0tR }hY i b[ĥL yb t|t| 4iSl@Z)KLuLjjc4i:4*&ӱLSLS&3Mbd4i<8LcF QhHc , *e*a*T34#f͇02 f44gTr>LyLLz10LY;drq;'SS:S)5:”HJbJdgS<;11r)L6(d` :+),2dTLL:;5. &2Ht23B^.̴i7K@ЏɠWo +K/>gC>'\Kf};gz]w8m.f Xי5W^azC^bz/0=ӳ |I'gz##L[溇dL3Ǵ#7s^ML6@@tP;bi=DOv۸V[nfF1]ɮ,2]uW3]t%r.]t)%\w1gB|2ˑpl0$Ԃ $*:@Hf,N $ dZ͗sSN$ԃN˘2-aZ̴i!^Odj $ԁNd9rS3Lsp&ܳFnTϑuLjjcɃ`΃ƩB>cSB^2i2$@41/0!/Uq>2it QQL#Y_ * įO[AŁr&S@. Vf@H@4(; 4b~X9@\L}yBS.'ԋ0df Y9g&'sq'SKgJcr02ՠ}&()`?Ȕԍ)r;;clLQɑ g2Y8̑&vL`"Og,ľ:ޘz{߀uߣ- P>>4 1g`;|?>G=;[o9nkvne;_^~Ŗ|x x/m9g~6mI[ lh= <x:a!D-py_" &mD@;Λ77 u;Z諑*r2Rb".׭K/o1tԄܙ /y؏bdbbDLLgAcњ=؜6M~tD 9QD:#5oQHYTRSPqn+. (UŲ+{"bLG4H9utgDvkӁӀVT`%X ,K"`!p"\`f@ p0fӁi@^` 0T x`0*QH(Jx"`8p 0 8  ` p0 |/r@/  dY@ t22t p@ $@tXр "  0 QO?{߁]߀_ooW_//ρπOO?ہww77ׁmk+K s3Sc#Va!A~>` l6 @h=]zOm-M :z:Zj*J r2Rb"B|<`-p.p VgRV/ֿX_` /ֿX_` /{ { { { { { { { _` /־Xk_` }/}ܪ-\`&-Ddhۓ4R+VZwiѭ'# yپ(JLaԍsw}.Qfrt;9;;:(hk^s7^(wem t{uw!sPIh:͠jZhfxjy4(Gl|6to } j"ZLK0Xu'Ŵ_$:N",GFyNŝ9N7b3Lܵ5t}UC>G^{P|]Hy. <W5x/7Wut=Yw)<J>@O&{ìyi4s#\ե<KJ]-8eޥŒ23W% rVn:|L^f(6kZOw僚;v ڈ;y/m7-tݏ'!ڊQ|)σ=>n(=ғvg9z^'Pz|Uz6WK|͟P4a4߹jTJu:vGQ䝸K\>@pRO;gscnwcdƮPNBh<]?3pJIbӦRkC8hJBxbLmsjj{zlE賱Ȳ}1;o`cؾm{'>նM776E'ȣY6#IrQnꋹ/羘4Dlfhbsw =hõGeGkAghz dYa[-=xWjrxk$>--ֺoH f1a#q 9Op梤 I$=GOSp=݇!#:nN"r"?گڱpbhUցAc |̤xk5Jv1jzG)eX.0zCc2\$GMNN v75`wSM v77˓ѣ_TTD]8F~ 0"Q#έ9*#l9s*81Ƣ!0m`߯b?`@9H>䠩qh]}M$,(crΔnVm=2!=>!#>R7R1HIvu9\=RXNu`gaM0,+o#*5S"û'tmj“҃V 3CJN9I"&RD^Ho#'NRSq )CCբ::ױyX'̘(SO=hP韘f6Sz'f&FY}UV=-3.$ 1Gp֌h.LfkHtDt-bæ"7G;)cwĔЉrzFe:4 OvѡSvVzMa@lm{!qCRﳐ-zj>&墑(dl={fZ1H3LIcٙYV}In=њU1E%Hq'EZ=bT 2,*|WQri ]`N-g,0Mϓa^w*S ,g6'?(]K!<.W~Z12om;}{cͱ],2>Ќx<+aJ IkxˬUQ=9pj ǎ>:5%2S2/Ҋc=&f5Wgfuy3yi̸XGٗUϺ~@;#,67wVrPa7954b1%^{ r1=_;fT+-SҁrQcbLM|/lo͗ǽ40؛{4ǿ>t̮Ry64DE N9)118M1/={׵UqiFq?ʾ<:ܳ^}WoR$K,6˖myC6`X'!, `dœ?e2cBBydə1#&-Ͻ.v~wmw4CQzwiiIHIAocJH7[@(01rA :U&d%W ̲qyF ChvWN9!p f.z2EL^WnFm\rוCq""-Fycۅ /C L&ؠ5hX84VQegx߁4@ݟ*234N@kB5m .`1}=p3[`~ {@ ,wFp[E #P@vY[zFg/y0:/qM N[HW5A jk$#2>Aa'wV@19)ZݯӫՓ|!AXOM5/x] ڂNWЦwT%V6R!|j;ۉDƞM8K26 lh{"nJ鋏n8,@ ˫a1{"ŵ؃d3֠0CH0b=F00qȄuHJyw8pH,MWAH._\0A~ю !] Yd5~l¼۹:F5Rjh՞wjРaOD_[$9sdɏ=0~r$Έ^都= zl)W-^'` DpO Gm_S|z4z$w;u:ŽKzt Khj̅'i?Nݼ] uB/VD!'Vu=]_};Oڋw~|"4u< C!?-u;{=tȻY]G 4؇Hfq†;)>nqoS~*w\`}K56 Mx#,V0M`9|ӞE窃jEB3*DhĴ<ӈظ }AHE88ގMﶷ;J@mSdSxxG!ƒnU'v-LR5hK^[1~"4Yc^On r߆z_P' m|syee?u=y 7v,+\^N"ފt]?q>_tWťEΛHM2[<[Jw%>[ +h&h.t%tI);+v+o*6,hMW[&Ӌ M\hU"l֎PK ݻ~l}_]qJ3:2ljhcB83}7#+p]l/RԒ|q.恔 xNt V@W>=KwB&@:~6d-I Ӹg܀|2@OL) 4\\SPQxͳﰖgߩu}(kЅsRK/Fo>$?7* V$v.l;r '(O)9*j~m6N7N#6JzBzk XGonm~D5^|jpuc! 60P ;}.QZtYaʭNϝ_6WIo@i>Ih0ܨ@X>_ȤcPHFecґ4`RFxi," ʗ|w;Mrh4EXw(’CQyqqT6cF V&6XIr 62VD/8Vċ\W4\a<iayl4?6 0>4 _6yd^5()Xt͜0.yS:iݼi i65)tc#:6jHSZȲC+v%[z#H<CAx~L'ʆ1zOQN+ց6r 6VN(ZQ2t sV) }\cקW49YD}_ c5֍M"?y}/.NiIB韬g]:議θd؏U? F?$G&LXrH+Q? ˖>*,m#zT#̛gDrƀ)J\2,5J`a$QKؔ)"a{' L&&:lK_v-VrV H;E!ND+7Yk3 {e$ؾGH2b5.i!Ҿ(Q?.׾7p61/xqݩ}/"@9/ᗀ  ͏F l\BAXpzIXQS12dKg(ą VRWݮ2hȾ†NKHMweXDH܆V}e4.EnK#)=M2foA-:seS_Rh,-qʷFirӓ=˭{&Ft[AX&lV tmx u+ fKL@jMޟTD8o?,Vei$a1uCPI&iEdJ o:Vz7r'tދNTz4ᦡaC3w;*< c4jKY}9q-C({L C`B'P[_UV gPC3 x,{BaKDӘ^;%_wq@kCU%?Lꥫ+N({-4uv{ &X1pǾ*Z4pueGuy%&g1RcH c%Eđv 3* [K(^σHWp^ y-5o~0k+%<:s\4Kw]cv,=FF"#qyIDV}jii1y1:ikg#}yC@wbS.gšv#lE*˿L_j~/ZA;u`¨Eoʹ y/V.U? Xt/dϦ Vkڋ\&{٣kXd9=>z ܏4c=~N⽗Sd_ubd-`-W">35[|(G@L0(=]k!; \IkKvZA42 tEFaqtYa^B°m\i2;BVF,͕TEO`z}O]9]{G`ND'nŇ~d^V]Ɇ: R'^yU\ ٫9s8(bJӘ g?!ز!;PWe)AQUE#,x)5*﫸[\}ˁ>:~Ph^~Z`@޷fk“<%q8בxZkע_?o 68!clWqqdž'8]p%?qjX+iVbkZ4ǧ~͉ 5[m{rksiϏmO<;Go y;G*;{ l@mB>JW- 4]3TAaOCHNC`;B:~RkJ( +J`N} RsA)qSͽ#JkdO?caRC+\qΥ׷=}BʪLu)н޾kjiuP-xUܓ&in:;UO N̓Brhfz/jw7E[j3>m_dYVo \|r['z5{&+/LqY1?XೃTAQf]em-z>tz=Ћzz~&F`i(Fd1+5ܟZ +-H-.We|Œ:]>39֒">P0Q"iV[LuQAmRU5#t.]#IxqkK1\I+VJ[ΐ$h6ܟSoV 6iQ#+byٯ)/p"@.H楾qٿPzNWKEᦁZ!_[E+nt-~=׍]Y+Rkch= J", 儹6nMޠ vmjۺ,NJ{aiÑta HV YWd5_"{VYFSv&RfJY8TM(Z0( P[IW/\Kj.L.Uf_@K6‚y!ֿgU:~YѥK#oT!f=8e`ׅ݄F]*˴ߟ=ɦtWEHϴ7UlfY`exܘ<.mJ(rS%fCt=' Lh"f4G~IuX(ypt.BUpV~wSz:JmXS:lyW:szb*XJ'Ό0%"E@ fz0Dh2&ݝteͦW!͞R5"Mw N" :GB,4i&=ru7`P1*XwW:+ t:ZJ`XEEiׂ&ȲWv,|[X b%V41/˲JY1 ^k9b*3/Fb~w96f6_#y su T+ɫ[fMP:*\Y/<.y\NJEB@% *#h?~ qh t*hL15fꏂlbNH5% j7:_BϵhAs|-);|"=Jt*GR߹c ma}w6"\%楱ukGCLȔ˂(Md ~&`izǎ`9"3x$'[1%hdX Ήz h+Q9! r'=G䤡9ᫎSAtBCX[f9T^g 4cYd1/+M-L%l4rXJʔr˲RS5Ybk׌RPJ$ q+'QmxlP $y,| Kp0{nҷ,G~\'31%$Rӽqd:vA'p?ӖNMa rohp b28C=H“VrU_tNWHx:{C\nM2fs 7}h1x?Gh,ݕoȏѻg$9J#I]K#ަP j}@cX;;t {=Oke>j6CAXw'#ν9͓s,s3f Gʮ=$/:'Źwn3ڙsu{ɾ@ ĭn!K!^. 9nh 9گDQ+ܮĒ:Ve6#뒽AGl%~R/sb,\5<^hN&R{{;V?(!G5Y WްN\3+,xvP-RJ1yt ;\l .MҼjL)ܝU]Ә;ۖ7[+}쑫N#ݘk{..dUEhW&_'1dma1')5WTi|APVH_ +r46H]]z.\,i+Qm $zzQƾؕN35} 5 ͇]%*6i8 uYc ˱C4,|XACHI5úhrؕkz̀Uwu X@d&Z)؞ ibt_9/cQ EK|Nutwa7Y` oHMMpF4R&r#N) x,4`kԱH .Xy.;qNcT+= +mg|'v[GUrGn&}LDX.p"Q% J%|^t6Hi+<u8)$'u7q rs~Pu] 0u=QIS1’ D9JKF@Q_Ns&|`p%w} hۘ!9Y8N Y0ܫO|2Y]VauqΨupsYkA^¨ǭ_!s^)0yUibi?xggQ B X=)r- ,jٟ $h|3Dp3t"aZ W$-OgȖf!I C g a;͸7Q=; БD>9^x~$L$O~80;?D nNOOyhZEG$;q-8,'O; Wo |͗ftMƅ {Ѻ^P:ot|BiJ7)  :pww}idthߪ#Iº׏> ~kN^I}rK h]dH4>Fn>c|,Z ({)aΌUtHki&AAs:Is,^3N \bПpG9]~z@lyۉU}D|c JĽS\0XL%m%mJxqz #@a`ܑCpJtBw9( {ZXnd°(^,$^&޷llhN#)ؾnȻ{hbXXF;A=ױeŲPB xŝ軺>hNI3ьiӺmɒ-ɶlc0/WIXX/P Y =B6,/%,7//$YqB{,{-TgU_~HAy\FlKbje-/H4l6Yq(v;?Ux; <2w>)5u:cziPt";njstbP pu gϰOw+ Cb}9h;{c!,QfZVlq0{cק"@)K64$U~s7ꨔ %".Cs7F3=C}2ΦטTBJmȹDY΍);&2JO+^!:xiK'+YZ *>N N>Q΍20ƥq6} W +HY,אB-)MUm1z NK9F#NAF:J3:59Z!:#vwMJ_Z]޷ %I\60nPDˉUO \fcNªOO*2t&wd&CQP-e"/gfP<ڟ*GI}pnD}9yirb'L%Rx:rE\W c~b[qD)#Zgg;Nzp6~'e$FѴ=7^o#HًSXcx w h?l;zٟrY^57?ɣ4_f$?tE )E$ 'lrȰS/dmK VIyd_9;WC\"Tmi8D=3wl?mL]94LC_=QMmk%J5m5͔XI)fTlmnsޭ0ՇTnC\ر&Nv(|I@'1IρړQ˚ S\R'YcWC B]Q@~5D%!jwJLHʿ RtH5+T[1z<5stEq* Mi4<<71riU ͛FUq"Plh L#d|֬R(ee ֲT!+ Bgܿ.ؽvqVlQgæl;gv/Y_mOݼ97ko]C׏F}Wtٛ{RVvק6Cɩ!c]7LQoO̰15Oc`@3S*,KB TtU}qPDhIYM%>~Ji+zloB#bI~ 9yt4' ?T)W$Ӱblw}>GkFc-CVrJCuzxLe+2Gvy7;pB,n[";elmP Y&lf1aM&4𹘐~ECG~G6`5ֲW.LUĄІ/23wnp> k}܌ fFEh{cLm +4j66@e7VTs \dc:Rh~L$ P! vTFZk&P6iLb"?ZBZt f_$w=s,NcB5'Ǩd CS art6&W#?F995TIN%"*90vVP[tV*Hss#7MMF?zV#cn=8ŴEcv=nS6"CZ"-Vgzz| :}bKEl_4-O8Zp T?خ}2*P*$Ɂ٦&Wh`UDBRED_23U)ER oy#Dllo ǶWl6Rky>~*5 .f:ؖ (A-Wz4_:&-E)d޼Xpq%*Aat&T4EXo ~?W][~O&йv (u3kz Y,_Pjޔ\g׃GWt,7j|d=M77MkkCߌv69,kS?\fiS^|#$ Qy)җ,Xry ${M/Dz AއtiŐ\_mx#ʙ.fO:8jZTR:1{[5Q;pn|5W_&\XMN?E'Sm.T]qjG.:@f1x fgA| NpUn-{hOȑѷTl z/_Y\~.:NNN2Lm"5B|8"Vm傯$P"?0 X-6[euEo*0:̽lAJ>imso'7yY*k쭾W?X!YSIZ UZC1Z5 I@Lz& 6>~[hZ"̨ 7fGlX2  -lV]skMb[Ew@y~}p;g! ;ccq}(+Yլ^j_.2#6GΖKN U 9<A:Cjء=mɮHD.PJ):Ḻ|9j*MZIIAGuhvIIQ ͢u7NW5FYڷ)q5[uNX<'I,6eTzY (sqS ^_ I=~PXl PpoT.(}! $ׂ[Z{_Ip?p/Yݢ]AɔRv_.Lx/O.0Bc{QE/1{cy}(y:PGҸB5HwyI?ؼpcD!_[`Go2vʠ5oP@",SVر-I0ƱJeYC"ZO;"Iۖ21kHH~BBس[b\*^,pv^E;`ԙhTCf#ïIk5DSg |slA3M2\3m[nk7qStem %ƅRY/mukMne:[u_"Si]7lvFE2e2\<$XޱZrc˕Ar[\B qlevK?jLt'LSlu &/ݐSLR+tf@A+'*Thin^~ͩ/ٞTM>ZE/#wRP?CFN-ӤOkd|쮙=u>:];n49՗eɾ\_WvlBsyl]mݺ-5RW/‰tgm4eeqo b^a 0nԂ`!a ,Yk p9R&&h\0 &^m/ jJ$Rj%xZ>" ~ !# >y* yJH)AW\.(Zb5 tq? 5Qh]#Z(ȤDma3fSQGFPtY,9&V^OhNto!0R:ꐴ+].QLxxFT*tW}օ>SOg ❷z*ؼ뮩`{GIp n;+.ism"QngdK{],ibY&u+%JJajM*(%f-Gy0{C=H{]# Nvtx 9 =qr߯ŷЫ !=Q gѝ֗;k>}Yw^O;бgw+:?z|gq٧6?v׎do>Z# eX'}e?ĩKׄ%dfP|pPb4z쪢mgm2:]ÙwygwDžg'yX#OD>YjY9>M>ٰ]Sf-&ߙą:3~ǣvǐCmHDM ㊩P;rbڪSh%cW6_}}oK|! /mkhVzi6zW^,/K+㼴`>K+[ܿ`;okػB)Ki),zig&;G\?۰>0wP*6̾:O%r1Q<+D)7 ZvzbZvprD ,M/9g=yOr^Z#+^=_ܨ>VXzkg^#Ψ]aWѠp$`_E0)p )Acb W'|'/e3reE+KXd 3hWyP}FQ, !_Jy4(\~u1H@?iq[bhkDQ~]PLh)%6XƙMuvr:$jg{H?\Hq1m`POAIDAp9i AA5HAſIL̤唘*Qh|>Bi{l+ r[0!p$E+G=auTp1J~q$' c^+tjYWF G( Ѻz>hN6zc`Oh=aZUX[,A5ռ_%%QQ-EL<A|\w: |x1GTuRCS {&"}cRL q -asCԻ5/Y'fIuƲ"x? R` JE@|?#W>2>|>, vLJfmm顺QzOweOwPw OKnĊfU@Ύc+X xc>#@Bi 4 qǂ| AJ _<%NUڌ! )y3h=|;Rs@Td z=FP3@1ySIt2e,?[ʅQx(Tc)5vL@ |6CVGA3Ppk1ׅ2إ UphCyxз*$/w֨-{ ؘٕ7 C_%Vt*=yFKXc2w ^mA= *İ++вSv p)t3WlCuėHQLEO'12rCňT^$%n i($x TصTͭwP|s/E5-(5)c-(4Q.RcE=Әp+*bYzF c4aH7Ba;ř꿿-lB#Ӛ-po'[欰Qwкop[}a3jOP^=#? X}ʭjO vM}[RuE4പF_ k+J+++;hr[ë0 O ̡rx 39iTdx؏s|e'DRdf G*9,̔gv|s6CJ9mV(@jv4m_ \}>@2 y5rk2 X)]41¯E +'i@S?ž,CU/ݜEpp:JJN`^y>ú\WlC8,aG ;K~ķ|yIR…6.LT~g[3ߙэ_8? FA9; |RЄ+&EmhTW BEgŔJBƅB3!t|4x=,He[Y“9<(c^e>t8DѦHox'46V 8;nP5%v& oV"dx)>5/[m&xawA[tthzYu2y95^. {hU@i5+5ePeSHfj3ؚSllByenZ$RI!IHH.~WKQBJs1o}߆=3Ī<#~8(OL\%l*Dlu+\swD=]\(@B:zX Buy-\oWD׷πvM Ӕ^C0)cV UJ_6锎tO5-_)V'~?Ig"xO_0 9vc̗#(:&”B?\n|iYDVd\#"ƗJE"|-|5|02ݐt<)2s1CԨ VfJ! [v4*FXIKp uHV\#nWzmЊq agNc8 qxhSG>O郵/UēؐvX/?˄}}BLnKi;bM5ٖ͵ە/mxKrso|PzSb۰Bk0#u[c6 zoJ8&dع:(!>BꟚ/]RH 4OXKB, _T T~JZ-X"B[/&6.. ΎZZN[9,)#<CZ 3qܔTe|12hLϛ ֹ/ Ҹ8#*9:{mk{:#>i{SӖ}hSrVz47ڻ݆-A:K;ÝI<7 Nh8Ի6*]h!Qc޺"խL ((- a~cgE0O/V zI<%?X_(Αs*3jz(-yܞUGq\h } 3A"U֖2o4p7&*3 uk|zb#)RJR/3\)bw`z͙ѴI} tcWߴ.}b[lRr9U,$Jɒ#THRKI CS @X; a4 KHIp*UdɵdizfM[?liՙ@+]Q zkP'R<:yBvP[OT'Op\rՌl^uMFmw.9íwNq}[Ǒ'8]]#G y؛-wPzTo=1(%IU֠[R5|S! :Z iˌDaP,*=B=?#%z K4I$Pf}v ZϣZ [̠Z6''.Otۊ[qTu="Д.ϛ i!&kJ(Tt>+TZGjA6MdMCh[ҩ Zڶ`kUPz:R_30iW]M;j:볃}u=uu=4zYB^n&vՓ]* tIc{ ~VuYMOJ3\`JkCVD=6J( } -.ַ'0$rmv; j$-u {:mڰ_ob6DQ[6JT[j4pݭJNƄjCj2\,i~!f~]}5Wcj]j賹dQʺKzvAvh VdFY\6% }"5ɈJSHWo~)VMЭܬSj)y #[ț uGw]wH' 䶧̄OAxN4>ZO(Fߺ6s}*Sw 5V*͌U~*9 !Bj'&&J'd pARqm%Q(&3TdBJ58Z*=ii/H2d ޮZg[kq8r:7:`_OK{cSkwi$VPvc`V|A{anmqHd,9Vlsi5N4Ƃ˹kKS?GD;rS ҇vr^(NtZp ȽͧS^H w8yy:cwYmm v9\3H̵!)Ҡ}y`~{ծFYN0y{="\QL  A;jw(4.]u8(ni&!@MY1iQjc{T@=fhtpy4COe{0Jil|hZ1海8PA?ӂvPtŧu/ub 5O[l]Ķ3ph_IoPb4 EnRom2ZL#PlMNHK2fx ,D@kj >iz''Oq qC &A~ieC7Hع| ع$DA {Ծd)[ڳ >=62ߑ5fZoIwQ;h\zH7. ,ޘȻo^ǽvۭF$}3#t5V7"p&t^x"cgښ?йpK 3ᖖ05uP(n=ZGD' G>?Z􃭨m ~ Cc6@W[C[PssD4~m[s[P}O}Ok(лz +Ϥ<*Q2-,7TS]QPN'Hk JNI}åw@IהP /g_1G+,?/þ!!7g1Es/P@ԕ~f>*}4+:])hMTSorje9Pff7hj9dZoӨ9jJӴl 8=# R7"~zswp6x_fڦ:ѣZM-~.K->6KQ?4h9M/&^mUڊ9jDZ%_vڊ7t9m:Lt6פDeTJ d~nZo?i ^N.^92FylRh*@Q&o0:|]hIu _P~6~zH@#׽s;~uQkֹ̨m@$x! GI}fRL_ױ;zreؑu-K:IN9F8FhHk(8A7G:hDBįWTBVǿ`TKϊ2yUw tznjلvtSFիn̠cҙ1CM)Fԟj읪oDJgK~>N2 .g^[bhՙ"An\l^KPL$ζ0#nR*rθnO%ЗL=9WntT~<'Vӕ_%3vEM6[?IZ;]D~'YjgB6T($4bxJ|P Wއ0B7-l N]BH$؃áӉϥU;-SdzC<2J~B --(wAС"FZ0>@œk;s#zʼ;5X;6ȝ޾}2H"=utMݖ}ѢicS=;>84Պ]#;kx6!yYF6P T^My\RJo3R iY[g(,,JNÄn֪F4lS : ~hX$>_ @GbFa|^~EgۖwOȿ:P<~xfxeltixx9~}3o>8zheLK\1rC0Aian g{f;Ϛml6'b <JW \]]ro_~]WEud[rت_7rF&'z,ԁ8 w}gOzꕙOܳ``:y6٭<މ}&g󞚺FC<Xg0*yc&5#=Vk #C7XWuB[ip(=퇧or'b=I~K| i3rSԏV<y:yM$/%GB$;-4;IJ4Z=D 2Y2XuV~⫔^#s*TV^8+ųF J7L1J7VqF({k:f-ƽ=~ jծi4Dm̓u5(#}&gnϧVAkl@=*SȂFrC^#u\9QFwo) T"{cgCJ!RNA澚򾁹TCDGSuFTFz赫ʨՉ:CJJ|3HdÍWMǃCͷP-jU}aW/ïvm=?y󑮾?tgzYO'Ͻg3=8="F>99R009 я<8Z3=X3剟L0˽{;upTvmSL7ǧNO4 ]fp ^؅]؅]؅]؅mدC 'wavavavavO4=1{ẁ>:'u_^8K>|bßa]؅]؅]؅]؅]؅]؅]؅7w%$A BL4^*~ cu0_,N@񧄗>Pl|8@$Dfcu?6`l->Ywo`|;ߋŇ?ڰP1LRGBհo 1p8?0{a?H^<`!⋀ [Y%;? f* &?3 XcV|Y ;H%+nC!h?G>_|Vj3r~Iaq}c/V;yF)gdF~q4ߣ/@~~X,^HP?̃r(q_/`+/EX"s 0"m !逞rVAm%A>R_ / 40'" h0ϗzz x~9_(@[X[ 5B7E8a,ca- kXFb-m0X3tV<xS 0(pXCeE;kAУ_3 `F"oaDO? ~ˆ~'O1F<  \Dm-GHόy` /6x` h) F ,m)G^/E^Ȑ? 0`ě `AXh?F|ʑ'O <0x<0x` y`!<ED<E|Q ,G=%>`>`>D,A=c׀o|h|kX|\,0~FƂa, cHX224ւ?{ w4QnRpD 3=$JmcƷ af^Š~qyo̤m)2bQߖ  PXQ)|R;& 6EǷilf*hxo +EDIńA/ߖj ߖezhr`zVh|[It8pB|qjszڜ6gTpzŠ~N\3̵9=smN\3V(omNϏ,J4-D'I"Fd T bC0:QIB+MLHp.@ &= b(l c8[,1 3y7)h`NX?4'`li s3F/u~fXZ֍:hq GЋn< П22K AQjy2,^eư%n,8 o1 vK \ɗi1~":u+w:Mnw`OKq(jgD#g,C ٝq<kǭ(Snlb=p6yI8pGc8 q9ުn+Y>bUQq1p'_>$drCɖLG4ѫJ^ʫ81'q|A)ׯ\-={i$+r*DI^ϨfRz$P'%-W{u[&qMw;wG\H:h$>jVp<΅9oQw;Zl7o{k1tiQ[Sk\⫴mVYʫWr坓<9/HkqQ;=e])pYpsɏ9ZBWYQ,gSv3;آ(-8Wc|eƼV֤I\{o<^ݶО:Wԃ Glװ%+GV)\&w]kg{lg DG5w8Q!Na[ȰK6ʶ% Csxl8M9a)Q^cfǔsx}; <1 =q֛Q5$9N-KX$^>8'{χGL19 M#{>gn2s`<'(e~3Fim"Xm ,p 8C쇑#XyQ^gHi|-ga, *ea2W1[T|cG5:m9عA쉣*%/{^wrk[ٶW#,xK_ kfF{Ou}}q}NKƲ\f9gl4̤l$b+;%b"Ml v:z"gSdeOdf3ESD4ĎAj؈: g9eKd,b&F6`lHY6`M.X"KDM-%DMql`X7^4X8( NdsAv2.'l"f E2kr6ƢFC6R:LXKd2 rz6@Tf]ɵh,&l8! c,K<1P>q['%,//ǮE'/ JFAl24hqzrہ<#,`[ 9Ol5\be#͖tEP2AGJl4Xf!9I˞_Gݱ N&r鍘?+L&ϯBҸ '3+P,Is`Dv-tK'T%wUd2Z!pX>8 1>jgh2KmosIܶ -|6\X${,9f:ƫTPc#Q @b"DjZw9rd$'ɥd'A̫:.Esk&]%#y_HcD< f+!tʛRnAJ<4R- R VwuD ,2NoTQڠx]BC[6 Ȍt p6.Łgo\ P4ĒQL BV:i2eHzQGCWquW[w7}t* ~ʭr VI@<>X! PnoXzim$  љ$Q*aInLlf֮!#403C1/$bm18<7^ѥDEMghp|;̞=̬>}||i2Ӓ\ZF~Iiuq^O; gTJKc+9G*KIa&2藞엕ٻO2Vfeda-ɽ3┛|IMLO!,ףwYiקNOJƓݓYb@Uبi}IOzc)YM)5Na}GߴF3z蛅n42oӥ7I%f1Ir.#9P!#In撔e1.n9s&S(͗:W.1F}qq싋ދ '?ˋ@}qg_`XG/1/19"싌/2ppafS 8(+?;[0?>F~y^#i_s00AQ~ GAQ$hCŌC'fzZF0JV Pͦsl*`a-+sZ,nbL `n1Yڋ qŋ ?!%~dwl8?{AVhװi̼ٖywC#~;yߎ'#w~y/Co! y~LCn}>y_/Gݐww򞀼+,y_Go#n3oo +wOyEޣ={:򞋼Eo w{7n@'+9k%z2ywGiȻF#w)򞌼g"{)^7 {/>Ohpf޶[Fm;yg#]E޳EyoF;w'6 C!𢡄w<oD{.FB3<y7!ϑ~8ƃ< y̼uO ^FCWy!b}~yG/ #] _tD Jy#~{0}?~y?@U?y_Bމ;9y_H=yW"ǐuyFއl* A#ːyAw ȻyOGss{ވ/waCޙ;yGAc5~_mtڵOzA'&%EEyv5sm^ Ϯ1ȸHo>6F#quQ14P\(Fv"5]0VkbAi ҕFùK[4 ~;cv7FY?Mm6EE|B0V]]g@'glT{} MNlӏy`R Mӽ^/!TktP z_B} W;Ϝ9%No:z>3q{ܫ^\zSI\[YZcV]&_훗:s46;Ա@b\6w,:K)pTǢܱ4w,:k4ulƎqpR&8]pط`'%{ɎumG%KATp8D\=wl,hW)J1WP|# (r8ysv5M+Z^^>8΂~  r (;e^/ĭt~ J0c#rzWKSdik?8#'XdD3fc McNtH[E䩈Siѧ Î^dTTjj ]o! ]a yB3Jh)-pieZQkQw9'Q`졸qLDrtۙ9|dj`q2.TV\BIqn!b|bq׃Xn"m%XWs E͛*jGvQ,%Q)S`4Q{܌MB 'qLB-O #KkrfN䗖R>.}wxxSG*NOr1O @3.X1,Osd1)Fd\r%T@%cj>/ΗXC]yҘjji%s{Էi9z{6Եn .c5 s% pjk7xǑl641nC&ϫztq ܺ[klú:t,Z:3VMTnYMli7.sܳ6m(c-40%ⸯ/mZjc-R*z8P6Əwfpq y˩SDڰ}!3w#Fng\HkQ!|͎gkǻxBce+茏=C!jNˌWmcOav+m4twhV40rksccnE )0 z ^M'i?s1>sqс$8qԠѾw*]?jmh_Kҝ| a>a>3 1[YX=-%1oAn$L업8)A2COX Z؍r\'0 8 X_*G8!~GtX۰(.H캁0iؑ5 4ȁ0#^ ] x}|߸&  n'A<̂m9,U>oU`\q\ rx x6> kQ~0~|Td(l/%X }Z&pyC!d@exއ-󱴛g"J . σw)3#HLY{`p߬|s΂.Y?^g/a5Ԇ Ȅt>p>Ѵ>p~s3[A8Upt  sxsf6?ug[Dpa z̈́ &H" +$CX=*عp.0I ™Մ W#|o4;̈́ 'b'O(R2rJa=%Op 2•n Bpw1ƆgaQJNJpք>ф  1p!ы*1F)3?m~Ε6IV8F+ -uę΅qM..kxD3g83?bh?7 \gWRpU3B2ecqtX r6o9sV ׆? 3 o$̢#M#jgtʕFό?! .z Wp| >d3K>]WO=/@g|.46^y"W SM?q٤8xLgVƳџ<:>9>GOAe>:=r|',g33ƙbb/X YL^+D"MG5JuRPD=-Ԗkn{{=ݞm/Oϵ/o +^9r2|f>qg3י,v;g:899:w;4Wҵڵɵ]qzFy&zfx{Vy=ǥSd\"WMN'ep9[m;ӻĻڻ[=="CچĄćdL Y:dSH]2mhLh|hFhNhahYЪ%C7օ =&چńŇeM [:lSX]2mxLx|xFxNxaxY%7ׅ ?!#FDGdDDFELX:bSD]dbZŷяz`> ?kZm-|͒nDžX f}}/o;m -[ҞY"?ט_ל~f;e-|;-[8ZWSw~+\mtSRe;)p]˕le-*uanef?E ~wj=&Zr^If-Q4}/işCE*e)L}Sqm6 *`:̆jXKaz ۡB ueLIF?VeQ@qV*;GʮRv;ݫ챀MQHJ+eUSe+[lUJ[̽iV=ogS,~Q 者{[ sk:◵ɏħx|U׌O`2LG X66=p0VHY3UK]6ew)٠lM1+lʪhϮ1>pY(=eUlT9Pf]Y9UWNwe=ZT)SVd9sU1RY#g{5- \0;w.hh b䃺SsUj$]CsU=A\YmGy |G^tMxeU?͠<.oݪ.m2E`*eʾd-sC,~6HşjY-|Ty>m[%+XF> )P=RSʪP*Vk+[fMSEwf{zQbCʞ 1*6;voTjV)EteXZozi&u5NMPsy]^!HGY/%߬oү4+q-b-*\sIIgZNR#ؤ`q'/7M滢bG*)SͧιƏtB\Ӛ+be*լQfJRTqSTg⯰T2kp?Z;JHb,oUiNeÕUʪU Ɨ:~h'E MjΫzHC'vniYju0K)?K%YJERUYq"COȂ[@TUyJ1Rk'UlRZEGjѼ6VNԚjz?o?dn "jm?ǚEWx-n=ZX3Q6ݬϒfsfq_c_2R~zޗ[qfez-_Cce-|#=\jb_o_-0_b_mwwe=2XiW7;̓fn;G7-j,D?4ﵵf}| -~/i1G~߯4džv 1hg{Z*_g)ya;pUcB}X(i֝*v50\Cpv!e"hs1Gp,Rx(3X +ch `.>qtd;՝hy ^܋%[+@;rp#뱭P#Ll܅v _msʹGFl{M'ocJ̖)=bn"))e lc r/ wzTFր9'77'b&̂n֚~Ţ²a+`CNV JV Q~`? v/i<p7w,C`6o[\~. 1~ X b>V|,q|'2|y9{m>τZ>ςw 'EwDHc\<.gB+1͖gcmClCؕaamm66uaWƲ8G vsyn;]_{F=w{K]y W^$/!byKDD\^.Z+Iv,&dh-.\Mvmd 8_&$ᓩ2U\ sdh'ePCE{9\C!.w;E#LjKX9V\*w˻"Z#IrB\.+e(gJ9S#q-gr#s\'dFVjEΗEW@.BP\+E"^>+rHK(_/e!_$|U$k"E._rHoɷD\+׊mA#]%ߗ Qn)?G#%?>+?~3/_y@ A,CyXʣ';/8b)=lg6tּ5oţ)Rqtsۖomm(HppV ![ JAl'=^AvV2JF9Ry)Dh 12ZX +F^%jh+ >y.p2 im/Erd̃909 ...dDbY Y1TLAK?\!e9t{J9ENNr Wirtj|aFΒ|T> ]c1õ yr\'OB|J>i4tg|N>Iy<$˥r)ȗKp\&A\.C\!W@OR H8v8vB\=mmM8q'(_nQ& r;78g"d*-@rG<"@4ԉ{1ll !WW BqDglkMg|ggclbFuvH@K#&B%̀90`%` ] ŎȎq#['ٱ hlnc~%{Ȏu WN1d%{ɎuLA[*;#;1l~c-vT|٪X)M)RSg*Vv*e>W)eP+eT|٭Z)G)Rd)N/*RdR;~JJCJ"G"G"?)E)E~V9U)rRʩ22q2NP)2{IaR)8g37}+i\¶'D-abMcXq *Q)SK|%v+wb8 EA?∧36XA&n7"C@QbXC\ Rpbv^H]l hlB# |߻4>ZvҢ@ZJ;G`z9`-T l] t]hR ּymIpZ-Z:CX(N-j;bxW')n|Z&OaOs9{q[5zSW*X-5-F=]S B,}X/p#n(=O[iARםatuF4uB 0 &Q?~La<7%<3axflx`.<UT<,a)1N /q vװ-?~8{':q48 ?1/p~p Øy~?7[<9<yCP>#H~/B~/wjwN9_z%ſ|};_8~Pa~?cg~O_I~ &B6aABq}D_-\1H.b,G\T(^ $>ŇbHlm]|*vN_zUm>Ҷjk۴Oڧ3mV}k_jmONۯjk !GvD;~֎kh'_w'EfY`EDKI3D$AA2H$d ry1!CDy{޽T5Ϸ+=O=UݽkHuD]QO DCH4MDSL4w.REVwGd{EGIt]DWq&֓h-E_O<$b$!b&1b+Ɖb(ģbxLL'ēb& )fb+ij"[ BH,KRL,+:^lbxAl/-b&b%^WqPxExC)owĻxO/NqJ|(NL|.gėJ|-ߊ98/~?WqI&.+⪸fa.+*ZkOgWqAb FX#Qhk5g&XGI֣d{=c D{=~̞b?n?a?iOv=^`/ً%R{^a?gW٫5Z{h?oo_7/[>{}>h/G?ooo߶bck߷??j_/ڿڗUr>s>wp8_:goosyG'gs͹\q:",#FČ"H(""HJĊ'D#y"n$"2"Hj$o$-/?R )R0rsPHȭ"bEő%e"+#"1>XlR)w;ɾgffg Cs9&7}d4gy<9,[g`: ξ6P5P3A+hrA7c$tD%~?YY zg^.0 iJaVP6Ÿk4.Ke+_XD+H##P!?M;}f4SyuH RFB{9jQJ< .HG@1X Z9F Fu:3FCfM&,`eł,k YۭLX\ko~ }XXmCwLO^bY m9lk֊,td#u̱=UdoXdZ;~'[:4cwǯqo4긇F0XL#Kj練E緛ϑrNIs9NsSй)vnu8EbNqSѿoo)Q+j!v7'FcGWa]+Gq456*_]EEKg+=]qrfL-%xgޓXEޛf#Y>ay6_:Md aca6gL`SGؓ46xʘ`YcA^"1=Tb+T32KenY6]oo`//|<R/CMBxМ<^<ZK-zh[:z C'B.Cn4:{`,Q gP|<%\9\ W W4Zv6=\7\ 7 7 7 7o7mm[p;%ܝ%;ܛB??auVO?ea0 Y[_^Wmþv'{qiifLdss!t#V^:mIΪ4p#C'=iO۔⥨dL2{UxJoDFo|m7 o ,,,,,,  f++w8O~O)οg,Ϳ@`b^45/+UDL|ׁh3Y\qii()LZƋ5hXMl|ah.L?%Y/;q>j<wYM / )׶df[[VޗeELQ>c>zqV?ɧ >`g3,XY~cyJ1n٧Θ};'v81'$MFy<+iXߨO#ǦFS96Zȱўiӓh XO5՚lkk={,:abiGg4meĊh JXJi}ʒ>?ʓF#Vtgv_*c}ɪNU# N;#Xucn^^Ke*奠//UtΑi$&@4R/ _򥬍&fk+<B [_Z_b7/Lfk-*STg93D?d8g"FneA/AeQqY?wUwww5@܍DܝB܃z{: %.:l !̧oa[AvtZ "΃JE|5C|-0q<K&Cvޠ0;3/Ga^®v4Fa^žv u3 AjLބ)xs .-(T̠)3 b(Ԫ) iAy 駚‚«0)̠'\}s&l=v;a|}]ZR"ԖʼƼi,TZTBwwww#7!_@|ߌqOtLy,c͈ !paW÷".cqQĵ"bR2r sŕUը5> %GPPQP(qy6+z:ϣ?'~m7J d3R<m-/HN[ֽO梭 +e% x<Md@hx{~潬d}r懍Xc16טWWH.Y%RkZAu:d^XXGD DPTD֯%7uźj]IO3Yl{=~ƞg?ko;.%{kO٧O3Yk[}NIq,v'D75{>$ѿIgt^| iͩQmMmRޝNesz2L Й]uw=[%-Vpr+s.&˭V[ª [.YՋAZ&5Z"mm:q_s䯑eOa}66tws仹]&T۔l6mN}Š;j, Bd':Rؑv>dPOJ<&Aӹs\̏چv1:TBx4=B^1,j~ =#VNt[+ (HQ>R H1)R)RGJ)#  >R H)) R)RG2A2A2},HY>RH9)RTRG*T#@*T@nG*T#rTRGT#@T AH  5|;@ 5AjH-Z |6Hm>RH RRG# @4 AH#F |1Hc> Hi 4iG4i#-@Z AZH6 m|H;v># rtG:t#]@t ]AH7n |;Hw>H <> H/ G#@ A |d @>2d0` 2d 2Gya2d2GF#c@ƀ cA8q |d >2d"D2 dL 2Gy 12dL 2GL#AL@yGf#O<  2Gf#Af9 s@\ s}$$$G# @, A"E |d1b>d Y d,YG,Y#+@V +AV*U |d5j>d Y dYGփY#@6l A6 σ<#@6l@^yG6l#/lGl#@l A ;|d'N> d.y %|d7n>d dG#@1X<1Uo5j)xefڕUM%TM ČMŌM#4ƌM3܉e0;CzffB,Ub5Yc֚udl$Ħy=؃=؃Ϗ=؃=0;iNaV{|[$ >8bXlA\ֳm({bgyvT^gֳ >f蹆d!$$!Y$Y 2Htˑ[*wouޚ+Grؘ{ {s^ےV-7Gۉv{n{R!7}```/tP{hoajoh<qGXju1:V{*Kzj#=DTNtwVDV$ZE̙-lGs?QEG 133!+bn,H_FTfiKO * a )97oP:5uF)F[] XPoF Kd=x_}uC-̤W}ʼ& vGy 7a4ģX?,ߧ{I|O^o/o-Oj)<cAvVoDK~&TLaz$弐OZjIHu+nχԚuJz f^0ykbܠY/w҂Y%Fܵvա3Q7}SyX=cz̋^ݜJHgbWz!=--: "cPΚ?gTKO6Hh7X2~Iw=[XVQ8gŊ䧓(s<$UI*m!g9T|cC<61I֝'IwpuhmKn}OY^>2X|֠"IҎd8#?θ$B4oI{ckITkVфTN}~M)o;jmנAGk+bPKWCmG'Dglo]L6]Nmۢ>zj,Y$/%Zz J?tQ+d7hKdk!Bvc*E>Bdǫ9?]b =}fx܀Hr*AgQzMi e#=6o܎X `Ť#t]ۑ嬓\ Iv&'Ka1aIKKS7LRl/,suv 8zIi ZH|m>mr7S{WǸl[! aKm6@X!hI5al cOXڕףQ?!i}I& $$@9,H&I;9oJeJt%R8?sRÝt! 7hqeqܯ#]KSͫuƘ+L)0l90MIM9Oߥx{nBc<-OiKsT Z#|$6(6YIiLŶŶc6zZ"WN))։5vt'o$DۯD:NNseZsLg$4p9ج”*ݼ{IqMl: iLĖ#}\GFW]bc{b{cbcbcbc/ǎ^{-zXL0܏QtdžĆƆ M=F9|$r$!r&Oi9SΒrB>'WʗnG~y@ɏ'S\~!/YyE^hUYUNWTEUIݦ*UUUUSU ujڪQUT5NWDVP-RTj[<%nģ>>>>>x_zgoosyMv=dOy|@e+O'T9MNT./-r&r%ߕ{}yBSCyZ^K7yYP&UPݬ [Tau*b*J UJVeT꠺W^jƨjzZTl5GUϨyy)>ZmVկM]VWUu-rR3qE;v|/j볮 _:JF죌 SaYzH`*}bc|5+5y? {9=er ׾&PׄzͼT[Ձ*zg{LUnKT_ dr.{)dL. nڟj 6YyC9rQ 3hEX 1Lw8d'ݤNIis1G(GlS5g|'T6՝T2Mo8M#ةaNMʴvjSלiw(fKsI%Q-Jؒ(ӖpIlIdlImK<[%QY[%QE[U%QU[UG_C_4=nS^gё06j8s^<"_Uμ_}[?=:e=Jq^~y<%{ZOkkЂ6;;έוg Y=0m7ٙ4fbpy>)ǜ&v |r1s׷h sy4k ]̰-{ONW:9>d )iJ:g<\q< 4eLF#WHPXcxpG˽[ίn)Y[SJc>ߚҙşM%w (H*i9ۏg)^:Ϥ(^ љ$p_w;]s4 *̡RUC5C!kX9db&0նLYM Flfњ=RF]NjDd5)j uU7MݪnuUw{yye6Vvx+6~bJүq׊-4}e2,~erٛ-vx^<>Q-ŊǑ8u]q7\<`uw[==z0˛5`{u$WvIIث[@((양f~7xZS5fU -"I,,}I[5=&րMZZr=J~%6rDG ?rNN:}ruA.+*K֌4U5C(ATLy4PJ*G`"UNUyD*P5T LRieQ9 {wnbӏũd|k?4kn_g7Q߂? ~;s.;ň:-xkxt >ѽ>qDǻ-de91WgEt*N?ooq?`~Ï#nue#,Llfjs=,LF<^;_K[&b/wU2Q'ݧ3r#|v_W+_J-Vnܧtc:qs{+Z R;c]y$gW4 -bpxn_%!-BN!#\n5-ɿS%uX{Th֛:[gkDNׂ0Ueldtf̛r&~S͖77{0W*W*+q[G<ϕsޡwr]VpPZw,˭6,fpG\ )^+c^+KPCze zŮ+Kg6lz9L?jOC#EMS!?S&͚#:¹FuNt&с?Ϧ2Σ]/TEwݩ{rPЃHQ==TzN z$5ң8\y[X̶=5y &f`M ~3G^#'4kףq(-£\~WUsm5hWqlŘ e2 2k<5X&jB&9G؛]ۣ/+ 4+IOWgstt-Y VPg5 wJiN|]A.lo>2Ou {vߎUgsŹ9=sf_xY^۽9xyg{ m <,@,Q3<_~Wyef,L -jU+O&? 6[EV5CeP=aöN T#Ք풵Hq+Ԍ@Ellqlg]-ڠ6lvRGuXsIWnSYwB}!u>I\=zz27[>P_Xs!{ˣS}k~\|z:v_D)%'?gqWy.{4,zX0(L? @x/־|Ñ(q.{c&)1pb0k% 3$yxȣ,(<*J1տm_w2KH0fkYn'<_% S~7`ɬG@lE]d/0{ٿCsPĞ~&~}u0=_+ c-0| ʉ&*y1xo'+n߁a;n#2lx3<:~;N]8-˛ fL %(ofyw`%:ޗޗl7vx;1qң(=2lfseCYD`) Rs$\"yXHg(kJɫ56BY=cq؎tQqO; u~4ycAaqz8CTg㦺9n5pǹ;Gܧ\5گw93VM [(4K#f܆;] )#eRf^E^o"px ww|;vw-}oqǸcOB 嗄O7!e-R%| )򏄔ϐr !e=R&l@ᄔmJv-HHH)NBWH$|hBʷHINHلz4%;^gWcI6*F&5Rw'ya!,Z\Myxg6oF|ŮOh^43"AnE) '͓f6O B*a%,ef9e`߷G,ɮTVCa`&DZݚ|l'^[Va "oID|k=D|C_qdBCHG5Pũ~n|}jhp41~eo:HG#?IBZrѕ"f+\2y&tN;<;E/<$}"e:bi 6-qPv|UJ1&`Lh1c-YЃ.ЃЃЃЃ~Ѓ@.\1q1HQ1&`L\11c q<&.h5&j,MJ nV76uMz?c= jcN1z5 tkt B"Rs;n9}!2;pldf)iiW䘵XU\+2I Z1nX"ѓɜeXe }Ⱦd_cƈ{6⦐h3-nhȲdk; ϠA*!JoH6@ecд5 NCkӡ%LhVVgA%ZA02hrhrEhrehrUhr44>4!414li i MMMM MM MM>|4y04y>< <|1 ?<|%4y<4y4jhu7ABo&߁>}vllalSOm9|u07(d/iP00H51cŌQ3F} 1c4o {%}+ְm`޷jQg YCYJ@3QkZDcZAQ:.Z{M9KJNJ#%I:esO;C|z~m'ݰ48ϬN[fRM6Mcsb|佢k9;^Ey /|6wuu ځktodsn؁gl H K(GZe|2{qraq+ aa J=4f%/kn-u1zy1Jk{zu> жaG[cˣ w\XFQ\8n@n>#^N/>T:«֣7#j-Ëko{Zp'_ſXǩ 'V /+*KjOħ3Yl;.qaVvvwg9L7F)-ᖠfQ˴R7RJn%JttAAL!!$âèdtLt eEFR,:.:{ē,c8=c(uġc㎂c}"NvG\8#(KdqSVJzޡ,w`߅vo[U _NGv5cɃ «#݃Ⱦ#""WG&Gn;2-PdNd~dQdyk"#k"#_ElO䤌$/ɐM,BX,  ,kkk[[[[;< x.?p|8p>IS K>|EK-ڎc{b;;;;.}؏bc?ǶĶƶm vvw%|"elcױob6Vދ aDZ5O> >?m L'j|!VC4"ZN/k5iKzns/_~ p5}?~r߮~ \w~c$l   m16x9p, 8?':oo z..;Kj|QϘ0#9\ gN \{.=qp3sb\3-6r]98k㸾x-R'"vݯؽ(Z'hﺆbv%M|̸&bL$TfM =&0(mrL)eJݥjꦆ)25Yv,w9ew2g٦9t2Mt3M2M]RH0%g2y)4.Qib2mPzoT>T%!gGzӏy =_?Oӿk%AIv%om9!b.>FI +7t=k>N~]+F~"O:\/?/F-9r|L>.'||J>-ȅr\,gRL.9|A(_+$_GP,ȭvCnG"]cvGI% v PWkOӅB;=<}qvc{%kFT'pjN sRT~bsFQi?OސpE Mޔp] ?$\pS$\oMޖp=zG΄] ׻_n邕]cE39t3h{75^NN%^8- Ix^? giyCx"<7T"mx ߇9<y[xwyWx>J*#{>_C!;r"*IEU]sT*a(J,GUR뤌V9*WRU:} TYUNWTEAUQUU5,KPEIQuU=!zb߮NN)yI,ȿ[mvy @Y.yG+ir!ʙ!dd$Q[NQSt]qK .tL{ڷkoZst.e3t. tY]Nku]+WC=Վ"S[}'=Q_'d}+Jj.5tkںnq~owr|/\ٮ twCԽtoǮ z B6~&2ƺn溅n[ֺn7{E]3̐d2S=Mb"B!*7CF@DdX@V# / bĠH#bT6/d2߯NWL'~|;NwUuuթ:3u{z?=]gL}\L_???Wkqrw>QOקSirwYz>[<}_v VV_˯u >o?㉟`ܫ*NGw4[HE 2T1ML3"OD-ExH|@,@%l1X CQ$T<"Gr1\9b%Q}b+r81^L$!rWX%V5bX'֋'cTYQlsbx^l%M(!xU&w]bx]o7[b'8({x_CqL.Ra$ILӢ\T3 Q)U+qV {I\5r,jqU$:I?#QCw^?g)s^W_ogٿ?/Mدcpדߨ๧\+w'h_zeVTFs}}nO d=:[vl50;iZj׸aSx*XߤSxe o%V{-Dg]Jtэ4fD}RVemRy6ZGG_&ZD%:@KFc+֗$L#?Lg -Vi&+>۽~EܨBgԱ>WYέV|-Uu~M˞Ix_|M]*V=Iij7 z?eD],ZB%LW%LU U өTt*a:0]`З{X|"e#*c O'tIuI7}ϏosFj4Vӈ[M#n54VӜ 9D7-$sj*kSϟ/-"YOR%$u %InjĭFjgՔ¨BgԱ>WYէyFɾ~ =iYl{'6}/QpeX ʮs+կZZ~ުd4&It)ѷ(5N",n$:fJ5J5tR ]TCW+WjM򍋁4M4v7ktyy Z-r-v`K<,}vݧ(>JO5cưx2v29wg#< W?Q\2fLD7Qٔ:N1}r_)u}r_W;'wQ'ҍd:y}0o;*6Xqv@<'9 D >ߌgh=(&}T>E*(9;P)T|3;PFy6}ruԕ;+w0lz+I9O Z,#"yv_ٝ3#*ߊ9wKkZ@猷4lqO ΛWR&]JD!&JtQ]D'27ɽH"t ysCRlIu&VFgh7. dS>K+oXэD7]Et5Q̦,'T?Sա/ ڪ m |k6nߍΌ;}Ӵ̳ܽo|POsO'Dz\D{b<0K@߀܏ {Ç#>"|r%|t84l 8+W" Al)uFwEkqt/F|!T^6(D'ٽWs9O2}C4ypN%JhF;KlESK^vhNjF{MlE'6ѾhvhņqacAX)/jvf/jvw Rjvfjvf4vB܋F V.Vkm,bv".,VYbVVv%0 ?6Ŏ{(+?a*9yBn>;4ml V{lsWv,k3 cD,Kz`s3]r*nToƢX[+,|pvX9+Yh6"8X1?Z-R2X6nnutqJ"X|p,1{XqHvXUt*d9,ԙ NR sa=y+4.oVO&B+ўD =uB >h1ѽDOTK!ڕhO}f4*AMt<)O%!Dk%؉$ډhˉ%zhSOFmM#ѮDӈfFt4щDgCt!ej:^Fr; ;Iwx"RYw< fCP<܋,ﱶacQF_9-zqǦhm:M8tNgBsFvX+pNƜcK`pcAkDXxD.1AF=Y=,OOوRL>!{|%/x)0jZ6Fӊ^vݒd280*GJ[-66V`[j[kj;b;eاWڏO/د57j֮Yf6[lK6;b i2:dF'BTZC[&z,,,OΆ]jnmlegy/UjHMpcxOųxKߠx{?)#iWMwOc|K4¶oQvZOoss(N+?dŻ*WOT|_w*~@qբ*/ܥͥvTK:*KqU?x _zŕ>*~\_2T/\ K~~eӬZbD-IKR4PhwJEFpt+~ZquJߣW=}1!T\]/&Mq5bf(Xq1:1W=B]N ujb6I>amݕ>!%˽cX5c`Z74;7S,Qif/C֨{ng(+ >l7y\[5⮭$B/鵰 C %51/tr7w+tw M;Qf")MS4MKR\o,4x_JWٮteU9fMKr|'< 5Zae{Zd5S6PbFV\ ;H 5Zen~0_i}jԫa%Iy(k6Tahj8TP Cc)+{ysޞawé 44rT/li"nZ$R4#Ulx&Ҩ1rMQ+őJF^xnyvWyNSLayJyfy//7兦-2կi?<^ŧ%&y%G[̜#>`tϸb_έǙ&yzyyI.)0kL>k,\bI&yI/Ob3]Mk&y<5$7}aZIU6h,>X&mcLrKkMrd;N&9$a&IcMrI6LrImsMD<$O1L gMrIc M|,?Xݴa\̘QbglY˳Z`} $p96HHxHo!BLJN(tv(L N] }YVi&C)г> MKr m"!LBKö_f]dGd5kB1MdΘsHC8D8/6!+|Z  vB M, Et)5#EO;xF6̥{^i*{TpFW\kIH2x %LI&t?pMB^Im^^owFVB I֤$g^Y9Ux֢ZMʚ;Rztvw\e RֵB˺UtA u=C= qwjwj=vܙJ==2R_4R!HO#}[OPYbG*3fe>s Ree<(h!TeVg^XDd1 gVgfY|uVNVEVՠ5ٱHW28=w`mf ;<|}II9NMV''!>#>0oqށڙ֙f͜83oܙgc枙f_-fwV>-H)-(=loϾ4'pNҜ>sv9`ڃ c )/,.|CzhC67den=5ݼym}^ż=ϝA Q '/,YXEe\<6Esn9mg^yCӛug?wPTv#C÷k1q&1Fda~%|}hiÜ e=an̝F+avNHQ9SWꤨa#C̊ErCRZ2UOΖHyKj E ZwEGZUIYr^Rr눵\|(@E e Ri(+fE@SyfY 'Po (&Ϡ>gX3voWWާ,gYG95koB(Y,ï Ye9Kg@W֖u{?f,׬{C,``!ˀGc q`%WxY[dn| 0k;2ZF`}4ܲEsP~osPZr݆ףgP~7Qʏ(%܌Rf(JiRnF-Ϡ6(iAO0.fjo$rf[fKGer1`88>  8 3@%%P|5r_痀@-p' x՜fů,9аQ-xV[?ʀg)s4PT^~ \?࿀Kbx8^ LAC98? Sqn&x>P@!E@?= |5GS(oA|+/!*Q(}> *z*Q(}T<_U([oy;K54G j@F_{9rYݡai )R?h!G=8ZҠrhf94Y,fC3ˡrvvvvvvZT Bcj1\{` By֔Ckʡ5КrhM9ZS)֔Ckʡ5Кrd-z=Y^,G/jkrV-z=U^)GoկկկկկUѪhZh-ZXVE+ъ4bO ]}R [1 Ǹ/}CrCA.F ᰓqqqqqqqqqqeee_b^Ƙ1{c2eS9s0fa̢Y/1N8 K.kmLcKaG[팃팃팃팃팃팃팃팃팃팃팃X> V8 poUAF/ۀn/ O!^ jZ@Ǡx HnZ{O j @ ANoùnA5oU@ 64BP h` ྃq`w0;D1p$M@kԩ|m'p'r'$ Lf`!(="@ ]V8{EzOp6-:4u8Zu8Zu8Zu8rpGp ~# 8 p k֖@+   h Sv[Hn@GVpt݀T; Ҁ@/H dyA@60 EÁ|!(K#2Q`9 {SӘ#&`3x (`6E`;xx\1;]nw!=}p0"Cc1Kh?v9fT[1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y1Y_3ʫk_.`7:{[^`+67px8Xf_{|u_g$L(w* w/,.RuwuWB[)ai HՀj @J6IIif&d+=<眇f~|t@Dɝa䮐)Y/ %I<fyO.%K\"SdyV[^SY?!qOv{kC-sBJo[b?vgQ!U/: [gJ\q=n 9䖓[Nn9䖓[Nn9䖓[Nn9䖓[Nn9䖓[Nn9䖓[Nn9䖓[Nn9$.p! =3Cތ֌rxk]fX3q{XEUoʽVr****1,+ s? ĵJ\ĵJ\ĵJ\ĵJ\s8PŁ*pJ|*hDK$"i#Tp$KwNsZuѵ]tk]9Sř*Tq3ULg8Sř*Tq3ULg8Sř*Tq3ULg8Sř*Tq3U@;)Nv ShLU*TRSΏtO{{?JT8 Sp_y:^ԘNԘNԘNԨF%5*QIJjTRԨF%5*QIJjTRԨF%5*QIJjTRԨF%5*QIJjTRc:5Sc:5Sc:5Sc:5S2*U #2N )2'4ZMմiM+`wjj[*VʿUhG8Z*VqUT_ͼN}ՌU 3 \ -g]qsx#17?@5f5?­0fclL 1a6&Ƅ٘0fc\L s1a.&ń0^QWsziO|f+6;E]{u,vKGxgc7pKhn;C_"(- 8GCps.eCf \ ո:\Yq#nœ{<1ĪC\;bcת0˫7&b?-[Bc^܇Ey=7qL$er$Db 8Sq 8, 40K, 408+4%Ƨ||_{".ǥ { \+1WZ\1 7`6nM&T\O-ÐZ% +\(p1+l'o;#O V&o[aV&O /P@ /P@ /P@ /P@ /P@ /P@Ǩ?F1QcU.o[VU.o[VgDi]i]/@c_6Ǐ>~}G?Ǐ>~}G?Ǐ>~}G?G 3P0C 3P0Һ!Һ!Һ!Һ!Һ!Һ!Һ!Һ! i9PCi9PCi9PCi9PCi9PCi9_)*cdL3/jqghqghqghqghqghqgh\1#nj3r1#nj3r1#nj3r1#nj3r$0 7`6ԛ3rDŹvxGoGugےDmiheոܗZA=:ԉSxA=UT݃͠T}oP A7w+J}HZKѶ mw6D^Ͽ;E^[zK^ҵtk]@6е tmk]@6е tmk]@6е tmkPSjj@M 55{K^ҽt{/{K^ҽt{/{K^ҽt{o,܀ٸ7aNxמN(DŞ^|Y]^ 0+4M ^O 㧅hF=Sؗrl]u+aClJ7¦Zw\ű}XNF1Q莗 ?)O;Tde$/ +`F$xuؔfBrc#4VW0RqWY㉐q|*PTTFvQu'UۨF<59LaJSrb9 Pml`(Nv vPo(8@v vP`7@rXb9(TR0)5LaJ SjRÔT:(5@rQj8:*T{&Rjp7~Iru6+U_i UgxII<R-ôeUQ?O=l?'|s_gu5<(̈Wſ_̤+Ïͮr~:zlwOqS~ysG#|zOĖ[7l|)SԳ>OlquW=G'py]>Y4'PwDPxAPҁ ) _Ǩ=F1jףz>F1jUS~bɧMex!\\&сwl UDO²bb ,ԃ=x0lxS, ˢV[9}t{0~~>UbF QCfԐG>"y{Pރr2d -Cfː2̖!sЬ2+̊ 宸@f&XnM"̶ݜ|_7x:γݬZnNgai[~;pgh5[MV9`5[g5[g·zI3zIZ?''m5eZWRuT].UjmZֶ۪~կjhZm{FM=i[ghUKGjR%;)Ь>ow vSMj9{3eטԝ]C5jcx4^o7׫QSÔP+Idm4YMF5bn4EM&bLmTFm&` l6&`3ezͦ^IlbX)n5b&X hZm4:Lөt0MFөth*uJ{RiaF빳d4Y:CkLͦfdiiZt &Cɩ&N5qTltrSM:Skt~o:Y7fߨu{nQwN.6N]ީ;uy{>nt.+g]E:e>5KNe<]e: ϋ:*E|/*/E|jEetZwjzhգwѫG%O}SGռK5QLT"Kur|%ܥbGeҳbυ,%~lPV@Zf2kN5Qt;E(")~_4;ES4M(ёsr+Ѱxj#jMs&Wkr5bîŰ+\9)WNbss\Ջ)˝-Y#i=o⶙'nFC;L'/NW;V==,[}p=mc,DI%(4ϏŒ0;6;MVb#ѱU~þx/Md lKa:nf:7oX ëbxU acⱎK׊䜍ߐĴ]LC!!bC=Cuvz]k $5+ǧj_he_ULq\W}9yfSXXܿWѝ[k=+c/FOfg>ǵFJ?0A+C;C%Z"}mZ)K8H%:]GsQ~/v9nf9nj=X=XVLv.7r1wEyor8ꛨi׈)+"o񀈋 P{7r@TDMTDMTDMJDMDMݤrO\x1YvEq<7Y=L$kRZ)G{~#L۳ xi+]J7j_/X=y6juw VQ+ۨ}GʖZiF4Vh|Hvg};|b|Lڏ7IN;+?=ۄq:D߳s-3qrt|J쯟NumGd?2 -|D#Y796c6وFd6}7G6}ܵ3mRzn|/~)hF_ݡy>+:m\=E۶ޭGdWxxn)k[ʊY1=KSS׵:m3r]v9}d#~~q=kwowowowo7o7/gdbbz֎ߎߎn?*\y+\qr7\hn[7q3{~ngw>KC/ h8K+V/iAfJnKOaeGp\xxS=4.JoKRt{[Xi;7y>] MeqL$NNl2ʑ@ {c) +wCP3 5>#7 3Lu>ST3h6nǏqptGp~rhmTPjVs~Z&w,܀ٸ7a<܌GLby1sh-hEڱ؄N zj^m̝ohQs̘G\e2shߒ8V쇣q|YQǓqj4LYrW\c1e~2?f3JRš}K8GC8CNkK|,=x:>Kq`+Ԝι_ܯsjuNm@44Aեe0 Cp(XZ&2MQ8y9LZ/ ו_pe7\ߔr}SoOwC>܏ ",O0 ) xKsTcnmM;;L3!΄8L3!΄8L3!ƄbL1!ƄbL1h& !KFGȁ٦Y4KfI,i%͆Y4͒^:::::::::::LLvi;@N m'H vi;)y)y)yy0WJU>5ׅ*jjjjj\\\\\Ӵ4-7MMrӴ4-7MMrӴ4-vZw;NnuӺœx G[߬7kZ߬7kZ߬7kZ555QLnuaa%%F#Q(jT(]D(QXą1QhiDuCŨ<穮u>Ox >(nGOga0l}6>VՇCI4}MD'I4}MD'I4}MD'I4}MD'I4}MD'I4}MD'I4}MD'I4}MD'I4}MD'I4}MD'+E \O(A&Jd(A&Jd(A&Jd(A&Jd(A&Jd(A&Jd(XXXXXo|*\39X.\Gb:Ty?Qdda6ޠ/혬fedl7#݌ rȒWW:dRkG2]L:GgL3}t̪8*P x&CC} 2_%I_%p )\B).!Eo٢}B "!0s"!{T$ͱ־-L Gp)E GQYp)>uf%kq)AgybuP בB!A!x8#HG(!|5coQ=.~O ?ᣂ\TP]|r>[x o-|mWp=i i i6i6jijiji6jij'I~'I~'I~'I~'ITWՕ@u%P] TWՕ@u%P] TWՕ@u%P] TWՕ@u%P] TWՕp`NNkЃ{qLY@%Ph nLEM/v^be_'BQ-ZKϏ{^ u{]{w2w*{<$<P|Cy(><P|Cy(><P|Cy(><P|Cy-W[E;ʸAe(.6zsZ󏢳W\#; egPϖKt;I6̕0ܦ%c]ԓB4KRC3{L'F_]An(9\Y'ݚQcAr^$Ke}&9nP/KxFȭ[3F>ї G~g/N^Խ*@&hF'+S菘GԼ9P{\NS9j_F}wOe# ޗ}u_!͗KzAOF5XxdSc+dzX&Sˌ'\YURC4^ " 8 \,PC/٠^/~ x1`,Ƴ P0Lސ`.85ArAm|P4@c4@sW`${}~x<Fz('d0E3O3A!1)sN坸+eY516;QFu ƪeEDVDJLWCJ۲:],B\V$]bBKie9J+^oڶbbӅV, A`0`F`x^fY0^/9w0 `>x E,` x>`g kA)| H^`K@hNټEd0c~}zlzW)^` "0wbS?` X)`7}`?E* ٠&T]PL^ yvp7< f 5J=iz׼{{Ё:{ÔOI*.*tQ&<_Qh׻tʽ@<.ϻ򼻏`; <O` Oii FYx:@{\ :ap0p/h0Ƃq0L$x<&)`*xR㝬eE;Y.wErxB 00#A?)s ?g}>?g}>?g}>?g}>?g}>y~ex22S:NS_}ѿ>G_}ѿ>G_WЫeUZ 147}fY4穳Ɯ\\%H0Js 4=_ y's"q|O)CRJ~.ڳxg_s6QY^h^h)K%a=cͨE"O8Z|/K+ }ts-2pte%--K(! F,st~b`olP-\a.ru֟Ɂ5:fROMmr"8G8h%,Q(" 8 \,F$j.h״>:( A`0`F`( xB+[ |udf'q\wrrn1->Zb - h;jg3maU-taDrw{>V$xp 3օkw_ _ZrC':FmxyN6]ˆ۵FmK=I-nwp;q?0`Llcve) O'DHةw@p  7+@IzNsJU 7q7E[x«KXЋa~U̯Ue>Lyl^NbRI/iz iޡ#b~U̯U1*W_8EmM`h#`)0b Fa;YV#"+:+.q1qzqVMhyd6`6%sDkbuNZ3DYhm#y/}'%'@?n`~č3W nsLLfV&^ŨD摖ՌƝceû,b驚+OV ^%M/J±maкiZkZdpbNaz=+!`a".N0sDom=UXQ*}D6N3YTxyɴ0iu8'&Arj3^R)jgJ)~~q3?rº}xӟ/\wx,\j3|ZV6 oȹFkʹ\Kε"XV#lc[pO<+#y\aۥrۮӔP|Q)XZ3?93ve/ɪcU/<2됽R.]šWDc z&N%?n_UptJedΗR_g*>8[s8|wUQ~Eu}LM99ި}=ljl?ϡə޷2Lo;@J"ڝ$s>cܻ}3gQ,Gξ_~NUctVKq>{³П5o?/tGBk=HduSgGcبȔ^~ .|3ޡGk}3]cؼ3R:>;=4?2r1w?R9} :ޙ?5W o F[sK!ĺ~I=!kĸ8N{~zTuap6`_7?amGr"xS8ьW|9W#^'~k3ˏCӏ>D~iMܩw`g*ac}D'+׊)+"7,>Ӧd2ѓͬފΪG#IGa3L_RSɜW}1+ډ@ f ?8㗧c?ꎺ;dj^{a:qL)qgFBo%SRF-։ Ql)I xqy_ 'sY4KYjo.ƘfgVUb|ńG1l$^<){4--Z-Gr"9SMMD|n_beĪӉRuE_O'6NNN<8x:Ӎ5nQVF4tFف3jjQ+tF-t{SpsH'btu5;etjNS7nv:N3qsSh܁k;m܅;k 1,D#bb{cL|coXx cU5u0 U506^x cW5݁0^è |Qp#L3+3UV:u 7aaĘDd1=M̢^J/OvO6񴘧] %>_"!bb5/([^'D]MOh0Qc9;AMdHGգyLMdL| 7x>gNj3LfQsC+j.m_Ưj\&"fT-cOs"2jEL*EoWڨ6&[T cmWڡXɝU*g]jsާQs UAyd&A?sjU-j0UZUh [؄-l6a M&la0`؎vF 3a"S g;%ffY#XRcb{Dv2B,#2E-oEz["m<+lW"vQ+v{>˽rvQg}~QUxԩPwU^yiOx`k֊[l#q[b]Q;Ǩ>Vx(MnlSaȍ77&77~xs>>JOggi\9zE/_62`CȆ^˰D^2dl8i yІ_axA!(2xPƒa5 ?wB!y0/A7:!K^BsC֫^~zuC+YS{^Zܗr_~}dqc8Gʽ"0wsGM1Jpѐؤؓ ' d{="'YraJxE 8%spJ͸KQ6Q"+䑜GraC;r (o&pGN!w #x>ggjf|&g9!kOm?쟾^2S#;fXM|w=uZI|?]|T>wٻc0$C*M(((EDH? ;+Ղ؝wn. ߞ̞3gs˗2E0xקH(lMl]!`*zm7)rDxDgu. s_,zUT9j9fC>d!z4My@\|*st|lI\k;%)Zk{\st`Ŕk?u?q1fIdn_Ly{`œ(G-Tljl1CLyb[G, Y I\>V@/Dm0Za|`9a6JgIE|ռ`F,ߢ8`Va .9TWFXy~Db1Zy83{h)1϶`^4CO4lXճM7b7q^k[gB33RstH7)s2r;b+GYOROz|wsO\d?rӷGoWdyT18_ys.LɷCYϚGJʊN(#~1 1wW7s|WmcOa朂2xnXu"1[|5'<1S,T"HsgUO]Wj)tV_$nȏJs ɷ8k19ݣ?+{TtvSph(11(\k9NI)t 4r5OEKRGݞuS+Ol+@=?)q84ޏ8ܳ'3fGuMra3]tDށ(Ra!\+EO8rE^p‘cFI2{e8.Y5co=JhDTߗ.͋"}>݈ÿ3~[86 yzJ.yໃrǢpf% -_*OWa Gz|xo{ƍͥ} f6sy _1^?]lzM0Ixy< ;/Xf^0K6_/MYry%Ӂ߃Y d~-x_mj&{M2|uѼ$ "oG0_uW.Zw'r sա!XTw.pyʆYI~&威y+f9y>ΈOU&}qN7E!VW Mss>mMWJ5=Cc>&`^3bž;֥Zs֕TGQ|f' ~_ž\ܓ5S]bbϲS67%.㪍<v /.Bn%;-y~2f@cˣ)y"q&= J<^֣]IX*E7;a-t[WZ8Ճ^-4cMNG^dv:ZtK[VezIetUΡwUЇj>VôCQshzJ-O2V+JVVkhڠ^uA[mf6P[VEmSW۳%f## s$ s]ͮfaVD;α*$rʕ>TJ*}}Uߕ2뛕άoVf`y`u%Y]ͺެXݜNՃެnkY3ߛ5ެf|o=Oֽfe7k2sY3͚ox0Ǜ9ެfm{MYfwSAfwSPIJ0Je^7UyTUuS5}Ur^cF7՜E3ɌnV~?N wvN7MT#dWw%ݒnq+h[Enjkf\ScqM=s̻&0zyD\Mf5(i̻g55ylM=kIw;P=kj̾wz}1Zkj ̾^tkjwUnsSvw~aV63_\ԯʦ`V6'V(-T3e eBC3B C JF&vP~]#2 B.. ]n7 %TJf1ݜ nlmv?fkoe6{%vL^o/2[//%~׻nYӦ؇T{]:5p&vA2`%CMȃ=T[$ɰ"e8S`7AԌn ma4P)giJQeaa=JV,6LܚԲjQU۪}].ib_ < M]/v˷C?̺hy,u9X(gMF$Xzbծ'V; V{a`_~ 5^ޤ_޵n-W/Ww[a KvqDg&Pjt ̢2فg)-0cGYNMy_ lwCAAg\gJ9ݔ{yOB//(J`g,[Ը:9QޫPOOT9|ggWWs~CwwjҊRT;! ;\Yh:(Y'D=*:L:I'! vA:yKR[V!}.G%ty]%WP+CVUPBU]jH_]Bں6ut::埥B,zHS_Gy㢮Fu.R6MPBnFnCV|bѝQ?urr雨RsODABְV=Jmhz8r#PH=%ܥD=JB-w뻑}jr( `>B(gNJލx{&ce<ʇ *%є/ 3f|x ?~Z2x2OSIӤOCOCOSITȮV n.@̌'8dFq A 2.-ȸ 㲂b{qG{G{#=$ #=V{R"i'= O{IROn[aiJҁ7Rr_buxAPہ|`~z7"L̀ŧQs/X/P k/~ "[Pms_VP83 a(ERa0 F`}~j1Uϩ稶Z\^UyuA5՛Mow+ kQDSd au>UAWdnlTM}!F5j?rTK@P)Cm a=a;0UjvM2wgiW?ˮ)?ˮcA8΀g7!nLgg >>?)@'.Ox %O /'q` x9Zo'Sp2 'S{ڈ'\ڊ? `+iAu>u> ~- 6K/+?1}+A%ӷL  OJzPz Ai[ . ./+8^ D,*³u6g,JPxsg[lFu  .-غ`벂=1z 0AIFϹ$= zF 17tH9z6r`r\=8~Pr9Aɗ>KRb#(ȻX`Ƃ~+J.'(|A-DBed'8ȸ Œ &nN-d| s7|A2>Op9sP&3% #\b",,b%X;asE vv@%XTak/,b".,b9"Xư% X% X9",,bN #,b"X #,b""X1GÜ0G,as?,YK0GK0G.*a X;X X9V X0G҅?́JXSsOZ5ݚ ju #̈́>͊-9n}'K6dmr؇irχlEi!M;5t/'w;C̽ 4ssC{]+NKx8=Q{8In3\й`w04; srŷ)'̍9cݱ'~N;х?'G<|w;''P;{;-۹̝G.ygg9wx>$s<-Q2<<\">Osyo'WPN<j&NKpQ}\) WCs}4gBO^ $r`@HK.+%\t.ERŷGb*tvbPRGa S^0uŇ ~KeXcp?fxAаRջͻ aR?.I]Oj?/ҳRכMBd'5I$77={& Vd$4OgRQ<,o R_O<`:W70S`oXC_`V3'$sfxj@#xpV6 MZQ8k`p 'g~p*42XKX #3kp j**e8XE{{ReĪ!CF.W:Axjx*\XC +sa0\ /oeꠒ:#!66+/p_: 6g[`u`5⿁}ƨFNh >wTX>*`6M~Olo,6#?,1Kaf!;E4".c%8)a!"29D.WHx"O|"T K sYv't7FF"{#fEyȡ"G%nE>"rI"%"牜/r"\)ru, 3if=xɼNfه߉eo739@4dh_9""'!if%"f\ )Yϋ\,"_2+EV:Uda̰*Yn=MO|af`rqW02·8*G3 β? }I*Q!>ёf?:,o94:,G[ȱ(rY"g'rQa4:,׊\+prv]L**kwBɾܾ| ݟ{=k+D?|?J#,qU T͟O\}י?_D)ev y*% fSLc<# tFG^~u_,(#(籋iy$/[ E.Җ sx JPbDU2е*GqӮbˌ);(`lbތph~߂5Pv nz=( ;XJoopZkM¾4şi$5\T먭NW<+=3IeLBB6voހ}3w -H!z}K!vf,-r?`uЙoŞ݉hp*߉G#PH.ȣ{Ѩ>~$ X:ǣ 0v0џI5л F͕QKŨ-Y#\/Ŏ/rK+ȵ_5hZuT:A:QړN 2AZ#"#weC2A?x8fă2⮌/#ʈ'ɈkG:E:0cveCI::f|\SS(:(cX^/=,qwe}{P=I=wD>D]X%::Xjkՙ~&zE|?./FJC.\Qx^g x^ Ckc})+VܬKa5ZPW+Vs;ʰ2h$dk+n|3Jjo ?gg6>YU ,}D֢ʫwu"_ϯ9ƮoݵXX$OqB|`o}z?-oo>!WUFRYQ<7 `3X㵁 PY%:6HejԮblQ'¾oãӭp|h:KzZz K_zYJzYZz&,',_ؿ9ҿ;qQGo7/*YIݣ/q7j7ϋF$Nn,R:N K_ 4]Ѧ-دbuw ]qZssNBX]is~THwFsJY"fQ+|Ik{.Y>CD#QmL+Z1GZ<1R:Dg|OvfqZVm&6dsP5W)?bKopǢJ)i7Ŗ@$xZW|̀,WB-zJ 1xDq|h;`~;dWb)_OZb ՓzƌMvk篈;6'11 ƅO);}Dn9hNG܋EW hmSo,+B@Rz$g۩gZ>e9:YޚS:I^uϘ"$h A>$/DEDB|C%.5:3;;ߝUuMuSN:ݸ;eXڌ{s 2vtq8="Bw,Clܭv3(ek4z=ܽusS+\]y&ؚەz k22ߵ\QyFP6}gk2w׎ XQz/Q\] ]+j<3ŵVa`]uOYI2U$ĩ.rR>$CwPA+'&L`޸sP9ꐁ.y'Y93 nT6KetqۭRy =B~Wđf*3z/ݙIyszLWd<9P3"8ʱ{02 ը{tluPf]쩖xSiv$C_ȇ*oK+؂&ނʹL%%$l&yOƓ3zyK?T؄4$A+30&+I ##$A=)T,(Jql+.[Dc<F{IQ»{!!һrgvJ;1l^v9MObi ?ASKh:l?)6QG)ͤz: eig OVe<7HD~bŗ s-Wl&s8ulLəqTe\\:R3-sLK瀼, \jf9K6 BtN[LޕRisx!'-3z9|rƙ5D"#U{T[4;3"mVV:iuր܌Ta GL씘nes;+ ->er VnPn^0C]a2UQU\ۻHFd ZEUZVOW}t_tQgZZg\g\Ow65zT`pӇ:?~Ɂpӹ%G2BE:qqfww=)O(?ެ3{68n>C N\Siᐯ@Ŝ3=1=9L v__(S81pd0]>ۉ쓍-Rx' ## W .ljӾɜdÆkS$9q2pzK4[\_KION/іfR;la# {`JJRBpxqÉ28qM &$'$ +D}ȔNKwލz?eTi4i=j< #>Dx6 p+=ЫQe\ps h@x/Jr/[)JUN3XIMB{Byoy1y{v I*'u:\qcUVEB]p:u7ԃTQ }aG BARRm*?Ԗ"|@[Qq*֞ж!v O!Ok'P )Z~>UO'#~>HY/CA_~Xȣoַ |\s_uz#¤\;w(en 1 (GXaT 46F1Fa#1#$#Kc\A*zzٸnoL3 c‡ ƣTjl2BY*1^4^F.׈jM05+M`VQf-K^f^rrWW"\mn1* "W뛀fBV[{| |}Nf=O*eX cA*½^*޲B|~k?wwG`Lu#¿['LڅdIJ)7.0;5q=)yjp6xOOW2Sȕt^lMh22Uąe=)=|ex깓BְAcW#y{(F@ϺMBgP*迓gU'G_}z|z#LW#>CThUX(O\\˓<˓6\'yd\[/E?׌BzRȧ 6hYϑlm#3>O0N"h4"0nI# Pp)S1k"g!o#1 ,BJYq"C"s!C͡6+ ΢UfBsQL@:;? cUU^< 2ٱM_fBo1oSI 0 h~$A[aXUx?a|j}YEV2L&oMZYnYe2ˊʊ*+)Ө(3S̢e:Pײe鬲eݨ{Y7BKT㋴_x<1'11XxˬxbtS${fw[lnܐ0rv@e, F~n}_&_uTK| pγ.spESϠa&S)3;e3ҝ(=>åe9̬usiY~i#vewe?Jω2R|[uf^}ƻȻ*{_9gH>}d66{ Nn)};%Pv⩗/]]x+xjw7y {{kԫNS -v_염/à1(d a5<+(f,}>تkS{ii\Gs;s]Nk8jZgY8o/M8!+{T>ITZSw}վ+j\Խ*&Z^,2Iz[ɰʭadZq2qLjs`+{gmL1S)2etɔ1e,LSS=SS#SS3S S+SS;SSg+|Vz:i؃iKUKy<8{/<_s5:Yle%DVL9I~^g L y5b&}E}OZ:_C#|ć.oYıQ6KW[æ;{Us>}XC ^^ f3` eQgn:C_PzQ(Jy_}r''Okr䨍vE%ˋt Y)Hq$E3`Ə2ҡ,ҡ1{im> Yжs ]hW ~-֕'жz6ȇ]`Q_( QF4HdTɤϽYs~~wZ>XΖ]U(QbPK}L݂P@)O\[8(kQ"g1ӋzJb7Lun:0 :߅.h:WJXӸ<@F{,dO 5i\l`2ʞHUE:Sf 8yYVΪD.^pZOgSdX4~#-eřIL"M>"IKr^=9y?ly +G}X݀<> T?S1z^TCcq25RS[dzjݠMNMlꢭC^=mެa=G}O: 5jIW7X?W?տK ՇE.4B54}ExE?߮N1ON@.(1} ճ7u 1FQLcYUM`]jz#h"zװw-zͩtμɼэ|s>M7 hyy4i\J٬ +ѭ>ͧ\_{_'ו}.;}}._/@?jǼ`RwrwjZ7͠edY*#^RU_Oe]@rݼ~\wklÄU^2.1.%ٸ ֑K]6nf[R~K8+yg&)fHLPǪcU̕)VUG.k5I|GKUN>Dٖ8WNءB[bFI=xݮU.4l*ȌV [݆mt67J%_PO?' kS]>eO$k.uN K-by%"vroWZ]pC`d{! FPeiCڟ%`YvcwWw,Y!zt;[O'k~^IIXBǻ\ VBH^ދ4L.43ϣ s(]n(˸rޗ4̼ޜW5?˲dݰy~ޯ6a~d~DQq1kqe,Xf`95.]{F"~4ޥK}F>ˬ++ilF |/ϓ+{ryf;g xV*65b4_ ;POw;e6Wo2újGsw62ڹ3pVH&k~o%>̺/|oz8u̱sWќ><ϣYKjgSZdܵKmgsz*Ks=ֹJUNub'=mJJ[} mm9%'ߋYi:ߎTn,ڣi*;-Y͖oWre$sl缶}2y{[rSDZ2в;mSnխȐ!hFB'R$索}6O%:g![(o&n$HiT,޺ ΂ =)|BR/m6NI`5S9|!O=Pzck&z(czublEt2[ zSϧ<󣘭e+BS[:1CG":^>Pk'>Й f%>Ѕ;m|AgK#-\4rS̩W˧rC.[%l?Pʧl9˧rC.[Ek zhǧ,梮|ꡈO=tS z'pJN)}gt>Б>tE`X-& (9 (e.Qx>g;s 3s9cpS猕Ɉ_2$.C~yq&8]egMeN ᢼ֭Rx.Fc^I%Mn2mg~^CazL\P.0=r2Cfz~ za.7K#9XЃؾ9g=J-GI5jV'lσB]k-$w{O?2wg"E&)v;~;1k"m>,K"lAl钴Z"לi(oR9mRJ6)mP&ȍڄ)%9ޟqͶ(nJ3+.gEr/G6i'۠uJۢwiqFD95Wډ7bU '&s2KW4~/_Dؖ)x3iC` I,N>ω7.k6 OgI{E(ai,:~,iG[%{i{%EWR[|;{4H-^ZxٹEDn====0z6.6!696-6.|,6+6'6?([k,<[{81%Tlb;c{bo<Qx,0|,|,z=: : vL@]v/-qA3T\i4k<RM'G- ӌ:!O"'r4N|գp#;/:0Z^]s /:z/:'4͊ΉΏ.9Hniu5ڵ)uz:?SadQdI~IdedQ<&04VX&"rPCX篫z+xz!f/EDD*#H<2*2&2>2)2523rKd|*08YdEdud=olmݑ#oEމnjg·縥j?.23)R8L=/F"ZWSyRx;)owK/OXn߂^Ǿ.#y=,."%#һƬY^]c77D!JD́]P`<8*ܽ1z|^^؆:Lp3Q 󴾚] \R$?0(Pk`Hy>;H\ 1UWUy|3 :ξPrML\oD|\qYן5|#q9|fu?7!sq3h ~n]9_TRy?߳jpgtTU3Sx/UykUizŎMi؎q:_B]>b}L KQIӟSY|,C dWoIoOδrtSTA8rIS 7@f@naN>BWMH>H_S_詚e 5 Bт@l ɚHJE Ϯqo e. 4AO^stYѽAҜg?Ѿ5g![J. >ުfk@繵𩹃DH`Ɩ-]Ľ-G[NOor GMjZKZ [KZ[Z յ Pevj]׺8L0[wn=ģNπ>zrjj:OT].UW5zuZ6@"`Rأ^\7=}#Sq9EzxC=(49|MzK4ryz/DSwSߚܓ&.ĵ~!rviF4[14{!*fxHsTsBsZsVs^sIsEsM3ijݑKLK _-Ԗh˵U:i-ZV:an*jmqA; ܬܡ #$=^^|vV{76^{M㑥UU52z##y"жmJp,Bܞ HeTpAq)x+33\4L 7 3t90iAb1X`,6+ˈ:7Zh3z1cq׸o]C7#ƭQ.^~!Q iYy {xx8mecb&)>.1Tn2-7ՙM-Dݼ.@)lJM\o`6m6m#&}txt xt8al ģnfMwͼYe3/5Kj` 373g1GL`zg[q>pfhNpf(H73s87#eJqG%6͢V46#h{k~࠸8"nr8*_<$Oųyx 8-2#;RD*=RT\"U tR(H:s$J@(Uꖑ'6HfyvǤ1I pB,]K7Yb-*ҒgQԢYJER !bɨdQ[ }r%`H,I Keee2dD} p;;-{,-Gdrr 8n9hNYng,sV9|kZf.k3Pk5YmV5hYL;֕쵮[#֭Q.^~!QI'`n`=odҺz: e#Gp8hSrmK4]B[ #Ve[QV]hkK6z_l>[ؖ0 ںml>z۰lc|ml;l4lcöc&Nmgl FUuͶY];뵫y"?K+.{!~dW sc?zGLu},c_c_g7ٷطwڏ؏O[}sI3ungs#Ǒ(p;eZGCe턣Y{١u6tڊ]m^ZM1 : Wq8:F{G|qqqq%f+ki-'çTf"3׹X,O9˝U:gũsN ;n*jgsssXifAΣ,Ź͹rWc) 1ͨsyyyyy9ⱦ:ϥR\K]ErrU2pUQqշVOet\0JjVz sѮrs \R8vN>Ag9lq98EפVrp͸n 9tbw{Y:fn$gn"zAw^m]^oto,2j{{4"/$CģxUN<侢Y枆|b-s`rsIsͳS,Ab),3UOӢ-<"ʡYq0zO-ӳʳrQH=} B^llk:҄oJsHk\U;;,ǻ=ywws7maݑt~qʯ %r߈ou~aB>ߝvifR.&57͓mnF 3Ⱥԍa,F???)32.l /HGBR&dWgwE,'E @Joی T5r85 c99"8XXX"x` 0x`K`{`<8y$pb1yq\nE@`20e Xcہ9p==}Y{m{C{3Pnm{tz}e{/Y {7oo?~Di%/_7_ڧo 2\, \rC$X\ 6[t}As0l4jcW/>!8ܦ;2yxpwp,x x8x 3lnByPi"T Շ^ :y#)B%Gyq#0Jb0l VCkBBЦЖ v C{BBqVPHf<+1Cq\8 |a{hHcйcC[pU1w$4 ͅlpN8?\ *p! /3Յk 6l `cp/9N#FHxkxF͐h"ûh4}-2Ç0:0] 1/×W54K[rK5|'M֡`N\QQb(拓+a;wu4#֎E:Dd #qi&#-+;Vu3ur(,1;-:vdv8 #G?v8qp1q㲩ju͎YS]<%"s#K#ER)R6HMTi"%±wW"H2"YcDֹ"!M-HwFDEFTdKerBƋ]K:9ss+x}ɼ4▸+GɈ+r*"r._ć[;{Gw#CSo;x\b|2>ߎ%D~ Q(KT&%j fHL [“&bDo{mbm8?Hbi81ؕ؛؟8=88888NJd%%|;:[=u-NQ Ϯ3ݹsugs}͝ۀ;:wwuk>ǹ2e{/`E=WO_c :A Ym(kU 8YIlyyfVg 01/e{){?T Yv屇`20WYֳa6~d/\.==q\Wɩ8 |\'W̥F8ͽׄ׸^EEsu~AW/q/+71>+S|V@KQ(&?^V\VLrRۦܨⷊq_Wz4QY?ɚv+9凕n)R.~|N^٨l)[j^*ͼR))|C㋕ *T+ʍsa6WxU嫼M9oW+**/𫔗?WN*'_*_PP俤?U,!%ُd?$e7#fMx(1}dz}H(>PBEO/_eNШ*T7|Px3yW3fhe3iR=WH+R#OV9şϨ^ٲZMviG'5Hucҩڞ:&K礋Ҥ46R[k8oq`wq6y8d'wdqkk߂˲TC  ,9s5oo<*w~,;D-{L@rYP  B!+BP/<#԰QhdB>(4 ϳ2|<_g;,oxoTw S/Tj)5|T~S:T~Kތ{Q|+zmz;F3[F>/<,x[HL-5pLzKi3X,U ɼ)seޖ⺉>E>o,߆mO*؇Y-= 5ۃL4К[X> (K¶zcl {}K 2lg[ {Wa #lO}p}ax=.3ت/`(%l \m_6۳eZ.ZZVQհϪjUUTMEYʤ2ϩªyUTevm3 2rpZ$76OϘ'̗W7ͳyQ%K"T^lբA.1 FĤB׈qHqS#G)q\<'^\:+NS qF rRT,I2YJ&l%za[+_ˊd T' dd폐?J־1B"'ڋK?@^J^F$Y{9Y+?L;[G旑?M6lg?6ϳ:N)sdd R}Sd`y.ąX y+ٹ\Cv;q_W dFs3_C@kڿ ;wF΀qnUNyT>yU)U /u @%Jدl-y7m} m2llm gڥ>9f;xo8׎3_q<ˎ1ɷ={?Kvo{ gw9Qyge6mh q2{̓v\<y9ZQ(ti.m~#><;z1oc}bxiZ0^7'9.SX18;%C!Pմ8r^L$Em12'ȇ8cCGx X7-˼ ^@} yGC'y݃}cбEE-<9LU o߯2eo9o::Fj;~+0wArIL"fq~pOZ#ҟJ~r$!֥`M\ gY >oih ʬD/)@ށ+S0@Y p 47snvn܁SpMZRQ> sLߝQ\s>\ÂL^/`L=\\T 7\Ce7f$'.sLX.#e侜n>GIw0ZaΕɻXpt,ɴ} _29+\+ez\kЮqSƏAWh\[\IO*BYp})8ڧ;פk !-\D]s>}­W#e+殔W&j$Zg_ca;=MnҹcnB '$\=>>k0}V^W b, y\l/OW8Wa;#pu'I`?D>8m\W.*@;a;s}q3Wy-^ڃ7YE+kSWtܿ["Ywt%/RkpS9= ^O%'/?t>?J5IWBOħ얧oƉۈֻFGL$m7d] p,_;^"5QdVt7?J֓#HNqO!R_P|H"P#c?װn"^QrS`Զl-QaԳ؟GO.t6"OI:]'稦oG>Og_N qq8Mw:C!N]gwz3ij4 qg8M4C!NSfz3ij4 qs8M:CD}uhij4 t%O՝#9GH$"RoIr/r'<7rJ_]] &}=XBO+{G{q,w3'Q쓣RN"mx,*#"ʱY:bGϽFLιR CԿxZgx![*⏩wR"}t+oS S|#rqz#TB^&P+\)z*~#yH [R)TD9SOz<9_Ḷr) ;Iy |j aQ>?<) :`@g7N{Q~Njv1AxIO˩3xtkHYH|cǨ}>NmVJ^*.P0?oKWhIsgt?L{z7SI[G&I_NzʻH3S9Y[C{S`kUZI?VdZ+Zk$Q#dd5a,V-Vj<缯{4z=~3s\:׹ukzb/z<AkerG^mع~+v;WyUbqzXx%V݋3L/f̸F#S?̃=b}of\?E6Aqbgh^g^C6 3{YC1CۙL}k;J̚ਤC[8׳q S,ABUqBւ{׳З+Λ`b*2K;XDڭ-NysM;^^ BY[-γތ0#9:"|s٧ؿVY =zxЇAw*Μb*_dbUun₈#]7ƞ@u g&[H!* y#*{s*>/Ò2}XK@z)(c]/eDCxLWx}h_3J]CwYLz*6O৤m>z'ߌ9 ^)z3vݴڍ͚+>NH5)v7+OGX߇^h 'm{*O6{oK\ٌ1pXNU8)vJ VaU[G;ЭmVI$pcd [{uVƸ11[!6NA"ŎY{i[iQ ^kF1Q\x;Ŵ-iHu:f8X抭c:wq9} zmժ _l:m^Jzn88o'oɚ]_jٯb{^u̝OzW긼cM2 BVIUV*}+h fЗA_L?U$>]b퓆f{~t~!^SlE\$orb۞B_ \dgd 3ߓb{>'UEpLk'Lk<3Lϴ3 !<`R,z)K#}5aOZ)OZ9%B01ƸV#?ja/bYXx5_Cr~~x|>rG= zwC 6ATxX{եNj|/?~QN/z?uWl7,vGU>+;1m__-Fg6gUC/?W1V0*Xcc† lb5&Q)]SJ1=h[=h[=꥙%9rZ#3/'жDցC u-f)Rl1΍~O6jŬY{T3x7vX@ϢN$߀cK*hFQSIUߋlqց!GΗ3UeJ%S_%Bx"_HۭF[!v).ECaàmh_/JfЛ5b/gOwkX~sWϸۉ{54Ňe[*: hFZ8>19Ve)vh8D>_0Ls<^cOstwZmmv<ƞrYrr| 9`x)[񶌷K1szDR.UGTF=+ Nr$wM6PbNRyo7XU~-W_;lrz9A'b3{  8yvS)ޡXi`H #~9*?5oz}ѹ}5}-Rg1&sZK\N 8E>sz|{]t#Rz$va>~rCy(g ɺ${db/A>hUP{w]ۂ=rn:΅:rnzLqliѪYJbV*VBƵ,Ğ6]6Ug}swDh pyKH1NpuV(q~DfKq-?c3r=Gy y?@O}b893쿟\Λ ̽l$퍴Z`Nb᯹XflJmu+b7J,D{̾vʿ3>x9GVbU<&c2GSZonʑӽr+1˞d3:MbO(MAbérE/ O2ڮװp ^D4Vi@Ƙ9 [đi p<,q1c~w[J;fO{JejnhfeU3J_{>yb3XؐF$2SHޠuWWs+VRkN/`ӄaG*QߋCY@HR=6y:OB->"2/ !y+_Xm[4u؂3Bߒ\>e+&*:Tkɧ?ɧhy:>{7;Fv1Q7c捼9S9[`syTT=[˜TbE{^dF՚rVcϬ3B?I9 c]?<s-S:tpS p6i]G^}<'c%~{З+/Fk^+rΙasI֍;{=ة颇[c50j-Ti~7~"Gwzؑn܍tI6Ӣ=Z|I s+䆼;ؼ;Ɂ'J?Yq֢,3/4T.)c=96 ~~Thx`^y16S|1wܝ~m+g:*1s5ft+\?QW5U뢹}]O_= V6w$~>pZcߴ$ k;UoyS~K8WjbݰVYJne7Wßy6ok|VZJEqr~O"AoR K.=jwp~p~BM c"v?3?N]+" !H .o; ;}ܤ.w8&}0y(J#SD]>+t=g.>ip3 ̠ m TC7H_P]O!BHb]OKw{V0`gxQ\WZ= .Ud}Vzܤ݄='mͲrl"?foZު2߅S NIX6;o &pbˉu=Y]7E&=r|-n!S Lଠյn9kBw? eXʊ (8JzBfz fD9+ufԳ827y{G#F&NeY9rTM܍pO"trTF=uU:ַͪ۞!+ױߜ^o`'>y|_nhjUV,Jb? =pc{l =^hW+o%ذH^c =}ҮԌ>yi+x3ۉ|Ojbrewz\Fl}= ޅ. ;Qm=[Zaje۲Ot}F|h~J_IƎ]إp2@wfh9 SpN)ݧ]grzJ Mۓ= E9O4o64o[%IwK go]K,l% mypehv| ͐ 6x))C_+;+ьhhH'vr*мWhQМmw!X>}8#_~Ai(M{tfN;t7?'П > eЃAσy=o+o̵\i4ĤGL {W̌k#ЏB?  -ע=:#CxO!Cx/4 ||k6"䛴&sQ\T UiUT;ڦ_FQdw=΂y<3lr;},fctʡiKԹDh_HvM$"UWA\?SB?z eUl<9L/BE&FCȁq1^ڎ+@sx6-7C^zًߌNfw4}yIC:c̬z:6& |m"I#{[}+9 d`s;/~\jr&}HEs[aX/-4/A~ XYJyJ1vY_{4\9Orh)~ 3;#?yx9w!w%Wx&WB}gCFeq@A;m=1h|lG6 M$D; z4ct;z*܄'7iIb#I_T/>Ջ?}{{ޥ-3njhG_co4>yzaMtn &;@fncᱏ O@ ,~ ~_}XUtLDDh֩:u5YFg4w}Ꚍ&A=ֵ{ 2`wЬ52 詋蟎Ȑ·o]y7*"+η|;>{[y/}~>ͷo~غ~~Ƿϓ|<)z__2r{u'_= Ooy?Zz˼ַw}_xǬ,@Ɯ᠍.h$ :{yF7~_;<8Y;<`8wS$DQ$5!GfJ'OfēFOɓ)ү'񒑨҈9K'PL"L#0|y."&KDH/_"Q]bNR⠐_&jʬ e/g1_c^˼`FfGȌkd0W3keV[?:)>3ɱɱ_xH>z=,J.MGzy6 gkrGr<9 >~/[PS=͂\#оS%~z!kI}+ ~Fg}M K8g?39|y<˂<j>.Lq՞ *ggG}sjyV?DZXX/X6:4 4 [m%+t'O& tÑRCETY;*È4kEg0>5#lHM~K<Ljrx,Hl\*ONOmJme-7OjGLpx.L7KL_ntIw֟tN6.3/+l.MweveRC#d.Ñ+]zq"=^a@Fi檘ؑQ/LLoLIOOL/N/ Wפ77+Nz{zOz_Ph1^n'oZ2m5N/tgffz~LFq-?3H\"QǗ?y03R2:3&9Ut\Ǡ}I'ƥ2ӓmaɡY%f[%7ҩ̪ZPNFB4nl]`$2Dp`Zf[%+Heʛ"7Lٓ/ԐL,I0:">9uuȂQ&6$.'U'U'-\~ܜ$nHONKGf+Å԰Cv}*'M -æ٪k(y5r-s$:N2%ř\\i.+707Dl(-kenXnDnt2'/<,MqKM f.y.-;6X--Kέ̭mmUvV=陹}C"}4W7;4wN+xgzw6In/#Р(8(h!&v?,,o@O>@P^Y0aP_"O LF0C`9Qڧ%}ڇC(FRRUMKQtE:qv/wԒk(.ǂy h_i։-XրQٵZ jZv56k\Y険f55j[X'g-]AULHl2Z.j:ath֕? *#Zఁ-8nrDPe3JXu 'y+ll|f eaOUҬPJ> ;t61R[H䏰W4H>J ^ \Jm?IMJ j O4q{a(_(~gRۅ2n='eF>1RJ5WPVDp{~g, Ɵ࿟gr|ҒOmu|}|5#;^???JxDDaYeDDDD<+QH%J L I KHNKOLLLILKLIIJĚĆDEbgb/q(qV8]bdQ5`Ueks@!g0HB18*GDd}gkb q(c-1!1kq 5z qXc c18õz5XBk ktַܶ{R_*g/;[@z+@~ߊؚ:> O*#̰ UP~+ʴ*D&$Q5E$8&stQ3M~@էDHw\Nؐ9'dzDdzc|z֋2Oaz6z >yQ<%ϫ<=bWs3.:׌F)PAFfq¤JJrqF$UdV<脆7iU8Zzc5Z}kJά-sä 3{5ƫļJ[$@lOpy =1=H|{bS-RZO3ӿv/}վnnܞ>ӿNScI1O,mXf,'g g`s c%`̈XElU:vgp}J83.Vrj~fKjYB{F<=='JF[Cka)Z DV}V$kD#E9/ME1ȋ"],Զk{N}Va֡@tmQan07[.sX"J(l2gMҳynhQh^0J~ٿQ55&!DPy#L3 -a%R #1ʭToje=Fv.fT^+K Fi* bP222k5{jmT[z6շZۭrY=V˳ZNٟ #Q:.:NYg pu5ߺj]GՅ aƭIkܸe 3=vlO\4z\ 9vqB]h`l{mhhpqk {`oLfw;;S44{/a?ϳ=0Eh}s>lG"4il|ؾh_?=0 Wfd}˾Ms8m%yܱKRb鱬7eFt~zEQ_4ëѪhM6Z6F7E 莶Dj|'Bˣm,펞 DE/DWע7?tlovt(3z':N6DX1RT##6rFA(1t#bJc57SǙz>xX,Qc:csY·np}Vc;{Ʈpv\wAzGޡ~8ם:8eᶄ4IZK#xF2G&NB4>Fgg͞]Bb]|A!ov V8O]/ѨԒgd4wGw pJ }PJFt S:DE)lE6=<\x݉oy wE-FH=JH=JygOyZ_S3^x/|)|5|=<p$<d{D$#xW9tjZZP vޛ.]܂dhW1Q%v`E ֟-DFõ:i76py~Uj ג,_yq'.z3 y=*a%PO#P\%Ԗ0] EjHzRjzh04 6 's v;$E}1ȩL1ߠo=?U?%z]ِ^JJ։y;}a|7cm;ZwRϷ{P a߬Ya:]Nyߙ>Vwc DZ"N 5Cf<+_zH},P逮qVm{~YY>|5I}(}fm t9}78cJ?W%O__'Toopa7aao,<pp0 .㺛u=YdHH'f,}>t^t_C/+*BPC^GF}-z3(Gzzһ֣/PCB&ه; H(L/R ҳH_(z V + !@=zV`l [Wp;o !5p魁vJ N8qF,x/piVq ~=0H A@O#dFiăd VV Fp-a}F¢`Mo# E|8< !#pӳ#⌋N -m(dFHgye޲2!DY//DZBl ٛ ;Z :-e]nI=d;Gp/}_Y? 7ˆ(S6V6Q6DR Cd%,IPL H_Z:&P@fB[ɶJ8juRA6 Gw40ga C ==p- gsyENX.  һA` ,y"V[  Dv K~!BSpHX4³^$\& oooF.!qSHidzH'd9<'O"H(bI_^AXK#rv9a a_d D}'x EHWpC舜\pOB@\&apq'2E!;єhj4#M%F@OD:!ℳ{ma%ՑPt aau>f'G>[`=A5uhgt£N؉}#.gIE/zסF¸mJHbiFcX_d>{z0rBa!hTXKXOh lD 66c}Ok78k"z0+*BYKaFs-f3; -|Jh×}fwy2|L=9/|;7cCKVb:`=MifU@/͈bXIXMXzfǏN7VAB3: G  G][R8 4;c瑾vdMIzҳU%=gsW=y-ن m4nv9+8n|mxN뱫Y2KWg { i4z7p(4grpExp>MYSE0׾L}p۾$m1ȈeQc@o~ILo)y"!5sXDo2#)cDSo;x_J %;UIeqm3K_4˧^ުbq`̊ +PL{l<؏kWQbH~Jxd~AO%$<4B/ӗCao+"}WAX]Ʒk"l&%'l"&! i{;|  9d7@ -m(ᮣ3[~7!5B:!00_l{ E"ТrBdղdo 5ˊ:a6w[U k7k u6kw8aM8?!˟_pp͛MHP'm[Mq&?Tp\T+=E8["!%RO0M& zݻ^O8NZ[M^@8#e_Ʉ^P*_AepS37+ M  `L޳Vҷvyo, ee o%l& }ki6Чo۟nN%NA${~T%~{ J7^oJV^{r ^U-SWAT+pv i\;H0KusY3}\#^5Xx@Z zN "B!8%^ H_ݭTF ` eoNj&O3\sMi\λ o&ʺgkΕ/POBI~Go4}dLC;D8Fe_`ׅv7xϖHHJa!}I H/&z{/"| (cبl > o6$'5#6x}37Ӹl%};aJ3 TP8I ⭄q췞 2rqX`a7ao`в1c>Fo&G# Y'\$\f=0 ~ۈ:wݠ;p6?tMP#/]?,+pfq>^opY`S[8,"eU45NX}jI,c[hY1hۖ*U;~N߇m]..B-@-'@>9@yqo*Muٸ[=z|8}aB>Baw B`Bnwc-ebXY1J1J1J1^,,Qp,`,0`xYqQN^)_$:!0="0ܻ'D4㹌&?(l@s !-/aoadb4\qlfp ˨"3֠Fs<4z;(m @UP`vfU9W px"*IHL[( uEtQObD#mG}n35ZVaʁ{' (mi;[Q5b B{fQ\e+2(GMlwM²m,On rKe,Y= >ϲtEE^m3׽F3qɨO*[YΩ{)1XOj9o9;K3Mفޫ3Z=j =9 ]qf,{rrLkSCE { Z{,AK `)YҎPhQ<≘>n6KWіWҿ%x|%WnHAb\BN~:h R:gsIIQ =:اr,g& ϋ Ի?X*qg{vV.W0ǰNI,Ž-$v91_f(f>ϟ3Y-kۏ˙ 1 o^Ǡh][vQ`W8L!?K`cvH>6QfZ)Cw7>缊23].$Z*ZeG_8 W9D Jr1r!נe;a|?F;+jR{|uwD-VG-3ˆçQn{ꏱ~_vwFxJqvJX^ 2 9ho-9 ~rBkTJ^_{j+C2_➛Ќ2tL`= RwB')ÄwNہa䮓|uNIF:E'96I90'V9$'9%&$Wdp N2I7&9v70I.7&6t%$wdhlǒ|lMrI5wM2CI~5YMrI65I5ɝ氦I4‚haD\h`gd;ϙd8fL2I3^&y$cd)dLr9ld <"{LI14pIv1ɂ\barH09 a_חd l^+L0wX$_d]Kr9\ȕTɿ%$ö-ɧ%$drx$c$dWJ]I+q%٭$䲒,VJ2W9Urՠ$C䠒S)+ HI)%Y%$GIF(%Y$$9$n'$yp8I6I&4afLQ0d]3M2H%Ur0jEI'I$ə%INAɍ"IBɄ$=Gבd9d6r8{H2I"Z$$S(r؉K$$B!yHrI!faHr 9BOHo=HI $Y~ 0IN %ٚBOK1E a_ 7&Y12Ȼ"斦r8o!ʯv/e,YHOcxZv|)SͰRK: PS?w/$yiB>ZRy|=뮫Г*X>1ePcId|᳎dnuЋQKZԧ~ AM|d})Ɛ(4JA&=K/Mx[xF؏@߁~9{E _F9VNa"-1r_;Ü/|`}5͉_0gOd9[ȥZ!\Ή߀;tku=vZ5ڻ!4<#m/BXL > ]䋸zsسz߅H{j S?reIIH}?EiF K/4sD Qב%v ǐns .@~/A fQ[K_a^鿃|EoA = W ] ȗ`yB?Һ Ϡ_ۗsS1v:K|+zk\i\(a9x"z~e(Я@T꩟ CQIxk-4Oٝ"OC$N%b$EːxQ!XNe/ ;qHyKHz|ˏ Q4fsX.C~/081x'ӏ"%+8-"Vذ_%aPK83)GW!q-'$JX [[ q#dd| J|yy򇨏\YhHiӲ7xP{f*ϑAT.Bay=Cn9 +h3j+X/aBPG`X  q¦buI9G.T*V4ˆdes<}QEɛuzv  f*w*ۈF.첄 kc|Uȹ1]r6{ȯKu.<*fZV VT9n ̍֩s؅C`qc]*WFa/A .9_a}ZbرZ9YWe߶;,nܧThՐEwftR;Nb!w?B LsrZ΢o&h=^J!;֩5H;X_$-ב@?`f]rtu93˗{vt(7Tǵu?utnb)|puc+Wprɵt5/.x\k Hy+_@+Z tT FʅX*(Džg F}>rn_|Pw;Rqst?.=K|t? V+]7 ݸӹSnqiWXqq̓8QdⒽgU~+A_WS1S)/,Maω>8ıkMa?q`S;Bޏ,7ϊqXq|5^9uX/N'd d ϟSXFBL@Bg/gjCT>ڒ>Il/2yoQ?Oa9MuvJIc(_%YCO6" 遘u~&O kCA~y QN.J{oG®2 K# |N]q}%)Z1'|k`iL݅%ϓ|!no)E41PJ&{fHi~wZG|nd7;$ȡ!Ii@n x-#,A!8,H Hږ ?@G eYnGgP'ym u K&o 1L?xA-H~{%YU#J5U7Yoo/@cr%W0rYZ>pཐl&~Hn*p>cq߆A | xr,@&\K/#C H^GBAϡ7M*Q_I2HOc s/?҇ ?6۠} [ ,71*z?L z&A,0y0J gu8+3\c9o*0M7ᄗ8?..o\'+4IgX"E9 Fn);C|$5v+6He" \sg 3j!?̵<>`giTW5ǀ?> |6w=JZ8(kW/1p*Vܑ>O6;}5`d!O,oGߑbO]xW+qjսI=b9yr| $Li`:$'o<Ö4S|1G6UgXa*|N ̜˿TKNv0&/<Č?x7'~OnƌA;NgR.6 *ϡOFڠql0-Fg Hj {}0jP4"?2b<~%jCx7|CОex_,e(%pQ0aQWӾ;"!{ʿNttu7c{ ;YƿOp=ㆂkb,ՑTŵ^gK׀쬎r*}r%9{Gl"cI =.ݫzw-AjFw rgʯu<\!ڷ0`jCBmeTvE\:OAۭl/O(a=X GFg{~+? *HOү#W2$w@<uz;4e ҇zXc/sVʐڗLQ׺N z" Q-p l`g`Гl*$8Ύ=~"9Ƹk,i{w񤀧ӀӐ SOof:͎ |x q3 pCyx~k? ^`$vFA DT{)Z?7p7M}!]Px>F0Cnkh\6<8y fߵ? KvW?fp^Kdz'痐nB.@<;B *@8P)xOH ?pZ vz?r={EZ=^hSÜW):𛨫G>BM0u->wB;B<> 'Jc fr(N >PzD =hwӊ} 1z"EG/{O}3^BzfLyCȽכfԓ]ΐؔW'Re˻[]lꮰ"<Gѿ.w6]9.7'.Yz꯬5@ĥTI|R4@&JSBiN*JIU(r.\ynںm[.D590+鮶C17LhdkLYQzgRHw5<ֲ&b[DHe@-%8l4k|5v)vlƎeŸg^gN +p#VKLTb.k@ccu,XʊU [؊l9VJƦk*cYchl0Y !"h_"@,1#!rcbtXY;sww؜u}]p_tw;Ds'ddѼҒ?d FZh1OsJ|/K+?Lxv ߔv_wO/=|$I@@zoJ ߜ_+/YӨMsOK)-y \#k&N$x'.KިDq {RfIm5úձ?V돈kCDSqj"r 3uA"9F8E9r>\"ΗU"s,9_wVw{@{ 78fȆ*n2R1ҸոUbc#F{ŭ,cxxL16ϊ|c^an. W?;nq cq.hw-'8c\^W bW]C]CŽq$׃ɮY\湞]\EEWvUWܯݯ_:$ӝǢ'o7t׮b৒-Vb8$8+.6Iaɐ8!Qsɖ-9SH24d% Y!3F=f]94,Lm`I+hBl30Cgq#]l<6H7!y09]B;idh1>Z{ %c5pp]h%p%RYPYǢFԳ=#9;{EOs*AyD4%%KE>4GwQ'=识?%ԬJ/S.bl)ݝU5s@eWħ$dz/Ϯ_ML]Th=?.{Zv)#G"GgL hgO%?${:ar]v7cI:6R.;-{8K)Q_z D+-@iKW=gstJk/e^ yGs沼Hx1K %D#Pz2CG3;ْIJ)h8R^̌gјL3[\9OWsr؍v#thO)6f /l /%\Yk3ɒn;,<ӓ.p[@It=ğTV0-`9tΟHKȬ =)]\W1_:1tG$^,FH FD##0rz 5|eac6,(.b92Z+""vJF_iBd?]hVhNh%R`6a.4jYpVҡ=K%vAi:fB[C;MIefyi%ۆ=utT;T]HU{C;'V3K:1.808$4Y*/Nbo'4Y(i :]( vsϱ =]]}vkZM{j>kV O-i+>mA'<Gy(0O"#+#b2-ep$"|4/!9i5oP|+z {W~识5!]KNyp >@+D_'sR=&ȾRLOM+'s Z+P)TeR* I%@P-QmjڬɚOJ%ZmvP88h1W+j;Ҝ 5̲@_mG@Yl$3XG$iN&Ika IfR.PdnZ6j܇mJ2aҬs]`SiT.*XX^f@$?i@$sӓY`j`vI榱/010=v$s#M;Ɠ/ff;Ҍ/p;܎@lGߚޒ0iz|wcf;Ҥ;wKm'd%6}vioI_?p Hs_^?^Jh2ڎ4גdH_>v`I%[''ͺt9;9iVο jA''ͶN/Y;93{{ܵе?Wۑ&=*qyv6npۜd.z8{.\Is0m=d.ӎ͈ۜdFSIy&q4yKh MwQk;oC4{wU&QE|c6f#M1՘D~53Qg{ |#;Bf# [F(4LQyɧ:/9/y63ڎ4gqqqJFۑ愧Źh;vVΓdjO2'}nιIilsNN:υNNf4ur!;s=M2g.izu:*eg۟+#Cϰ<'>AQ {bMd\'NϏF\z"#]^U5bls6Eͧj>c>#4{hΗd0Y.Y?kҼ+A|{﫨(՞uMx }|$͍\"ybq()>B~lz|Q:lJ^rӚA] ɲ¿rw]]/+w?sYN\c775̝6 1'Yr6b3ԌFAr2d2=W忄[5ĵ[_z_=",=Kz__/ oԇf߮lz_1}>U#TZq!n7{L_53L "3ͯIu qy9Do3oߢsyxм#ma~CM-g >P(R`ՇQIH}H 'ܩ)zD/[4if0̞fO2uS4 a̐~k4 y9X͡PnAsyѢy/"D^vq]`\*k4xJ $_^-?.qE6 zKgvY.˨k{Qg!`0k2N$k"j7.Aɕf4tֹL+rI;555XklSIGԹJg\s] rW9}rq9zfP ĜhtI^OiNsϺ.tk?t俢\Sb0SyJcooG\F~`ľ# 1bň=#={"F0bOň= #v FK1b=HҤ8;ELyoEmN~AcIg/mN|pzAg9Kt!Ln~ڶU}Fc s9ȉ9ȅ9ȍ99ȃ9ȇ9ȏ9(9 0栾"0ec_;wv^{'+Ea;c˙98>~RL ?⼧]'֪hxmFgmS(a>l//gyJC,3sIO dVjHдBlNY<%lxWck ˱i!u{w#NQĒ;mxoێ^r&>q^|D$ul|⸍:i_k;W5ͨm8 $kCY;:fpI^yO Uoǎ=?o;w!&o|[RyKSI=rx%8BݭcK%#oy|2^; ׉COxr8(B1x'ǫjgGx?ڂw$wk9 (]hP7-\B%ʃ-ޑ(A>_=q Ŀ(*r(︌b $t .Yr yҟs.;p_+qcHI܀w஑γ~[\pD$> c{6A^18;똏6iV8M*xӎF,yg|c #7~[ ;!iqR6BO - „c([jJ\A0-D$Ig Q>Na۞=Y.Wywkd=8]m7v==LTR泑"Nr\)8[ME =ΟR@+5 |'! ig&fji$a/5 Y|3>_y%۹o5y%EU;Zv+|΅wU38, qDZ͋6~z$u ӒJa-^|ʁ>eK$;KkRne|*P<|ŭ,v$+؏}&,˨+< t3AB~v)@~j8?GHDRY8Eک7}}3J. ~fGxu"y[:z`6^)k1>DA6{7bn~#'i:r<6To/"2oshطr 1_3Y:l<ýW-Qltg ސW3N_9׼!!Emq7a7۹]qh21ozU<3xȆiM#OUj ߏ432l~3[ t#ik%>'/gHܓǠg(/bEg=3^|;%A{z+jxaoEmw| cN| 8u- lFr+øu(o|H©P<p7p:)'(|Fn%?#P]s=':(T6j תr7ס5kRshm%Z;JQyRlc-S !?OLy?˕߃|ƑOl\'OoYRi$?jE5Fؗ(ײDuhs`(W0&,7Jmm4@ BV E.X#gOفO=}Ff9lf?dnt d>gHE^$$^|:Z7}3~hk(a r/Ba4LFA%邟nH~mظP؛] pg .<ǚ-JFԀGXĩH]`=4B-D坊vPQZEjM+:o_"-(фB;bƷ 8HG!!pl q^._O6b{v9]՜y8gƌe솑;c*eZBuc6lEIDN*D[EelY#ZN|q~]5}{U;`[(Wz7Q}XIQH+7wW2є (3g'oVi(#wGџB&|$;x Bӡ6[{;c޾&ښ0/+VPX5(ng3Όa-_Eamw_7gnsQs6]e*FcT5*-;;*t" tT(;#aT(R,.P-ͭ2vtfWa%}v-$kQSV]Wo`> G2-KlԚ|{ԡՕƻ{1G/ < uЩYn0Vv!R%TzWuah7<@ߦ7|m{BBbJ/Gُ{ARL6RDj#tY\D0*&~zF01p0,r I3@ c)!hQby5zD5QlƀZQ\ၝ8G۪-/<0\cɉrq0-v&V 4v7F9WdTV3@$߽>pQ쾤L3ۻw){( ʬ|FobRg 8}GƢŝUw5*8TMHDX )ÕD= msVs5j687bk8\rwK a J'*LDͦA1ƠZ~iccפhwr5wwn*V`Az@b"ߛIStvMgl O"-S'DMjEڕ9UeWLlעad&RJIXtv e{kSfB C-s6$7kqa%KO9X,܋#]Kmɔb{xHEIr 3dJ9?/CӁxd>/X  ?`F$t'с,@v#ɔu"ê)@~q4_!-T/j/*u1ksb[ܤd4mpJ,Z=-3W R"j[ўZ4= \ ;;}J^Ϋꯥݒ/-xG>+' G ~s5˿KYf(P8wsk.jD pHo~'geG,[=Ǝcs:cEOOlb%Ǐ\5P͈ͨk?JJL^%|q,H9$\hC42"TAyҁ<+ YY>˾I`hN1 t(Tr P"{}Q1ŦSh0evvLv1 6WIJ"uZVWU,RnvNj 構bM-a ;k| Ys-_Wc5>~vf8RDiGe;syTC BCqT9B@YRV?4Ew]6 =ʉAqܝchi"we_Lg݇%]Tj^jd}J\],󙋽s$U玈V?|am2ڮ0L]n~>eL3ll(1tғ^-w}s'e Y/t"*o1 MoHxIJ%qhgaS}N]Oy'(+r"2+()⑥+! zRi@JR[0+2f72Q씲f?V;2)plRs\OX mHpLu'DB<'tB=Ba?nȉzOڣ6-_ix](0>oP goP@b++j4~cEE&{wfW@~c|C/m"Hz|yrXN{Jso L} KaS0Ü9' M2apo+u1ׅ5Amig;n .݀och'˕ͱޠ|oX<kf;9I wCvǚ#T-nvLg͂^wl|WN*+X+AerƐUBȢӏCg"MqCy%y< &q^!nV;+VG/ʳ.0 l"\:$%UebM݅N9-kԆ5 Ī˒&}"{H3ﺫxC+ʭ9άu˜$6T;@yl/S>0fj9~7ѫHmo'JMG(nVc~(Wݡ]>4}p^!YpOK2yY6wMyykSZ(E,4p:ߔYD6\^ɭv֓/l#ֆI9gӨw,nyd:Td "~>e)V٥PLs٪ћ-.MykY_$D6+ }lbg͞l{>%lܜ:czLa>grr;Vfm@vϲB.}hfTg,02 t_E kk/e5؁3 hNcA(W;Nt \-k8m8k Xdm A!!CD! \A Bɖ d]In*p*g?L_V@2ޔwKa /ɨ,(Zf]yueddRg fu =\zcjI*b )wD;gKy qNLrΠؙ204(7*'%cKz9y;xԇ^{r+tV!N˒KBYSwyO^QiJ6)(su!̇Q= ''+-kJ=ybBvj/cCzIP<ϯN/_ةZQ L endstream endobj 430 0 obj <> stream Microsoft® Excel® 2016 Dell Microsoft® Excel® 20162017-08-17T15:51:54-04:002017-08-17T15:51:54-04:00 uuid:8229727F-323C-484A-8E5F-97D9835A8FEFuuid:8229727F-323C-484A-8E5F-97D9835A8FEF endstream endobj 431 0 obj <> endobj 432 0 obj <<7F7229823C324A488E5F97D9835A8FEF>] /Filter/FlateDecode/Length 868>> stream x5wWsdFDdKlhHHLMGeVFLIV6]{y>ǘO ~,l|/W;<||$?W߰w?XdYK}(E#|_d0ME xQ(^05Eѿ_/EQa/jŋFQ_/Eѿ(^85E xQ(^0 Eѿ_/ŋGQa/jŋFѿ_/Ex(^05E xQ(P/Eѿ_/ZŋFQa/jE"|_d0GUѿ_/2 F"`.#*'܎sr:,,Ww+Yо nS\ endstream endobj xref 0 433 0000000018 65535 f 0000000017 00000 n 0000000168 00000 n 0000000224 00000 n 0000000449 00000 n 0000000766 00000 n 0000004195 00000 n 0000004248 00000 n 0000004422 00000 n 0000004667 00000 n 0000004720 00000 n 0000004891 00000 n 0000005132 00000 n 0000005270 00000 n 0000005300 00000 n 0000005466 00000 n 0000005540 00000 n 0000005786 00000 n 0000000019 65535 f 0000000020 65535 f 0000000021 65535 f 0000000022 65535 f 0000000023 65535 f 0000000024 65535 f 0000000025 65535 f 0000000026 65535 f 0000000027 65535 f 0000000028 65535 f 0000000029 65535 f 0000000030 65535 f 0000000031 65535 f 0000000032 65535 f 0000000033 65535 f 0000000034 65535 f 0000000035 65535 f 0000000036 65535 f 0000000037 65535 f 0000000038 65535 f 0000000039 65535 f 0000000040 65535 f 0000000041 65535 f 0000000042 65535 f 0000000043 65535 f 0000000044 65535 f 0000000045 65535 f 0000000046 65535 f 0000000047 65535 f 0000000048 65535 f 0000000049 65535 f 0000000050 65535 f 0000000051 65535 f 0000000052 65535 f 0000000053 65535 f 0000000054 65535 f 0000000055 65535 f 0000000056 65535 f 0000000057 65535 f 0000000058 65535 f 0000000059 65535 f 0000000060 65535 f 0000000061 65535 f 0000000062 65535 f 0000000063 65535 f 0000000064 65535 f 0000000065 65535 f 0000000066 65535 f 0000000067 65535 f 0000000068 65535 f 0000000069 65535 f 0000000070 65535 f 0000000071 65535 f 0000000072 65535 f 0000000073 65535 f 0000000074 65535 f 0000000075 65535 f 0000000076 65535 f 0000000077 65535 f 0000000078 65535 f 0000000079 65535 f 0000000080 65535 f 0000000081 65535 f 0000000082 65535 f 0000000083 65535 f 0000000084 65535 f 0000000085 65535 f 0000000086 65535 f 0000000087 65535 f 0000000088 65535 f 0000000089 65535 f 0000000090 65535 f 0000000091 65535 f 0000000092 65535 f 0000000093 65535 f 0000000094 65535 f 0000000095 65535 f 0000000096 65535 f 0000000097 65535 f 0000000098 65535 f 0000000099 65535 f 0000000100 65535 f 0000000101 65535 f 0000000102 65535 f 0000000103 65535 f 0000000104 65535 f 0000000105 65535 f 0000000106 65535 f 0000000107 65535 f 0000000108 65535 f 0000000109 65535 f 0000000110 65535 f 0000000111 65535 f 0000000112 65535 f 0000000113 65535 f 0000000114 65535 f 0000000115 65535 f 0000000116 65535 f 0000000117 65535 f 0000000118 65535 f 0000000119 65535 f 0000000120 65535 f 0000000121 65535 f 0000000122 65535 f 0000000123 65535 f 0000000124 65535 f 0000000125 65535 f 0000000126 65535 f 0000000127 65535 f 0000000128 65535 f 0000000129 65535 f 0000000130 65535 f 0000000131 65535 f 0000000132 65535 f 0000000133 65535 f 0000000134 65535 f 0000000135 65535 f 0000000136 65535 f 0000000137 65535 f 0000000138 65535 f 0000000139 65535 f 0000000140 65535 f 0000000141 65535 f 0000000142 65535 f 0000000143 65535 f 0000000144 65535 f 0000000145 65535 f 0000000146 65535 f 0000000147 65535 f 0000000148 65535 f 0000000149 65535 f 0000000150 65535 f 0000000151 65535 f 0000000152 65535 f 0000000153 65535 f 0000000154 65535 f 0000000155 65535 f 0000000156 65535 f 0000000157 65535 f 0000000158 65535 f 0000000159 65535 f 0000000160 65535 f 0000000161 65535 f 0000000162 65535 f 0000000163 65535 f 0000000164 65535 f 0000000165 65535 f 0000000166 65535 f 0000000167 65535 f 0000000168 65535 f 0000000169 65535 f 0000000170 65535 f 0000000171 65535 f 0000000172 65535 f 0000000173 65535 f 0000000174 65535 f 0000000175 65535 f 0000000176 65535 f 0000000177 65535 f 0000000178 65535 f 0000000179 65535 f 0000000180 65535 f 0000000181 65535 f 0000000182 65535 f 0000000183 65535 f 0000000184 65535 f 0000000185 65535 f 0000000186 65535 f 0000000187 65535 f 0000000188 65535 f 0000000189 65535 f 0000000190 65535 f 0000000191 65535 f 0000000192 65535 f 0000000193 65535 f 0000000194 65535 f 0000000195 65535 f 0000000196 65535 f 0000000197 65535 f 0000000198 65535 f 0000000199 65535 f 0000000200 65535 f 0000000201 65535 f 0000000202 65535 f 0000000203 65535 f 0000000204 65535 f 0000000205 65535 f 0000000206 65535 f 0000000207 65535 f 0000000208 65535 f 0000000209 65535 f 0000000210 65535 f 0000000211 65535 f 0000000212 65535 f 0000000213 65535 f 0000000214 65535 f 0000000215 65535 f 0000000216 65535 f 0000000217 65535 f 0000000218 65535 f 0000000219 65535 f 0000000220 65535 f 0000000221 65535 f 0000000222 65535 f 0000000223 65535 f 0000000224 65535 f 0000000225 65535 f 0000000226 65535 f 0000000227 65535 f 0000000228 65535 f 0000000229 65535 f 0000000230 65535 f 0000000231 65535 f 0000000232 65535 f 0000000233 65535 f 0000000234 65535 f 0000000235 65535 f 0000000236 65535 f 0000000237 65535 f 0000000238 65535 f 0000000239 65535 f 0000000240 65535 f 0000000241 65535 f 0000000242 65535 f 0000000243 65535 f 0000000244 65535 f 0000000245 65535 f 0000000246 65535 f 0000000247 65535 f 0000000248 65535 f 0000000249 65535 f 0000000250 65535 f 0000000251 65535 f 0000000252 65535 f 0000000253 65535 f 0000000254 65535 f 0000000255 65535 f 0000000256 65535 f 0000000257 65535 f 0000000258 65535 f 0000000259 65535 f 0000000260 65535 f 0000000261 65535 f 0000000262 65535 f 0000000263 65535 f 0000000264 65535 f 0000000265 65535 f 0000000266 65535 f 0000000267 65535 f 0000000268 65535 f 0000000269 65535 f 0000000270 65535 f 0000000271 65535 f 0000000272 65535 f 0000000273 65535 f 0000000274 65535 f 0000000275 65535 f 0000000276 65535 f 0000000277 65535 f 0000000278 65535 f 0000000279 65535 f 0000000280 65535 f 0000000281 65535 f 0000000282 65535 f 0000000283 65535 f 0000000284 65535 f 0000000285 65535 f 0000000286 65535 f 0000000287 65535 f 0000000288 65535 f 0000000289 65535 f 0000000290 65535 f 0000000291 65535 f 0000000292 65535 f 0000000293 65535 f 0000000294 65535 f 0000000295 65535 f 0000000296 65535 f 0000000297 65535 f 0000000298 65535 f 0000000299 65535 f 0000000300 65535 f 0000000301 65535 f 0000000302 65535 f 0000000303 65535 f 0000000304 65535 f 0000000305 65535 f 0000000306 65535 f 0000000307 65535 f 0000000308 65535 f 0000000309 65535 f 0000000310 65535 f 0000000311 65535 f 0000000312 65535 f 0000000313 65535 f 0000000314 65535 f 0000000315 65535 f 0000000316 65535 f 0000000317 65535 f 0000000318 65535 f 0000000319 65535 f 0000000320 65535 f 0000000321 65535 f 0000000322 65535 f 0000000323 65535 f 0000000324 65535 f 0000000325 65535 f 0000000326 65535 f 0000000327 65535 f 0000000328 65535 f 0000000329 65535 f 0000000330 65535 f 0000000331 65535 f 0000000332 65535 f 0000000333 65535 f 0000000334 65535 f 0000000335 65535 f 0000000336 65535 f 0000000337 65535 f 0000000338 65535 f 0000000339 65535 f 0000000340 65535 f 0000000341 65535 f 0000000342 65535 f 0000000343 65535 f 0000000344 65535 f 0000000345 65535 f 0000000346 65535 f 0000000347 65535 f 0000000348 65535 f 0000000349 65535 f 0000000350 65535 f 0000000351 65535 f 0000000352 65535 f 0000000353 65535 f 0000000354 65535 f 0000000355 65535 f 0000000356 65535 f 0000000357 65535 f 0000000358 65535 f 0000000359 65535 f 0000000360 65535 f 0000000361 65535 f 0000000362 65535 f 0000000363 65535 f 0000000364 65535 f 0000000365 65535 f 0000000366 65535 f 0000000367 65535 f 0000000368 65535 f 0000000369 65535 f 0000000370 65535 f 0000000371 65535 f 0000000372 65535 f 0000000373 65535 f 0000000374 65535 f 0000000375 65535 f 0000000376 65535 f 0000000377 65535 f 0000000378 65535 f 0000000379 65535 f 0000000380 65535 f 0000000381 65535 f 0000000382 65535 f 0000000383 65535 f 0000000384 65535 f 0000000385 65535 f 0000000386 65535 f 0000000387 65535 f 0000000388 65535 f 0000000389 65535 f 0000000390 65535 f 0000000391 65535 f 0000000392 65535 f 0000000393 65535 f 0000000394 65535 f 0000000395 65535 f 0000000396 65535 f 0000000397 65535 f 0000000398 65535 f 0000000399 65535 f 0000000400 65535 f 0000000401 65535 f 0000000402 65535 f 0000000403 65535 f 0000000404 65535 f 0000000405 65535 f 0000000406 65535 f 0000000407 65535 f 0000000408 65535 f 0000000409 65535 f 0000000410 65535 f 0000000411 65535 f 0000000412 65535 f 0000000413 65535 f 0000000414 65535 f 0000000415 65535 f 0000000416 65535 f 0000000417 65535 f 0000000418 65535 f 0000000419 65535 f 0000000420 65535 f 0000000421 65535 f 0000000422 65535 f 0000000423 65535 f 0000000000 65535 f 0000015991 00000 n 0000016400 00000 n 0000190452 00000 n 0000191003 00000 n 0000191567 00000 n 0000192057 00000 n 0000357865 00000 n 0000361009 00000 n 0000361055 00000 n trailer <<7F7229823C324A488E5F97D9835A8FEF>] >> startxref 362125 %%EOF xref 0 0 trailer <<7F7229823C324A488E5F97D9835A8FEF>] /Prev 362125/XRefStm 361055>> startxref 370944 %%EOFrows-0.4.1/tests/data/nation.dict.parquet000077500000000000000000000054421343135453400204130ustar00rootroot00000000000000PAR1,22 L2ALGERIA ARGENTINABRAZILCANADAEGYPTETHIOPIAFRANCEGERMANYINDIA INDONESIAIRANIRAQJAPANJORDANKENYAMOROCCO MOZAMBIQUEPERUCHINAROMANIA SAUDI ARABIAVIETNAMRUSSIAUNITED KINGDOM UNITED STATES88,22 A9(Ś{0I,22L23 haggle. carefully final deposits detect slyly agaiLal foxes promise slyly according to the regular accounts. bold requests alonky alongside of the pending deposits. carefully special packages are about the ironic forges. slyly special eeas hang ironic, silent packages. slyly regular packages are furiously over the tithes. fluffily boldcy above the carefully unusual theodolites. final dugouts are quickly across the furiously regular dven packages wake quickly. regu&refully final requests. regular, ironi:l platelets. regular accounts x-ray: unusual, regular accoAss excuses cajole slyly across the packages. deposits print arounr slyly express asymptotes. regular deposits haggle slyly. carefully ironic hockey players sleep blithely. carefull2efully alongside of the slyly final dependencies. Bnic deposits boost atop the quickly final requests? quickly regula$ously. final, express gifts cajole a7ic deposits are blithely about the carefully regular pa] pending excuses haggle furiously deposits. pending, express pinto beans wake fluffily past tZrns. blithely bold courts among the closely regular packages use furiously bold platelets?-s. ironic, unusual asymptotes wake blithely rjplatelets. blithely pending dependencies use fluffily across the even pinto beans. carefully silent accoun[c dependencies. furiously express notornis sleep slyly regular accounts. ideas sleep. deposoular asymptotes are about the furious multipliers. express dependencies nag above the ironically ironic accountNts. silent requests haggle. closely express packages sleep across the blithely.hely enticingly express accounts. even, final O requests against the platelets use never according to the quickly regular pint=eans boost carefully special requests. accounts are. carefullny final packages. slow foxes cajole quickly. quickly silent platelets breach ironic accounts. unusual pinto be88,22 A9(Ś{0I\Hm% nation_key %name% region_key % comment_col2L& nation_key2&& name2&& region_key2&&   comment_col2& 2( parquet-mrPAR1rows-0.4.1/tests/data/nested-table.html000066400000000000000000000022271343135453400200260ustar00rootroot00000000000000
t0,0r0c0 t0,0r0c1 t0,0r0c2
t0,0r1c0 t0,0r1c1 t0,0r1c2
t0,0r2c0
t0,1r0c0 t0,1r0c1
t0,1r1c0 t0,1r1c1
t0,1r2c0 t0,1r2c1
t0,2r0c0 t0,2r0c1
t0,2r1c0 t0,2r1c1
t0,1r3c1
t0,1r4c0 t0,1r4c1
t0,1r5c0 t0,1r5c1
t0,0r2c2
t0,0r3c0 t0,0r3c1 t0,0r3c2
rows-0.4.1/tests/data/properties-table.html000066400000000000000000000004521343135453400207360ustar00rootroot00000000000000
field1 field2
row1field1 row1field2
row2field1 row2field2
rows-0.4.1/tests/data/scripts/000077500000000000000000000000001343135453400162555ustar00rootroot00000000000000rows-0.4.1/tests/data/scripts/create_sqlite.py000066400000000000000000000022161343135453400214540ustar00rootroot00000000000000# coding: utf-8 import os import sqlite3 from collections import OrderedDict input_filename = "../all-field-types.csv" output_filename = "../all-field-types.sqlite" field_types = OrderedDict( [ ("bool_column", "INTEGER"), ("integer_column", "INTEGER"), ("float_column", "FLOAT"), ("decimal_column", "FLOAT"), ("percent_column", "TEXT"), ("date_column", "TEXT"), ("datetime_column", "TEXT"), ("unicode_column", "TEXT"), ] ) column_types = ", ".join( ["{} {}".format(key, value) for key, value in field_types.items()] ) create_sql = "CREATE TABLE table1 ({})".format(column_types) field_names = ", ".join(field_types.keys()) placeholders = ", ".join(["?" for _ in field_types]) insert_sql = "INSERT INTO table1 ({}) VALUES ({})".format(field_names, placeholders) if os.path.exists(output_filename): os.unlink(output_filename) connection = sqlite3.connect(output_filename) connection.execute(create_sql) with open(input_filename) as fobj: data = fobj.read().decode("utf-8").splitlines() for row in data[1:]: connection.execute(insert_sql, row.split(",")) connection.commit() rows-0.4.1/tests/data/simple-table.html000066400000000000000000000005121343135453400200300ustar00rootroot00000000000000
t0r0c0 t0r0c1
t0r1c0 t0r1c1
t1r0c0 t1r0c1
t1r1c0 t1r1c1
t1r2c0 t1r2c1
rows-0.4.1/tests/data/table-thead-tbody.html000066400000000000000000000003141343135453400207430ustar00rootroot00000000000000
t1 t2
456 123
qqq aaa
rows-0.4.1/tests/data/table-with-sections.html000066400000000000000000000031131343135453400213370ustar00rootroot00000000000000
t2r0c0 t2r0c1
t2r0c0 t2r0c1
t2r1c0 t2r1c1
t2r2c0 t2r2c1
id username name signup_date
1 turicas Álvaro Justen 2014-04-01
2 test Test User 2014-04-02
3 example Example User 2014-04-03
4 python Python Heavy User 2014-04-04
5 erlang Erlang User 2014-04-05
rows-0.4.1/tests/data/text_in_percent_cell.ods000066400000000000000000000175341343135453400215000ustar00rootroot00000000000000PK+Ml9..mimetypeapplication/vnd.oasis.opendocument.spreadsheetPK+MThumbnails/thumbnail.pngPNG  IHDR'eWPLTE...222:::DDDLLLUUU\\\ccckkkttt{{{öIDATxM& ʹm7hprK*%y@S,!CW}hkZVkY6%䵨O}MnVvu_;cG/ӃTN=П!6N;U7"j\aa|ZMQǦ3wrd5_1??r6 }]۵)e2\]@_C#Ucyov2&Ɇ'[7aeY=TܡfؾL丢~NܬV9Գ~>ERժӬi^Tꭶhu3RUbV{=o:m&{!|ҽ^Xҏ/VNinyJ~ZxaUfR0ݧ4`ۈhM'cGQ5ʓ૙ݕn4v}n/|1e9)ce=ԟsc>Ʊso6s6;Wt囎snW)}nڧ:52\~"H~fva{(Fe9[a5_#WҧTcKeYC9jZG1}jU:[hd{ѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=zѣG=z?ۧIENDB`PK+MConfigurations2/accelerator/PK+MConfigurations2/popupmenu/PK+MConfigurations2/toolpanel/PK+MConfigurations2/menubar/PK+MConfigurations2/images/Bitmaps/PK+MConfigurations2/toolbar/PK+MConfigurations2/floater/PK+MConfigurations2/statusbar/PK+MConfigurations2/progressbar/PK+M content.xmlXK6WP67Y`n{J0Ul^GVV J 6I[_!fsDb*=Z"aEۊ4e1W9-4EBEtWQLEɩtV#W/V`W[ӳ^l]rXډ$ReX|VD3gŧԺ0zUoVBf8V{\YInQI)f3U;lN5Y`]JE\ɳS-%UwMb.3 -ήS6H:f2I&@g@z;B`KeϧTTEҴ&\RɌp ,ſ04 3ʻ]4#$$(#G{87d~^f$HұQJm~2cCs]Xn(%1E o/{ͻ ^PiЈ;he@J~E6xBLd:z"÷߫۴Ji5đi==d3a-~HY,3'8f\&+ӧ}=4j%dBD<ڢ[Nw¶GDqM)hVF۟[= dtO?VU+;L a)#+;6R#{]9w )ޓذ -~0GɲYOwS ĴŒ=BJ,eM /krQ+)nX\pS5Y9V%>M&^=hvy{"iGm[Y,v'~ һ6 %z/ bL&={Xކxh%jŢ&oPKJIPK+M styles.xmlZ[o6~߯T`؀ɔ6&zY(+% m'EuudN9AS9:;/.ol0 Kn0]$w/sr",dhTx8wp]t`NY  VhVËV6T\߈ [wtvJN*|S/bbIiXqCIiflv2b<t:z4lͩF`r` p} [5)]'KSlE58a2ăkЌV3 $**@9׻{)l*I6M3JS)vms]Ao·+p E%,"M ިh p1.JCN3.Ku%_jBcPiȲEm>OU^ Swly\.[);C0'j R-6if eG,֐Zc%j+]TØIelVU~ODW(޽jS}*]ؕ3brՌ ^ÎVFDO2Ie[Є۹-Xcg]ZkYLNrVv W\VZGŅL{Z< ;׾9UBRb)\6%}60wgTvWD+ a5Շ/?-u|d)5D (PD)K 1dBHG<&OOP奝S=Kk*}h JdΤޙ:{'B3Fy폮.U$' -mPF*[rt`,1A oA|Ke/u`h)պE.|7 arMA\~F.0ķ~K&5J h9yїK5$J4n&/)C~ y~AsCF՟C1kG)yLC =E7_Ug <墕ΪJʑ Eψ\􉰨:5YAR[!7Ae{ȀpkD {:aX2`E]%aXëj8CqzT\_5rt2 R./XB'nz~c3LgO )h3)N(@3Lr?8?r!_LԮGa<6ȈRcVGJ2BoHJ,<~IYv+]N9ΈTPdU2`2faR>Xx7ZtCe-;ߔ:xKZD _Yc }[_>A&{ -/pLA%;.&{ ]dO{>a(MKJC= C5$` ɶZp0dG QIO\g1-kʉYvmOO 1N"FT%7կCN?Y|[B3G`Zy"֜Nk+Ei,э~%~*5&ǐoOgWJWvѿPK7`PK+MMETA-INF/manifest.xmlAn E9Ŷ2YU(Nz  Qrb+N\Ub);`|fw]udž}wV^[}?n DrZT۶a9*$Q$@/GnfWiEػ*H@ 4F873WWY*PHMj-j71̢JB'2lyR^gCFr(׋ZjHba jpqpp=7et0+;hqFߜ]J|tOPK."PK+Mxl/worksheets/sheet1.xmlWn6+ZNjE8]7ZYD(RKR&_!)ɲ$EQ8GμyC% |GOEFf{\&<#LpϠ/N'Uhp5 Pi%Q#QGO.dI4.&PM% 0JB2\C9MFu \$_R~ ) a)@_E6~(к$IQ`X$3j|y=56/ ;{T!v Y3t-V- vs H|7H$rݥdoYkEVZ.A96}g>72L)*Sb6}/5bs{\!I0yKEmii欬x2&74C]z+bUZ\Gv e,Q7L _仵4 G-la-ٷ!D#m~Q4w颳4ˀZ-ϙ|1ͷNtyĒ?Ѥ){֕ cK $yBt9.AG-b禜9VM8Mqĸu=:19oE@?;1#`(:ߋ:h-.&'n` !c_ŻBG-0| t{1VrJ Q^uf*]I_ׄQQi;'xXY%F^x[7izSXO {[uw|rE_zz"Rir ¬ewoIv&vDpz"V JH- EEf$}Ny$=]҈JeG[`1D7MEkig` rZX9Ήk5/&2>ПQ*5( 6(Bsڸqd]>>۴M8gBPK,%Rrj PK+Mxl/workbook.xmlSn0+m-^jWəFke)C69 ߼yZޜkXUaz{ecV!6 ߷y5C݀HM͑LmcZQL : ]"5(@ HVl,!7g^˜W_|hRʎYɥjҧ/wȑ:R2<)@hL*C$d[!i#^B.R Zٶzg }~rgQ`E fwbWaf ՗j13ͼbf5͌fF3}LrAla^:GL߼Se{l^{jÞ zWfKrmccxG Pjj%%IՑNiX&wh:hKcPK?}IPK+M _rels/.relsJ1}{wDdЛH}a70u}{ZI~7CfGFoZ+{kW#VJ$cʪl n0\QX^:`dd{m]_dhVFw^F9W-(F/3ODSUNl/w{N([qTuZ9뺎q+|^R5′xڕzQLa< Y h|_.LְcaGHx\03PfJTsʂYGJ'q#wTsiJzl^6k?>nIW@?1PK'eAdPK+M[Content_Types].xmlN0DWJGD9#co+m횒=VDKEJ3o&RրdĤ ƺU%wMGb2uT:p#%:ExgSq%ҍZ.yLbZ>0l0ޫ*!V>#$<6/7;n*j9\;r!ScDL{/VcIAA"7}$G>HYȱ;~ ^NYw<⦅`zq8}  =+ <%`[#uMQ)PKj5WPK+MOz%xl/_rels/workbook.xml.relsPK+M."xl/sharedStrings.xmlPK+M,%Rrj  xl/worksheets/sheet1.xmlPK+Mx3n1xl/workbook.xmlPK+M?}I exl/styles.xmlPK+Mf; D _rels/.relsPK+M=|] docProps/app.xmlPK+M'eAd docProps/core.xmlPK+Mj5W)[Content_Types].xmlPK ?rows-0.4.1/tests/data/to-merge-1.csv000066400000000000000000000000421343135453400171540ustar00rootroot00000000000000id,username 1,turicas 2,abc 3,def rows-0.4.1/tests/data/to-merge-2.csv000066400000000000000000000001141343135453400171550ustar00rootroot00000000000000id,username,birthday 1,turicas,1987-04-29 3,def,2000-01-01 4,qwe,1999-12-31 rows-0.4.1/tests/data/to-merge-3.csv000066400000000000000000000000671343135453400171650ustar00rootroot00000000000000id,username,gender 1,turicas,M 2,abc,F 3,def,F 4,qwe,F rows-0.4.1/tests/tests_cli.py000066400000000000000000000016141343135453400162220ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import unittest class CliTestCase(unittest.TestCase): # TODO: test everything pass rows-0.4.1/tests/tests_fields.py000066400000000000000000000527771343135453400167410ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import datetime import json import platform import unittest from base64 import b64encode from decimal import Decimal import six import rows from rows import fields if platform.system() == "Windows": locale_name = "ptb_bra" else: locale_name = "pt_BR.UTF-8" class FieldsTestCase(unittest.TestCase): def test_Field(self): self.assertEqual(fields.Field.TYPE, (type(None),)) self.assertIs(fields.Field.deserialize(None), None) self.assertEqual(fields.Field.deserialize("Álvaro"), "Álvaro") self.assertEqual(fields.Field.serialize(None), "") self.assertIs(type(fields.Field.serialize(None)), six.text_type) self.assertEqual(fields.Field.serialize("Álvaro"), "Álvaro") self.assertIs(type(fields.Field.serialize("Álvaro")), six.text_type) def test_BinaryField(self): deserialized = "Álvaro".encode("utf-8") serialized = b64encode(deserialized).decode("ascii") self.assertEqual(type(deserialized), six.binary_type) self.assertEqual(type(serialized), six.text_type) self.assertEqual(fields.BinaryField.TYPE, (bytes,)) self.assertEqual(fields.BinaryField.serialize(None), "") self.assertIs(type(fields.BinaryField.serialize(None)), six.text_type) self.assertEqual(fields.BinaryField.serialize(deserialized), serialized) self.assertIs(type(fields.BinaryField.serialize(deserialized)), six.text_type) with self.assertRaises(ValueError): fields.BinaryField.serialize(42) with self.assertRaises(ValueError): fields.BinaryField.serialize(3.14) with self.assertRaises(ValueError): fields.BinaryField.serialize("Álvaro") with self.assertRaises(ValueError): fields.BinaryField.serialize("123") self.assertIs(fields.BinaryField.deserialize(None), b"") self.assertEqual(fields.BinaryField.deserialize(serialized), deserialized) self.assertIs(type(fields.BinaryField.deserialize(serialized)), six.binary_type) with self.assertRaises(ValueError): fields.BinaryField.deserialize(42) with self.assertRaises(ValueError): fields.BinaryField.deserialize(3.14) with self.assertRaises(ValueError): fields.BinaryField.deserialize("Álvaro") self.assertEqual(fields.BinaryField.deserialize(deserialized), deserialized) self.assertEqual(fields.BinaryField.deserialize(serialized), deserialized) self.assertEqual( fields.BinaryField.deserialize(serialized.encode("ascii")), serialized.encode("ascii"), ) def test_BoolField(self): self.assertEqual(fields.BoolField.TYPE, (bool,)) self.assertEqual(fields.BoolField.serialize(None), "") false_values = ("False", "false", "no", False) for value in false_values: self.assertIs(fields.BoolField.deserialize(value), False) self.assertIs(fields.BoolField.deserialize(None), None) self.assertEqual(fields.BoolField.deserialize(""), None) true_values = ("True", "true", "yes", True) for value in true_values: self.assertIs(fields.BoolField.deserialize(value), True) self.assertEqual(fields.BoolField.serialize(False), "false") self.assertIs(type(fields.BoolField.serialize(False)), six.text_type) self.assertEqual(fields.BoolField.serialize(True), "true") self.assertIs(type(fields.BoolField.serialize(True)), six.text_type) # '0' and '1' should be not accepted as boolean values because the # sample could not contain other integers but the actual type could be # integer with self.assertRaises(ValueError): fields.BoolField.deserialize("0") with self.assertRaises(ValueError): fields.BoolField.deserialize(b"0") with self.assertRaises(ValueError): fields.BoolField.deserialize("1") with self.assertRaises(ValueError): fields.BoolField.deserialize(b"1") def test_IntegerField(self): self.assertEqual(fields.IntegerField.TYPE, (int,)) self.assertEqual(fields.IntegerField.serialize(None), "") self.assertIs(type(fields.IntegerField.serialize(None)), six.text_type) self.assertIn( type(fields.IntegerField.deserialize("42")), fields.IntegerField.TYPE ) self.assertEqual(fields.IntegerField.deserialize("42"), 42) self.assertEqual(fields.IntegerField.deserialize(42), 42) self.assertEqual(fields.IntegerField.serialize(42), "42") self.assertIs(type(fields.IntegerField.serialize(42)), six.text_type) self.assertEqual(fields.IntegerField.deserialize(None), None) self.assertEqual( fields.IntegerField.deserialize("10152709355006317"), 10152709355006317 ) with rows.locale_context(locale_name): self.assertEqual(fields.IntegerField.serialize(42000), "42000") self.assertIs(type(fields.IntegerField.serialize(42000)), six.text_type) self.assertEqual( fields.IntegerField.serialize(42000, grouping=True), "42.000" ) self.assertEqual(fields.IntegerField.deserialize("42.000"), 42000) self.assertEqual(fields.IntegerField.deserialize(42), 42) self.assertEqual(fields.IntegerField.deserialize(42.0), 42) with self.assertRaises(ValueError): fields.IntegerField.deserialize(1.23) with self.assertRaises(ValueError): fields.IntegerField.deserialize("013") self.assertEqual(fields.IntegerField.deserialize("0"), 0) def test_FloatField(self): self.assertEqual(fields.FloatField.TYPE, (float,)) self.assertEqual(fields.FloatField.serialize(None), "") self.assertIs(type(fields.FloatField.serialize(None)), six.text_type) self.assertIn( type(fields.FloatField.deserialize("42.0")), fields.FloatField.TYPE ) self.assertEqual(fields.FloatField.deserialize("42.0"), 42.0) self.assertEqual(fields.FloatField.deserialize(42.0), 42.0) self.assertEqual(fields.FloatField.deserialize(42), 42.0) self.assertEqual(fields.FloatField.deserialize(None), None) self.assertEqual(fields.FloatField.serialize(42.0), "42.0") self.assertIs(type(fields.FloatField.serialize(42.0)), six.text_type) with rows.locale_context(locale_name): self.assertEqual(fields.FloatField.serialize(42000.0), "42000,000000") self.assertIs(type(fields.FloatField.serialize(42000.0)), six.text_type) self.assertEqual( fields.FloatField.serialize(42000, grouping=True), "42.000,000000" ) self.assertEqual(fields.FloatField.deserialize("42.000,00"), 42000.0) self.assertEqual(fields.FloatField.deserialize(42), 42.0) self.assertEqual(fields.FloatField.deserialize(42.0), 42.0) def test_DecimalField(self): deserialized = Decimal("42.010") self.assertEqual(fields.DecimalField.TYPE, (Decimal,)) self.assertEqual(fields.DecimalField.serialize(None), "") self.assertIs(type(fields.DecimalField.serialize(None)), six.text_type) self.assertEqual(fields.DecimalField.deserialize(""), None) self.assertIn( type(fields.DecimalField.deserialize("42.0")), fields.DecimalField.TYPE ) self.assertEqual(fields.DecimalField.deserialize("42.0"), Decimal("42.0")) self.assertEqual(fields.DecimalField.deserialize(deserialized), deserialized) self.assertEqual(fields.DecimalField.serialize(deserialized), "42.010") self.assertEqual( type(fields.DecimalField.serialize(deserialized)), six.text_type ) self.assertEqual( fields.DecimalField.deserialize("21.21657469231"), Decimal("21.21657469231") ) self.assertEqual(fields.DecimalField.deserialize("-21.34"), Decimal("-21.34")) self.assertEqual(fields.DecimalField.serialize(Decimal("-21.34")), "-21.34") self.assertEqual(fields.DecimalField.deserialize(None), None) with rows.locale_context(locale_name): self.assertEqual( six.text_type, type(fields.DecimalField.serialize(deserialized)) ) self.assertEqual(fields.DecimalField.serialize(Decimal("4200")), "4200") self.assertEqual(fields.DecimalField.serialize(Decimal("42.0")), "42,0") self.assertEqual( fields.DecimalField.serialize(Decimal("42000.0")), "42000,0" ) self.assertEqual(fields.DecimalField.serialize(Decimal("-42.0")), "-42,0") self.assertEqual( fields.DecimalField.deserialize("42.000,00"), Decimal("42000.00") ) self.assertEqual( fields.DecimalField.deserialize("-42.000,00"), Decimal("-42000.00") ) self.assertEqual( fields.DecimalField.serialize(Decimal("42000.0"), grouping=True), "42.000,0", ) self.assertEqual(fields.DecimalField.deserialize(42000), Decimal("42000")) self.assertEqual(fields.DecimalField.deserialize(42000.0), Decimal("42000")) def test_PercentField(self): deserialized = Decimal("0.42010") self.assertEqual(fields.PercentField.TYPE, (Decimal,)) self.assertIn( type(fields.PercentField.deserialize("42.0%")), fields.PercentField.TYPE ) self.assertEqual(fields.PercentField.deserialize("42.0%"), Decimal("0.420")) self.assertEqual( fields.PercentField.deserialize(Decimal("0.420")), Decimal("0.420") ) self.assertEqual(fields.PercentField.deserialize(deserialized), deserialized) self.assertEqual(fields.PercentField.deserialize(None), None) self.assertEqual(fields.PercentField.serialize(deserialized), "42.010%") self.assertEqual( type(fields.PercentField.serialize(deserialized)), six.text_type ) self.assertEqual(fields.PercentField.serialize(Decimal("42.010")), "4201.0%") self.assertEqual(fields.PercentField.serialize(Decimal("0")), "0.00%") self.assertEqual(fields.PercentField.serialize(None), "") self.assertEqual(fields.PercentField.serialize(Decimal("0.01")), "1%") with rows.locale_context(locale_name): self.assertEqual( type(fields.PercentField.serialize(deserialized)), six.text_type ) self.assertEqual(fields.PercentField.serialize(Decimal("42.0")), "4200%") self.assertEqual( fields.PercentField.serialize(Decimal("42000.0")), "4200000%" ) self.assertEqual( fields.PercentField.deserialize("42.000,00%"), Decimal("420.0000") ) self.assertEqual( fields.PercentField.serialize(Decimal("42000.00"), grouping=True), "4.200.000%", ) with self.assertRaises(ValueError): fields.PercentField.deserialize(42) def test_DateField(self): # TODO: test timezone-aware datetime.date serialized = "2015-05-27" deserialized = datetime.date(2015, 5, 27) self.assertEqual(fields.DateField.TYPE, (datetime.date,)) self.assertEqual(fields.DateField.serialize(None), "") self.assertIs(type(fields.DateField.serialize(None)), six.text_type) self.assertIn( type(fields.DateField.deserialize(serialized)), fields.DateField.TYPE ) self.assertEqual(fields.DateField.deserialize(serialized), deserialized) self.assertEqual(fields.DateField.deserialize(deserialized), deserialized) self.assertEqual(fields.DateField.deserialize(None), None) self.assertEqual(fields.DateField.deserialize(""), None) self.assertEqual(fields.DateField.serialize(deserialized), serialized) self.assertIs(type(fields.DateField.serialize(deserialized)), six.text_type) with self.assertRaises(ValueError): fields.DateField.deserialize(42) with self.assertRaises(ValueError): fields.DateField.deserialize(serialized + "T00:00:00") with self.assertRaises(ValueError): fields.DateField.deserialize("Álvaro") with self.assertRaises(ValueError): fields.DateField.deserialize(serialized.encode("utf-8")) def test_DatetimeField(self): # TODO: test timezone-aware datetime.date serialized = "2015-05-27T01:02:03" self.assertEqual(fields.DatetimeField.TYPE, (datetime.datetime,)) deserialized = fields.DatetimeField.deserialize(serialized) self.assertIn(type(deserialized), fields.DatetimeField.TYPE) self.assertEqual(fields.DatetimeField.serialize(None), "") self.assertIs(type(fields.DatetimeField.serialize(None)), six.text_type) value = datetime.datetime(2015, 5, 27, 1, 2, 3) self.assertEqual(fields.DatetimeField.deserialize(serialized), value) self.assertEqual(fields.DatetimeField.deserialize(deserialized), deserialized) self.assertEqual(fields.DatetimeField.deserialize(None), None) self.assertEqual(fields.DatetimeField.serialize(value), serialized) self.assertIs(type(fields.DatetimeField.serialize(value)), six.text_type) with self.assertRaises(ValueError): fields.DatetimeField.deserialize(42) with self.assertRaises(ValueError): fields.DatetimeField.deserialize("2015-01-01") with self.assertRaises(ValueError): fields.DatetimeField.deserialize("Álvaro") with self.assertRaises(ValueError): fields.DatetimeField.deserialize(serialized.encode("utf-8")) def test_EmailField(self): # TODO: accept spaces also serialized = "test@domain.com" self.assertEqual(fields.EmailField.TYPE, (six.text_type,)) deserialized = fields.EmailField.deserialize(serialized) self.assertIn(type(deserialized), fields.EmailField.TYPE) self.assertEqual(fields.EmailField.serialize(None), "") self.assertIs(type(fields.EmailField.serialize(None)), six.text_type) self.assertEqual(fields.EmailField.serialize(serialized), serialized) self.assertEqual(fields.EmailField.deserialize(serialized), serialized) self.assertEqual(fields.EmailField.deserialize(None), None) self.assertEqual(fields.EmailField.deserialize(""), None) self.assertIs(type(fields.EmailField.serialize(serialized)), six.text_type) with self.assertRaises(ValueError): fields.EmailField.deserialize(42) with self.assertRaises(ValueError): fields.EmailField.deserialize("2015-01-01") with self.assertRaises(ValueError): fields.EmailField.deserialize("Álvaro") with self.assertRaises(ValueError): fields.EmailField.deserialize("test@example.com".encode("utf-8")) def test_TextField(self): self.assertEqual(fields.TextField.TYPE, (six.text_type,)) self.assertEqual(fields.TextField.serialize(None), "") self.assertIs(type(fields.TextField.serialize(None)), six.text_type) self.assertIn(type(fields.TextField.deserialize("test")), fields.TextField.TYPE) self.assertEqual(fields.TextField.deserialize("Álvaro"), "Álvaro") self.assertIs(fields.TextField.deserialize(None), None) self.assertIs(fields.TextField.deserialize(""), "") self.assertEqual(fields.TextField.serialize("Álvaro"), "Álvaro") self.assertIs(type(fields.TextField.serialize("Álvaro")), six.text_type) with self.assertRaises(ValueError) as exception_context: fields.TextField.deserialize("Álvaro".encode("utf-8")) self.assertEqual(exception_context.exception.args[0], "Binary is not supported") def test_JSONField(self): self.assertEqual(fields.JSONField.TYPE, (list, dict)) self.assertEqual(type(fields.JSONField.deserialize("[]")), list) self.assertEqual(type(fields.JSONField.deserialize("{}")), dict) deserialized = {"a": 123, "b": 3.14, "c": [42, 24]} serialized = json.dumps(deserialized) self.assertEqual(fields.JSONField.deserialize(serialized), deserialized) class FieldUtilsTestCase(unittest.TestCase): maxDiff = None def setUp(self): with open("tests/data/all-field-types.csv", "rb") as fobj: data = fobj.read().decode("utf-8") lines = [line.split(",") for line in data.splitlines()] self.fields = lines[0] self.data = lines[1:] self.expected = { "bool_column": fields.BoolField, "integer_column": fields.IntegerField, "float_column": fields.FloatField, "decimal_column": fields.FloatField, "percent_column": fields.PercentField, "date_column": fields.DateField, "datetime_column": fields.DatetimeField, "unicode_column": fields.TextField, } def test_detect_types_no_sample(self): expected = {key: fields.TextField for key in self.expected.keys()} result = fields.detect_types(self.fields, []) self.assertDictEqual(dict(result), expected) def test_detect_types_binary(self): # first, try values as (`bytes`/`str`) expected = {key: fields.BinaryField for key in self.expected.keys()} values = [ [b"some binary data" for _ in range(len(self.data[0]))] for __ in range(20) ] result = fields.detect_types(self.fields, values) self.assertDictEqual(dict(result), expected) # second, try base64-encoded values (as `str`/`unicode`) expected = {key: fields.TextField for key in self.expected.keys()} values = [ [b64encode(value.encode("utf-8")).decode("ascii") for value in row] for row in self.data ] result = fields.detect_types(self.fields, values) self.assertDictEqual(dict(result), expected) def test_detect_types(self): result = fields.detect_types(self.fields, self.data) self.assertDictEqual(dict(result), self.expected) def test_detect_types_different_number_of_fields(self): result = fields.detect_types(["f1", "f2"], [["a", "b", "c"]]) self.assertEquals(list(result.keys()), ["f1", "f2", "field_2"]) def test_precedence(self): field_types = [ ("bool", fields.BoolField), ("integer", fields.IntegerField), ("float", fields.FloatField), ("datetime", fields.DatetimeField), ("date", fields.DateField), ("float", fields.FloatField), ("percent", fields.PercentField), ("json", fields.JSONField), ("email", fields.EmailField), ("binary1", fields.BinaryField), ("binary2", fields.BinaryField), ("text", fields.TextField), ] data = [ [ "false", "42", "3.14", "2016-08-15T05:21:10", "2016-08-15", "2.71", "76.38%", '{"key": "value"}', "test@example.com", b"cHl0aG9uIHJ1bGVz", b"python rules", "Álvaro Justen", ] ] result = fields.detect_types( [item[0] for item in field_types], data, field_types=[item[1] for item in field_types], ) self.assertDictEqual(dict(result), dict(field_types)) class FieldsFunctionsTestCase(unittest.TestCase): def test_is_null(self): self.assertTrue(fields.is_null(None)) self.assertTrue(fields.is_null("")) self.assertTrue(fields.is_null(" \t ")) self.assertTrue(fields.is_null("null")) self.assertTrue(fields.is_null("nil")) self.assertTrue(fields.is_null("none")) self.assertTrue(fields.is_null("-")) self.assertFalse(fields.is_null("Álvaro")) self.assertFalse(fields.is_null("Álvaro".encode("utf-8"))) def test_as_string(self): self.assertEqual(fields.as_string(None), "None") self.assertEqual(fields.as_string(42), "42") self.assertEqual(fields.as_string(3.141592), "3.141592") self.assertEqual(fields.as_string("Álvaro"), "Álvaro") with self.assertRaises(ValueError) as exception_context: fields.as_string("Álvaro".encode("utf-8")) self.assertEqual(exception_context.exception.args[0], "Binary is not supported") def test_get_items(self): func = fields.get_items(2) self.assertEqual(func("a b c d e f".split()), ("c",)) func = fields.get_items(0, 2, 3) self.assertEqual(func("a b c d e f".split()), ("a", "c", "d")) self.assertEqual(func("a b c".split()), ("a", "c", None)) rows-0.4.1/tests/tests_localization.py000066400000000000000000000027611343135453400201470ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import platform import unittest import rows import rows.fields from rows.localization import locale_context class LocalizationTestCase(unittest.TestCase): def test_locale_context_present_in_main_namespace(self): self.assertIn("locale_context", dir(rows)) self.assertIs(locale_context, rows.locale_context) def test_locale_context(self): self.assertTrue(rows.fields.SHOULD_NOT_USE_LOCALE) if platform.system() == "Windows": name = str("ptb_bra") else: name = "pt_BR.UTF-8" with locale_context(name): self.assertFalse(rows.fields.SHOULD_NOT_USE_LOCALE) self.assertTrue(rows.fields.SHOULD_NOT_USE_LOCALE) rows-0.4.1/tests/tests_operations.py000066400000000000000000000101761343135453400176410ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import datetime import unittest from collections import OrderedDict import rows import rows.operations import tests.utils as utils class OperationsTestCase(utils.RowsTestMixIn, unittest.TestCase): def test_join_imports(self): self.assertIs(rows.join, rows.operations.join) def test_join_feature(self): tables = [ rows.import_from_csv("tests/data/to-merge-1.csv"), rows.import_from_csv("tests/data/to-merge-2.csv"), rows.import_from_csv("tests/data/to-merge-3.csv"), ] merged = rows.join(keys=("id", "username"), tables=tables) expected = rows.import_from_csv("tests/data/merged.csv") self.assert_table_equal(merged, expected) def test_transform_imports(self): self.assertIs(rows.transform, rows.operations.transform) def test_transform_feature(self): def transformation_function(row, table): if row.percent_column is None or row.percent_column < 0.1269: return None # discard this row new = row._asdict() new["meta"] = ", ".join( ["{} => {}".format(key, value) for key, value in table._meta.items()] ) return new fields = utils.table.fields.copy() fields.update({"meta": rows.fields.TextField}) tables = [utils.table] * 3 result = rows.transform(fields, transformation_function, *tables) self.assertEqual(result.fields, fields) not_discarded = [ transformation_function(row, utils.table) for row in utils.table ] * 3 not_discarded = [row for row in not_discarded if row is not None] self.assertEqual(len(result), len(not_discarded)) for expected_row, row in zip(not_discarded, result): self.assertEqual(expected_row, dict(row._asdict())) def test_transpose_imports(self): self.assertIs(rows.transpose, rows.operations.transpose) def test_transpose_feature(self): new_fields = OrderedDict( [ ("key", rows.fields.TextField), ("value_1", rows.fields.TextField), ("value_2", rows.fields.TextField), ] ) table = rows.Table(fields=new_fields) table.append( {"key": "first_key", "value_1": "first_value_1", "value_2": "first_value_2"} ) table.append({"key": "second_key", "value_1": 1, "value_2": 2}) table.append({"key": "third_key", "value_1": 3.14, "value_2": 2.71}) table.append( {"key": "fourth_key", "value_1": "2015-09-04", "value_2": "2015-08-29"} ) new_table = rows.transpose(table, fields_column="key") self.assertEqual(len(new_table), 2) self.assertEqual(len(new_table.fields), len(table)) self.assertEqual(new_table.field_names, [row.key for row in table]) self.assertEqual(new_table[0].first_key, "first_value_1") self.assertEqual(new_table[0].second_key, 1) self.assertEqual(new_table[0].third_key, 3.14) self.assertEqual(new_table[0].fourth_key, datetime.date(2015, 9, 4)) self.assertEqual(new_table[1].first_key, "first_value_2") self.assertEqual(new_table[1].second_key, 2) self.assertEqual(new_table[1].third_key, 2.71) self.assertEqual(new_table[1].fourth_key, datetime.date(2015, 8, 29)) rows-0.4.1/tests/tests_plugin_csv.py000066400000000000000000000316501343135453400176270ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import csv import tempfile import textwrap import unittest from collections import OrderedDict from io import BytesIO from textwrap import dedent import mock import rows import rows.plugins.plugin_csv import tests.utils as utils def make_csv_data(quote_char, field_delimiter, line_delimiter): data = [["field1", "field2", "field3"], ["value1", "value2", "value3"]] lines = [ ["{}{}{}".format(quote_char, value, quote_char) for value in line] for line in data ] lines = line_delimiter.join([field_delimiter.join(line) for line in data]) return data, lines class PluginCsvTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "csv" file_extension = "csv" filename = "tests/data/all-field-types.csv" encoding = "utf-8" assert_meta_encoding = True def test_imports(self): self.assertIs(rows.import_from_csv, rows.plugins.plugin_csv.import_from_csv) self.assertIs(rows.export_to_csv, rows.plugins.plugin_csv.export_to_csv) @mock.patch("rows.plugins.plugin_csv.create_table") def test_import_from_csv_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"some_key": 123, "other": 456} result = rows.import_from_csv(self.filename, encoding="utf-8", **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = { "imported_from": "csv", "filename": self.filename, "encoding": "utf-8", } self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.plugin_csv.create_table") def test_import_from_csv_retrieve_desired_data(self, mocked_create_table): mocked_create_table.return_value = 42 # import using filename rows.import_from_csv(self.filename) call_args = mocked_create_table.call_args_list[0] self.assert_create_table_data(call_args) # import using fobj with open(self.filename, "rb") as fobj: rows.import_from_csv(fobj) call_args = mocked_create_table.call_args_list[1] self.assert_create_table_data(call_args) @mock.patch("rows.plugins.plugin_csv.create_table") def test_import_from_csv_discover_dialect(self, mocked_create_table): data, lines = make_csv_data( quote_char="'", field_delimiter=";", line_delimiter="\r\n" ) fobj = BytesIO() fobj.write(lines.encode("utf-8")) fobj.seek(0) rows.import_from_csv(fobj) call_args = mocked_create_table.call_args_list[0] self.assertEqual(data, list(call_args[0][0])) def test_import_from_csv_discover_dialect_decode_error(self): # Create a 1024-bytes line (if encoded to ASCII, UTF-8 etc.) line = '"' + ("a" * 508) + '", "' + ("b" * 508) + '"\r\n' lines = 256 * line # 256KiB # Now change the last byte (in the 256KiB sample) to have half of a # character representation (when encoded to UTF-8) data = lines[:-3] + '++Á"\r\n' data = data.encode("utf-8") # Should not raise `UnicodeDecodeError` table = rows.import_from_csv( BytesIO(data), encoding="utf-8", sample_size=262144 ) last_row = table[-1] last_column = "b" * 508 self.assertEqual(getattr(last_row, last_column), "b" * 508 + "++Á") def test_import_from_csv_impossible_dialect(self): # Fix a bug from: https://github.com/turicas/rows/issues/214 # The following CSV will make the `csv`'s sniff to return an impossible # dialect to be used (having doublequote = False and escapechar = # None). See more at: # https://docs.python.org/3/library/csv.html#csv.Dialect.doublequote encoding = "utf-8" data = dedent( """ field1,field2 1,2 3,4 5,6 """.strip() ).encode(encoding) dialect = rows.plugins.plugin_csv.discover_dialect(data, encoding) self.assertIs(dialect.doublequote, True) self.assertIs(dialect.escapechar, None) @mock.patch("rows.plugins.plugin_csv.create_table") def test_import_from_csv_force_dialect(self, mocked_create_table): data, lines = make_csv_data( quote_char="'", field_delimiter="\t", line_delimiter="\r\n" ) fobj = BytesIO() fobj.write(lines.encode("utf-8")) fobj.seek(0) rows.import_from_csv(fobj, dialect="excel-tab") call_args = mocked_create_table.call_args_list[0] self.assertEqual(data, list(call_args[0][0])) def test_detect_dialect_more_data(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) # If the sniffer reads only the first line, it will think the delimiter # is ',' instead of ';' data = textwrap.dedent( """ field1,samefield;field2,other row1value1;row1value2 row2value1;row2value2 """ ).strip() with open(filename, "wb") as fobj: fobj.write(data.encode("utf-8")) table = rows.import_from_csv(filename, encoding="utf-8") self.assertEqual(table.field_names, ["field1samefield", "field2other"]) self.assertEqual(table[0].field1samefield, "row1value1") self.assertEqual(table[0].field2other, "row1value2") self.assertEqual(table[1].field1samefield, "row2value1") self.assertEqual(table[1].field2other, "row2value2") def test_detect_weird_dialect(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) # If the sniffer reads only the first line, it will think the delimiter # is ',' instead of ';' encoding = "utf-8" data = BytesIO( textwrap.dedent( """ field1|field2|field3|field4 1|2|3|4 5|6|7|8 9|0|1|2 """ ) .strip() .encode(encoding) ) table = rows.import_from_csv(data, encoding=encoding, lazy=False) self.assertEqual(table.field_names, ["field1", "field2", "field3", "field4"]) expected = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 0, 1, 2]] for expected_data, row in zip(expected, table): row = [row.field1, row.field2, row.field3, row.field4] self.assertEqual(expected_data, row) def test_detect_dialect_using_json(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) encoding = "utf-8" self.files_to_delete.append(filename) # Using JSON will force the sniffer to do not include ':', '}' in the # possible delimiters table = rows.Table( fields=OrderedDict( [ ("jsoncolumn1", rows.fields.JSONField), ("jsoncolumn2", rows.fields.JSONField), ] ) ) table.append({"jsoncolumn1": '{"a": 42}', "jsoncolumn2": '{"b": 43}'}) table.append({"jsoncolumn1": '{"c": 44}', "jsoncolumn2": '{"d": 45}'}) rows.export_to_csv(table, filename, encoding=encoding) table = rows.import_from_csv(filename, encoding=encoding) self.assertEqual(table.field_names, ["jsoncolumn1", "jsoncolumn2"]) self.assertDictEqual(table[0].jsoncolumn1, {"a": 42}) self.assertDictEqual(table[0].jsoncolumn2, {"b": 43}) self.assertDictEqual(table[1].jsoncolumn1, {"c": 44}) self.assertDictEqual(table[1].jsoncolumn2, {"d": 45}) @mock.patch("rows.plugins.plugin_csv.serialize") def test_export_to_csv_uses_serialize(self, mocked_serialize): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) kwargs = {"test": 123, "parameter": 3.14} mocked_serialize.return_value = iter([utils.table.fields.keys()]) rows.export_to_csv(utils.table, temp.name, encoding="utf-8", **kwargs) self.assertTrue(mocked_serialize.called) self.assertEqual(mocked_serialize.call_count, 1) call = mocked_serialize.call_args self.assertEqual(call[0], (utils.table,)) self.assertEqual(call[1], kwargs) def test_export_to_csv_filename(self): # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) rows.export_to_csv(utils.table, temp.name) table = rows.import_from_csv(temp.name) self.assert_table_equal(table, utils.table) temp.file.seek(0) result = temp.file.read() export_in_memory = rows.export_to_csv(utils.table, None) self.assertEqual(result, export_in_memory) def test_export_to_csv_fobj(self): # TODO: may test with codecs.open passing an encoding # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) rows.export_to_csv(utils.table, temp.file) table = rows.import_from_csv(temp.name) self.assert_table_equal(table, utils.table) def test_issue_168(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) table = rows.Table(fields=OrderedDict([("jsoncolumn", rows.fields.JSONField)])) table.append({"jsoncolumn": '{"python": 42}'}) rows.export_to_csv(table, filename) table2 = rows.import_from_csv(filename) self.assert_table_equal(table, table2) def test_quotes(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) table = rows.Table( fields=OrderedDict( [ ("field_1", rows.fields.TextField), ("field_2", rows.fields.TextField), ("field_3", rows.fields.TextField), ("field_4", rows.fields.TextField), ] ) ) table.append( { "field_1": '"quotes"', "field_2": 'test "quotes"', "field_3": '"quotes" test', "field_4": 'test "quotes" test', } ) # we need this line row since `"quotes"` on `field_1` could be # `JSONField` or `TextField` table.append( { "field_1": "noquotes", "field_2": 'test "quotes"', "field_3": '"quotes" test', "field_4": 'test "quotes" test', } ) rows.export_to_csv(table, filename) table2 = rows.import_from_csv(filename) self.assert_table_equal(table, table2) def test_export_to_csv_accepts_dialect(self): result_1 = rows.export_to_csv(utils.table, dialect=csv.excel_tab) result_2 = rows.export_to_csv(utils.table, dialect=csv.excel) self.assertEqual(result_1.replace(b"\t", b","), result_2) def test_export_callback(self): table = rows.import_from_dicts([{"id": number} for number in range(10)]) myfunc = mock.Mock() rows.export_to_csv(table, callback=myfunc, batch_size=3) self.assertEqual(myfunc.call_count, 4) self.assertEqual([x[0][0] for x in myfunc.call_args_list], [3, 6, 9, 10]) def test_import_field_limit(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) table = rows.import_from_dicts([{"f1": "a" * 132000}]) rows.export_to_csv(table, filename) # The following line must not raise the exception: # `_csv.Error: field larger than field limit (131072)` new = rows.import_from_csv(filename) rows-0.4.1/tests/tests_plugin_dicts.py000066400000000000000000000114321343135453400201360ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import random import string import unittest from collections import OrderedDict import mock import rows import rows.plugins.dicts import tests.utils as utils class PluginDictTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "dicts" data = [ {"name": "Álvaro", "ids": 123, "number": 3}, {"name": "Test", "ids": "456"}, # missing 'number', 'ids' as str {"name": "Python", "ids": "123, 456", "other": 3.14}, ] def test_imports(self): self.assertIs(rows.import_from_dicts, rows.plugins.dicts.import_from_dicts) self.assertIs(rows.export_to_dicts, rows.plugins.dicts.export_to_dicts) @mock.patch("rows.plugins.dicts.create_table") def test_import_from_dicts_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"some_key": 123, "other": 456} result = rows.import_from_dicts(self.data, **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = {"imported_from": "dicts"} kwargs["samples"] = None self.assertEqual(call[1], kwargs) def test_import_from_dicts_return_desired_data(self): table = rows.import_from_dicts(self.data) self.assertEqual(len(table), 3) self.assertEqual(len(table.fields), 4) self.assertEqual( set(table.field_names), set(["ids", "name", "number", "other"]) ) self.assertEqual(table.fields["name"], rows.fields.TextField) self.assertEqual(table.fields["ids"], rows.fields.TextField) self.assertEqual(table.fields["number"], rows.fields.IntegerField) self.assertEqual(table.fields["other"], rows.fields.FloatField) self.assertEqual(table[0].name, "Álvaro") self.assertEqual(table[0].ids, "123") self.assertEqual(table[0].number, 3) self.assertEqual(table[0].other, None) self.assertEqual(table[1].name, "Test") self.assertEqual(table[1].ids, "456") self.assertEqual(table[1].number, None) self.assertEqual(table[1].other, None) self.assertEqual(table[2].name, "Python") self.assertEqual(table[2].ids, "123, 456") self.assertEqual(table[2].number, None) self.assertEqual(table[2].other, 3.14) def test_import_from_dicts_accepts_generator(self): max_size = 1000 samples = 200 generator = utils.LazyDictGenerator(max_size) datagen = iter(generator) table = rows.import_from_dicts(datagen, lazy=True, samples=samples) # `create_table` will consume the whole generator self.assertEqual(generator.last, max_size - 1) data = list(table) self.assertTrue(len(data), max_size) self.assertEqual(generator.last, max_size - 1) def test_import_from_dicts_maintains_header_order(self): headers = list(string.ascii_lowercase) random.shuffle(headers) data = [ OrderedDict([(header, 1) for header in headers]), OrderedDict([(header, 2) for header in headers]), OrderedDict([(header, 3) for header in headers]), OrderedDict([(header, 4) for header in headers]), OrderedDict([(header, 5) for header in headers]), ] table = rows.import_from_dicts(data) self.assertEqual(table.field_names, headers) def test_export_to_dicts(self): table = rows.import_from_dicts(self.data) result = rows.export_to_dicts(table) full_data = [ {"name": "Álvaro", "ids": "123", "number": 3, "other": None}, {"name": "Test", "ids": "456", "number": None, "other": None}, {"name": "Python", "ids": "123, 456", "number": None, "other": 3.14}, ] self.assertEqual(len(result), len(table)) for expected, actual in zip(full_data, result): self.assertDictEqual(expected, actual) rows-0.4.1/tests/tests_plugin_html.py000066400000000000000000000455231343135453400200040ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import tempfile import unittest from collections import OrderedDict from io import BytesIO from textwrap import dedent import mock import rows import rows.plugins.plugin_html import tests.utils as utils # TODO: test unescape # TODO: test colspan # TODO: test rowspan # TODO: test more nested tables # URL = 'https://finance.yahoo.com/q;_ylt=At7WXTIEGzyrIHemoSMI7I.iuYdG;_ylu=X3oDMTBxdGVyNzJxBHNlYwNVSCAzIERlc2t0b3AgU2VhcmNoIDEx;_ylg=X3oDMTByaDM4cG9kBGxhbmcDZW4tVVMEcHQDMgR0ZXN0AzUxMjAxMw--;_ylv=3?s=GOOG&uhb=uhb2&type=2button&fr=uh3_finance_web_gs' # URL_2 = 'http://www.rio.rj.gov.br/dlstatic/10112/2147539/DLFE-237833.htm/paginanova2.0.0.0._B.htm' def cleanup_lines(lines): return [line.strip() for line in lines.strip().split("\n") if line.strip()] class PluginHtmlTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "html" file_extension = "html" filename = "tests/data/all-field-types.html" encoding = "utf-8" assert_meta_encoding = True def test_imports(self): self.assertIs(rows.import_from_html, rows.plugins.plugin_html.import_from_html) self.assertIs(rows.export_to_html, rows.plugins.plugin_html.export_to_html) def test_import_from_html_filename(self): table = rows.import_from_html(self.filename, encoding=self.encoding) self.assert_table_equal(table, utils.table) expected_meta = { "imported_from": "html", "filename": self.filename, "encoding": self.encoding, } self.assertEqual(table.meta, expected_meta) def test_import_from_html_fobj(self): # TODO: may test with codecs.open passing an encoding with open(self.filename, mode="rb") as fobj: table = rows.import_from_html(fobj, encoding=self.encoding) self.assert_table_equal(table, utils.table) expected_meta = { "imported_from": "html", "filename": self.filename, "encoding": self.encoding, } self.assertEqual(table.meta, expected_meta) @mock.patch("rows.plugins.plugin_html.create_table") def test_import_from_html_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"some_key": 123, "other": 456} result = rows.import_from_html(self.filename, encoding="iso-8859-1", **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = { "imported_from": "html", "filename": self.filename, "encoding": "iso-8859-1", } self.assertEqual(call[1], kwargs) def test_export_to_html_filename(self): # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) rows.export_to_html(utils.table, temp.name) table = rows.import_from_html(temp.name) self.assert_table_equal(table, utils.table) def test_export_to_html_fobj(self): # TODO: may test with codecs.open passing an encoding # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False, mode="wb") self.files_to_delete.append(temp.name) rows.export_to_html(utils.table, temp.file) table = rows.import_from_html(temp.name) self.assert_table_equal(table, utils.table) @mock.patch("rows.plugins.plugin_html.serialize") def test_export_to_html_uses_serialize(self, mocked_serialize): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) kwargs = {"test": 123, "parameter": 3.14} mocked_serialize.return_value = iter([utils.table.fields.keys()]) rows.export_to_html(utils.table, temp.name, encoding="utf-8", **kwargs) self.assertTrue(mocked_serialize.called) self.assertEqual(mocked_serialize.call_count, 1) call = mocked_serialize.call_args self.assertEqual(call[0], (utils.table,)) self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.plugin_html.export_data") def test_export_to_html_uses_export_data(self, mocked_export_data): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) kwargs = {"test": 123, "parameter": 3.14, "encoding": "utf-8"} mocked_export_data.return_value = 42 result = rows.export_to_html(utils.table, temp.name, **kwargs) self.assertTrue(mocked_export_data.called) self.assertEqual(mocked_export_data.call_count, 1) self.assertEqual(result, 42) call = mocked_export_data.call_args self.assertEqual(call[0][0], temp.name) self.assertEqual(call[1], {"mode": "wb"}) def test_export_to_html_none(self): # TODO: may test with codecs.open passing an encoding # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False, mode="rb+") self.files_to_delete.append(temp.name) result = rows.export_to_html(utils.table) rows.export_to_html(utils.table, temp.file) temp.file.seek(0) self.assertEqual(temp.file.read(), result) def test_table_index(self): filename = "tests/data/simple-table.html" fobj = open(filename, mode="rb") table_1 = rows.import_from_html(fobj) self.assertEqual(set(table_1.fields.keys()), set(["t0r0c0", "t0r0c1"])) self.assertEqual(len(table_1), 1) self.assertEqual(table_1[0].t0r0c0, "t0r1c0") self.assertEqual(table_1[0].t0r0c1, "t0r1c1") fobj.seek(0) table_2 = rows.import_from_html(fobj, index=1) self.assertEqual(set(table_2.fields.keys()), set(["t1r0c0", "t1r0c1"])) self.assertEqual(len(table_2), 2) self.assertEqual(table_2[0].t1r0c0, "t1r1c0") self.assertEqual(table_2[0].t1r0c1, "t1r1c1") self.assertEqual(table_2[1].t1r0c0, "t1r2c0") self.assertEqual(table_2[1].t1r0c1, "t1r2c1") def test_table_thead_tbody(self): filename = "tests/data/table-thead-tbody.html" fobj = open(filename, mode="rb") table = rows.import_from_html(fobj) self.assertEqual(set(table.fields.keys()), set(["t1", "t2"])) self.assertEqual(len(table), 2) self.assertEqual(table[0].t1, "456") self.assertEqual(table[0].t2, "123") self.assertEqual(table[1].t1, "qqq") self.assertEqual(table[1].t2, "aaa") def test_nested_tables_outer(self): filename = "tests/data/nested-table.html" fobj = open(filename, mode="rb") table = rows.import_from_html(fobj) self.assertEqual( set(table.fields.keys()), set(["t00r0c0", "t00r0c1", "t00r0c2"]) ) self.assertEqual(len(table), 3) self.assertEqual(table[0].t00r0c0, "t0,0r1c0") self.assertEqual(table[0].t00r0c1, "t0,0r1c1") self.assertEqual(table[0].t00r0c2, "t0,0r1c2") # if there are nested tables, the inner ones will be represented as # strings (each

...
inside it) inner_table = ( "t0,1r0c0 t0,1r0c1 t0,1r1c0 t0,1r1c1 t0,1r2c0 " "t0,1r2c1 t0,2r0c0 t0,2r0c1 t0,2r1c0 t0,2r1c1 " "t0,1r3c1 t0,1r4c0 t0,1r4c1 t0,1r5c0 t0,1r5c1" ) self.assertEqual(table[1].t00r0c0, "t0,0r2c0") self.assertEqual(table[1].t00r0c1, inner_table) self.assertEqual(table[1].t00r0c2, "t0,0r2c2") self.assertEqual(table[2].t00r0c0, "t0,0r3c0") self.assertEqual(table[2].t00r0c1, "t0,0r3c1") self.assertEqual(table[2].t00r0c2, "t0,0r3c2") def test_nested_tables_first_inner(self): filename = "tests/data/nested-table.html" fobj = open(filename, mode="rb") table = rows.import_from_html(fobj, index=1) self.assertEqual(set(table.fields.keys()), set(["t01r0c0", "t01r0c1"])) self.assertEqual(len(table), 5) self.assertEqual(table[0].t01r0c0, "t0,1r1c0") self.assertEqual(table[0].t01r0c1, "t0,1r1c1") self.assertEqual(table[1].t01r0c0, "t0,1r2c0") self.assertEqual(table[1].t01r0c1, "t0,1r2c1") inner_table = "t0,2r0c0 t0,2r0c1 t0,2r1c0 t0,2r1c1" self.assertEqual(table[2].t01r0c0, inner_table) self.assertEqual(table[2].t01r0c1, "t0,1r3c1") self.assertEqual(table[3].t01r0c0, "t0,1r4c0") self.assertEqual(table[3].t01r0c1, "t0,1r4c1") self.assertEqual(table[4].t01r0c0, "t0,1r5c0") self.assertEqual(table[4].t01r0c1, "t0,1r5c1") def test_nested_tables_second_inner(self): filename = "tests/data/nested-table.html" fobj = open(filename, mode="rb") table = rows.import_from_html(fobj, index=2) self.assertEqual(set(table.fields.keys()), set(["t02r0c0", "t02r0c1"])) self.assertEqual(len(table), 1) self.assertEqual(table[0].t02r0c0, "t0,2r1c0") self.assertEqual(table[0].t02r0c1, "t0,2r1c1") def test_preserve_html(self): filename = "tests/data/nested-table.html" fobj = open(filename, mode="rb") table = rows.import_from_html(fobj, preserve_html=True) # TODO: test without passing encoding expected_data = [ "
", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "
t0,1r0c0 t0,1r0c1
t0,1r1c0 t0,1r1c1
t0,1r2c0 t0,1r2c1
", "", "", "", "", "", "", "", "", "", "
t0,2r0c0 t0,2r0c1
t0,2r1c0 t0,2r1c1
", "
t0,1r3c1
t0,1r4c0 t0,1r4c1
t0,1r5c0 t0,1r5c1
", ] self.assertEqual(cleanup_lines(table[1].t00r0c1), expected_data) def test_preserve_html_None(self): html = dedent( """
f1 f2 f3
r0f1 r0f2 r0f3
""" ).encode("utf-8") table = rows.import_from_html( BytesIO(html), encoding="utf-8", preserve_html=True ) table2 = rows.import_from_html( BytesIO(html), encoding="utf-8", preserve_html=False ) self.assertEqual(table[0].f1, "r0f1") self.assertEqual(table[0].f2, "r0f2") self.assertEqual(table[0].f3, "r0f3") @mock.patch("rows.plugins.plugin_html.create_table") def test_preserve_html_and_not_skip_header(self, mocked_create_table): filename = "tests/data/table-with-sections.html" # If `import_from_html` needs to identify field names, then it # should not preserve HTML inside first row table_1 = rows.import_from_html(filename, index=1, preserve_html=True) call_args = mocked_create_table.call_args_list.pop() data = list(call_args[0][0]) kwargs = call_args[1] self.assertEqual(kwargs.get("fields", None), None) self.assertEqual(len(data), 6) self.assertNotIn("<", data[0][1]) self.assertNotIn(">", data[0][1]) for row in data[1:]: # Second field has HTML self.assertIn("<", row[1]) self.assertIn(">", row[1]) # If we provide fields and ask to preserve HTML and to don't skip # header then it should strip HTML from every row fields = OrderedDict( [ ("first", rows.fields.TextField), ("second", rows.fields.TextField), ("third", rows.fields.TextField), ("fourth", rows.fields.TextField), ] ) table_2 = rows.import_from_html( filename, index=1, fields=fields, preserve_html=True, skip_header=False ) call_args = mocked_create_table.call_args_list.pop() data = list(call_args[0][0]) kwargs = call_args[1] self.assertEqual(kwargs.get("fields", None), fields) self.assertEqual(len(data), 6) for row in data: # Second field has HTML and should not be stripped self.assertIn("<", row[1]) self.assertIn(">", row[1]) def test_ignore_colspan(self): filename = "tests/data/colspan-table.html" fobj = open(filename, mode="rb") table = rows.import_from_html(fobj, ignore_colspan=True) self.assertEqual(set(table.fields.keys()), set(["field1", "field2"])) self.assertEqual(len(table), 2) self.assertEqual(table[0].field1, "row1field1") self.assertEqual(table[0].field2, "row1field2") self.assertEqual(table[1].field1, "row2field1") self.assertEqual(table[1].field2, "row2field2") fobj = open(filename, mode="rb") table = rows.import_from_html(fobj, ignore_colspan=False) self.assertEquals(list(table.fields.keys()), ["huge_title", "field_1"]) self.assertEquals(len(table), 3) expected_data = [ ["field1", "field2"], ["row1field1", "row1field2"], ["row2field1", "row2field2"], ] for row_data, table_row in zip(expected_data, table): self.assertEqual(row_data, [table_row.huge_title, table_row.field_1]) def test_extract_properties(self): filename = "tests/data/properties-table.html" fobj = open(filename, mode="rb") table = rows.import_from_html(fobj, properties=True) self.assertEqual(table.field_names, ["field1", "field2", "properties"]) self.assertEqual( table.field_types, [rows.fields.TextField, rows.fields.TextField, rows.fields.JSONField], ) properties_1 = {"class": "some-class another-class", "data-test": "value"} properties_2 = {"class": "css-class", "data-test": "value2"} self.assertEqual(len(table), 2) self.assertEqual(table[0].field1, "row1field1") self.assertEqual(table[0].field2, "row1field2") self.assertEqual(table[0].properties, properties_1) self.assertEqual(table[1].field1, "row2field1") self.assertEqual(table[1].field2, "row2field2") self.assertEqual(table[1].properties, properties_2) def test_issue_168(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) table = rows.Table(fields=OrderedDict([("jsoncolumn", rows.fields.JSONField)])) table.append({"jsoncolumn": '{"python": 42}'}) rows.export_to_html(table, filename) table2 = rows.import_from_html(filename) self.assert_table_equal(table, table2) def test_export_to_html_unescaped_content(self): table = rows.Table( fields=OrderedDict([("unescaped_content", rows.fields.TextField)]) ) table.append({"unescaped_content": "<&>"}) output = rows.export_to_html(table) self.assertIn(b" <&> ", output) class PluginHtmlUtilsTestCase(unittest.TestCase): html = ' some text other' def test_tag_to_dict(self): result = rows.plugins.plugin_html.tag_to_dict(self.html) expected = {"text": " some text ", "class": "some-class", "href": "some-url"} self.assertEqual(result, expected) def test_extract_node_text(self): from lxml.html import document_fromstring html = """""" node = document_fromstring(html) desired_node = node.xpath("//a")[0] expected = "bold link bold text" result = rows.plugins.plugin_html._extract_node_text(desired_node) self.assertEqual(result, expected) def test_extract_text_from_html(self): expected = "some text other" result = rows.plugins.plugin_html.extract_text(self.html) self.assertEqual(result, expected) # Real HTML from # html = """ 0 ( 0 %) """ expected = "0 ( 0 %)" result = rows.plugins.plugin_html.extract_text(html) self.assertEqual(result, expected) # test HTML unescape html = "Álvaro & Python" expected = "Álvaro & Python" result = rows.plugins.plugin_html.extract_text(html) self.assertEqual(result, expected) def test_extract_links_from_html(self): # Real HTML from # html = """ abcl [1] [2] """ expected = [ "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=608466", "http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=701712", ] result = rows.plugins.plugin_html.extract_links(html) self.assertEqual(result, expected) rows-0.4.1/tests/tests_plugin_json.py000066400000000000000000000202321343135453400177770ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import json import tempfile import unittest from collections import Counter, OrderedDict, defaultdict import mock import six import rows import tests.utils as utils class PluginJsonTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "json" file_extension = "json" filename = "tests/data/all-field-types.json" encoding = "utf-8" assert_meta_encoding = True def test_imports(self): self.assertIs(rows.import_from_json, rows.plugins.plugin_json.import_from_json) self.assertIs(rows.export_to_json, rows.plugins.plugin_json.export_to_json) @mock.patch("rows.plugins.plugin_json.create_table") def test_import_from_json_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"some_key": 123, "other": 456} result = rows.import_from_json(self.filename, encoding=self.encoding, **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = { "imported_from": "json", "filename": self.filename, "encoding": self.encoding, } self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.plugin_json.create_table") def test_import_from_json_retrieve_desired_data(self, mocked_create_table): mocked_create_table.return_value = 42 # import using filename rows.import_from_json(self.filename) call_args = mocked_create_table.call_args_list[0] self.assert_create_table_data(call_args, field_ordering=False) # import using fobj with open(self.filename) as fobj: rows.import_from_json(fobj) call_args = mocked_create_table.call_args_list[1] self.assert_create_table_data(call_args, field_ordering=False) @mock.patch("rows.plugins.plugin_json.create_table") def test_import_from_json_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"some_key": 123, "other": 456} encoding = "iso-8859-15" result = rows.import_from_json(self.filename, encoding=encoding, **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = { "imported_from": "json", "filename": self.filename, "encoding": encoding, } self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.plugin_json.prepare_to_export") def test_export_to_json_uses_prepare_to_export(self, mocked_prepare_to_export): temp = tempfile.NamedTemporaryFile(delete=False, mode="wb") self.files_to_delete.append(temp.name) kwargs = {"test": 123, "parameter": 3.14} mocked_prepare_to_export.return_value = iter([utils.table.fields.keys()]) rows.export_to_json(utils.table, temp.name, **kwargs) self.assertTrue(mocked_prepare_to_export.called) self.assertEqual(mocked_prepare_to_export.call_count, 1) call = mocked_prepare_to_export.call_args self.assertEqual(call[0], (utils.table,)) self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.plugin_json.export_data") def test_export_to_json_uses_export_data(self, mocked_export_data): temp = tempfile.NamedTemporaryFile(delete=False, mode="wb") self.files_to_delete.append(temp.name) kwargs = {"test": 123, "parameter": 3.14} mocked_export_data.return_value = 42 result = rows.export_to_json(utils.table, temp.name, **kwargs) self.assertTrue(mocked_export_data.called) self.assertEqual(mocked_export_data.call_count, 1) self.assertEqual(result, 42) call = mocked_export_data.call_args self.assertEqual(call[0][0], temp.name) self.assertEqual(call[1], {"mode": "wb"}) def test_export_to_json_filename(self): # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False, mode="wb") self.files_to_delete.append(temp.name) rows.export_to_json(utils.table, temp.name) table = rows.import_from_json(temp.name) self.assert_table_equal(table, utils.table) def test_export_to_json_fobj(self): # TODO: may test with codecs.open passing an encoding # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False, mode="wb") self.files_to_delete.append(temp.name) rows.export_to_json(utils.table, temp.file) table = rows.import_from_json(temp.name) self.assert_table_equal(table, utils.table) def test_export_to_json_filename_save_data_in_correct_format(self): temp = tempfile.NamedTemporaryFile(delete=False, mode="wb") self.files_to_delete.append(temp.name) rows.export_to_json(utils.table, temp.name) with open(temp.name) as fobj: imported_json = json.load(fobj) COLUMN_TYPE = { "float_column": float, "decimal_column": float, "bool_column": bool, "integer_column": int, "date_column": six.text_type, "datetime_column": six.text_type, "percent_column": six.text_type, "unicode_column": six.text_type, } field_types = defaultdict(list) for row in imported_json: for field_name, value in row.items(): field_types[field_name].append(type(value)) # We test if the JSON was created serializing all the fields correctly # (some as native JSON values, like int and float) and others needed to # be serialized, like date, datetime etc. for field_name, value_types in field_types.items(): if field_name != "unicode_column": self.assertEqual( Counter(value_types), Counter({type(None): 1, COLUMN_TYPE[field_name]: 6}), ) else: self.assertEqual( Counter(value_types), Counter({COLUMN_TYPE[field_name]: 7}) ) def test_export_to_json_indent(self): temp = tempfile.NamedTemporaryFile(delete=False, mode="rb+") self.files_to_delete.append(temp.name) table = rows.Table(fields=utils.table.fields) table.append(utils.table[0]._asdict()) rows.export_to_json(table, temp.name, indent=2) temp.file.seek(0) result = temp.file.read().strip().replace(b"\r\n", b"\n").splitlines() self.assertEqual(result[0], b"[") self.assertEqual(result[1], b" {") for line in result[2:-2]: self.assertTrue(line.startswith(b" ")) self.assertEqual(result[-2], b" }") self.assertEqual(result[-1], b"]") def test_issue_168(self): temp = tempfile.NamedTemporaryFile(delete=False, mode="wb") filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) table = rows.Table(fields=OrderedDict([("jsoncolumn", rows.fields.JSONField)])) table.append({"jsoncolumn": '{"python": 42}'}) rows.export_to_json(table, filename) table2 = rows.import_from_json(filename) self.assert_table_equal(table, table2) rows-0.4.1/tests/tests_plugin_ods.py000066400000000000000000000054601343135453400176210ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import unittest from decimal import Decimal import mock import rows import rows.plugins.ods import tests.utils as utils class PluginOdsTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "ods" filename = "tests/data/all-field-types.ods" assert_meta_encoding = False def test_imports(self): self.assertIs(rows.import_from_ods, rows.plugins.ods.import_from_ods) @mock.patch("rows.plugins.ods.create_table") def test_import_from_ods_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"encoding": "test", "some_key": 123, "other": 456} result = rows.import_from_ods(self.filename, **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = {"imported_from": "ods", "filename": self.filename} self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.ods.create_table") def test_import_from_ods_retrieve_desired_data(self, mocked_create_table): mocked_create_table.return_value = 42 # import using filename rows.import_from_ods(self.filename) call_args = mocked_create_table.call_args_list[0] self.assert_create_table_data(call_args) # import using fobj with open(self.filename, "rb") as fobj: rows.import_from_ods(fobj) call_args = mocked_create_table.call_args_list[1] self.assert_create_table_data(call_args) def test_issue_290_one_hundred_read_as_1(self): result = rows.import_from_ods("tests/data/text_in_percent_cell.ods") # As this test is written, file numeric file contents on first column are # 100%, 23.20%, 1.00%, 10.00%, 100.00% assert result[0][0] == Decimal("1") assert result[2][0] == Decimal("0.01") assert result[3][0] == Decimal("0.1") assert result[4][0] == Decimal("1") rows-0.4.1/tests/tests_plugin_parquet.py000066400000000000000000000132401343135453400205100ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import unittest from collections import OrderedDict import mock import rows DATA = [ ["nation_key", "name", "region_key", "comment_col"], [0, b"ALGERIA", 0, b" haggle. carefully final deposits detect slyly agai"], [ 1, b"ARGENTINA", 1, b"al foxes promise slyly according to the regular accounts. bold requests alon", ], [ 2, b"BRAZIL", 1, b"y alongside of the pending deposits. carefully special packages are about the ironic forges. slyly special ", ], [ 3, b"CANADA", 1, b"eas hang ironic, silent packages. slyly regular packages are furiously over the tithes. fluffily bold", ], [ 4, b"EGYPT", 4, b"y above the carefully unusual theodolites. final dugouts are quickly across the furiously regular d", ], [5, b"ETHIOPIA", 0, b"ven packages wake quickly. regu"], [6, b"FRANCE", 3, b"refully final requests. regular, ironi"], [7, b"GERMANY", 3, b"l platelets. regular accounts x-ray: unusual, regular acco"], [ 8, b"INDIA", 2, b"ss excuses cajole slyly across the packages. deposits print aroun", ], [ 9, b"INDONESIA", 2, b" slyly express asymptotes. regular deposits haggle slyly. carefully ironic hockey players sleep blithely. carefull", ], [10, b"IRAN", 4, b"efully alongside of the slyly final dependencies. "], [ 11, b"IRAQ", 4, b"nic deposits boost atop the quickly final requests? quickly regula", ], [12, b"JAPAN", 2, b"ously. final, express gifts cajole a"], [13, b"JORDAN", 4, b"ic deposits are blithely about the carefully regular pa"], [ 14, b"KENYA", 0, b" pending excuses haggle furiously deposits. pending, express pinto beans wake fluffily past t", ], [ 15, b"MOROCCO", 0, b"rns. blithely bold courts among the closely regular packages use furiously bold platelets?", ], [16, b"MOZAMBIQUE", 0, b"s. ironic, unusual asymptotes wake blithely r"], [ 17, b"PERU", 1, b"platelets. blithely pending dependencies use fluffily across the even pinto beans. carefully silent accoun", ], [ 18, b"CHINA", 2, b"c dependencies. furiously express notornis sleep slyly regular accounts. ideas sleep. depos", ], [ 19, b"ROMANIA", 3, b"ular asymptotes are about the furious multipliers. express dependencies nag above the ironically ironic account", ], [ 20, b"SAUDI ARABIA", 4, b"ts. silent requests haggle. closely express packages sleep across the blithely", ], [21, b"VIETNAM", 2, b"hely enticingly express accounts. even, final "], [ 22, b"RUSSIA", 3, b" requests against the platelets use never according to the quickly regular pint", ], [ 23, b"UNITED KINGDOM", 3, b"eans boost carefully special requests. accounts are. carefull", ], [ 24, b"UNITED STATES", 1, b"y final packages. slow foxes cajole quickly. quickly silent platelets breach ironic accounts. unusual pinto be", ], ] class PluginParquetTestCase(unittest.TestCase): plugin_name = "parquet" filename = "tests/data/nation.dict.parquet" def test_imports(self): self.assertIs( rows.import_from_parquet, rows.plugins.plugin_parquet.import_from_parquet ) @mock.patch("rows.plugins.plugin_parquet.create_table") def test_import_from_parquet_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"some_key": 123, "other": 456} result = rows.import_from_parquet(self.filename, **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["force_types"] = OrderedDict( [ ("nation_key", rows.fields.IntegerField), ("name", rows.fields.BinaryField), ("region_key", rows.fields.IntegerField), ("comment_col", rows.fields.BinaryField), ] ) kwargs["meta"] = {"imported_from": "parquet", "filename": self.filename} self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.plugin_parquet.create_table") def test_import_from_parquet_retrieve_desired_data(self, mocked_create_table): mocked_create_table.return_value = 42 # import using filename rows.import_from_parquet(self.filename) args = mocked_create_table.call_args[0][0] self.assertEqual(args, DATA) # TODO: test all supported field types rows-0.4.1/tests/tests_plugin_pdf.py000066400000000000000000000072061343135453400176050ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2018 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import re import unittest import rows import rows.plugins.plugin_pdf as pdf import tests.utils as utils class PDFTestCase(utils.RowsTestMixIn): backend = "" file_extension = "pdf" plugin_name = "pdf" def test_imports(self): self.assertIs(rows.import_from_pdf, pdf.import_from_pdf) def test_real_data_1(self): filename = "tests/data/balneabilidade-26-2010" result = rows.import_from_pdf(filename + ".pdf", backend=self.backend) expected = rows.import_from_csv(filename + ".csv") self.assertEqual(list(expected), list(result)) def test_real_data_2(self): filename = "tests/data/milho-safra-2017" result = rows.import_from_pdf( filename + ".pdf", backend=self.backend, starts_after=re.compile("MILHO SAFRA 16/17: ACOMPANHAMENTO DE .*"), ends_before="*Variação em pontos percentuais.", ) expected = rows.import_from_csv(filename + ".csv") self.assertEqual(list(expected), list(result)) def test_real_data_3(self): filename = "tests/data/eleicoes-tcesp-161-162.pdf" expected1 = "tests/data/expected-eleicoes-tcesp-161-{}.csv".format(self.backend) expected2 = "tests/data/expected-eleicoes-tcesp-162-{}.csv".format(self.backend) begin = re.compile("Documento gerado em.*") end = re.compile("Página: [0-9]+ de.*") result = rows.import_from_pdf( filename, backend=self.backend, page_numbers=(1,), starts_after=begin, ends_before=end, algorithm="header-position", ) expected = rows.import_from_csv(expected1) self.assertEqual(list(expected), list(result)) result = rows.import_from_pdf( filename, backend=self.backend, page_numbers=(2,), starts_after=begin, ends_before=end, algorithm="header-position", ) expected = rows.import_from_csv(expected2) self.assertEqual(list(expected), list(result)) class PyMuPDFTestCase(PDFTestCase, unittest.TestCase): backend = "pymupdf" # TODO: add test using rects-boundaries algorithm (will need to implement # RectObject extraction on this backend) class PDFMinerSixTestCase(PDFTestCase, unittest.TestCase): backend = "pdfminer.six" def test_rects_boundaries(self): filename = "tests/data/ibama-autuacao-amazonas-2010-pag2" result = rows.import_from_pdf( filename + ".pdf", backend=self.backend, starts_after=re.compile("DIRETORIA DE PROTE.*"), ends_before=re.compile("Pag [0-9]+/[0-9]+"), algorithm="rects-boundaries", ) expected = rows.import_from_csv(filename + ".csv") self.assertEqual(list(expected), list(result)) rows-0.4.1/tests/tests_plugin_postgresql.py000066400000000000000000000205061343135453400212350ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2018 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import os import unittest import mock import six import rows import rows.plugins.postgresql import rows.plugins.utils import tests.utils as utils from rows import fields from rows.plugins.postgresql import pgconnect class PluginPostgreSQLTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "postgresql" assert_meta_encoding = False override_fields = { "bool_column": fields.BoolField, "percent_column": fields.FloatField, } @classmethod def setUpClass(cls): cls.uri = os.environ["POSTGRESQL_URI"] cls.meta = {"imported_from": "postgresql", "source": cls.uri} def get_table_names(self): connection = pgconnect(self.uri) cursor = connection.cursor() cursor.execute(rows.plugins.postgresql.SQL_TABLE_NAMES) header = [item[0] for item in cursor.description] result = [dict(zip(header, row))["tablename"] for row in cursor.fetchall()] cursor.close() connection.close() return result def tearDown(self): connection = pgconnect(self.uri) for table in self.get_table_names(): if table.startswith("rows_"): cursor = connection.cursor() cursor.execute("DROP TABLE " + table) cursor.close() connection.commit() connection.close() def test_imports(self): self.assertIs( rows.import_from_postgresql, rows.plugins.postgresql.import_from_postgresql ) self.assertIs( rows.export_to_postgresql, rows.plugins.postgresql.export_to_postgresql ) @mock.patch("rows.plugins.postgresql.create_table") def test_import_from_postgresql_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"encoding": "test", "some_key": 123, "other": 456} rows.export_to_postgresql(utils.table, self.uri, table_name="rows_1") result = rows.import_from_postgresql(self.uri, table_name="rows_1", **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = self.meta self.assertEqual(call[1], kwargs) @unittest.skipIf(six.PY2, "psycopg2 on Python2 returns binary, skippging test") @mock.patch("rows.plugins.postgresql.create_table") def test_import_from_postgresql_retrieve_desired_data(self, mocked_create_table): mocked_create_table.return_value = 42 rows.export_to_postgresql( utils.table, self.uri, close_connection=True, table_name="rows_2" ) # import using uri table_1 = rows.import_from_postgresql( self.uri, close_connection=True, table_name="rows_2" ) call_args = mocked_create_table.call_args_list[0] self.assert_create_table_data(call_args, expected_meta=self.meta) # import using connection connection = pgconnect(self.uri) table_2 = rows.import_from_postgresql( connection, close_connection=False, table_name="rows_2" ) call_args = mocked_create_table.call_args_list[1] meta = self.meta.copy() meta["source"] = connection self.assert_create_table_data(call_args, expected_meta=meta) connection.close() def test_postgresql_injection(self): with self.assertRaises(ValueError): rows.import_from_postgresql( self.uri, table_name=('table1","postgresql_master') ) with self.assertRaises(ValueError): rows.export_to_postgresql( utils.table, self.uri, table_name='table1", "postgresql_master' ) @unittest.skipIf(six.PY2, "psycopg2 on Python2 returns binary, skippging test") def test_export_to_postgresql_uri(self): rows.export_to_postgresql(utils.table, self.uri, table_name="rows_3") table = rows.import_from_postgresql(self.uri, table_name="rows_3") self.assert_table_equal(table, utils.table) @unittest.skipIf(six.PY2, "psycopg2 on Python2 returns binary, skippging test") def test_export_to_postgresql_connection(self): connection = pgconnect(self.uri) rows.export_to_postgresql( utils.table, connection, close_connection=True, table_name="rows_4" ) table = rows.import_from_postgresql(self.uri, table_name="rows_4") self.assert_table_equal(table, utils.table) @unittest.skipIf(six.PY2, "psycopg2 on Python2 returns binary, skippging test") def test_export_to_postgresql_create_unique_table_name(self): first_table = utils.table second_table = utils.table + utils.table table_names_before = self.get_table_names() rows.export_to_postgresql( first_table, self.uri, table_name_format="rows_{index}" ) table_names_after = self.get_table_names() rows.export_to_postgresql( second_table, self.uri, table_name_format="rows_{index}" ) table_names_final = self.get_table_names() diff_1 = list(set(table_names_after) - set(table_names_before)) diff_2 = list(set(table_names_final) - set(table_names_after)) self.assertEqual(len(diff_1), 1) self.assertEqual(len(diff_2), 1) new_table_1 = diff_1[0] new_table_2 = diff_2[0] result_first_table = rows.import_from_postgresql( self.uri, table_name=new_table_1 ) result_second_table = rows.import_from_postgresql( self.uri, table_name=new_table_2 ) self.assert_table_equal(result_first_table, first_table) self.assert_table_equal(result_second_table, second_table) @unittest.skipIf(six.PY2, "psycopg2 on Python2 returns binary, skippging test") def test_export_to_postgresql_forcing_table_name_appends_rows(self): repeat = 3 for _ in range(repeat): rows.export_to_postgresql(utils.table, self.uri, table_name="rows_7") expected_table = utils.table for _ in range(repeat - 1): expected_table += utils.table result_table = rows.import_from_postgresql(self.uri, table_name="rows_7") self.assertEqual(len(result_table), repeat * len(utils.table)) self.assert_table_equal(result_table, expected_table) @mock.patch("rows.plugins.postgresql.prepare_to_export") def test_export_to_postgresql_prepare_to_export(self, mocked_prepare_to_export): encoding = "iso-8859-15" kwargs = {"test": 123, "parameter": 3.14} mocked_prepare_to_export.return_value = iter( rows.plugins.utils.prepare_to_export(utils.table) ) rows.export_to_postgresql( utils.table, self.uri, encoding=encoding, table_name="rows_8", **kwargs ) self.assertTrue(mocked_prepare_to_export.called) self.assertEqual(mocked_prepare_to_export.call_count, 1) call = mocked_prepare_to_export.call_args self.assertEqual(call[0], (utils.table,)) kwargs["encoding"] = encoding self.assertEqual(call[1], kwargs) def test_import_from_postgresql_query_args(self): connection, table_name = rows.export_to_postgresql( utils.table, self.uri, close_connection=False, table_name="rows_9" ) table = rows.import_from_postgresql( connection, query="SELECT * FROM rows_9 WHERE float_column > %s", query_args=(3,), ) for row in table: self.assertTrue(row.float_column > 3) rows-0.4.1/tests/tests_plugin_sqlite.py000066400000000000000000000200561343135453400203330ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2018 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import sqlite3 import tempfile import unittest from collections import OrderedDict import mock import rows import rows.plugins.sqlite import rows.plugins.utils import tests.utils as utils from rows import fields class PluginSqliteTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "sqlite" file_extension = "sqlite" filename = "tests/data/all-field-types.sqlite" assert_meta_encoding = False override_fields = { # SQLite does not support "Decimal" type, so `PercentField` will be # identified as a float and also does not support "boolean" type, so # it's saved as integer internally "bool_column": fields.IntegerField, "percent_column": fields.FloatField, } def test_imports(self): self.assertIs(rows.import_from_sqlite, rows.plugins.sqlite.import_from_sqlite) self.assertIs(rows.export_to_sqlite, rows.plugins.sqlite.export_to_sqlite) @mock.patch("rows.plugins.sqlite.create_table") def test_import_from_sqlite_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"encoding": "test", "some_key": 123, "other": 456} result = rows.import_from_sqlite(self.filename, **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = {"imported_from": "sqlite", "filename": self.filename} self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.sqlite.create_table") def test_import_from_sqlite_retrieve_desired_data(self, mocked_create_table): mocked_create_table.return_value = 42 # import using filename rows.import_from_sqlite(self.filename) call_args = mocked_create_table.call_args_list[0] self.assert_create_table_data(call_args) # import using connection connection = sqlite3.connect(self.filename) rows.import_from_sqlite(connection) call_args = mocked_create_table.call_args_list[1] self.assert_create_table_data(call_args, filename=connection) connection.close() def test_sqlite_injection(self): connection = rows.export_to_sqlite(utils.table, ":memory:") with self.assertRaises(ValueError): rows.import_from_sqlite(connection, table_name='table1", "sqlite_master') with self.assertRaises(ValueError): rows.export_to_sqlite( utils.table, ":memory:", table_name='table1", "sqlite_master' ) def test_export_to_sqlite_filename(self): # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) rows.export_to_sqlite(utils.table, temp.name) table = rows.import_from_sqlite(temp.name) self.assert_table_equal(table, utils.table) def test_export_to_sqlite_connection(self): # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False, mode="wb") self.files_to_delete.append(temp.name) connection = sqlite3.connect(temp.name) rows.export_to_sqlite(utils.table, connection) connection.close() table = rows.import_from_sqlite(temp.name) self.assert_table_equal(table, utils.table) def test_export_to_sqlite_create_unique_table_name(self): # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) first_table = utils.table second_table = utils.table + utils.table rows.export_to_sqlite(first_table, temp.name) # table1 rows.export_to_sqlite(second_table, temp.name) # table2 result_first_table = rows.import_from_sqlite(temp.name, table_name="table1") result_second_table = rows.import_from_sqlite(temp.name, table_name="table2") self.assert_table_equal(result_first_table, first_table) self.assert_table_equal(result_second_table, second_table) def test_export_to_sqlite_forcing_table_name_appends_rows(self): # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) rows.export_to_sqlite(utils.table, temp.name, table_name="rows") rows.export_to_sqlite(utils.table, temp.name, table_name="rows") result_table = rows.import_from_sqlite(temp.name, table_name="rows") self.assertEqual(len(result_table), 2 * len(utils.table)) self.assert_table_equal(result_table, utils.table + utils.table) @mock.patch("rows.plugins.sqlite.prepare_to_export") def test_export_to_sqlite_uses_prepare_to_export(self, mocked_prepare_to_export): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) encoding = "iso-8859-15" kwargs = {"test": 123, "parameter": 3.14} mocked_prepare_to_export.return_value = iter( rows.plugins.utils.prepare_to_export(utils.table) ) rows.export_to_sqlite(utils.table, temp.name, encoding=encoding, **kwargs) self.assertTrue(mocked_prepare_to_export.called) self.assertEqual(mocked_prepare_to_export.call_count, 1) call = mocked_prepare_to_export.call_args self.assertEqual(call[0], (utils.table,)) kwargs["encoding"] = encoding self.assertEqual(call[1], kwargs) def test_issue_170(self): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) table = rows.Table( fields=OrderedDict( [ ("intvalue", rows.fields.IntegerField), ("floatvalue", rows.fields.FloatField), ] ) ) table.append({"intvalue": 42, "floatvalue": 3.14}) table.append({"intvalue": None, "floatvalue": None}) # should not raise an exception rows.export_to_sqlite(table, temp.name) def test_issue_168(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) table = rows.Table(fields=OrderedDict([("jsoncolumn", rows.fields.JSONField)])) table.append({"jsoncolumn": '{"python": 42}'}) rows.export_to_sqlite(table, filename) table2 = rows.import_from_sqlite(filename) self.assert_table_equal(table, table2) def test_import_from_sqlite_query_args(self): connection = rows.export_to_sqlite(utils.table, ":memory:") table = rows.import_from_sqlite( connection, query="SELECT * FROM table1 WHERE float_column > ?", query_args=(3,), ) for row in table: self.assertTrue(row.float_column > 3) def test_export_callback(self): table = rows.import_from_dicts([{"id": number} for number in range(10)]) myfunc = mock.Mock() rows.export_to_sqlite(table, ":memory:", callback=myfunc, batch_size=3) self.assertEqual(myfunc.call_count, 4) self.assertEqual( [(x[0][0], x[0][1]) for x in myfunc.call_args_list], [(3, 3), (3, 6), (3, 9), (1, 10)], ) rows-0.4.1/tests/tests_plugin_txt.py000066400000000000000000000225431343135453400176540ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import sys import tempfile import unittest from collections import OrderedDict import mock import six import rows import rows.plugins.txt import tests.utils as utils class PluginTxtTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "txt" file_extension = "txt" filename = "tests/data/all-field-types.txt" encoding = "utf-8" assert_meta_encoding = True def test_imports(self): self.assertIs(rows.import_from_txt, rows.plugins.txt.import_from_txt) self.assertIs(rows.export_to_txt, rows.plugins.txt.export_to_txt) @mock.patch("rows.plugins.txt.create_table") def test_import_from_txt_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"some_key": 123, "other": 456} result = rows.import_from_txt(self.filename, encoding=self.encoding, **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args called_data = call[1] meta = called_data.pop("meta") self.assertEqual(call[1], kwargs) expected_meta = { "imported_from": "txt", "filename": self.filename, "encoding": self.encoding, } for key in expected_meta: self.assertEqual(expected_meta[key], meta[key]) # test won't break if frame_style default changes in the future self.assertIn("frame_style", meta) @mock.patch("rows.plugins.txt.create_table") def test_import_from_txt_retrieve_desired_data(self, mocked_create_table): mocked_create_table.return_value = 42 # import using filename rows.import_from_txt(self.filename) call_args = mocked_create_table.call_args_list[0] self.assert_create_table_data(call_args) # import using fobj with open(self.filename, mode="rb") as fobj: rows.import_from_txt(fobj) call_args = mocked_create_table.call_args_list[1] self.assert_create_table_data(call_args) @mock.patch("rows.plugins.txt.serialize") def test_export_to_txt_uses_serialize(self, mocked_serialize): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) kwargs = {"test": 123, "parameter": 3.14} mocked_serialize.return_value = iter([utils.table.fields.keys()]) rows.export_to_txt(utils.table, temp.name, encoding=self.encoding, **kwargs) self.assertTrue(mocked_serialize.called) self.assertEqual(mocked_serialize.call_count, 1) call = mocked_serialize.call_args self.assertEqual(call[0], (utils.table,)) self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.txt.export_data") def test_export_to_txt_uses_export_data(self, mocked_export_data): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) kwargs = {"test": 123, "parameter": 3.14} mocked_export_data.return_value = 42 result = rows.export_to_txt( utils.table, temp.name, encoding=self.encoding, **kwargs ) self.assertTrue(mocked_export_data.called) self.assertEqual(mocked_export_data.call_count, 1) self.assertEqual(result, 42) call = mocked_export_data.call_args self.assertEqual(call[0][0], temp.name) self.assertEqual(call[1], {"mode": "wb"}) def test_export_to_txt_filename(self): # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) rows.export_to_txt(utils.table, temp.name, encoding="utf-8") table = rows.import_from_txt(temp.name, encoding="utf-8") self.assert_table_equal(table, utils.table) with open(temp.name, mode="rb") as fobj: content = fobj.read() self.assertEqual(content[-10:].count(b"\n"), 1) def test_export_to_txt_fobj(self): # TODO: may test with codecs.open passing an encoding # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) rows.export_to_txt(utils.table, temp.file, encoding="utf-8") table = rows.import_from_txt(temp.name, encoding="utf-8") self.assert_table_equal(table, utils.table) def test_issue_168(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) table = rows.Table(fields=OrderedDict([("jsoncolumn", rows.fields.JSONField)])) table.append({"jsoncolumn": '{"python": 42}'}) rows.export_to_txt(table, filename, encoding="utf-8") table2 = rows.import_from_txt(filename, encoding="utf-8") self.assert_table_equal(table, table2) def test_export_to_text_should_return_unicode(self): result = rows.export_to_txt(utils.table) self.assertEqual(type(result), six.text_type) def _test_export_to_txt_frame_style(self, frame_style, chars, positive=True): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) rows.export_to_txt( utils.table, temp.file, encoding="utf-8", frame_style=frame_style ) if sys.version_info.major < 3: from io import open as open_ else: open_ = open file_data = open_(temp.name, "rt", encoding="utf-8").read() for char in chars: if positive: self.assertIn(char, file_data) else: self.assertNotIn(char, file_data) def test_export_to_txt_frame_style_ASCII(self): self._test_export_to_txt_frame_style(frame_style="ASCII", chars="+-|") def test_export_to_txt_frame_style_single(self): self._test_export_to_txt_frame_style(frame_style="single", chars="│┤┐└┬├─┼┘┌") def test_export_to_txt_frame_style_double(self): self._test_export_to_txt_frame_style(frame_style="double", chars="╣║╗╝╚╔╩╦╠═╬") def test_export_to_txt_frame_style_none(self): self._test_export_to_txt_frame_style( frame_style="None", chars="|│┤┐└┬├─┼┘┌╣║╗╝╚╔╩╦╠═╬", positive=False ) @staticmethod def _reset_txt_plugin(): # The txt plugin makes (or can make use) of 'defaultdict's # and perform certain operations against their existing values. # Those existing values may change if certain txt operations # are performed in the same proccess. # Therefore, some tests have to run against # pristine copies of rows.plugins.txt try: from imp import reload except ImportError: pass import rows.plugins.txt original_txt_plugin = rows.plugins.txt reload(rows.plugins.txt) rows.import_from_txt = rows.plugins.txt.import_from_txt rows.export_to_txt = rows.plugins.txt.export_to_txt original_txt_plugin.FRAME_SENTINEL = rows.plugins.txt.FRAME_SENTINEL def _test_import_from_txt_works_with_custom_frame(self, frame_style): temp = tempfile.NamedTemporaryFile(delete=False) original_data = rows.import_from_txt(self.filename) rows.export_to_txt( utils.table, temp.file, encoding="utf-8", frame_style=frame_style ) self._reset_txt_plugin() new_data = rows.import_from_txt(temp.name) self.assertEqual( list(new_data), list(original_data), msg='failed to read information with frame_style == "{0}"'.format( frame_style ), ) def test_import_from_txt_works_with_ASCII_frame(self): self._test_import_from_txt_works_with_custom_frame("ASCII") def test_import_from_txt_works_with_unicode_single_frame(self): self._test_import_from_txt_works_with_custom_frame("single") def test_import_from_txt_works_with_unicode_double_frame(self): self._test_import_from_txt_works_with_custom_frame("double") def test_import_from_txt_works_with_no_frame(self): self._test_import_from_txt_works_with_custom_frame("None") def test__parse_col_positions(self): result1 = rows.plugins.txt._parse_col_positions("ASCII", "|----|----|") self.assertEqual(result1, [0, 5, 10]) result2 = rows.plugins.txt._parse_col_positions("None", " col1 col2 ") self.assertEqual(result2, [0, 7, 14]) rows-0.4.1/tests/tests_plugin_utils.py000066400000000000000000000404161343135453400201740ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2018 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import itertools import random import tempfile import types import unittest from collections import OrderedDict import mock import six import rows import rows.plugins.utils as plugins_utils import tests.utils as utils from rows import fields class GenericUtilsTestCase(unittest.TestCase): def test_slug(self): self.assertEqual(plugins_utils.slug(None), "") self.assertEqual(plugins_utils.slug("Álvaro Justen"), "alvaro_justen") self.assertEqual(plugins_utils.slug("Moe's Bar"), "moes_bar") self.assertEqual(plugins_utils.slug("-----te-----st------"), "te_st") # As in self.assertEqual( plugins_utils.slug('Query Occurrence"( % ),"First Seen'), "query_occurrence_first_seen", ) self.assertEqual(plugins_utils.slug(" ÁLVARO justen% "), "alvaro_justen") self.assertEqual(plugins_utils.slug(42), "42") def test_ipartition(self): iterable = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] result = plugins_utils.ipartition(iterable, 3) self.assertEqual(type(result), types.GeneratorType) self.assertEqual(list(result), [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]]) result = plugins_utils.ipartition(iterable, 2) self.assertEqual(type(result), types.GeneratorType) self.assertEqual(list(result), [[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]) def possible_field_names_errors(error_fields): error_fields = ['"{}"'.format(field_name) for field_name in error_fields] fields_permutations = itertools.permutations(error_fields, len(error_fields)) fields_permutations_str = [ ", ".join(field_names) for field_names in fields_permutations ] return [ "Invalid field names: {}".format(field_names) for field_names in fields_permutations_str ] class PluginUtilsTestCase(utils.RowsTestMixIn, unittest.TestCase): def test_create_table_skip_header(self): field_types = OrderedDict( [("integer", fields.IntegerField), ("string", fields.TextField)] ) data = [["1", "Álvaro"], ["2", "turicas"], ["3", "Justen"]] table_1 = plugins_utils.create_table(data, fields=field_types, skip_header=True) table_2 = plugins_utils.create_table( data, fields=field_types, skip_header=False ) self.assertEqual(field_types, table_1.fields) self.assertEqual(table_1.fields, table_2.fields) self.assertEqual(len(table_1), 2) self.assertEqual(len(table_2), 3) first_row = {"integer": 1, "string": "Álvaro"} second_row = {"integer": 2, "string": "turicas"} third_row = {"integer": 3, "string": "Justen"} self.assertEqual(dict(table_1[0]._asdict()), second_row) self.assertEqual(dict(table_2[0]._asdict()), first_row) self.assertEqual(dict(table_1[1]._asdict()), third_row) self.assertEqual(dict(table_2[1]._asdict()), second_row) self.assertEqual(dict(table_2[2]._asdict()), third_row) def test_create_table_import_fields(self): header = ["field1", "field2", "field3"] table_rows = [ ["1", 3.14, "Álvaro"], ["2", 2.71, "turicas"], ["3", 1.23, "Justen"], ] table = plugins_utils.create_table([header] + table_rows, import_fields=None) self.assertEqual(list(table.fields.keys()), header) self.assertEqual(table[0].field1, 1) self.assertEqual(table[0].field2, 3.14) self.assertEqual(table[0].field3, "Álvaro") import_fields = ["field3", "field2"] table = plugins_utils.create_table( [header] + table_rows, import_fields=import_fields ) self.assertEqual(list(table.fields.keys()), import_fields) self.assertEqual( table[0]._asdict(), OrderedDict([("field3", "Álvaro"), ("field2", 3.14)]) ) def test_create_table_import_fields_ordering(self): # From: https://github.com/turicas/rows/issues/239 data = [ ["intfield", "textfield", "floatfield"], [1, "str1", 1.2], [2, "str2", 2.3], [3, "str3", 3.4], ] # `fields` parameter on `create_table` must always be in the same order # as the data. fields = OrderedDict( [ ("intfield", rows.fields.IntegerField), ("textfield", rows.fields.TextField), ("floatfield", rows.fields.FloatField), ] ) # Regular case: no `import_fields` specified table = plugins_utils.create_table(data, fields=fields, skip_header=True) self.assertEqual(table.fields, fields) for row, row_data in zip(table, data[1:]): self.assertEqual(row_data, [row.intfield, row.textfield, row.floatfield]) # Special case: `import_fields` has different order from `fields` import_fields = ["textfield", "intfield"] table = plugins_utils.create_table( data, fields=fields, import_fields=import_fields, skip_header=True ) self.assertEqual(list(table.fields.keys()), import_fields) for row, row_data in zip(table, data[1:]): self.assertEqual(row_data[1], row.textfield) self.assertEqual(row_data[0], row.intfield) def test_create_table_import_fields_dont_exist(self): header = ["field1", "field2", "field3"] table_rows = [ ["1", 3.14, "Álvaro"], ["2", 2.71, "turicas"], ["3", 1.23, "Justen"], ] error_fields = ["doesnt_exist", "ruby"] import_fields = list(header)[:-1] + error_fields with self.assertRaises(ValueError) as exception_context: plugins_utils.create_table( [header] + table_rows, import_fields=import_fields ) self.assertIn( exception_context.exception.args[0], possible_field_names_errors(error_fields), ) def test_create_table_repeated_field_names(self): header = ["first", "first", "first"] table_rows = [ ["1", 3.14, "Álvaro"], ["2", 2.71, "turicas"], ["3", 1.23, "Justen"], ] table = plugins_utils.create_table([header] + table_rows) self.assertEqual(list(table.fields.keys()), ["first", "first_2", "first_3"]) self.assertEqual(table[0].first, 1) self.assertEqual(table[0].first_2, 3.14) self.assertEqual(table[0].first_3, "Álvaro") header = ["field", "", "field"] table_rows = [ ["1", 3.14, "Álvaro"], ["2", 2.71, "turicas"], ["3", 1.23, "Justen"], ] table = plugins_utils.create_table([header] + table_rows) self.assertEqual(list(table.fields.keys()), ["field", "field_1", "field_2"]) self.assertEqual(table[0].field, 1) self.assertEqual(table[0].field_1, 3.14) self.assertEqual(table[0].field_2, "Álvaro") def test_create_table_empty_data(self): header = ["first", "first", "first"] table_rows = [] table = plugins_utils.create_table([header] + table_rows) self.assertEqual(list(table.fields.keys()), ["first", "first_2", "first_3"]) self.assertEqual(len(table), 0) def test_create_table_force_types(self): header = ["field1", "field2", "field3"] table_rows = [ ["1", "3.14", "Álvaro"], ["2", "2.71", "turicas"], ["3", "1.23", "Justen"], ] force_types = {"field2": rows.fields.DecimalField} table = plugins_utils.create_table( [header] + table_rows, force_types=force_types ) for field_name, field_type in force_types.items(): self.assertEqual(table.fields[field_name], field_type) def test_create_table_different_number_of_fields(self): header = ["field1", "field2"] table_rows = [ ["1", "3.14", "Álvaro"], ["2", "2.71", "turicas"], ["3", "1.23", "Justen"], ] table = plugins_utils.create_table([header] + table_rows) self.assertEqual(list(table.fields.keys()), ["field1", "field2", "field_2"]) self.assertEqual(table[0].field1, 1) self.assertEqual(table[0].field2, 3.14) self.assertEqual(table[0].field_2, "Álvaro") self.assertEqual(table[1].field1, 2) self.assertEqual(table[1].field2, 2.71) self.assertEqual(table[1].field_2, "turicas") self.assertEqual(table[2].field1, 3) self.assertEqual(table[2].field2, 1.23) self.assertEqual(table[2].field_2, "Justen") def test_prepare_to_export_all_fields(self): result = plugins_utils.prepare_to_export(utils.table, export_fields=None) self.assertEqual(list(utils.table.fields.keys()), next(result)) for row in utils.table._rows: self.assertEqual(row, next(result)) with self.assertRaises(StopIteration): next(result) def test_prepare_to_export_some_fields(self): field_names = list(utils.table.fields.keys()) number_of_fields = random.randint(2, len(field_names) - 1) some_fields = [field_names[index] for index in range(number_of_fields)] random.shuffle(some_fields) result = plugins_utils.prepare_to_export(utils.table, export_fields=some_fields) self.assertEqual(some_fields, next(result)) for row in utils.table: expected_row = [getattr(row, field_name) for field_name in some_fields] self.assertEqual(expected_row, next(result)) with self.assertRaises(StopIteration): next(result) def test_prepare_to_export_some_fields_dont_exist(self): field_names = list(utils.table.fields.keys()) error_fields = ["does_not_exist", "java"] export_fields = field_names + error_fields result = plugins_utils.prepare_to_export( utils.table, export_fields=export_fields ) with self.assertRaises(ValueError) as exception_context: next(result) self.assertIn( exception_context.exception.args[0], possible_field_names_errors(error_fields), ) def test_prepare_to_export_with_FlexibleTable(self): flexible = rows.FlexibleTable() for row in utils.table: flexible.append(row._asdict()) field_names = list(flexible.fields.keys()) prepared = plugins_utils.prepare_to_export(flexible) self.assertEqual(next(prepared), field_names) for row, expected_row in zip(prepared, flexible._rows): values = [expected_row[field_name] for field_name in field_names] self.assertEqual(values, row) def test_prepare_to_export_with_FlexibleTable_and_export_fields(self): flexible = rows.FlexibleTable() for row in utils.table: # convertion to text_type is needed on Python 2 since namedtuples' # keys are bytes, not unicode flexible.append( {six.text_type(key): value for key, value in row._asdict().items()} ) field_names = list(flexible.fields.keys()) export_fields = field_names[: len(field_names) // 2] print([(x, type(x)) for x in export_fields]) prepared = plugins_utils.prepare_to_export( flexible, export_fields=export_fields ) self.assertEqual(next(prepared), export_fields) for row, expected_row in zip(prepared, flexible._rows): values = [expected_row[field_name] for field_name in export_fields] self.assertEqual(values, row) def test_prepare_to_export_wrong_obj_type(self): """`prepare_to_export` raises exception if obj isn't `*Table`""" expected_message = "Table type not recognized" with self.assertRaises(ValueError) as exception_context: next(plugins_utils.prepare_to_export(1)) self.assertEqual(exception_context.exception.args[0], expected_message) with self.assertRaises(ValueError) as exception_context: next(plugins_utils.prepare_to_export(42.0)) self.assertEqual(exception_context.exception.args[0], expected_message) with self.assertRaises(ValueError) as exception_context: next(plugins_utils.prepare_to_export([list("abc"), [1, 2, 3]])) self.assertEqual(exception_context.exception.args[0], expected_message) @mock.patch("rows.plugins.utils.prepare_to_export", return_value=iter([[], [], []])) def test_serialize_should_call_prepare_to_export(self, mocked_prepare_to_export): table = utils.table kwargs = {"export_fields": 123, "other_parameter": 3.14} result = plugins_utils.serialize(table, **kwargs) self.assertFalse(mocked_prepare_to_export.called) field_names, table_rows = next(result), list(result) self.assertTrue(mocked_prepare_to_export.called) self.assertEqual(mocked_prepare_to_export.call_count, 1) self.assertEqual(mock.call(table, **kwargs), mocked_prepare_to_export.call_args) def test_serialize(self): result = plugins_utils.serialize(utils.table) field_types = list(utils.table.fields.values()) self.assertEqual(next(result), list(utils.table.fields.keys())) for row, expected_row in zip(result, utils.table._rows): values = [ field_type.serialize(value) for field_type, value in zip(field_types, expected_row) ] self.assertEqual(values, row) def test_make_header_should_add_underscore_if_starts_with_number(self): result = plugins_utils.make_header(["123", "456", "123"]) expected_result = ["field_123", "field_456", "field_123_2"] self.assertEqual(result, expected_result) def test_make_header_should_not_ignore_permit_not(self): result = plugins_utils.make_header(["abc", "^qwe", "rty"], permit_not=True) expected_result = ["abc", "^qwe", "rty"] self.assertEqual(result, expected_result) def test_make_unique_name(self): name = "test" existing_names = [] name_format = "{index}_{name}" result = plugins_utils.make_unique_name(name, existing_names, name_format) self.assertEqual(result, name) existing_names = ["test"] result = plugins_utils.make_unique_name(name, existing_names, name_format) self.assertEqual(result, "2_test") existing_names = ["test", "2_test", "3_test", "5_test"] result = plugins_utils.make_unique_name(name, existing_names, name_format) self.assertEqual(result, "4_test") existing_names = ["test", "2_test", "3_test", "5_test"] result = plugins_utils.make_unique_name( name, existing_names, name_format, start=1 ) self.assertEqual(result, "1_test") def test_export_data(self): data = "python rules".encode("utf-8") temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) filename_or_fobj = temp.file result = plugins_utils.export_data(filename_or_fobj, data) temp.file.seek(0) output = temp.file.read() self.assertIs(result, temp.file) self.assertEqual(output, data) filename_or_fobj = None result = plugins_utils.export_data(filename_or_fobj, data) self.assertIs(result, data) # TODO: test make_header # TODO: test all features of create_table # TODO: test if error is raised if len(row) != len(fields) # TODO: test get_fobj_and_filename (BytesIO should return filename = None) rows-0.4.1/tests/tests_plugin_xls.py000066400000000000000000000136271343135453400176460ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2018 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import datetime import tempfile import time import unittest from collections import OrderedDict import mock import rows import rows.plugins.xls import tests.utils as utils def date_to_datetime(value): return datetime.datetime.fromtimestamp(time.mktime(value.timetuple())) class PluginXlsTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "xls" file_extension = "xls" filename = "tests/data/all-field-types.xls" assert_meta_encoding = False def test_imports(self): self.assertIs(rows.import_from_xls, rows.plugins.xls.import_from_xls) self.assertIs(rows.export_to_xls, rows.plugins.xls.export_to_xls) @mock.patch("rows.plugins.xls.create_table") def test_import_from_xls_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"some_key": 123, "other": 456} result = rows.import_from_xls(self.filename, **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = { "imported_from": "xls", "filename": self.filename, "sheet_name": "Sheet1", } self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.xls.create_table") def test_import_from_xls_retrieve_desired_data(self, mocked_create_table): mocked_create_table.return_value = 42 # import using filename rows.import_from_xls(self.filename) call_args = mocked_create_table.call_args_list[0] self.assert_create_table_data( call_args, expected_meta={ "imported_from": "xls", "filename": self.filename, "sheet_name": "Sheet1", }, ) # import using fobj with open(self.filename, "rb") as fobj: rows.import_from_xls(fobj) call_args = mocked_create_table.call_args_list[1] self.assert_create_table_data( call_args, expected_meta={ "imported_from": "xls", "filename": self.filename, "sheet_name": "Sheet1", }, ) def test_export_to_xls_filename(self): # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) rows.export_to_xls(utils.table, temp.name) table = rows.import_from_xls(temp.name) self.assert_table_equal(table, utils.table) temp.file.seek(0) result = temp.file.read() export_in_memory = rows.export_to_xls(utils.table, None) self.assertEqual(result, export_in_memory) def test_export_to_xls_fobj(self): # TODO: may test with codecs.open passing an encoding # TODO: may test file contents temp = tempfile.NamedTemporaryFile(delete=False, mode="wb") self.files_to_delete.append(temp.name) rows.export_to_xls(utils.table, temp.file) temp.file.close() table = rows.import_from_xls(temp.name) self.assert_table_equal(table, utils.table) @mock.patch("rows.plugins.xls.prepare_to_export") def test_export_to_xls_uses_prepare_to_export(self, mocked_prepare_to_export): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) encoding = "iso-8859-15" kwargs = {"test": 123, "parameter": 3.14} mocked_prepare_to_export.return_value = iter([utils.table.fields.keys()]) rows.export_to_xls(utils.table, temp.name, encoding=encoding, **kwargs) self.assertTrue(mocked_prepare_to_export.called) self.assertEqual(mocked_prepare_to_export.call_count, 1) call = mocked_prepare_to_export.call_args self.assertEqual(call[0], (utils.table,)) kwargs["encoding"] = encoding self.assertEqual(call[1], kwargs) def test_issue_168(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) table = rows.Table(fields=OrderedDict([("jsoncolumn", rows.fields.JSONField)])) table.append({"jsoncolumn": '{"python": 42}'}) rows.export_to_xls(table, filename) table2 = rows.import_from_xls(filename) self.assert_table_equal(table, table2) @mock.patch("rows.plugins.xls.create_table") def test_start_and_end_row(self, mocked_create_table): rows.import_from_xls( self.filename, start_row=6, end_row=8, start_column=6, end_column=8 ) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) call_args = mocked_create_table.call_args_list[0] expected_data = [ ["12.0%", "2050-01-02", "2050-01-02T23:45:31"], ["13.64%", "2015-08-18", "2015-08-18T22:21:33"], ["13.14%", "2015-03-04", "2015-03-04T16:00:01"], ] self.assertEqual(expected_data, call_args[0][0]) rows-0.4.1/tests/tests_plugin_xlsx.py000066400000000000000000000156461343135453400200410ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2018 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import datetime import tempfile import unittest from collections import OrderedDict from decimal import Decimal from io import BytesIO import mock import rows import rows.plugins.xlsx import tests.utils as utils class PluginXlsxTestCase(utils.RowsTestMixIn, unittest.TestCase): plugin_name = "xlsx" file_extension = "xlsx" filename = "tests/data/all-field-types.xlsx" assert_meta_encoding = False def test_imports(self): self.assertIs(rows.import_from_xlsx, rows.plugins.xlsx.import_from_xlsx) self.assertIs(rows.export_to_xlsx, rows.plugins.xlsx.export_to_xlsx) @mock.patch("rows.plugins.xlsx.create_table") def test_import_from_xlsx_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 kwargs = {"encoding": "iso-8859-15", "some_key": 123, "other": 456} result = rows.import_from_xlsx(self.filename, **kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = { "imported_from": "xlsx", "filename": self.filename, "sheet_name": "Sheet1", } self.assertEqual(call[1], kwargs) @mock.patch("rows.plugins.xlsx.create_table") def test_import_from_xlsx_retrieve_desired_data(self, mocked_create_table): mocked_create_table.return_value = 42 # import using filename rows.import_from_xlsx(self.filename) call_args = mocked_create_table.call_args_list[0] self.assert_create_table_data( call_args, expected_meta={ "imported_from": "xlsx", "filename": self.filename, "sheet_name": "Sheet1", }, ) # import using fobj with open(self.filename, "rb") as fobj: rows.import_from_xlsx(fobj) call_args = mocked_create_table.call_args_list[1] self.assert_create_table_data( call_args, expected_meta={ "imported_from": "xlsx", "filename": self.filename, "sheet_name": "Sheet1", }, ) def test_export_to_xlsx_filename(self): temp = tempfile.NamedTemporaryFile() filename = temp.name + ".xlsx" temp.close() self.files_to_delete.append(filename) rows.export_to_xlsx(utils.table, filename) table = rows.import_from_xlsx(filename) self.assert_table_equal(table, utils.table) export_in_memory = rows.export_to_xlsx(utils.table, None) result_fobj = BytesIO() result_fobj.write(export_in_memory) result_fobj.seek(0) result_table = rows.import_from_xlsx(result_fobj) self.assert_table_equal(result_table, utils.table) def test_export_to_xlsx_fobj(self): temp = tempfile.NamedTemporaryFile() filename = temp.name + ".xlsx" temp.close() fobj = open(filename, "wb") self.files_to_delete.append(filename) rows.export_to_xlsx(utils.table, fobj) fobj.close() table = rows.import_from_xlsx(filename) self.assert_table_equal(table, utils.table) @mock.patch("rows.plugins.xlsx.prepare_to_export") def test_export_to_xlsx_uses_prepare_to_export(self, mocked_prepare_to_export): temp = tempfile.NamedTemporaryFile() filename = temp.name + ".xlsx" temp.file.close() self.files_to_delete.append(filename) kwargs = {"test": 123, "parameter": 3.14} mocked_prepare_to_export.return_value = iter([utils.table.fields.keys()]) rows.export_to_xlsx(utils.table, temp.name, **kwargs) self.assertTrue(mocked_prepare_to_export.called) self.assertEqual(mocked_prepare_to_export.call_count, 1) call = mocked_prepare_to_export.call_args self.assertEqual(call[0], (utils.table,)) self.assertEqual(call[1], kwargs) def test_issue_168(self): temp = tempfile.NamedTemporaryFile(delete=False) filename = "{}.{}".format(temp.name, self.file_extension) self.files_to_delete.append(filename) table = rows.Table(fields=OrderedDict([("jsoncolumn", rows.fields.JSONField)])) table.append({"jsoncolumn": '{"python": 42}'}) rows.export_to_xlsx(table, filename) table2 = rows.import_from_xlsx(filename) self.assert_table_equal(table, table2) @mock.patch("rows.plugins.xlsx.create_table") def test_start_and_end_row(self, mocked_create_table): rows.import_from_xlsx( self.filename, start_row=6, end_row=8, start_column=4, end_column=7 ) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) call_args = mocked_create_table.call_args_list[0] expected_data = [ [4.56, 4.56, "12%", datetime.datetime(2050, 1, 2, 0, 0)], [7.89, 7.89, "13.64%", datetime.datetime(2015, 8, 18, 0, 0)], [9.87, 9.87, "13.14%", datetime.datetime(2015, 3, 4, 0, 0)], ] self.assertEqual(expected_data, call_args[0][0]) def test_issue_290_can_read_sheet(self): rows.import_from_xlsx("tests/data/text_in_percent_cell.xlsx") # Before fixing the first part of #290, this would simply crash assert True def test_issue_290_one_hundred_read_as_1(self): result = rows.import_from_xlsx("tests/data/text_in_percent_cell.xlsx") # As this test is written, file numeric file contents on first column are # 100%, 23.20%, 1.00%, 10.00%, 100.00% assert result[0][0] == Decimal("1") assert result[1][0] == Decimal("0.2320") assert result[2][0] == Decimal("0.01") assert result[3][0] == Decimal("0.1") assert result[4][0] == Decimal("1") def test_issue_290_textual_value_in_percent_col_is_preserved(self): result = rows.import_from_xlsx("tests/data/text_in_percent_cell.xlsx") # As this test is written, file contents on first column are # 100%, 23.20%, 1.00%, 10.00%, 100.00% assert result[5][1] == "text" rows-0.4.1/tests/tests_plugin_xpath.py000066400000000000000000000130421343135453400201530ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import tempfile import unittest from collections import OrderedDict from io import BytesIO import mock import rows import rows.plugins.xpath import tests.utils as utils class PluginXPathTestCase(utils.RowsTestMixIn, unittest.TestCase): filename = "tests/data/ecuador-medios-radiodifusoras.html" encoding = "utf-8" expected_data = "tests/data/ecuador-medios-radiodifusoras.csv" assert_meta_encoding = True def setUp(self): rows_xpath = ( '//*[@class="entry-container"]/*[@class="row-fluid"]/*[@class="span6"]' ) fields_xpath = OrderedDict( [ ("url", ".//h2/a/@href"), ("name", ".//h2/a/text()"), ("address", './/div[@class="spField field_direccion"]/text()'), ("phone", './/div[@class="spField field_telefono"]/text()'), ("website", './/div[@class="spField field_sitio_web"]/text()'), ("email", './/div[@class="spField field_email"]/text()'), ] ) self.kwargs = {"rows_xpath": rows_xpath, "fields_xpath": fields_xpath} self.expected_table = rows.import_from_csv(self.expected_data) self.files_to_delete = [] def test_imports(self): self.assertIs(rows.import_from_xpath, rows.plugins.xpath.import_from_xpath) def test_import_from_xpath_filename(self): table = rows.import_from_xpath( self.filename, encoding=self.encoding, **self.kwargs ) expected_meta = { "imported_from": "xpath", "filename": self.filename, "encoding": self.encoding, } self.assertEqual(table.meta, expected_meta) temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) fobj = temp.file rows.export_to_csv(table, fobj) fobj.seek(0) table = rows.import_from_csv(fobj) self.assert_table_equal(table, self.expected_table) def test_import_from_xpath_fobj(self): # TODO: may test with codecs.open passing an encoding with open(self.filename, mode="rb") as fobj: table = rows.import_from_xpath(fobj, encoding=self.encoding, **self.kwargs) expected_meta = { "imported_from": "xpath", "filename": self.filename, "encoding": self.encoding, } self.assertEqual(table.meta, expected_meta) temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) fobj = temp.file rows.export_to_csv(table, fobj) fobj.seek(0) table = rows.import_from_csv(fobj) self.assert_table_equal(table, self.expected_table) def test_import_from_xpath_unescape_and_extract_text(self): html = """ """.encode( "utf-8" ) rows_xpath = "//ul/li" fields_xpath = OrderedDict([("name", ".//text()"), ("link", ".//a/@href")]) table = rows.import_from_xpath( BytesIO(html), rows_xpath=rows_xpath, fields_xpath=fields_xpath, encoding="utf-8", ) self.assertEqual(table[0].name, "Abadia de Goiás (GO)") self.assertEqual(table[1].name, "Abadiânia (GO)") @mock.patch("rows.plugins.xpath.create_table") def test_import_from_xpath_uses_create_table(self, mocked_create_table): mocked_create_table.return_value = 42 encoding = "iso-8859-15" kwargs = {"some_key": 123, "other": 456} self.kwargs.update(kwargs) result = rows.import_from_xpath(self.filename, encoding=encoding, **self.kwargs) self.assertTrue(mocked_create_table.called) self.assertEqual(mocked_create_table.call_count, 1) self.assertEqual(result, 42) call = mocked_create_table.call_args kwargs["meta"] = { "imported_from": "xpath", "filename": self.filename, "encoding": encoding, } self.assertEqual(call[1], kwargs) def test_xpath_must_be_text_type(self): with self.assertRaises(TypeError): rows.import_from_xpath( self.filename, encoding=self.encoding, rows_xpath=b"//div", fields_xpath={"f1": ".//span"}, ) with self.assertRaises(TypeError): rows.import_from_xpath( self.filename, encoding=self.encoding, rows_xpath="//div", fields_xpath={"f1": b".//span"}, ) rows-0.4.1/tests/tests_table.py000066400000000000000000000401401343135453400165370ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import collections import datetime import unittest import mock import six import rows import rows.fields as fields from rows.table import FlexibleTable, Table binary_type_name = six.binary_type.__name__ class TableTestCase(unittest.TestCase): def setUp(self): self.table = Table( fields={"name": rows.fields.TextField, "birthdate": rows.fields.DateField} ) self.first_row = { "name": "Álvaro Justen", "birthdate": datetime.date(1987, 4, 29), } self.table.append(self.first_row) self.table.append({"name": "Somebody", "birthdate": datetime.date(1990, 2, 1)}) self.table.append({"name": "Douglas Adams", "birthdate": "1952-03-11"}) def test_table_init_slug_creation_on_fields(self): table = rows.Table( fields=collections.OrderedDict( [('Query Occurrence"( % ),"First Seen', rows.fields.FloatField)] ) ) self.assertIn("query_occurrence_first_seen", table.fields) def test_Table_is_present_on_main_namespace(self): self.assertIn("Table", dir(rows)) self.assertIs(Table, rows.Table) def test_table_iteration(self): # TODO: may test with all field types (using tests.utils.table) table_rows = [row for row in self.table] self.assertEqual(len(table_rows), 3) self.assertEqual(table_rows[0].name, "Álvaro Justen") self.assertEqual(table_rows[0].birthdate, datetime.date(1987, 4, 29)) self.assertEqual(table_rows[1].name, "Somebody") self.assertEqual(table_rows[1].birthdate, datetime.date(1990, 2, 1)) self.assertEqual(table_rows[2].name, "Douglas Adams") self.assertEqual(table_rows[2].birthdate, datetime.date(1952, 3, 11)) def test_table_slicing(self): self.assertEqual(len(self.table[::2]), 2) self.assertEqual(self.table[::2][0].name, "Álvaro Justen") def test_table_slicing_error(self): with self.assertRaises(ValueError) as context_manager: self.table[[1]] self.assertEqual(type(context_manager.exception), ValueError) def test_table_insert_row(self): self.table.insert( 1, {"name": "Grace Hopper", "birthdate": datetime.date(1909, 12, 9)} ) self.assertEqual(self.table[1].name, "Grace Hopper") def test_table_append_error(self): # TODO: may mock these validations and test only on *Field tests with self.assertRaises(ValueError) as context_manager: self.table.append( {"name": "Álvaro Justen".encode("utf-8"), "birthdate": "1987-04-29"} ) self.assertEqual(type(context_manager.exception), ValueError) self.assertEqual(context_manager.exception.args[0], "Binary is not supported") with self.assertRaises(ValueError) as context_manager: self.table.append({"name": "Álvaro Justen", "birthdate": "WRONG"}) self.assertEqual(type(context_manager.exception), ValueError) self.assertIn("does not match format", context_manager.exception.args[0]) def test_table_getitem_invalid_type(self): with self.assertRaises(ValueError) as exception_context: self.table[3.14] self.assertEqual( exception_context.exception.args[0], "Unsupported key type: float" ) with self.assertRaises(ValueError) as exception_context: self.table[b"name"] self.assertEqual( exception_context.exception.args[0], "Unsupported key type: {}".format(binary_type_name), ) def test_table_getitem_column_doesnt_exist(self): with self.assertRaises(KeyError) as exception_context: self.table["doesnt-exist"] self.assertEqual(exception_context.exception.args[0], "doesnt-exist") def test_table_getitem_column_happy_path(self): expected_values = ["Álvaro Justen", "Somebody", "Douglas Adams"] self.assertEqual(self.table["name"], expected_values) expected_values = [ datetime.date(1987, 4, 29), datetime.date(1990, 2, 1), datetime.date(1952, 3, 11), ] self.assertEqual(self.table["birthdate"], expected_values) def test_table_setitem_row(self): self.first_row["name"] = "turicas" self.first_row["birthdate"] = datetime.date(2000, 1, 1) self.table[0] = self.first_row self.assertEqual(self.table[0].name, "turicas") self.assertEqual(self.table[0].birthdate, datetime.date(2000, 1, 1)) def test_field_names_and_types(self): self.assertEqual(self.table.field_names, list(self.table.fields.keys())) self.assertEqual(self.table.field_types, list(self.table.fields.values())) def test_table_setitem_column_happy_path_new_column(self): number_of_fields = len(self.table.fields) self.assertEqual(len(self.table), 3) self.table["user_id"] = [4, 5, 6] self.assertEqual(len(self.table), 3) self.assertEqual(len(self.table.fields), number_of_fields + 1) self.assertIn("user_id", self.table.fields) self.assertIs(self.table.fields["user_id"], rows.fields.IntegerField) self.assertEqual(self.table[0].user_id, 4) self.assertEqual(self.table[1].user_id, 5) self.assertEqual(self.table[2].user_id, 6) def test_table_setitem_column_happy_path_replace_column(self): number_of_fields = len(self.table.fields) self.assertEqual(len(self.table), 3) self.table["name"] = [4, 5, 6] # change values *and* type self.assertEqual(len(self.table), 3) self.assertEqual(len(self.table.fields), number_of_fields) self.assertIn("name", self.table.fields) self.assertIs(self.table.fields["name"], rows.fields.IntegerField) self.assertEqual(self.table[0].name, 4) self.assertEqual(self.table[1].name, 5) self.assertEqual(self.table[2].name, 6) def test_table_setitem_column_slug_field_name(self): self.assertNotIn("user_id", self.table.fields) self.table["User ID"] = [4, 5, 6] self.assertIn("user_id", self.table.fields) def test_table_setitem_column_invalid_length(self): number_of_fields = len(self.table.fields) self.assertEqual(len(self.table), 3) with self.assertRaises(ValueError) as exception_context: self.table["user_id"] = [4, 5] # list len should be 3 self.assertEqual(len(self.table), 3) self.assertEqual(len(self.table.fields), number_of_fields) self.assertEqual( exception_context.exception.args[0], "Values length (2) should be the same as Table " "length (3)", ) def test_table_setitem_invalid_type(self): fields = self.table.fields.copy() self.assertEqual(len(self.table), 3) with self.assertRaises(ValueError) as exception_context: self.table[3.14] = [] self.assertEqual(len(self.table), 3) # should not add any row self.assertDictEqual(fields, self.table.fields) # should not add field self.assertEqual( exception_context.exception.args[0], "Unsupported key type: float" ) with self.assertRaises(ValueError) as exception_context: self.table[b"some_value"] = [] self.assertEqual(len(self.table), 3) # should not add any row self.assertDictEqual(fields, self.table.fields) # should not add field self.assertEqual( exception_context.exception.args[0], "Unsupported key type: {}".format(binary_type_name), ) def test_table_delitem_row(self): table_rows = [row for row in self.table] before = len(self.table) del self.table[0] after = len(self.table) self.assertEqual(after, before - 1) for row, expected_row in zip(self.table, table_rows[1:]): self.assertEqual(row, expected_row) def test_table_delitem_column_doesnt_exist(self): with self.assertRaises(KeyError) as exception_context: del self.table["doesnt-exist"] self.assertEqual(exception_context.exception.args[0], "doesnt-exist") def test_table_delitem_column_happy_path(self): fields = self.table.fields.copy() self.assertEqual(len(self.table), 3) del self.table["name"] self.assertEqual(len(self.table), 3) # should not del any row self.assertEqual(len(self.table.fields), len(fields) - 1) self.assertDictEqual( dict(self.table[0]._asdict()), {"birthdate": datetime.date(1987, 4, 29)} ) self.assertDictEqual( dict(self.table[1]._asdict()), {"birthdate": datetime.date(1990, 2, 1)} ) self.assertDictEqual( dict(self.table[2]._asdict()), {"birthdate": datetime.date(1952, 3, 11)} ) def test_table_delitem_column_invalid_type(self): fields = self.table.fields.copy() self.assertEqual(len(self.table), 3) with self.assertRaises(ValueError) as exception_context: del self.table[3.14] self.assertEqual(len(self.table), 3) # should not del any row self.assertDictEqual(fields, self.table.fields) # should not del field self.assertEqual( exception_context.exception.args[0], "Unsupported key type: float" ) with self.assertRaises(ValueError) as exception_context: self.table[b"name"] = [] # 'name' actually exists self.assertEqual(len(self.table), 3) # should not del any row self.assertDictEqual(fields, self.table.fields) # should not del field self.assertEqual( exception_context.exception.args[0], "Unsupported key type: {}".format(binary_type_name), ) def test_table_add(self): self.assertIs(self.table + 0, self.table) self.assertIs(0 + self.table, self.table) new_table = self.table + self.table self.assertEqual(new_table.fields, self.table.fields) self.assertEqual(len(new_table), 2 * len(self.table)) self.assertEqual(list(new_table), list(self.table) * 2) def test_table_add_error(self): with self.assertRaises(ValueError): self.table + 1 with self.assertRaises(ValueError): 1 + self.table def test_table_order_by(self): with self.assertRaises(ValueError): self.table.order_by("doesnt_exist") before = [row.birthdate for row in self.table] self.table.order_by("birthdate") after = [row.birthdate for row in self.table] self.assertNotEqual(before, after) self.assertEqual(sorted(before), after) self.table.order_by("-birthdate") final = [row.birthdate for row in self.table] self.assertEqual(final, list(reversed(after))) self.table.order_by("name") expected_rows = [ {"name": "Douglas Adams", "birthdate": datetime.date(1952, 3, 11)}, {"name": "Somebody", "birthdate": datetime.date(1990, 2, 1)}, {"name": "Álvaro Justen", "birthdate": datetime.date(1987, 4, 29)}, ] for expected_row, row in zip(expected_rows, self.table): self.assertEqual(expected_row, dict(row._asdict())) def test_table_repr(self): expected = "" self.assertEqual(expected, repr(self.table)) def test_table_add_should_not_iterate_over_rows(self): table1 = rows.Table( fields={"f1": rows.fields.IntegerField, "f2": rows.fields.FloatField} ) table2 = rows.Table( fields={"f1": rows.fields.IntegerField, "f2": rows.fields.FloatField} ) table1._rows = mock.Mock() table1._rows.__add__ = mock.Mock() table1._rows.__iter__ = mock.Mock() table2._rows = mock.Mock() table2._rows.__add__ = mock.Mock() table2._rows.__iter__ = mock.Mock() self.assertFalse(table1._rows.__add__.called) self.assertFalse(table2._rows.__add__.called) self.assertFalse(table1._rows.__iter__.called) self.assertFalse(table2._rows.__iter__.called) table1 + table2 self.assertTrue(table1._rows.__add__.called) self.assertFalse(table2._rows.__add__.called) self.assertFalse(table1._rows.__iter__.called) self.assertFalse(table2._rows.__iter__.called) class TestFlexibleTable(unittest.TestCase): def setUp(self): self.table = FlexibleTable() def test_FlexibleTable_is_present_on_main_namespace(self): self.assertIn("FlexibleTable", dir(rows)) self.assertIs(FlexibleTable, rows.FlexibleTable) def test_inheritance(self): self.assertTrue(issubclass(FlexibleTable, Table)) def test_flexible_append_detect_field_type(self): self.assertEqual(len(self.table.fields), 0) self.table.append({"a": 123, "b": 3.14}) self.assertEqual(self.table[0].a, 123) self.assertEqual(self.table[0].b, 3.14) self.assertEqual(self.table.fields["a"], fields.IntegerField) self.assertEqual(self.table.fields["b"], fields.FloatField) # Values are checked based on field types when appending with self.assertRaises(ValueError): self.table.append({"a": "spam", "b": 1.23}) # invalid value for 'a' with self.assertRaises(ValueError): self.table.append({"a": 42, "b": "ham"}) # invalid value or 'b' # Values are converted self.table.append({"a": "42", "b": "2.71"}) self.assertEqual(self.table[1].a, 42) self.assertEqual(self.table[1].b, 2.71) def test_flexible_insert_row(self): self.table.append({"a": 123, "b": 3.14}) self.table.insert(0, {"a": 2357, "b": 1123}) self.assertEqual(self.table[0].a, 2357) def test_flexible_update_row(self): self.table.append({"a": 123, "b": 3.14}) self.table[0] = {"a": 2357, "b": 1123} self.assertEqual(self.table[0].a, 2357) def test_table_slicing(self): self.table.append({"a": 123, "b": 3.14}) self.table.append({"a": 2357, "b": 1123}) self.table.append({"a": 8687, "b": 834798}) self.assertEqual(len(self.table[::2]), 2) self.assertEqual(self.table[::2][0].a, 123) def test_table_slicing_error(self): self.table.append({"a": 123, "b": 3.14}) self.table.append({"a": 2357, "b": 1123}) self.table.append({"a": 8687, "b": 834798}) with self.assertRaises(ValueError) as context_manager: self.table[[1]] self.assertEqual(type(context_manager.exception), ValueError) def test_table_iadd(self): table = rows.Table( fields={"f1": rows.fields.IntegerField, "f2": rows.fields.FloatField} ) table.append({"f1": 1, "f2": 2}) table.append({"f1": 3, "f2": 4}) self.assertEqual(len(table), 2) table += table self.assertEqual(len(table), 4) data_rows = list(table) self.assertEqual(data_rows[0], data_rows[2]) self.assertEqual(data_rows[1], data_rows[3]) def test_table_name(self): table = rows.Table(fields=collections.OrderedDict([("a", fields.TextField)])) self.assertTrue("filename" not in table.meta) self.assertEqual(table.name, "table1") table.meta["filename"] = "This is THE name.csv" self.assertTrue("filename" in table.meta) self.assertEqual(table.name, "this_is_the_name") rows-0.4.1/tests/tests_utils.py000066400000000000000000000150651343135453400166200ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2019 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import io import tempfile import unittest from textwrap import dedent import rows.fields as fields import rows.utils import tests.utils as utils class UtilsTestCase(utils.RowsTestMixIn, unittest.TestCase): def assert_encoding(self, first, second): """Assert encoding equality `iso-8859-1` should be detected as the same as `iso-8859-8` as described in (affects Debian and Fedora packaging) """ self.assertEqual(first.lower().split("-")[:-1], second.lower().split("-")[:-1]) def test_local_file_sample_size(self): temp = tempfile.NamedTemporaryFile(delete=False) self.files_to_delete.append(temp.name) header = b"field1,field2,field3\r\n" row_data = b"non-ascii-field-1,non-ascii-field-2,non-ascii-field-3\r\n" encoding = "iso-8859-1" temp.file.write(header) counter = len(header) increment = len(row_data) while counter <= 8192: temp.file.write(row_data) counter += increment temp.file.write("Álvaro,àáááããçc,ádfáffad\r\n".encode(encoding)) temp.file.close() result = rows.utils.local_file(temp.name) self.assertEqual(result.uri, temp.name) self.assert_encoding(result.encoding, encoding) self.assertEqual(result.delete, False) class SchemaTestCase(unittest.TestCase): def assert_generate_schema(self, fmt, expected, export_fields=None): # prepare a consistent table so we can test all formats using it table_fields = utils.table.fields.copy() table_fields["json_column"] = fields.JSONField table_fields["decimal_column"] = fields.DecimalField table_fields["percent_column"] = fields.DecimalField if export_fields is None: export_fields = list(table_fields.keys()) table = rows.Table(fields=table_fields) for row in utils.table: data = row._asdict() data["json_column"] = {} table.append(data) table.meta["filename"] = "this is my table.csv" obj = io.StringIO() rows.utils.generate_schema(table, export_fields, fmt, obj) obj.seek(0) result = obj.read() self.assertEqual(expected.strip(), result.strip()) def test_generate_schema_txt(self): expected = dedent( """ +-----------------+------------+ | field_name | field_type | +-----------------+------------+ | bool_column | bool | | integer_column | integer | | float_column | float | | decimal_column | decimal | | percent_column | decimal | | date_column | date | | datetime_column | datetime | | unicode_column | text | | json_column | json | +-----------------+------------+ """ ) self.assert_generate_schema("txt", expected) def test_generate_schema_sql(self): expected = dedent( """ CREATE TABLE IF NOT EXISTS this_is_my_table ( bool_column BOOL, integer_column INT, float_column FLOAT, decimal_column FLOAT, percent_column FLOAT, date_column DATE, datetime_column DATETIME, unicode_column TEXT, json_column TEXT ); """ ) self.assert_generate_schema("sql", expected) def test_generate_schema_django(self): expected = dedent( """ from django.db import models from django.contrib.postgres.fields import JSONField class ThisIsMyTable(models.Model): bool_column = models.BooleanField() integer_column = models.IntegerField() float_column = models.FloatField() decimal_column = models.DecimalField() percent_column = models.DecimalField() date_column = models.DateField() datetime_column = models.DateTimeField() unicode_column = models.TextField() json_column = JSONField() """ ) self.assert_generate_schema("django", expected) def test_generate_schema_restricted_fields(self): expected = dedent( """ +-------------+------------+ | field_name | field_type | +-------------+------------+ | bool_column | bool | | json_column | json | +-------------+------------+ """ ) self.assert_generate_schema( "txt", expected, export_fields=["bool_column", "json_column"] ) expected = dedent( """ CREATE TABLE IF NOT EXISTS this_is_my_table ( bool_column BOOL, json_column TEXT ); """ ) self.assert_generate_schema( "sql", expected, export_fields=["bool_column", "json_column"] ) expected = dedent( """ from django.db import models from django.contrib.postgres.fields import JSONField class ThisIsMyTable(models.Model): bool_column = models.BooleanField() json_column = JSONField() """ ) self.assert_generate_schema( "django", expected, export_fields=["bool_column", "json_column"] ) # TODO: test detect_local_source # TODO: test detect_source # TODO: test download_file # TODO: test export_to_uri # TODO: test extension_by_plugin_name # TODO: test import_from_source # TODO: test import_from_uri # TODO: test local_file # TODO: test normalize_mime_type # TODO: test plugin_name_by_mime_type # TODO: test plugin_name_by_uri rows-0.4.1/tests/utils.py000066400000000000000000000317001343135453400153700ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . from __future__ import unicode_literals import copy import datetime import os from collections import OrderedDict from decimal import Decimal import six import rows.fields as fields from rows.table import Table NONE_VALUES = list(fields.NULL) + ["", None] FIELDS = OrderedDict( [ ("bool_column", fields.BoolField), ("integer_column", fields.IntegerField), ("float_column", fields.FloatField), ("decimal_column", fields.FloatField), ("percent_column", fields.PercentField), ("date_column", fields.DateField), ("datetime_column", fields.DatetimeField), ("unicode_column", fields.TextField), ] ) FIELD_NAMES = list(FIELDS.keys()) EXPECTED_ROWS = [ { "float_column": 3.141592, "decimal_column": 3.141592, "bool_column": True, "integer_column": 1, "date_column": datetime.date(2015, 1, 1), "datetime_column": datetime.datetime(2015, 8, 18, 15, 10), "percent_column": Decimal("0.01"), "unicode_column": "Álvaro", }, { "float_column": 1.234, "decimal_column": 1.234, "bool_column": False, "integer_column": 2, "date_column": datetime.date(1999, 2, 3), "datetime_column": datetime.datetime(1999, 2, 3, 0, 1, 2), "percent_column": Decimal("0.1169"), "unicode_column": "àáãâä¹²³", }, { "float_column": 4.56, "decimal_column": 4.56, "bool_column": True, "integer_column": 3, "date_column": datetime.date(2050, 1, 2), "datetime_column": datetime.datetime(2050, 1, 2, 23, 45, 31), "percent_column": Decimal("0.12"), "unicode_column": "éèẽêë", }, { "float_column": 7.89, "decimal_column": 7.89, "bool_column": False, "integer_column": 4, "date_column": datetime.date(2015, 8, 18), "datetime_column": datetime.datetime(2015, 8, 18, 22, 21, 33), "percent_column": Decimal("0.1364"), "unicode_column": "~~~~", }, { "float_column": 9.87, "decimal_column": 9.87, "bool_column": True, "integer_column": 5, "date_column": datetime.date(2015, 3, 4), "datetime_column": datetime.datetime(2015, 3, 4, 16, 0, 1), "percent_column": Decimal("0.1314"), "unicode_column": "álvaro", }, { "float_column": 1.2345, "decimal_column": 1.2345, "bool_column": False, "integer_column": 6, "date_column": datetime.date(2015, 5, 6), "datetime_column": datetime.datetime(2015, 5, 6, 12, 1, 2), "percent_column": Decimal("0.02"), "unicode_column": "test", }, { "float_column": None, "decimal_column": None, "bool_column": None, "integer_column": None, "date_column": None, "datetime_column": None, "percent_column": None, "unicode_column": "", }, ] table = Table(fields=FIELDS) for row in EXPECTED_ROWS: table.append(row) table._meta = {"test": 123} class LazyGenerator(object): def __init__(self, max_number): self.max_number = max_number self.last = None def __iter__(self): yield ["number", "number_sq", "number_double"] for number in range(self.max_number): self.last = number yield [self.last, self.last ** 2, self.last * 2] class LazyDictGenerator(LazyGenerator): def __iter__(self): header = ("number", "number_sq", "number_double") for number in range(self.max_number): self.last = number data = (self.last, self.last ** 2, self.last * 2) yield dict(zip(header, data)) class RowsTestMixIn(object): maxDiff = None override_fields = None def setUp(self): self.files_to_delete = [] def tearDown(self): for filename in self.files_to_delete: if os.path.exists(filename): os.unlink(filename) def assert_table_equal(self, first, second): expected_fields = dict(second.fields) override_fields = self.override_fields or {} expected_fields = copy.deepcopy(expected_fields) for key, value in override_fields.items(): if key in expected_fields: expected_fields[key] = value self.assertDictEqual(dict(first.fields), expected_fields) self.assertEqual(len(first), len(second)) for first_row, second_row in zip(first, second): first_row = dict(first_row._asdict()) second_row = dict(second_row._asdict()) for field_name, field_type in expected_fields.items(): value = first_row[field_name] expected_value = second_row[field_name] if field_name in override_fields: expected_value = override_fields[field_name].deserialize( expected_value ) if float not in (type(value), type(expected_value)): self.assertEqual( value, expected_value, "Field {} value mismatch".format(field_name), ) else: self.assertAlmostEqual(value, expected_value, places=5) def assert_file_contents_equal(self, first_filename, second_filename): with open(first_filename, "rb") as fobj: first = fobj.read() with open(second_filename, "rb") as fobj: second = fobj.read() self.assertEqual(first, second) def assert_create_table_data( self, call_args, field_ordering=True, filename=None, expected_meta=None ): if filename is None and getattr(self, "filename", None): filename = self.filename kwargs = call_args[1] if expected_meta is None: expected_meta = {"imported_from": self.plugin_name, "filename": filename} if self.assert_meta_encoding: expected_meta["encoding"] = self.encoding # Don't test 'frame_style' metadata, # as it is specific for txt importing # (and the default values for it might change) if "frame_style" not in expected_meta: kwargs["meta"].pop("frame_style", "") self.assertDictEqual(kwargs["meta"], expected_meta) del kwargs["meta"] self.assert_table_data( call_args[0][0], args=[], kwargs=kwargs, field_ordering=field_ordering ) def assert_table_data(self, data, args, kwargs, field_ordering): data = list(data) data[0] = list(data[0]) if field_ordering: self.assertEqual(data[0], FIELD_NAMES) for row_index, row in enumerate(data[1:]): for column_index, value in enumerate(row): field_name = FIELD_NAMES[column_index] expected_value = EXPECTED_ROWS[row_index][field_name] self.field_assert( field_name, expected_value, value, *args, **kwargs ) else: self.assertEqual(set(data[0]), set(FIELD_NAMES)) for row_index, row in enumerate(data[1:]): for column_index, value in enumerate(row): field_name = data[0][column_index] expected_value = EXPECTED_ROWS[row_index][field_name] self.field_assert( field_name, expected_value, value, *args, **kwargs ) # Fields asserts: input values we expect from plugins def field_assert(self, field_name, expected_value, value, *args, **kwargs): assert_methods = { fields.BoolField: self.assert_BoolField, fields.DateField: self.assert_DateField, fields.DatetimeField: self.assert_DatetimeField, fields.DecimalField: self.assert_DecimalField, fields.FloatField: self.assert_FloatField, fields.IntegerField: self.assert_IntegerField, fields.PercentField: self.assert_PercentField, fields.TextField: self.assert_TextField, } if self.override_fields and field_name in self.override_fields: FieldClass = self.override_fields[field_name] else: FieldClass = FIELDS[field_name] return assert_methods[FieldClass](expected_value, value, *args, **kwargs) def assert_BoolField(self, expected_value, value, *args, **kwargs): if expected_value is None: assert value is None or value.lower() in NONE_VALUES elif expected_value is True: assert str(value).lower() in ("true", b"true", "yes", b"yes") elif expected_value is False: assert str(value).lower() in ("false", b"false", "no", b"no") else: raise ValueError("expected_value is not True or False") def assert_IntegerField(self, expected_value, value, *args, **kwargs): if expected_value is None: assert value is None or value.lower() in NONE_VALUES else: self.assertIn(value, (expected_value, str(expected_value))) def assert_FloatField(self, expected_value, value, *args, **kwargs): if expected_value is None: assert value is None or value.lower() in NONE_VALUES elif type(value) != type(expected_value): self.assertEqual(str(value), str(expected_value)) else: self.assertAlmostEqual(expected_value, value, places=5) def assert_DecimalField(self, expected_value, value, *args, **kwargs): if expected_value is None: assert value is None or value.lower() in NONE_VALUES else: self.assert_FloatField(expected_value, value) def assert_PercentField(self, expected_value, value, *args, **kwargs): if expected_value is None: assert value is None or value.lower() in NONE_VALUES else: float_value = str(Decimal(expected_value) * 100)[:-2] if float_value.endswith("."): float_value = float_value[:-1] possible_values = [] if "." not in float_value: possible_values.append(str(int(float_value)) + "%") possible_values.append(str(int(float_value)) + ".00%") float_value = float(float_value) possible_values.extend( [ six.text_type(float_value) + "%", six.text_type(float_value) + ".0%", six.text_type(float_value) + ".00%", ] ) self.assertIn(value, possible_values) def assert_DateField(self, expected_value, value, *args, **kwargs): if expected_value is None: assert value is None or value.lower() in NONE_VALUES else: value = str(value) if value.endswith("00:00:00"): value = value[:-9] self.assertEqual(str(expected_value), value) def assert_DatetimeField(self, expected_value, value, *args, **kwargs): if expected_value is None: assert value is None or value.lower() in NONE_VALUES elif ( type(value) is datetime.datetime and type(expected_value) is datetime.datetime ): # if both types are datetime, check delta # XLSX plugin has not a good precision and will change milliseconds delta_1 = expected_value - value delta_2 = value - expected_value self.assertTrue( str(delta_1).startswith("0:00:00") or str(delta_2).startswith("0:00:00") ) else: # if not, convert values to string and verify if are equal value = str(value) self.assertEqual(str(expected_value).replace(" ", "T"), value) def assert_TextField(self, expected_value, value, *args, **kwargs): if expected_value is None: assert value is None or value.lower() in NONE_VALUES elif expected_value == "": # Some plugins return `None` instead of empty strings for cells # with blank values and we don't have an way to differentiate assert value in (None, "") else: self.assertEqual(expected_value, value) rows-0.4.1/to-do/000077500000000000000000000000001343135453400135355ustar00rootroot00000000000000rows-0.4.1/to-do/plugin_mysql.py000066400000000000000000000134021343135453400166320ustar00rootroot00000000000000# coding: utf-8 # Copyright 2014-2017 Álvaro Justen # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU Lesser General Public License for more details. # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . import datetime import MySQLdb from .rows import Table from .utils import ipartition, slug __all__ = ["import_from_mysql", "export_to_mysql"] # TODO: replace 'None' with '' on export_to_* # TODO: need converters in and out # TODO: lazy=True|False # TODO: datetime.time on MYSQL_TYPE # TODO: import from mysql # TODO: logging? # TODO: _mysql_exceptions.OperationalError: (2006, 'MySQL server has gone # # away') MYSQL_TYPE = { str: "TEXT", int: "INT", float: "FLOAT", datetime.date: "DATE", datetime.datetime: "DATETIME", bool: "BOOL", } # 'BOOL' on MySQL is a shortcut to TINYINT(1) MYSQLDB_TYPE = { getattr(MySQLdb.FIELD_TYPE, x): x for x in dir(MySQLdb.FIELD_TYPE) if not x.startswith("_") } MYSQLDB_TO_PYTHON = { "ENUM": str, "STRING": str, "VAR_STRING": str, "BLOB": bytes, "LONG_BLOB": bytes, "MEDIUM_BLOB": bytes, "TINY_BLOB": bytes, "DECIMAL": float, "DOUBLE": float, "FLOAT": float, "INT24": int, "LONG": int, "LONGLONG": int, "TINY": int, "YEAR": int, "DATE": datetime.date, "NEWDATE": datetime.date, "TIME": int, "TIMESTAMP": int, "DATETIME": datetime.datetime, } def _get_mysql_config(connection_str): colon_index = connection_str.index(":") at_index = connection_str.index("@") slash_index = connection_str.index("/") config = {} config["user"] = connection_str[:colon_index] config["passwd"] = connection_str[colon_index + 1 : at_index] config["host"] = connection_str[at_index + 1 : slash_index] config["port"] = 3306 if ":" in config["host"]: data = config["host"].split(":") config["host"] = data[0] config["port"] = int(data[1]) if connection_str.count("/") == 1: table_name = None config["db"] = connection_str[slash_index + 1 :] else: second_slash_index = connection_str.index("/", slash_index + 1) config["db"] = connection_str[slash_index + 1 : second_slash_index] table_name = connection_str[second_slash_index + 1 :] return config, table_name def _connect_to_mysql(config): return MySQLdb.connect(**config) def import_from_mysql(connection_string, limit=None, order_by=None, query=""): # TODO: add 'lazy' option config, table_name = _get_mysql_config(connection_string) connection = _connect_to_mysql(config) cursor = connection.cursor() if query: sql = query else: sql = "SELECT * FROM " + table_name if limit is not None: sql += " LIMIT {0[0]}, {0[1]}".format(limit) if order_by is not None: sql += " ORDER BY " + order_by cursor.execute(sql) column_info = [(x[0], x[1]) for x in cursor.description] table = Table(fields=[x[0] for x in cursor.description]) table.types = { name: MYSQLDB_TO_PYTHON[MYSQLDB_TYPE[type_]] for name, type_ in column_info } table_rows = [list(row) for row in cursor.fetchall()] encoding = connection.character_set_name() for row in table_rows: for column_index, value in enumerate(row): if type(value) is str: row[column_index] = value.decode(encoding) table._rows = table_rows cursor.close() connection.close() return table def export_to_mysql( table, connection_string, encoding=None, batch_size=1000, commit_every=10000, callback=None, callback_every=10000, ): config, table_name = _get_mysql_config(connection_string) connection = _connect_to_mysql(config) cursor = connection.cursor() # Create table fields, types = table.fields, table.types field_slugs = [slug(field) for field in fields] field_types = [MYSQL_TYPE[types[field]] for field in fields] columns_definition = [ "{} {}".format(field, type_) for field, type_ in zip(field_slugs, field_types) ] sql = "CREATE TABLE IF NOT EXISTS {} ({})".format( table_name, ", ".join(columns_definition) ) cursor.execute(sql) # Insert items columns = ", ".join(field_slugs) # placeholders = ['%s' if types[field] in (int, float, bool) else '"%s"' # for field in fields] # TODO: fix this string/formatting problem placeholders = ["%s" for field in fields] sql = "INSERT INTO {} ({}) VALUES ({})".format( table_name, columns, ", ".join(placeholders) ) total = last_commit = last_callback = 0 for rows in ipartition(iter(table), batch_size): values = [[row[field] for field in fields] for row in rows] added = len(values) total += added last_commit += added last_callback += added cursor.executemany(sql, values) if last_commit >= commit_every: connection.commit() last_commit = 0 if callback is not None and last_callback >= callback_every: callback(total) last_callback = 0 if callback is not None and last_callback > 0: callback(total) if last_commit > 0: connection.commit() connection.close() rows-0.4.1/tox.ini000066400000000000000000000002571343135453400140320ustar00rootroot00000000000000[tox] envlist = py27, py35, py36 [testenv] deps = -rrequirements-development.txt commands = coverage erase && nosetests -dsv --with-yanc --with-coverage --cover-package rows