pax_global_header00006660000000000000000000000064142300622150014505gustar00rootroot0000000000000052 comment=3596640e6e0582cc5fb76a342e5d8e7413aa4b34 tools-0.6.0/000077500000000000000000000000001423006221500126505ustar00rootroot00000000000000tools-0.6.0/.gitignore000066400000000000000000000001771423006221500146450ustar00rootroot00000000000000# keep that alphabetical please *~ *.bak build */build/* *.egg-info freedict-database.xml *.mo *__pycache__* *.pyc *.swo *.swp tools-0.6.0/COPYING000066400000000000000000000440751423006221500137150ustar00rootroot00000000000000This software is (c) 2000-2020 The FreeDict Project . This code is licensed under the terms of the GNU General Public License, version 2.0 or any later version, as published by the Free Software Foundation. For individual files, special licensing terms might apply. These are noted within the file to which they apply. The full license text for this package can be found below. GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the 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 Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. tools-0.6.0/JMdict/000077500000000000000000000000001423006221500140225ustar00rootroot00000000000000tools-0.6.0/JMdict/README.md000066400000000000000000000023031423006221500152770ustar00rootroot00000000000000 Jim Breen's JMdict database =========================== The JMdict_e project aims at providing at dictionary databases with Japanese as its pivot language. The meta language of these databases is english. The project home is at . Copyright holders are: James William BREEN and The Electronic Dictionary Research and Development Group Copyright information at: Conversion Process ---------------- Four manual steps are required to build a dictionary and these are given as an example for the jpn-deu dictionary (ToDo: automate): 1. `xsltproc -o draft.tei -novalid jmdict2tei.xsl JMdict.xml` 2. ToDo:Copy the header ! 3. Indent: `xmllint --format draft.tei > jpn-deu.tei` 4. Check validity: - unixoid: `xmllint --noout --valid jpn-deu.tei > /dev/null` - Windows: `xmllint --noout --valid jpn-deu.tei > nul` JMdict Internationalization --------------------------- Please note the JMdict Internationalization effort by Transifex : https://www.transifex.com/gnurou/jmdict-i18n/ And the scripts going with by GitHub : https://github.com/Gnurou/jmdict-i18n tools-0.6.0/JMdict/jmdict2tei.xsl000066400000000000000000000144611423006221500166160ustar00rootroot00000000000000 Transformation file to convert the JMdict into TEI P5 Freedict file

The Japanese Multilingual Dictionary (jmdict) is a free xml database by Jim Breens et al. (http://www.edrdg.org/jmdict/j_jmdict.html)

This stylesheet aims to convert this multilingual resource into a bilingual Freedict file. JMdict and TEI P5 DTD are quite different, so the xml elements of JMdict have been turned into TEI elements. Following the pretty good JMdict DTD comments, here is how I tried to fit the TEI pattern (http://www.tei-c.org/release/doc/tei-p5-doc/fr/html/DI.html): ant => ref type="ant" dial => usg type="geo" field => usg type="dom" gloss => cit type="trans"/quote k_ele => form type="k_ele" keb => orth ke_inf => lbl type="ke_inf" ke_pri => usg type="pri" lsource => lang misc => note pos => note type="pos" pri => pri reb => orth r_ele => form type="r_ele re_inf => lbl typ="re_inf" re_nokanji => type="re_nokanji" re_pri => usg type="pri" re_restr => lbl type="re_restr" s_inf => note type="sense" stagk => note type="stagk" stagr => note type="stagr" xref => ref

Denis ARNAUD the author(s), 2016; license: GPL v3 or any later version (http://www.gnu.org/licenses/gpl.html).
trans ls_wasei
tools-0.6.0/JMdict/jmdict_e2tei.xsl000066400000000000000000000136471423006221500171270ustar00rootroot00000000000000 Transformation file to convert the JMdict into TEI P5 Freedict file

The Japanese Multilingual Dictionary (jmdict) is a free xml database by Jim Breens et al. (http://www.edrdg.org/jmdict/j_jmdict.html)

This stylesheet aims to convert this multilingual resource into a bilingual Freedict file. JMdict and TEI P5 DTD are quite different, so the xml elements of JMdict have been turned into TEI elements. Following the pretty good JMdict DTD comments, here is how I tried to fit the TEI pattern (http://www.tei-c.org/release/doc/tei-p5-doc/fr/html/DI.html): ant => ref type="ant" dial => usg type="geo" field => usg type="dom" gloss => cit type="trans"/quote k_ele => form type="k_ele" keb => orth ke_inf => lbl type="ke_inf" ke_pri => usg type="pri" lsource => lang misc => note pos => note type="pos" pri => pri reb => orth r_ele => form type="r_ele re_inf => lbl typ="re_inf" re_nokanji => type="re_nokanji" re_pri => usg type="pri" re_restr => lbl type="re_restr" s_inf => note type="sense" stagk => note type="stagk" stagr => note type="stagr" xref => ref

Denis ARNAUD the author(s), 2016; license: GPL v3 or any later version (http://www.gnu.org/licenses/gpl.html).
trans ls_wasei
tools-0.6.0/Makefile000066400000000000000000000150551423006221500143160ustar00rootroot00000000000000# This makefile contains all recipes to build and manage the FreeDict # tools, including the API. It also provides targets to build a release archive # with the latest tools included. FREEDICT_TOOLS ?= . include $(FREEDICT_TOOLS)/mk/config.mk VERSION = 0.4 PREFIX ?= usr DESTDIR ?= INSTALLDIR ?= $(abspath $(DESTDIR)/$(PREFIX)/share/freedict) dirs = api JMdict lib mk xquery xsl/inc TARGET_INSTALL_DIRS = $(addprefix $(INSTALLDIR)/tools/, $(dirs)) api: #! generate the api with information about all dictionaries and their downloads at the configured api path api: $(call mount_or_reuse); \ $(call exc_pyscript,fd_api) || sleep 1; \ $(call umount_or_keep) @$(MAKE) -C $(FREEDICT_TOOLS) --no-print-directory api-validation # allow retrieval of API path from Makefile and from rule below get_api_path=$(call exc_pyscript,fd_file_mgr,-a) | tr -d '\n' api-path: #! print the output directory to the generated API file (read from configuration) (trailing newline is removed) @$(call get_api_path) api-validation: #! validate the freedict-database.xml against its RNG schema xmllint --noout --relaxng freedict-database.rng $(shell $(call get_api_path))/freedict-database.xml mount: #! mount or synchronize FreeDict releases / generated dictionaries $(call exc_pyscript,fd_file_mgr,-m) need-update: #! queries for unreleased dictionaries or for those with newer source changes @$(call mount_or_reuse); \ $(call exc_pyscript,fd_api,-n)\ || sleep 1; \ $(call umount_or_keep) umount: #! runs umount / clean up actions for unmounting remote volumes (if SSH is used) @$(call exc_pyscript,fd_file_mgr,-u) $(BUILD_DIR)/freedict-tools-$(VERSION).tar.bz2: Makefile* *.pl ifeq ($(wildcard $(BUILD_DIR)),) mkdir -p $(BUILD_DIR) endif tar --totals --exclude="*/.svn/*" --exclude="*/.*" \ --exclude="*/charlint*" --exclude="*/UnicodeData.txt" \ --exclude="*/ergane/jet4/*" \ --exclude="*/ergane/unzip/*" \ --exclude="*/ergane/zip/*" \ --exclude="*/__pycache__/*" \ -cvjf $@ ../tools install-deps: #! probe current operating system to install build prerequisites for dictionary development echo -n "Do you want to use unison or sshfs? Enter one of them or nothing: "; \ read ACCESS_METHOD; \ if command -v apt-get; then \ sudo apt-get install unzip tar xsltproc libxml-libxml-perl python3 $$ACCESS_METHOD; \ elif command -v pacman; then \ if [ `uname -o` = 'GNU/Linux' ]; then \ sudo pacman -S unzip tar libxslt libxml-perl python $$ACCESS_METHOD; \ else \ echo "Sorry, but it seems you're not running pacman on GNU/Linux. There's currently no installation rule for this platform."; \ fi; \ else \ echo "Unknown platform. Feel free to report the corresponding packages to us."; \ fi # This helps to express \n as a special variable to Make; it **needs** two # newlines within the define define newline endef # This may not contain apostrophes define VENV_HELP A virtual environment is a local copy of all Python utilities, scripts and libraries for Python development. With a virtual environment, you do not need to install all the FreeDict scripts globally in your system, but you are of course free to do so. The mk_venv command will make sure that the virtual environment is created at the correct place; use "P=/some/path" to specify the path. Example: make mk_venv P=../fd-venv NOTE: If you have not pkg-config on your system, you need to manually set the environment variable ICU_VERSION to the version of libicu on your system. This is due to some internal restructuring of the libicu library that we depend on. After installation, you will be asked whether the virtual environment should be added to the FreeDict configuration. This is generally a good idea, because this means that the FreeDict build system will take care of all the required steps. endef mk_venv-help: echo -e '$(subst $(newline),\n,${VENV_HELP})' mk_venv: #! initialise a new (Python) virtual environment; use mk_venv-help for detailled help @if [ "${P}" = '' ]; then \ echo Need to give a path with P=, e.g. "make mk_venv P=/some/dir"; \ exit 222; fi @if ! command -v virtualenv &> /dev/null; then \ echo '`virtualenv` not found, please install it and try again.'; exit 10; \ fi @virtualenv -q -p $(PYTHON) ${P} @if [ -z "$(ICU_VERSION)" ]; then \ if ! command -v pkg-config; then \ echo "Environment variable ICU_VERSION unset and 'pkg-config' not found."; \ echo "Please set ICU_VERSION to the version of libicu installed on your system."; \ exit 127; \ fi; \ export ICU_VERSION=`pkg-config --modversion icu-i18n`; \ fi; \ source ${P}/bin/activate; pip install -r requirements.txt @if [ "$(FREEDICTRC)" = "" ]; then \ echo "You don't have a FreeDict configuration yet. Please create one, "; \ echo 'as described in the chapter "Build System" of the FreeDict HOWTO';\ echo "from the Wiki";fi if ! [ -f $(FREEDICTRC) ]; then \ NO_CONF=1; \ elif ! grep virtual_env < $(FREEDICTRC) &> /dev/null; then \ NO_CONF=1; \ else NO_CONF=0; \ fi; \ if [ $$NO_CONF -eq 1 ]; then \ echo -n "Do you want to add the virtual_env to the FreeDict configuration? [y|n] "; \ read CHOICE;\ if [ "$$CHOICE" = "y" ]; then \ mkdir -p $(dir $(FREEDICTRC));\ touch $(FREEDICTRC);\ PATH=$(abspath ${P}); \ if ! grep -r '[DEFAULT]' $(FREEDICTRC) &> /dev/null; then \ echo >> $(FREEDICTRC);\ echo '[DEFAULT]' >> $(FREEDICTRC); \ echo "virtual_env = $$PATH" >> $(FREEDICTRC) ;\ else \ sed -i 's|\[DEFAULT\]|[DEFAULT]\nvirtual_env = '$$PATH'|' $(FREEDICTRC); \ fi; \ echo done; \ fi; \ fi # NOTE: the directories below HAVE to be on one line INSTALL_DIRS := api api/generator api/file_manager api/generator/apigen mk xquery xsl/inc INSTALL_PATHS := $(wildcard api/*.py api/generator/*.py api/generator/apigen/*.py api/file_manager/*.py \ freedict-database.rng \ mk/*.mk xsl/inc/* \ xquery/* xsl/tei2c5.xsl) install: #! install the tools to $$DESTDIR/$PREFIX/share/freedict (default /usr/local/share/freedict) install: @echo Creating directories in $(INSTALLDIR)… @set -e; for d in $(INSTALL_DIRS); do \ install -d $(INSTALLDIR)/$$d; \ done @echo Copying files to $(INSTALLDIR)… @set -e; for f in $(INSTALL_PATHS); do \ if [ -f $$f ]; then \ install -m 644 $$f $(INSTALLDIR)/$$f; \ fi \ done release: #! build a release tarball in $$BUILD_DIR, ../build by default release: $(BUILD_DIR)/freedict-tools-$(VERSION).tar.bz2 release-path: #! print the output directory to which releases are deployed (read from configuration); trailing newline is removed @$(call exc_pyscript,fd_file_mgr,-r) | tr -d '\n' .PHONY: release install api all mount umount api-validation tools-0.6.0/README.md000066400000000000000000000055321423006221500141340ustar00rootroot00000000000000FreeDict Tools =============== The FreeDict tools are used to import, export (build) and manage FreeDict dictionaries. Getting Started --------------- FreeDict databases are encoded in the TEI XML format (chapter 9), see . The conversion is based on XSL stylesheets (see directory `xsl/`). These can in principle transform to any format, but only the .dict format is supported at the moment. More information is in the wiki at . ### Dependencies You should have at least the following tools installed, to build the dictionaries: make, xsltproc, tar, gzip, dictzip, dictfmt For proper use of all our tools, Perl, eSpeakNG, Python > 3.4 are required, and Git and a XML-capable editor are recommended. #### Debian/Ubuntu Dependencies If you use Debian/Ubuntu, you should install the following packages: sudo apt-get install make xsltproc libicu-dev python3 python3-icu virtualenv python3-virtualenv espeak-ng git #### Windows On Windows, it is easiest to install [Msys2](https://www.msys2.org/) or [Cygwin](https://www.cygwin.com). Both offer an easy method to install the required tools. ### Setting Up The Build System You should clone this repository to a path with no spaces and add an environment variable `FREEDICT_TOOLS` to point to this directory. A lot of the internal scripts need additional Python libraries. To fully make use of them, you should set up a Python virtual environment for that. To help you getting started, `make mk_venv` is there to guide you through the process and `make mk_venv-help` will explain you why and how you should use `make mk_venv`. Hint: It is possible to set up a environment without virtualenv. See the file requirements.txt for more details. Once done, you can get help on the available actions in any directory containing a `Makefile` by typing `make help`. ### Documentation Most of the documentation can be found in the FreeDict HOWTO at https://github.com/freedict/fd-dictionaries/wiki/FreeDict-HOWTO In general, it is a good idea to have a look at our wiki at https://github.com/freedict/fd-dictionaries/wiki Furthermore, the whole build system is explained in chapter 8 of the HOWTO, mentioned above. Additional Output Formats ------------------------- For creating slob files, you need to install tei2slob: virtualenv env-slob -p python3 --system-site-packages # create self contained python env source env-slob/bin/activate # activate it pip install git+https://github.com/itkach/slob.git # install general slob tools pip install git+https://github.com/itkach/tei2slob.git # install tei2slob converter Now tei2slob will be in your path. For new shells, you will have to execute `source env-slob/bin/activate` again, or put env-slob/bin/tei2slob into your PATH. Sebastian Humenda, Mar 2018 tools-0.6.0/buildhelpers/000077500000000000000000000000001423006221500153325ustar00rootroot00000000000000tools-0.6.0/buildhelpers/README.md000066400000000000000000000003641423006221500166140ustar00rootroot00000000000000Build Helpers ============= This directory contains scripts to help building and installing FreeDict dictionaries. They are complementing the (GNU) make build system. Please document anything regarding those scripts here or link to the wiki. tools-0.6.0/buildhelpers/dict_restart_helper.sh000077500000000000000000000032021423006221500217140ustar00rootroot00000000000000#!/bin/sh #vim:set expandtab sts=4 ts=4 sw=4: # This script updates the dictd database and restarts dictd. It'll also try to # figure out whether dictd is installed in the first place and what the correct # way of restart is (upstart / systemd /init). set -e isDictInstalled() { if test -f /usr/sbin/dictd || command -v dictd > /dev/null || [ -f /sbin/dictd ] then return 0 else echo "The dictd server hasn't been found on this system." if command -v apt-get > /dev/null then echo 'Type `apt-get install dictd` before re-executing this script.' elif command -v pacman > /dev/null then echo 'Type `pacman -Syu dictd` before re-executing this script.' elif command -v yum > /dev/null then echo 'Type `yum install dictd` before re-executing this script.' else echo "Please install the dictd server before proceeding." fi exit 20 fi } restart_dictd_server() { echo "Restarting dictd..." if command -v service > /dev/null then service dictd restart elif command -v systemctl > /dev/null then systemctl restart dictd elif test -f /etc/init.d/dictd then /etc/init.d/dictd restart stop /etc/init.d/dictd restart start else echo "Error, could not detect init system. DICT SERVER HAS NOT BEEN RESTARTED!" fi } update_dictd_database() { dictdconfig -w } ################################################################################ # abort if dictd is not installed isDictInstalled update_dictd_database restart_dictd_server exit 0; tools-0.6.0/dic2ipk.sh000077500000000000000000000052511423006221500145370ustar00rootroot00000000000000#!/bin/bash # this script packages .dic (or .dic.dz) files # for the zbedic program on the Zaurus platform # into .ipk (installable packages) # the iPKG file format is loosely based on Debian's # package format. iPKG is documented at: # # * http://www.zaurususergroup.com/modules.php?op=modload&name=phpWiki&file=index&pagename=IPKG%20Howto # * http://www.ossh.com/zaurus/mirrors/docs.zaurus.com/ipkg_howto.shtml # (same text like forst page) # * http://www.handhelds.org/ # (The forst two links refer to the wiki here as reference, but # that page is currently (Jan 2005) unavailable # ===== the filenames INFILE=$1 EXTENSION=${INFILE#*.} OUTFILE=`basename ${1/.$EXTENSION}` # ===== check the syntax if [[ "$1" == "" ]]; then echo "USAGE: $0 " exit 1 fi # ==== is there a file-conflict if [ -d ./home ]; then echo "There's already a ./home folder. Please remove that first!" exit 1 fi if [ -e ./control ]; then echo "There's already a control file. Remove it first!" exit 1 fi if [ -e ./control.tar.gz ]; then echo "There's already a control.tar.gz file. Remove it first!" exit 1 fi if [ -e ./data.tar.gz ]; then echo "There's already a data.tar.gz file. Remove it first!" exit 1 fi # ===== image type for the maps # ===== make it lowercase EXTENSION=`echo "$EXTENSION" | tr [A-Z] [a-z]` if [[ "$EXTENSION" == "jpeg" ]]; then EXTENSION=jpg fi # ===== no converter specified? #if [[ "`which convert`" == "" ]]; then # echo Cannot find the program convert! # exit 2 #fi if [[ "$EXTENSION" == "" ]]; then echo Cannot identify the image-type or cannot find the file! exit 2 fi if [ -d ./${OUTFILE} ]; then echo "There's already an image folder ${OUTFILE}." exit 2 fi # ===== renaming files # ===== adding overview # ===== leaving image-folder # ===== creating folders for ipkg mkdir ./home mkdir ./home/QtPalmtop mkdir ./home/QtPalmtop/share mkdir ./home/QtPalmtop/share/zbedic # ===== moving the image-files into the right folder cp ${INFILE} ./home/QtPalmtop/share/zbedic/ # ===== make a nice ipkg DATE=$(date +"%Y%m%d") FILE=./zbedic-${OUTFILE}_${DATE}_arm.ipk # ===== package-information echo -ne "Package: FreeDict-${OUTFILE} InstalledSize: $(du -sh ./home | cut -f1) Filename: ${FILE} Maintainer: $(whoami) Architecture: arm Version: ${DATE} Description: FreeDict ${OUTFILE} dictionary for zbedic " > ./control # ===== creating tar -czf data.tar.gz ./home tar -czf control.tar.gz ./control tar -czf ${FILE} ./data.tar.gz ./control.tar.gz # ===== removing temp-files rm ./data.tar.gz ./control.tar.gz ./control # ===== removing the NEW folders and NEW created images rm -rf ./home echo ${FILE} tools-0.6.0/dict-configure.sh000077500000000000000000000031721423006221500161140ustar00rootroot00000000000000#!/bin/sh # Shell script to configure a dictionary into place. DICTD_CONF=/etc/dictd.conf DICT_SCRIPT=/etc/rc.d/init.d/dict GDICT_CONF=/var/lib/gdict-dicts.conf Dict_Add_Dictionary () { # Add a dictionary to the DICTD_CONF file DICT=$* cat >> $DICTD_CONF >> EOF database $DICT { data "/usr/lib/dict/$DICT.dict.dz" # $DICT index "/usr/lib/dict/$DICT.index" # $DICT access { # $DICT allow * # $DICT } # $DICT } # $DICT EOF Restart_Dict_Server } Dict_Remove_Dictionary () { # Remove a dictionary from the DICTD_CONF file DICT=$* grep -v $DICT $DICTD_CONF > /tmp/dictd.conf.tmp mv /tmp/dictd.conf.tmp > /tmp/dictd.conf.tmp } Restart_Dict_Server () { if [ -f $DICT_SCRIPT ] ; then $DICT_SCRIPT stop $DICT_SCRIPT start } Gdict_Add_Dictionary () { DICT=$1 LANG=$2 cat GDICT_CONF << EOF (define-gdict-dict "$LANG" "$DICT") ; installed by dict-$LANG EOF } Gdict_Remove_Dictionary () { LANG=$1 grep -v "dict-$DICT" $GDICT_CONF > /tmp/gdict-dicts.conf mv /tmp/gdict-dicts.conf $GDICT_CONF } # # Program starts here # case "$1" in '--install') DICT=$2 LANG=$4 Dict_Add_Dictionary $DICT GDict_Add_Dictionary $DICT $LANG exit 0; ;; '--remove') DICT=$2 Dict_Remove_Dictionary $DICT Gdict_Remove_Dictionary $DICT exit 0; ;; default) echo "Usage: $0 --install --lang , or" echo " $0 --remove " exit 1; ;; esac exit 1;tools-0.6.0/exporters/000077500000000000000000000000001423006221500147035ustar00rootroot00000000000000tools-0.6.0/exporters/tei2anki/000077500000000000000000000000001423006221500164115ustar00rootroot00000000000000tools-0.6.0/exporters/tei2anki/tei2anki.py000077500000000000000000000031051423006221500204730ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import random from xml.etree import ElementTree as etree from ankisync.apkg import Apkg NS = 'http://www.tei-c.org/ns/1.0' NS_MAP = {'': NS, 't': NS} def ns(name): return '{%s}%s' % (NS, name) TAG_ENTRY = ns('entry') TAG_HEADER = ns('teiHeader') def parse_entry(element): orths = element.findall('./t:form//t:orth', NS_MAP) cits = element.findall('./t:sense//t:cit/t:quote', NS_MAP) return orths[0].text, ', '.join(c.text for c in cits) assert len(sys.argv) == 2, 'Usage: tei2anki.py FROM-TO.tei' input_filename = sys.argv[1] output_filename = input_filename.replace('.tei', '.apkg') entries = [] with open(input_filename) as input_file: for _, element in etree.iterparse(input_file): if element.tag == TAG_HEADER: deck_name = element.find('./t:fileDesc/t:titleStmt/t:title', NS_MAP).text if element.tag == TAG_ENTRY: entries.append(parse_entry(element)) with Apkg(output_filename) as apkg: model_id = apkg.init( first_model=dict( name='default model', fields=['source', 'target'], templates={ 'Forward': ('{{source}}', '{{source}}
{{target}}'), } ), first_deck=deck_name, first_note_data=False, ) for source, target in random.sample(entries, 100): apkg.add_note({ 'modelName': 'default model', 'deckName': deck_name, 'fields': { 'source': source, 'target': target, } }) tools-0.6.0/fd_tool/000077500000000000000000000000001423006221500142765ustar00rootroot00000000000000tools-0.6.0/fd_tool/README.md000066400000000000000000000050001423006221500155500ustar00rootroot00000000000000File Mgmt ========= The scripts in this directory are able to set up everything required to - release dictionaries - access out-of-VCS auto-imported dictionaries - generate the FD API As a dictionary developer, you don't need to care about this. :) Configuration ------------- The utilities require a configuration file with a few options set. Open or create a configuration file in `$HOME/.config/freedict/freedictrc` in your favourite editor and enter something like this: [DEFAULT] file_access_with = sshfs [release] user=SFACCOUNT,freedict [generated] user=USER - `file_access_with`: sets the file access strategy; possible values are unison or sshfs, default is unison - unison: synchronisation of all files to local disk -- quicker operations, more initial download - sshfs (UNIX only): direct access over a network connection, slower actions and problematic, since direct file manipulation - `[release]`: server on which to look for the released dictionaries, by default freedict.org. - `[generated]`: this configures account information for the file services containing all auto-generated dictionaries This minimal configuration should be enough for most of the use cases. For a more in-depth explanation, you should visit the [wiki page](https://github.com/freedict/fd-dictionaries/wiki/FreeDict-HOWTO-%E2%80%93-FreeDict-Build-System). There is a freedictrc.example in this directory, too. API Generation -------------- The API generation consists of two steps. In the first step, remote files are made available, in the second, the actual XML file is generated. Remote files are all released files and the auto-imported dictionaries. In order to make them available, either SSHFS or Unison can be used. This can be configured in the FreeDict configuration, see . The `file_manager` will take care of doing this work, transparently. The API generation requires the `FREEDICT_TOOLS` environment variable to be set. Information about this variable can be found [here](https://github.com/freedict/fd-dictionaries/wiki/FreeDict-HOWTO).\ The invocation of the API generator is automated in the make rule `api`, so in most cases, `make api` should be enough. File Management =============== Fsetup is designed to set up the environment for managing and releasing FreeDict dictionaries, as well as generating the API. This mainly involves copying (or remote-mounting) released dictionaries and out-of-VCS (auto-imported) dictionaries. tools-0.6.0/fd_tool/fd_api.py000066400000000000000000000130021423006221500160660ustar00rootroot00000000000000"""FreeDict API generator This script parses the meta data of all available dictionaries, queries information about available releases and writes all information to an XML file. For more information, please have a look at the wiki at . For usage of the script, try the -h option. """ import argparse import os import sys import time from fd_tool.api import dictionary, jsonhandlers, metadata, releases, xmlhandlers from fd_tool import config from fd_tool.config import get_path def exec_or_fail(command): """If command is not None, execute command. Exit upon failure.""" if command: ret = os.system(command) if ret: print("Failed to execute `%s`:" % command) sys.exit(ret) def read_dict_info(conf, generate_api=True): """Parse dictionary meta data from the dictionary source and extract information about all released dictionaries. Return a list of dictionaries (Dictionary objects). generate_api is used to emit a message which is only useful if the API is generated.""" dictionaries = [] for dict_source in ['crafted', 'generated']: dict_source = get_path(conf[dict_source]) print("Parsing meta data for all dictionaries from", dict_source) dictionaries.extend(metadata.get_meta_from_xml(dict_source)) release_path = get_path(conf['release']) print("Parsing release information from", release_path) release_files = releases.get_all_downloads(release_path) for dict in dictionaries: name = dict.get_name() if not name in release_files: if generate_api: print("Skipping %s, no releases found." % name) continue try: version = releases.get_latest_version(release_files[name]) except releases.ReleaseError as e: # add file name to error raise releases.ReleaseError(list(e.args) + [name]) for full_file, format, sha in release_files[name][version]: dict.add_download(dictionary.mklink(full_file, format, version, sha)) return dictionaries def find_outdated_releases(dictionaries): """This function finds dictionaries which have been updated, but not released yet.""" candidates = [] for dict in dictionaries: if dict.get_downloads() == []: candidates.append((dict.get_name(), None, None)) else: released = max(l.version for l in dict.get_downloads()) if dict['edition'] > released: candidates.append((dict.get_name(), dict['edition'], released)) return candidates def main_body(args): parser = argparse.ArgumentParser(description='FreeDict API generator') parser.add_argument("-n", "--need-update", dest="check_for_unreleased_dicts", action="store_true", default=False, help="check for unreleased dictionaries instead of generating the API file") parser.add_argument('-p', "--pre-exec-script", dest="prexec", metavar="PATH", help=('script/command to execute before this script, e.g. to set up a sshfs ' 'connection to a remote server, or to invoke unison.')) parser.add_argument('-o', "--post-exec-script", dest="postexc", metavar="PATH", help=("script/command to execute after this script is done, e.g. to " "umount mounted volumes.")) args = parser.parse_args(args[1:]) conf = config.discover_and_load() exec_or_fail(args.prexec) # mount / synchronize release files dictionaries = read_dict_info(conf, not args.check_for_unreleased_dicts) if args.check_for_unreleased_dicts: outdated = find_outdated_releases(dictionaries) if not outdated: print("Everything up-to-date.") else: print("\nName Source Version Release Version") print("------- --------------- --------------------------") for data in sorted(outdated, key=lambda x: x[0]): name, v1, v2 = [str(e if e else 'unknown') for e in data] print('{} {:<15} {:<15}'.format(name, v1, v2)) else: # remove dictionaries without download links dictionaries = sorted((d for d in dictionaries if d.get_downloads() != []), key=lambda entry: entry.get_name()) tools = releases.get_latest_tools_release() api_path = config.get_path(conf['DEFAULT'], key='api_output_path') xml_path = os.path.join(api_path, 'freedict-database.xml') json_path = os.path.join(api_path, 'freedict-database.json') if not os.path.exists(api_path): os.makedirs(os.path.dirname(api_path)) print("Writing XML API file to",xml_path) xmlhandlers.write_freedict_database(xml_path, dictionaries, tools) print("Writing JSON API file to",json_path) jsonhandlers.write_freedict_database(json_path, dictionaries, tools) # if the files had been mounted with sshfs, it's a good idea to give it some # time to synchronize its state, otherwise umounting fails time.sleep(2) exec_or_fail(args.postexc) # umount or unison files, if required def main(): """Wrapper for nicer error case handling.""" #pylint: disable=broad-except try: main_body(sys.argv) except Exception as e: if 'DEBUG' not in os.environ: print('Error:',str(e)) print(("\nNote: Rerun the script with the environment variable DEBUG=1 " "to obtain a traceback.")) sys.exit(9) else: raise e if __name__ == '__main__': main() tools-0.6.0/fd_tool/fd_changelog.py000066400000000000000000000215571423006221500172620ustar00rootroot00000000000000#!/usr/bin/env python3 """Releasing a bunch of dictionaries can be tedious because of the adjustments to the header. This script automated the following: * update the date tag * update the copyright year within the availability information, because a new release also renews the copyright * open $EDITOR to ask the user for a changelog message * count headwords * update the edition For the concrete usage see the corresponding usage message.""" #pylint: disable=wrong-import-position import datetime import os import re import shutil import sys from fd_tool import config def get_editor(): """Detect an editor to use. Try to use $EDITOR or probe for a bunch of more common ones.""" editor = None if 'EDITOR' in os.environ: editor = os.environ['EDITOR'] if not editor: for name in ('vim', 'vi', 'nvim', 'emacs', 'nano', 'edit', 'notepad', 'gedit', 'pluma', 'kate'): # a few more popular ones if shutil.which(name): editor = name break if not editor: print(("Failed to detect an editor. Please set the EDITOR environment " "variable to a program in your PATH.")) sys.exit(1) else: return editor class TagNotFoundException(Exception): pass def find_tag(document, tag): """Find a tag in a document, returning start and end positions of the opening and closing tag. Raise TagNotFoundException if no such tag exists.""" match = re.search(r'<\s*%s\b.*?>' % tag, document) if not match: raise TagNotFoundException(tag) opening_start, opening_end = match.span() # find closing tag match = re.search(r'<\s*/\s*%s\s*>' % tag, document[opening_end:]) if not match: raise TagNotFoundException('no closing tag for `%s`' % tag) closing_start, closing_end = (c + opening_end for c in match.span()) # python's ranges are exclusive, so add + 1 return (opening_start, opening_end, closing_start, closing_end) def get_text(document, tag): """Get the text of a specified tag. Raise TagNotFoundException if no such tag exists.""" _, start, end, _ = find_tag(document, tag) return document[start:end] def replace_tag_content(document, tag, new_text): """Insert the given new content into the first occurrence of this tag in the given document.""" _, start, end, _ = find_tag(document, tag) return document[:start] + new_text + document[end:] def get_user_info(conf): """Obtain (GitHub) user name and full name from configuration. Exit if user name is not set. Use system name if full name (AKA real name) is not set.""" try: user_name = conf['DEFAULT']['user_name'] except KeyError: sys.stderr.write(("user_name not set in FreeDict configuration. Please " "set it and try again.")) sys.exit(28) try: real_name = conf['DEFAULT']['full_name'] except KeyError: # guess name, could be not correct if 'win32' in sys.platform or 'wind' in sys.platform: import getpass real_name = getpass.getuser() else: # on unixoids, use pwd import pwd real_name = pwd.getpwuid(os.getuid())[4] # on some systems, real name end with commas, strip those while real_name and not real_name[-1].isalpha(): real_name = real_name[:-1] return (user_name, real_name) def add_changelog_entry(document, edition, date, username, author): """Try to detect an text editor, open it and add the written content to a new change tag within the supplied revision_desc.""" editor = get_editor() fn = 'changelog.tmp' with open(fn, 'w', encoding='UTF-8') as f: f.write("""\n # Please enter your change notes as you would enter them in a tag in # the revisionDesc tag of a TEI header. Empty lines will be ignored and lines # starting with a hash `#` will be ignored. # Valid formatting include either plain text or lists like # `blah`. Please note that you need to take care of # escaping yourself. """) ret = os.system('%s %s' % (editor, fn)) if ret: print("Error while starting", editor) sys.exit(2) with open(fn, encoding='UTF-8') as f: data = '\n'.join(l.rstrip() for l in f if l.strip() and not l.lstrip().startswith('#')) os.remove(fn) if not data.strip(): print("No changes, aborting…") sys.exit(0) # insert full name if no reference to the user name found, otherwise use the # reference change = None if re.search('id\\s*=\\s*(?:\'|")#' + username, document): change = '\n'.format(edition, date, username.rplace('#', '')) else: change = '\n{}\n' \ .format(edition, date, author) change += '%s\n' % data latest_change, _, _, _ = find_tag(document, 'change') latest_change_tag = latest_change if latest_change > 0 and document[latest_change-1].isspace(): latest_change -= 1 while latest_change > 0 and document[latest_change].isspace() and \ document[latest_change] != '\n': latest_change -= 1 indent = document[latest_change+1:latest_change_tag] change = change.rstrip().replace('\n', '\n' + indent) return ''.join((document[:latest_change], '\n', indent, change, '\n', indent, document[latest_change_tag:])).lstrip() def update_date(document, date): """Find publicationStmt/date, update it.""" try: opening_start, _, _, closing_end = find_tag(document, 'date') except TagNotFoundException: sys.stderr.write(("Warning: tag not found. It's advised that " "this is added to the header.\n")) return document if opening_start < 0: return document # no date, no action # try to detect whether this date is within a change tag change = document[:opening_start].rfind(' 0: return document date = datetime.datetime.now().strftime('%b %d, %Y') return document[:opening_start] + date + document[closing_end:] def update_edition(document, version): """Update edition within the edition tag.""" return replace_tag_content(document, 'edition', version) def update_extent(document): """Count headwords within document and update the headword count of the extend tag.""" headwordcount = len(re.findall(r'<\s*entry.*?>', document)) return replace_tag_content(document, 'extent', '%s headwords' % headwordcount) def update_copyright(document): """Find a stanza containing "(c) 2014-2017 xyz" or "© 2020 foo" to update the year and therfore update copyright information.""" availability = get_text(document, 'availability') match = re.search(r'(©|\([cC]\))\s*([0-9]{4})(?:-)([0-9]{4})', availability) if not match: return document start, end = match.span() year = match.groups()[1] if match.groups()[2]: year = match.groups()[2] availability = availability[:start] + availability[start:end].replace(year, datetime.datetime.now().strftime('%Y')) + \ availability[end:] return replace_tag_content(document, 'availability', availability) def parse_args(): def usage(msg=None): if msg: print(msg) print("Usage: make E=") print(" or: %s " % \ os.path.basename(sys.argv[0])) print("\nThis script assists in changing a TEI header for release. It does the following:") print("- update the edition") print("- updates the release date") print("- adds a new changelog entry") print("- update the year in the availability / copyright section, if appropriate") print("\nExample: make E=1.5.2 lat-deu.tei") sys.exit(0) if len(sys.argv) == 2 and sys.argv[1] in ('-h', '--help'): usage() elif len(sys.argv) != 3: usage("The arguments edition and input file are mandatory.") else: return sys.argv[1:3] def main(): edition, input_file = parse_args() conf = config.discover_and_load() isodate = datetime.datetime.today().strftime('%Y-%m-%d') username, full_name = get_user_info(conf) with open(input_file, 'r', encoding='UTF-8') as f: document = f.read() document = add_changelog_entry(document, edition, isodate, username, full_name) document = update_date(document, isodate) document = update_copyright(document) document = update_extent(document) document = update_edition(document, edition) with open(input_file, 'w', encoding='UTF-8') as f: f.write(document) print("You might want to re-indent the latest to fit it to the rest of the document.") if __name__ == '__main__': main() tools-0.6.0/fd_tool/fd_file_mgr.py000066400000000000000000000166731423006221500171220ustar00rootroot00000000000000#!/usr/bin/env python3 """This script makes remote files available for local processing. Remote files are e.g. the released files hosted on a server as downloads or the auto-generated dictionaries, kept outside the git repository. This script requires a configuration. Please see the README for more details. Running this script with the `-h` option will give an overview about its usage.""" import argparse import os import subprocess import sys from fd_tool import config def execute(cmd, raise_on_error=False): """Execute a command; if the return value is != 0, the program either terminates or an exception is raised.""" proc = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) text = (e.decode(sys.getdefaultencoding()) for e in proc.communicate()) ret = proc.wait() if ret: text = '\n'.join(text).strip() if text.startswith('fusermount: ') and 'not found in /etc/mtab' in text: return # umounting something which isn't mounted is not harmful, ignore text = ('Subcommand failed with exit code %s\n' 'Command: %s\n%s\n') % (ret, cmd, text) if raise_on_error: raise OSError(text) else: print(text) if ret >= 255: ret = 1 sys.exit(ret) class UnisonFileAccess: """This class is one of two classes to allow access to remote files using unison. The drawback with unison is that before the usage by other scripts, all files have to be downloaded. On the other hand, this might speed up subsequent runs and allows offline work. On Windows, it might be desirable to use unison, because sshfs is not officially ported to Windows.""" def __init__(self): # save make_available arguments for make_unavailable self.args = tuple() def name(self): return "unison" def make_available(self, user, server, remote_path, path): """Synchronize files to have them available locally.""" self.args = (user, server, remote_path, path) # set UNISON=`path` to avoid usage of any $HOME/.unison/default.prf oldunison = None if 'UNISON' in os.environ: oldunison = os.environ['UNISON'] os.environ['UNISON'] = os.path.join(path, '.unison') ret = os.system("unison -auto -log -times -contactquietly -terse " + \ "-ignore 'Regex .*.swp' -ignore 'Regex .*.swo' " + \ "-ignore 'Regex .*/build' " + \ "-ignore 'Regex .*~' -ignore 'Regex .unison.*' " + \ "ssh://{}@{}/{}/ {}".format(user, server, remote_path, path)) if ret: raise OSError("Process gave error code %d" % ret) if oldunison: os.environ['UNISON'] = oldunison #pylint: disable=unused-argument def make_unavailable(self, path): """Synchronise in case files were created.""" if not self.args: return self.make_available(*self.args) class SshfsAccess: """This class mounts and umounts the remote files using sshfs. This will work on any system that fuse runs on, namely GNU/Linux, FreeBSD and Mac.""" def name(self): return 'sshfs' def make_available(self, user, server, remote_path, path): """Mount remote file system using sshfs.""" # is mounted? if os.path.ismount(path): # mounted, -m help says we need to return 201 return 201 # and no action if len(os.listdir(path)) > 0: print("Error: %s has to be empty, otherwise mounting impossible." % path) sys.exit(41) return execute('sshfs {}@{}:{} {}'.format(user, server, remote_path, path)) def make_unavailable(self, path): execute('fusermount -u {}'.format(path), raise_on_error=True) def setup(): """Find freedict directory and parse command line arguments. Return a tuple with the freedict directory and the configuration object.""" # parse command line options parser = argparse.ArgumentParser(description='FreeDict build setup utility') parser.add_argument('-a', dest="print_api_path", action='store_true', help=("print output directory where the freedict-database.xml and " "json are stored; the value is read from the local " "configuration")) parser.add_argument('-m', dest="make_available", action='store_true', help='''make files in generated/ and release/ available; this will \ use internally either sshfs or unison, depending on the \ configuration. The script will exit with 0 on success, \ with 201 if remote filesystem was mounted already and with \ the error code of the appropriate subcommand otherwise.''') parser.add_argument('-r', dest="print_release_path", action='store_true', default=False, help=("print output directory to which releases are " "deployed. the value is read from the local configuration")) parser.add_argument('-u', dest='umount', action='store_true', help='clean up actions for release/ and generated/, e.g. umount of fuse mount points, etc.') args = parser.parse_args() # check for contradicting options if args.umount and args.make_available: print("Error: you can only specify -u or -m exclusively.") sys.exit(44) if not any((args.umount, args.make_available, args.print_api_path, args.print_release_path)): print("Error: No option specified") parser.print_help() return args def main(): args = setup() try: # load configuration conf = config.discover_and_load() except config.ConfigurationError as e: print(e) sys.exit(42) if args.print_api_path: print(config.get_path(conf['DEFAULT'], key='api_output_path')) sys.exit(0) elif args.print_release_path: print(config.get_path(conf['release'], key='local_path')) sys.exit(0) access_method = UnisonFileAccess() if conf['DEFAULT']['file_access_via'] == 'sshfs': access_method = SshfsAccess() release_directory = config.get_path(conf['release']) if not os.path.exists(release_directory): try: os.makedirs(release_directory) except OSError: # if the file does exist, but the fuse endpoint is _not_ connected, # we could try running fusermount -u: os.system('fusermount -u "%s"' % release_directory) ret = 0 if args.make_available: for section in ('release', 'generated'): if conf[section].getboolean('skip'): print("Skipping",section) continue print('Making files for "%s" available...' % section) options = conf[section] target_path = config.get_path(options) ret = access_method.make_available(options['user'], options['server'], options['remote_path'], target_path) elif args.umount: for section in ('generated', 'release'): if conf[section].getboolean('skip'): print("Skipping",section) continue target_path = config.get_path(conf[section]) try: access_method.make_unavailable(target_path) except OSError as e: print(e.args[0]) continue sys.exit(ret) if __name__ == '__main__': main() tools-0.6.0/fd_tool/fd_tool/000077500000000000000000000000001423006221500157245ustar00rootroot00000000000000tools-0.6.0/fd_tool/fd_tool/__init__.py000066400000000000000000000000001423006221500200230ustar00rootroot00000000000000tools-0.6.0/fd_tool/fd_tool/api/000077500000000000000000000000001423006221500164755ustar00rootroot00000000000000tools-0.6.0/fd_tool/fd_tool/api/config.py000066400000000000000000000014301423006221500203120ustar00rootroot00000000000000"""Constants for the freedict-API generator.""" import re # domain where downloads are stored PROJECTHOME_HOST = 'download.freedict.org' # base on web server, used for the link generation RELEASE_HTTP_BASE = '/dictionaries' # base for the freedict tools releases, **ful URL** RELEASE_HTTP_TOOL_BASE = 'https://download.freedict.org/tools/' # pattern to identify dictionaries; matches three-digit ISO 6639 letter codes DICTIONARY_PATTERN = re.compile(r'(?:freedict-)?([a-z]{3}-[a-z]{3}).*') # Pattern to identify directory names of directories; matches English names of # languages, separated by " - " DICTIONARY_DIRECTORY_PATTERN = re.compile(r"[A-Z][a-z]+%20-%20[A-z][a-z]+") # Identification of versions; basically distutils.version.StrictVersion, but # permits "-" as separator, too tools-0.6.0/fd_tool/fd_tool/api/dictionary.py000066400000000000000000000176341423006221500212270ustar00rootroot00000000000000"""This module holds datastructures to represent a dictionary and links to their downloads and a few function to retrieve useful information about these objects.""" import datetime import distutils.version import enum import os import re import urllib.request import semver from . import config class Dictionary: """Dictionary class holding various properties of a dictionary. This class is intended to hold all properties of a Dictionary node in the resulting XML API file. A dictionary object consists of optional and mandatory information: mandatory: headwords, edition, date optional = maintainerName, maintainerEmail, status, sourceURL The date should be the date of the last release. Additionally, a dictionary keeps a list of downloads which may be empty. Each download has to be a Link() object. """ def __init__(self, name): self.__name = name # mandatory dictionary information mandatory = ["headwords", "edition", "date"] self.__mandatory = {f: None for f in mandatory} # optional dictionary info self.__optional = { f: None for f in ["maintainerName", "maintainerEmail", "status", "sourceURL"] } self.__downloads = [] def get_name(self): """Return name of dictionary. This is a three-letter-code followed by a hyphen and followed by a three-letter-code. Example: deu-fra""" return self.__name def add_download(self, link): """Add a link (of type Link) to the linst of downloadss.""" if not isinstance(link, Link): raise TypeError("Link must be of type Link()") self.__downloads.append(link) def get_downloads(self): """Return all download links.""" return self.__downloads def __getitem__(self, key): """Transparently select a key from either optional or mandatory keys.""" if key in self.__mandatory: return self.__mandatory[key] if key in self.__optional: return self.__optional[key] raise KeyError(key) def __contains__(self, key): try: self.__getitem__(key) except KeyError: return False else: return True def __setitem__(self, key, value): if key in self.__mandatory: self.__mandatory[key] = value elif key in self.__optional: self.__optional[key] = value else: raise KeyError(key) def get_mandatory_keys(self): """Return all mandatory keys.""" return self.__mandatory.keys() def is_complete(self): """Return true if all mandatory fields are set, else false.""" return not self._get_missing_keys() def _get_missing_keys(self): """Return list of keys which haven't been set yet but which are mandatory. Empty list means everything has been set.""" return [k for k in self.__mandatory if self.__mandatory[k] is None] def update(self, other): """This method works like the .update method on a dictionary, but it raises an exception whenever an unknown key is found in the supplied dictionary.""" if not hasattr(other, "__getitem__") or not hasattr(other, "keys"): raise TypeError("Object must provide methods keys() and __getitem__.") for key in other.keys(): self[key] = other[key] def get_attributes(self): """Return all attributes which make up the dictionary node in the FreeDict XML. If mandatory attributes are not set, this function WILL NOT raise an exception, consequently, is_complete() must be called beforehand. Unset values are None. Hint: the name is not contained, use get_name() instead.""" attributes = self.__mandatory.copy() attributes.update(self.__optional) return attributes # only match x.x, x.x.x, x-y-z, x-z DICTIONARY = "([a-z]{3}-[a-z]{3})" class DownloadFormat(enum.Enum): """The download format, consisting both of the platform and the archive format. Some formats might not have a archive format, though.""" Source = re.compile( r"freedict-%s-(.*?).src.(?:zip|tar.bz2|tar.gz|tar.xz)" % DICTIONARY ) DictTxz = re.compile(r"freedict-%s-(.*?).dictd.tar.xz" % DICTIONARY) Slob = re.compile(r"freedict-%s-(.*?).slob" % DICTIONARY) # legacy formats DictTgz = re.compile(r"freedict-%s-(.*?).dictd.tar.gz" % DICTIONARY) DictBz2 = re.compile(r"freedict-%s-(.*?).dictd.tar.bz2" % DICTIONARY) Dic = re.compile(r"freedict-%s-(.*?).dic.tar.*" % DICTIONARY) @staticmethod def get_type(file_name): """This function allows to get the correct enum value from a given file_name. The file name is parsed and the corresponding enum value returned. If the format could not be extracted, None is returned.""" if file_name.endswith(".sha512"): return None format = DownloadFormat.Source for format in DownloadFormat: if format.value.search(file_name): return format return None def __str__(self): """Return a string representation of the enum value, as used in the type attribute of the release tag in the FreeDict XML API.""" if self is DownloadFormat.Source: return "src" if self is DownloadFormat.DictTgz: return "dict-tgz" if self is DownloadFormat.DictBz2: return "dict-bz2" if self is DownloadFormat.DictTxz: return "dictd" if self is DownloadFormat.Slob: return "slob" raise ValueError("Unsupported format: " + self.name) class Link: """Represent a (download) link. The link is made of of multiple parts. The hostname and base URI is taken from variables defined in the module config. The given path is assumed to exist on disk, so that the file can be determined. Additionally to the given link path, a link also saves the information about the file format (dict-bz2 or source) and also the version of the dictionary. It also mandates a hash for verification of the file.""" def __init__(self, path, format, version, sha): self.path = path self.format = format self.version = version self.size = -1 self.last_modification_date = "NONE" # YYYY-MM-dd self.hash = sha def __str__(self): """Get a download link.""" # split the path into chunks, url-quote them path = tuple(map(urllib.request.quote, self.path.split(os.sep))) if len(path) < 3: raise ValueError( "Required is a path with the structure LongName/version/filename" ) path_base = config.RELEASE_HTTP_BASE if not path_base.endswith("/"): path_base += "/" return "https://{}{}{}/{}/{}".format( config.PROJECTHOME_HOST, path_base, path[-3], path[-2], path[-1] ) def normalize_version(vers_string): """Make a semantic version out of a less strict version number , e.g. 0.5 to 0.5.0.""" try: return semver.parse(vers_string) except ValueError: return semver.parse(".".join(distutils.version.StrictVersion(vers_string).version)) def mklink(full_path, format, version, sha): """Create a Link object with all the required information, i.e. file size. It queries the referenced `full_path` for its size and last modification date. It doesn't care whether it's actually on the file system or mounted with e.g. sshfs.""" chunks = full_path.split(os.sep) path = "{}/{}/{}".format(chunks[-3], chunks[-2], chunks[-1]) link = Link(path, format, version, sha) # get file size link.size = os.path.getsize(full_path) # get last modification date link.last_modification_date = datetime.datetime.fromtimestamp( os.path.getmtime(full_path) ).strftime("%Y-%m-%d") return link tools-0.6.0/fd_tool/fd_tool/api/jsonhandlers.py000066400000000000000000000023431423006221500215430ustar00rootroot00000000000000"""JSON output writer.""" import json def write_freedict_database(path, dicts, tools): """Write a freedict database to ''path`` in JSON format.""" serialized = [] for dictionary in dicts: if not dictionary.is_complete(): raise ValueError("Not all mandatory keys set.") essence = {k: v for k, v in dictionary.get_attributes().items() if not v is None} essence['name'] = dictionary.get_name() # set maintainer if none present if 'maintainerName' not in dictionary or not dictionary['maintainerName']: essence['maintainerName'] = 'FreeDict - no maintainer assigned' essence['releases'] = [] for release in dictionary.get_downloads(): essence['releases'].append({'platform': str(release.format), 'size': str(release.size), 'date': release.last_modification_date, 'URL': str(release), 'version': str(release.version), 'checksum': release.hash, }) serialized.append(essence) serialized.append({'software': {'tools': tools}}) with open(path, 'w', encoding='utf-8') as fhandle: json.dump(serialized, fhandle, indent=2, sort_keys=True) tools-0.6.0/fd_tool/fd_tool/api/metadata.py000066400000000000000000000206341423006221500206340ustar00rootroot00000000000000"""This file provides classes to parse all dictionaries to extract the required meta data for the freedict-database.xml. The meta data comes from the header of each dictionary and gives details on the quality or size of a dictionary.""" import datetime import html.parser import io import os import re from xml.etree import ElementTree as ET from . import dictionary from . import xmlhandlers from .xmlhandlers import istag class MetaDataParser(xmlhandlers.TeiHeadParser): """Parse a TEI XML dictionary for required and optional meta data using Python's iterparse facility. It guesses the path on it's own, unless a path is given as optional parameter. For each element where data should be extracted, there is a handle_ which is called if it exists. This is a common class from which concrete parserfs can be derived. They all share the facility to parse TEI XML, but they can differ in they way they retrieve the TEI XML. While the local file system is a natural choice, one could choose to retrieve the information over HTTP(s). After the header has been parsed, the .dictionary attribute holds a reference to a populated Dictionary object.""" # pattern to match mtaintainer in tag MAINTAINER_PATTERN = re.compile(r'^([^<]+)\s*$') # match number of headwords in pattern HEADWORD_PATTERN = re.compile(r'(\d+(?:\.|,|\s*)\d*).*') # ISO date pattern DATE_PATTERN = re.compile(r'\d{4}-\d{2}-\d{2}') def __init__(self, name, xml): # can be a file object or a string with XML data xml = self.get_file_object(xml) super().__init__(xml) self.dictionary = dictionary.Dictionary(name) self.dictionary['date'] = None def get_file_object(self, src): """Transparently create a file object. If this method gets a file object, it will return is unchanged, otherwise the given string is wrapped in a StringIO object.""" if hasattr(src, 'read') and hasattr(src, 'close'): return src # is already file object elif isinstance(src, bytes): return io.BytesIO(src) elif isinstance(src, str): return io.StringIO(src) else: raise ValueError("Either file object, str or byte array " + \ "expected, got %s." % type(src)) def parse(self): try: super().parse() except ET.ParseError as e: print(("Warning: while parsing {} an error occurred. Might be still " "ok though.{}").format(self.dictionary.get_name(), '; '.join(e.args))) # check whether parsing was successful if not self.dictionary.is_complete(): missing = [k for k in self.dictionary.get_mandatory_keys() if not self.dictionary[k]] raise ValueError("%s: the following information couldn't be read: "\ % self.dictionary.get_name() + ', '.join(missing)) def handle_tag(self, elem): """Delegate parsing of XML tags to specialised functions.""" if istag(elem, 'date') or istag(elem, 'change'): self.__extract_date(elem) tag = elem.tag.split(self._namespace)[-1] # strip etree namespace funcname = 'handle_%s' % tag if not hasattr(self, funcname): return result = getattr(self, funcname)(elem) if result: self.dictionary.update(result) def handle_sourceDesc(self, node): """Extract a source url, if any.""" ptr = node.findall('.//%sptr' % self._namespace) if ptr: return {'sourceURL': ptr[0].get('target')} # search for source URL within ' encountered.') def handle_extent(self, elem): """Extract extent (number of headwords).""" match = MetaDataParser.HEADWORD_PATTERN.search(elem.text) if not match: raise ValueError("Could not extract number of headwords from " + repr(elem.text)) headwords = ''.join(char for char in match.groups()[0] if char.isdigit()) return {'headwords': headwords} def handle_respStmt(self, respStmt): """Maintainer is in , this one can contain nesting for author and maintainer, hide complexity and return either maintainer, then author or None.""" # find name attribute name = respStmt.find(self._namespace + 'name') if name is None: return resp = respStmt.findall(self._namespace + 'resp') if resp is None or not any('maintainer' in t.text.lower() for t in resp): return maintainer = name.text if not maintainer: return if 'up for grab' in maintainer.lower(): # not a real maintainer return # try to extract email address: if '@' in maintainer: maintainer = html.parser.unescape(maintainer) match = self.MAINTAINER_PATTERN.search(maintainer) if match: return {'maintainerName': match.groups()[0].rstrip().lstrip(), 'maintainerEmail': match.groups()[1].rstrip().lstrip() } else: return {'maintainerName': maintainer.rstrip().lstrip()} def __extract_date(self, elem): """If date has not been set with the attrbiute in the header, guess it from change log.""" parse_date = lambda x: datetime.datetime.strptime(x, '%Y-%m-%d') def set_date(isodate): try: if self.dictionary['date'] is None: self.dictionary['date'] = isodate elif parse_date(isodate) > parse_date(self.dictionary['date']): self.dictionary['date'] = isodate except ValueError: pass # we cannot parse all date formats if istag(elem, 'date'): if elem.get('when'): # use when, is usually in desired format set_date(elem.get('when').strip()) else: # check for correct format and if present, take it if self.DATE_PATTERN.search(elem.text): set_date(elem.text.strip()) elif istag(elem, 'change') and elem.get('when'): set_date(elem.get('when').strip()) def __format_date(self, date): """Bring date into the following format: YYYY-MM-dd.""" if re.search(r"\d+-\d+-\d+", date): return date try: dateobj = datetime.datetime.strptime(date, "%d %B %Y") date = '%d-%d-%d' % (dateobj.year, dateobj.month, dateobj.day) except ValueError: pass def parse_dicts(self): raise TypeError("This class is not meant to be used directly.""") class LocalMetaDataParser(MetaDataParser): """Parse meta data from XML dictionaries from a local fs path.""" def __init__(self, name, xml): super().__init__(name, xml) def get_meta_from_xml(path): """Parse meta data for all dictionaries in the FreeDict Root. Returns a list of Dictionary() objects.""" dictionaries = [] dict_pattern = re.compile(r'^[a-z]{3}-[a-z]{3}$') for item in os.listdir(path): full_path = os.path.join(path, item) matched = dict_pattern.search(item) if not matched: # eng-hun and hun-eng end on .header; auto-generated continue # is not a dictionary # append dictname.tei: full_path = os.path.join(full_path, item) + '.tei' if not os.path.exists(full_path): full_path += '.header' if not os.path.exists(full_path): raise FileNotFoundError("For dictionary %s no dictionary file was found, assumed path: %s" \ % (item, os.path.abspath(full_path))) with open(full_path, 'rb') as f: dparser = LocalMetaDataParser(item, f) dparser.parse() dictionaries.append(dparser.dictionary) return dictionaries tools-0.6.0/fd_tool/fd_tool/api/releases.py000066400000000000000000000161751423006221500206640ustar00rootroot00000000000000"""``parsers'' fetching all information about downloadable releases belong in here.""" from datetime import datetime import distutils.version import hashlib import json import os import re import shutil import subprocess import sys import urllib.request import semver from .config import RELEASE_HTTP_TOOL_BASE from .dictionary import DownloadFormat, normalize_version # relative to github.com/freedict/ TOOLS_REPO = 'tools' USER_AGENT = 'Friendly FreeDict Helper' class ReleaseError(Exception): """This error can occur, when information about a release is gathered. It wraps all sorts of ValueErrors and IoErrors.""" def git(cmd): """Execute a git command with the given list as arguments. Return stdout.""" proc = subprocess.Popen(['git'] + cmd, cwd=os.environ['FREEDICT_TOOLS'], stdout=subprocess.PIPE) stdout = proc.communicate()[0].strip() ret = proc.wait() if ret: raise ReleaseError("`git %s` exited with exit code %d" % \ (' '.join(cmd), ret)) return stdout.decode(sys.getdefaultencoding()) def get_tools_release(): """Retrieve the latest FreeDicttools release as a tuple with containing (version, date, downloadlink).""" if not 'FREEDICT_TOOLS' in os.environ or not shutil.which('git'): raise ReleaseError(("Unable to retrieve list of rleases of " "FreeDict tools. Either FREEDICT_TOOLS is unset or git not " "installed.")) releases = git(['tag']).split('\n') max_ver = '0.0.0' for tag in releases: max_ver = semver.max_ver(max_ver, tag) if max_ver == '0.0.0': raise ReleaseError("No tools releases found.") date = re.search('^([0-9]{4}-[0-9]{2}-[0-9]{2})', git(['show', '-s', '--pretty=format:%ci'])).groups()[0] return (max_ver, date, '{}/freedict-tools-{}.tar.xz'.format( RELEASE_HTTP_TOOL_BASE, max_ver)) def get_release_info_for_dict(path, version): """Retrieve information about the releases of a dictionary.""" files = {} name = None version = normalize_version(version) for file in os.listdir(path): format = DownloadFormat.get_type(file) if not format: continue # ignore unknown file naming, possibly outdated or unsupported formats parsed_name, file_version_str = format.value.search(file).groups() file_version = normalize_version(file_version_str) if file_version != version: raise ReleaseError('Version from file name "%s" did not match version of directory "%s"' \ % (file, version)) if not name: name = parsed_name elif parsed_name != name: raise ReleaseError('Found two different dictionary identifiers in %s: %s and %s' \ % (path, parsed_name, name)) full_path = os.path.join(path, file) try: with open(full_path + '.sha512', 'r') as f: sha = f.read().strip().split(' ')[0] except FileNotFoundError: raise FileNotFoundError('expected a sha512 checksum, found nothing', full_path + '.sha512', 'r') files[full_path] = (name, format, sha) if not files: raise ReleaseError("no downloads available for %s" % path) return files def get_all_downloads(root): """Get all paths to a downloadable file from a given root. This function walks the given path and returns all paths to files with the relative to the given root.""" # only match directories with two iso 639-3 codes, separated by "-" dirpattern = re.compile(r'[a-z]{3}-[a-z]{3}') release_directories = tuple(os.path.join(root, e) for e in os.listdir(root) if os.path.isdir(os.path.join(root, e)) and dirpattern.search(e) \ and not 'tools' in e.lower()) if not release_directories: raise ReleaseError("%s: no released dictionary found" % root) dictionaries_with_release = {} # collect dictionaries which have a release for dictdir in release_directories: # iterate over subdirectories which represent versions for version_dir in (os.path.join(dictdir, f) for f in os.listdir(dictdir)): version = os.path.basename(version_dir) try: files = get_release_info_for_dict(version_dir, version) except ReleaseError as e: if e.args[0].startswith('no downloads available'): continue # skip versions with no or unknown releases else: raise if not files: print('Warning: no files in "%s", skipping...' % version) continue name = next(iter(files.values()))[0] if not name in dictionaries_with_release: dictionaries_with_release[name] = {} # transform {path: (name, format, hash)} to (path, format, hash) dictionaries_with_release[name][version] = tuple((k, v[1], v[2]) for k,v in files.items()) return dictionaries_with_release def get_latest_version(release_information): """Iterate over object and return the latest version, as defined by semver. Versions that are valid distutils.version.StrictVersions, but not valid semver versions are converted.""" latest = None latest_strict = None # might contain '-' replaced through '.' for version in release_information: version_strict = version[:] try: normalize_version(version_strict) except ValueError as e: raise ReleaseError(e.args) if not latest: latest = version latest_strict = version_strict else: if version_strict > latest_strict: latest = version latest_strict = version_strict if not latest: raise ReleaseError("No versions found for " % repr(release_information)) return latest def github_request(path): """Make a GitHub API request and return aJSON object.""" url = "https://api.github.com/{}".format(path) request_headers = {'User-Agent': USER_AGENT} request = urllib.request.Request(url, headers=request_headers) with urllib.request.urlopen(request) as f: return json.loads(f.read().decode('UTF-8')) def get_latest_tools_release(): latest = {'name': '0.0.0'} for tag in github_request("repos/freedict/{}/tags".format(TOOLS_REPO)): latest = max(latest, tag, key=lambda t: t['name']) if latest['name'] == '0.0.0': raise ValueError("could not find a release for FreeDict tools") commit_url = latest['commit']['url'] api_suffix = commit_url[commit_url.find('.com/') + 5:] commit_meta = github_request(api_suffix)['commit']['committer'] # validate format date = datetime.strptime(commit_meta['date'].split('T')[0], '%Y-%m-%d') date = date.strftime('%Y-%m-%d') request = urllib.request.Request(latest['tarball_url'], headers={'User-Agent': USER_AGENT}) with urllib.request.urlopen(request) as f: checksum = hashlib.sha512(f.read()).hexdigest() return {'version': latest['name'], 'date': date, 'URL': latest['tarball_url'], 'checksum': checksum} tools-0.6.0/fd_tool/fd_tool/api/xmlhandlers.py000066400000000000000000000075501423006221500213770ustar00rootroot00000000000000"""Everything concerned with XML processing and writing.""" from xml.etree import ElementTree as ET class TeiHeadParser: """This parser uses a SAX parser to parse the header information of a TEI file, wrapping it in an easy-to-use interface only emitting completely parsed tags and aborting when a tag is encountered. Whenever a tag is encountered, self.handle_tag(...) is called.""" def __init__(self, input_file_object): self.__input = input_file_object # make name space available for derived classes self._namespace = None def parse(self): # get an iterable from XML context = iter(ET.iterparse(self.__input, events=("start", "end"))) # get the root element _event, root = next(context) # extract namespace end = root.tag.find('}') if end > 0: self._namespace = root.tag[:end+1] for event, elem in context: if event == 'start': if elem.tag.endswith('body'): break # do not parse body else: continue # skip node, not fully populated self.handle_tag(elem) def handle_tag(self, node): pass def istag(elem, name): """Tag comparison ignoring xml namespaces.""" return elem.tag.endswith(name) def create_node(tag, attrs=None): """Create ET.Element node with specified tag and attributes.""" e = ET.Element(tag) if attrs: e.attrib = attrs.copy() return e def create_child(parent, tag, attrs): """Create child node and attach to parent.""" c = create_node(tag, attrs) parent.append(c) def dictionary2xml(dictionary): """Return the ElementNode (ElementTree) representation of this dictionary. Raise ValueError if a mandatory field is missing""" if not dictionary.is_complete(): raise ValueError("Not all mandatory keys set.") downloads = dictionary.get_downloads() attributes = {k: v for k, v in dictionary.get_attributes().items() if not v is None} attributes['name'] = dictionary.get_name() # set maintainer if none present if 'maintainerName' not in dictionary or not dictionary['maintainerName']: attributes['maintainerName'] = 'FreeDict - no maintainer assigned' dictionary = create_node('dictionary', attributes) for download in downloads: attrib = {'platform': str(download.format), 'size': str(download.size), 'date': download.last_modification_date, 'URL': str(download), 'version': str(download.version), 'checksum': download.hash } create_child(dictionary, 'release', attrib) return dictionary def indent(elem, level=0, more_sibs=False): i = "\n" if level: i += (level-1) * ' ' num_kids = len(elem) if num_kids: if not elem.text or not elem.text.strip(): elem.text = i + " " if level: elem.text += ' ' count = 0 for kid in elem: indent(kid, level+1, count < num_kids - 1) count += 1 if not elem.tail or not elem.tail.strip(): elem.tail = i if more_sibs: elem.tail += ' ' else: if level and (not elem.tail or not elem.tail.strip()): elem.tail = i if more_sibs: elem.tail += ' ' def write_freedict_database(path, dicts, tools): """Write a freedict database to ''path``.""" root = ET.Element('FreeDictDatabase') root.extend([dictionary2xml(n) for n in dicts]) sw_node = create_node('software') tools_node = create_node('tools', tools) sw_node.append(tools_node) root.append(sw_node) indent(root) tree = ET.ElementTree() #pylint: disable=protected-access tree._setroot(root) tree.write(path, encoding="utf-8", xml_declaration=True) tools-0.6.0/fd_tool/fd_tool/config.py000066400000000000000000000070461423006221500175520ustar00rootroot00000000000000"""This file offers common configuration parsing facilities required for the Freedict build system.""" import configparser import os import re class ConfigurationError(Exception): def __init__(self, msg, path=None): super().__init__() self.path = path self.msg = msg def __repr__(self): if self.path: return 'error in configuration "%s": %s' % (self.path, self.msg) else: return self.msg def __str__(self): return repr(self) def load_configuration(conffile): """Load given `config` from given path. Default values are provided and missing mandatory options will raise a ConfigurationError.""" config = configparser.ConfigParser() config['DEFAULT'] = { 'file_access_via': 'sshfs', # unison or sshfs possible 'api_output_path': '.'} config['crafted'] = {} config['crafted']['local_path'] = '' config['generated'] = {} config['generated']['server'] = 'freedict.org' config['generated']['remote_path'] = '/var/www/download/generated' config['generated']['local_path'] = '' config['generated']['user'] = 'anonymous' config['generated']['skip'] = 'no' config['release'] = {} config['release']['server'] = 'freedict.org' config['release']['remote_path'] = '/var/www/download/dictionaries' config['release']['local_path'] = '' config['release']['user'] = 'anonymous' config['release']['skip'] = 'no' # overwrite defaults with user settings with open(conffile) as configfile: config.read_file(configfile) if config['DEFAULT']['file_access_via'] not in ['sshfs', 'unison']: raise ConfigurationError(('section=DEFAULT, file_access_via="%s": ' 'invalid value, possible values are sshfs and unison') \ % config['DEFAULT']['file_access_via'], conffile) api_path = config['DEFAULT']['api_output_path'] if api_path and os.path.exists(api_path) and os.path.isfile(api_path): raise ConfigurationError("expected directory, found file", path=api_path) for section in ('generated', 'crafted', 'release'): if not config[section]['local_path']: raise ConfigurationError("error, local_path not set for section [%s]" \ % section, conffile) path = get_path(config[section]) if not os.path.exists(path): raise ConfigurationError("path \"%s\" configured in %s doesn't exist" \ % (path, section), conffile) return config def discover_and_load(): """This file attempts to discover and load a FreeDict configuration file at the usual places. If no configuration was found, a ConfigurationError is raised.""" paths = [os.path.join(os.path.expanduser("~"), '.config/freedict/freedictrc')] if os.environ.get('LOCALAPPDATA'): paths.append(os.path.join(os.environ['LOCALAPPDATA'], 'freedict/freedict.ini')) conffile = [path for path in paths if os.path.exists(path)] if not conffile: phrase = ('one of the following directories' if len(paths) > 1 else 'the following directory') raise ConfigurationError(("no configuration found. Please initialize " "one in " + phrase + ' ' + ', '.join(paths))) return load_configuration(conffile[0]) def get_path(section, key='local_path'): """Return a local_path from a section with $HOME, ~/ or %HOME% replaced.""" home = os.path.expanduser('~') path = section[key] for pattern in (r'\$HOME', '~', '%HOME%'): path = re.sub('^' + pattern, home, path) return path tools-0.6.0/fd_tool/freedictrc.example000066400000000000000000000003631423006221500177670ustar00rootroot00000000000000[DEFAULT] file_access_via = sshfs api_output_path = ~/freedict/build/ [release] user = john,freedict local_path = ~/freedict/release [generated] user=john local_path = ~/freedict/generated [crafted] local_path = ~/freedict/fd-dictionaries tools-0.6.0/fd_tool/pyproject.toml000066400000000000000000000002361423006221500172130ustar00rootroot00000000000000[project] name = "fd_tool" version = "0.1" dependencies = [ "semver", ] [build-system] requires = ["setuptools"] build-backend = "setuptools.build_meta" tools-0.6.0/fd_tool/rm_duplicates.py000066400000000000000000000301711423006221500175050ustar00rootroot00000000000000#!/usr/bin/env python3 """ This script attempts to deduplicate entries in a dictionary or to detect whether duplicates are present. In the normal operation mode, the questionable entries are removed, the result is written to a file-dedup.py and then it is converted to the C5 format. From there, a diff with the original format is done. With the diff, the human in front of this very computer can then reason about the changes. There is also a mode where the script tries to find a duplicated translation and exits upon the first match. Dependencies: xsltproc, diff python >= 3.4. The script tries to preserve the original XML, but may do the following changes: - Manual sense enumeration (using the attribute `n`) may be stripped. Because of the removal of doubled senses, the enumeration might be broken and it's automatically inserted by the output formatters, anyway. - XML declarations may get lost, feel free to add support for it. Comments work fine. """ import argparse import io import itertools import os import shlex import shutil import sys import xml.etree.ElementTree as ET # TEI name space, Python's parser doesn't handle them nicely TEI_NS = '{http://www.tei-c.org/ns/1.0}' NS = {'t': TEI_NS.strip('{}')} # findall/iter with TEI namespace removed findall = lambda x,y: x.findall(TEI_NS + y) tei_iter = lambda x,y: x.iter(TEI_NS + y) class HelpfulParser(argparse.ArgumentParser): """Unlike the super class, this arg parse instance will print the error it encountered as well as the complete usage of the program. It will also redirect the usage to a output formatter.""" def __init__(self, name, description=None): super().__init__(prog=name, description=description) def error(self, message): """Print error message and usage information.""" sys.stderr.write('Error: ' + message + '\n') self.print_help() sys.exit(2) class CommentedTreeBuilder(ET.TreeBuilder): """A TreeBuilder subclass that retains XML comments from the source. It can be treated as a black box saving the contents before and after the root tag, so that it can be re-added when writing back a XML ElementTree to disk. This is necessary because of ElementTree's inability to handle declarations nicely.""" def comment(self, data): self.start(ET.Comment, {}) self.data(data) self.end(ET.Comment) # These helpers allow the identification of similar nodes (comparison of quote # or usg) def nodes_eq(node1, node2, tag=None): """Check whether both given nodes contain equal text. If tag is given, not the nodes themselves, but the given child nodes are compared.""" if not tag: tag = node1.tag node1 = node1.find('.//' + TEI_NS + tag) node2 = node2.find('.//' + TEI_NS + tag) if node1 is None and node2 is None: return True # means equality, differences are searched if node1 is None or node2 is None: return False return node1.text == node2.text usages_match = lambda n1, n2: nodes_eq(n1, n2, 'usg') def translations_of(sense): trans = sense.findall('.//t:quote', NS) if not trans: trans = sense.findall('.//t:def', NS) return trans def rm_doubled_senses(entry): """Some entries have multiple senses. A few of them are exactly the same, remove these. This function returns True if an element has been altered""" senses = list(findall(entry, 'sense')) if len(senses) == 1: return # obtain a mapping from XML node -> list of words within `` senses = {sense: translations_of(sense) for sense in senses} changed = False # pair each sense with another and compare their content for s1, s2 in itertools.combinations(senses.items(), 2): # both sets match if not len(s1[1]) == len(s2[1]): continue if not set(t.text for t in s1[1]) == set(t.text for t in s2[1]): continue # don't match # two senses are *exacctly* identical? try: entry.remove(s2[0]) # remove sense from entry changed = True except ValueError: # already removed? pass return changed def node_is_empty(node): """Check that this node and all its children contain no text (apart from white space).""" if node.text.strip(): return False for child in node.getchildren(): if not node_is_empty(child): return False return True def rm_empty_nodes(entry): """This function removes nodes which have no text and no children and are hence without semantic. It also resets the fixed counters on sense, since they are generated by the output formatter anyway. This function returns True if an empty node has been removed.""" changed = False # sometimes parent nodes are empty after their empty children have been # removed, so do this three times (won't work with deeper nestings…) for _ in range(0, 2): changed_here = False nodes = [(None, entry)] for parent, node in nodes: if node_is_empty(node): if parent: parent.remove(node) changed_here = changed = True else: nodes.extend((node, c) for c in node.getchildren()) if not changed_here: break # nothing removed, no further iterations else: changed = True # try to strip enumeration of senses they aren't adjacent anymore; map # sense: numeration first or discard if no numbering sense_numbers = {sense: int(sense.attrib.get('n')) for sense in tei_iter(entry, 'sense') if sense.attrib.get('n')} if sense_numbers: # if not all values between min and max sense number, enumeration is # broken and can be stripped if not all(n in sense_numbers.values() for n in range(1, max(iter(sense_numbers.values())))): for node in sense_numbers: # strip manual enumeration, handled by output formatters and might # be wrong after node removal del node.attrib['n'] changed = True return changed def rm_doubled_quotes(entry): """Some entries have doubled quotes (translations) within different senses. Remove the doubled quotes. This function return True, if the entry has been modified.""" senses = list(findall(entry, 'sense')) # add quote elements senses = [(s, cit, q) for s in senses for cit in findall(s, 'cit') for q in findall(cit, 'quote')] if len(senses) <= 1: return changed = False # pair each sense with another and compare their content for trans1, trans2 in itertools.combinations(senses, 2): sense1, _cit1, quote1 = trans1 sense2, cit2, quote2 = trans2 # translation could have been removed by a previous pairing if quote1.text == quote2.text and usages_match(sense1, sense2): try: cit2.remove(quote2) except ValueError: continue # already removed changed = True return changed def exec(command): """Execute a command or fail straight away.""" ret = os.system(command) if ret: sys.stderr.write("Process exited with %i: %s" % (ret, command)) sys.exit(ret) #pylint: disable=too-few-public-methods class XmlParserWrapper: """This thin wrapper guards the parsing process. It manually finds the TEI element and copies everything before and afterwards *verbatim*. This is due to the inability of the ElementTree parser to handle multiple "root elements", for instance comments before or after the root node or '') if tei_end < 0: raise ValueError("Couldn't find `` in the input file, please extend the parser.") tei_end += len('') self.after_root = content[tei_end:] content = content[:tei_end] parser = ET.XMLParser(target = CommentedTreeBuilder()) try: parser.feed(content) except ET.ParseError as e: sys.stderr.write("Error while parsing input file\n") sys.stderr.write(str(e) + '\n') sys.exit(15) self.root = parser.close() def write(self, file_name): """Write the XML element tree to a file, with hopefully a very similar formatting as before.""" tree = ET.ElementTree(self.root) in_mem = io.BytesIO() tree.write(in_mem, encoding="UTF-8") in_mem.seek(0) with open(file_name, 'wb') as file: file.write(self.before_root.encode('UTF-8')) file.write(in_mem.read()) file.write(self.after_root.encode('UTF-8')) if not self.after_root.endswith('\n'): file.write(b'\n') def main(): parser = HelpfulParser("deduplicator", description=("Fnd and remove " "duplicated translations and empty TEI nodes")) parser.add_argument("-s", "--detect_changes", dest="detect_changes", help=("check whether duplicates or empty nodes can be detected " "and exit with exit code 42 if the first change would " "need to be made"), action="store_true", default=False) parser.add_argument('dictionary_path', help='input TEI file', nargs="+") args = parser.parse_args(sys.argv[1:]) # register TEI name space without prefix to dump the *same* XML file ET.register_namespace('', 'http://www.tei-c.org/ns/1.0') dictionary_path = args.dictionary_path[0] tree = XmlParserWrapper(dictionary_path) changed = False if args.detect_changes: # abort if first change detected for entry in tei_iter(tree.root, 'entry'): changed = changed or rm_doubled_senses(entry) # this one is dangerous #changed = changed or rm_doubled_quotes(entry) # the processing above might leave empty parent nodes, remove those changed = changed or rm_empty_nodes(entry) if args.detect_changes and changed: print(("E1: Found duplicated entries or empty XML nodes. Try " "`make rm_duplicates`.")) sys.exit(42) else: # always apply changes for entry in tei_iter(tree.root, 'entry'): changed1 = rm_doubled_senses(entry) changed2 = rm_doubled_quotes(entry) # the processing above might leave empty parent nodes, remove those changed3 = rm_empty_nodes(entry) if changed1 or changed2 or changed3: changed = True if args.detect_changes and changed: print(("E1: Found duplicated entries or empty XML nodes. Try " "`make rm_duplicates`.")) if changed: output_fn = os.path.join('build', 'tei', dictionary_path.replace('.tei', '-dedup.tei')) exec('mkdir -p build/tei') tree.write(output_fn) # get a human-readable diff of the changes c5 = lambda x: shlex.quote(x.replace('.tei', '.c5')) shutil.copy('freedict-P5.dtd', os.path.dirname(output_fn)) exec('xsltproc $FREEDICT_TOOLS/xsl/tei2c5.xsl %s > %s' % (output_fn, c5(output_fn))) # convert original dictionary to c5 exec('make --no-print-directory build-dictd') # execute diff without checking the return type if not shutil.which('less'): exec('diff -u build/dictd/%s %s' % (c5(dictionary_path), c5(output_fn))) else: os.system('diff -u build/dictd/%s %s | less' % (c5(dictionary_path), c5(output_fn))) if input("Do you want to overwrite %s with the new version (y|n): " % \ dictionary_path) == 'y': shutil.copy(output_fn, dictionary_path) if __name__ == '__main__': main() tools-0.6.0/fd_tool/setup.cfg000066400000000000000000000006551423006221500161250ustar00rootroot00000000000000[metadata] name = "fd_tool version = 0.1.0 description = FreeDict tooling for the build system [options] zip_safe = False include_package_data = True #packages = find: #install_requires = # requests # importlib-metadata; python_version<"3.8" [options.entry_points] console_scripts = fd_api = fd_api:main fd_file_mgr = fd_file_mgr:main fd_changelog = fd_changelog:main rm_duplicates = rm_duplicates:main tools-0.6.0/fd_tool/setup.py000066400000000000000000000000461423006221500160100ustar00rootroot00000000000000from setuptools import setup setup() tools-0.6.0/fd_tool/tests/000077500000000000000000000000001423006221500154405ustar00rootroot00000000000000tools-0.6.0/fd_tool/tests/test_dictionary.py000066400000000000000000000013571423006221500212240ustar00rootroot00000000000000"""Tests for fd_tool.api.dictionary.""" #pylint: disable=too-many-public-methods,import-error,too-few-public-methods,missing-docstring,unused-variable,multiple-imports import unittest from fd_tool.api.dictionary import Dictionary class TestTictionary(unittest.TestCase): def test_unset_mandatory_fields_are_detected(self): # test for `date` d = Dictionary('lat-deu') d['headwords'] = 80 d['edition'] = '0.1.2' self.assertFalse(d.is_complete()) def test_is_complete_recognizes_all_mandatory_fields(self): # test for `date` d = Dictionary('lat-deu') d['headwords'] = 80 d['edition'] = '0.1.2' d['date'] = '1871-12-13' self.assertTrue(d.is_complete()) tools-0.6.0/freedict-database.rng000066400000000000000000000054401423006221500167120ustar00rootroot00000000000000 [a-z]{3}-[a-z]{3} \d+(\.\d+(\.\d+)?)?(-.+)? \d+(\.\d+(\.\d+)?)?(-.+)? \d+(\.\d+(\.\d+)?)?(-(beta|alpha).*|\+.*)? tools-0.6.0/importers/000077500000000000000000000000001423006221500146745ustar00rootroot00000000000000tools-0.6.0/importers/README.md000066400000000000000000000011651423006221500161560ustar00rootroot00000000000000This directory contains all importer scripts and programs to automatically import new dictionaries. If an importer is specific to a particular dictionary, a name with the appropriate language code should be used. For instance, an importer for Danish-German should be called dan-deu. Importer scripts which import dictionaries from a certain project should be named after the project. Each dictionary importer should have a README with basic instructions on how to run and configure the importer. Furthermore, importers should be documented on the [import centre](https://github.com/freedict/fd-dictionaries/wiki/Import-Centre). tools-0.6.0/importers/dan-eng/000077500000000000000000000000001423006221500162055ustar00rootroot00000000000000tools-0.6.0/importers/dan-eng/dan-eng.py000066400000000000000000000001111423006221500200610ustar00rootroot00000000000000#!/usr/bin/env python3 SRC_URL = "http://wordnet.dk/DanNet-2.2_csv.zip" tools-0.6.0/importers/ding2tei/000077500000000000000000000000001423006221500164015ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/.gitignore000066400000000000000000000001201423006221500203620ustar00rootroot00000000000000/dict/ding /dict/tei/*/* !/dict/tei/*/Makefile /build/ /ding2tei /util/results/ tools-0.6.0/importers/ding2tei/COPYING000066400000000000000000001033331423006221500174370ustar00rootroot00000000000000 GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 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. Preamble The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software. A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public. The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version. An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU Affero General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Remote Network Interaction; Use with the GNU General Public License. Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU Affero 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 Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements. You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see . tools-0.6.0/importers/ding2tei/GNUmakefile000066400000000000000000000110321423006221500204500ustar00rootroot00000000000000# # GNU Makefile # # Copyright 2021,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # SHELL = /bin/sh # Note: ghc probably cannot be substituted due to the use of GHC extensions. HC = ghc HAPPY = happy ALEX = alex INSTALL = install INSTALL_PROGRAM = $(INSTALL) -m 755 INSTALL_DATA = $(INSTALL) -m 644 INSTALL_DIR = $(INSTALL) -d -m 755 DOWNLOAD = curl # Notes: # * Consider to manually add `-jN'. # * Some of the below -Wx flags may have merged into -Wall in later releases # of ghc. HCFLAGS = -Wall -Wcompat \ -Wincomplete-uni-patterns -Wincomplete-record-updates \ -Wmissing-export-lists \ -Widentities -Wredundant-constraints -Wpartial-fields -Wcpp-undef \ -O2 # Notes: # * In production, omit `--info'. # * Consider the option `--array'. # * The option `--coerce' # - is claimed to generate # - "smaller faster parsers", # - at the cost of "some type safety". # - Thus, one should always make sure the result compiles w/o the flag. # - actually seems to increase run-time. HAPPYFLAGS = --ghc --info=$(@:.hs=.info) # Note: In production, omit `--info'. ALEXFLAGS = --ghc --info=$(@:.hs=.info) # Clear suffixes, no implicit rules needed. .SUFFIXES : srcdir = src builddir = build dictdir = dict dingdir = $(dictdir)/ding teidir = $(dictdir)/tei .DEFAULT_GOAL = ding2tei SRCS_ALEX = $(srcdir)/Language/Ding/AlexScanner.x SRCS_HAPPY = $(srcdir)/Language/Ding/Parser/Line.y SRCS_HS := $(filter-out $(srcdir)/Test.hs,$(shell find $(srcdir) -name '*.hs')) SRCS_SED = $(wildcard $(srcdir)/preprocess/de-en/*.sed) SRCS = $(SRCS_ALEX) $(SRCS_HAPPY) $(SRCS_HS) $(SRCS_SED) SRCS_HS_GEN = $(patsubst $(srcdir)/%.x,$(builddir)/%.hs,$(SRCS_ALEX)) \ $(patsubst $(srcdir)/%.y,$(builddir)/%.hs,$(SRCS_HAPPY)) # Notes: # * We could also use `ghc -M' to generate the dependencies in make format. # * Prefer $(builddir) over $(srcdir) for `-i' such that $(SRCS_HS_GEN) # are always found first in the former. ding2tei : $(SRCS_HS) $(SRCS_HS_GEN) $(HC) $(HCFLAGS) --make \ -i -i$(builddir):$(srcdir) -outputdir $(builddir) \ -XRecursiveDo \ -o $@ \ $(srcdir)/Main.hs .PHONY : haskell-source-files haskell-source-files : $(SRCS_HS_GEN) $(builddir)/%.hs : $(srcdir)/%.y $(INSTALL_DIR) $(@D) $(HAPPY) $(HAPPYFLAGS) $< -o $@ $(builddir)/%.hs : $(srcdir)/%.x $(INSTALL_DIR) $(@D) $(ALEX) $(ALEXFLAGS) $< -o $@ .PHONY : clean clean : clean-objcode clean-dicts .PHONY : clean-objcode clean-objcode : rm -f -r $(builddir)/ rm -f ding2tei .PHONY : clean-dicts clean-dicts : rm -f $(dingdir)/de-en.txt.pp rm -f $(teidir)/deu-eng/deu-eng.tei rm -f $(teidir)/eng-deu/eng-deu.tei .PHONY : clean-ding clean-ding : rm -f $(dingdir)/de-en.txt .PHONY : deu-eng deu-eng : $(teidir)/deu-eng/deu-eng.tei $(teidir)/deu-eng/deu-eng.tei : $(dingdir)/de-en.txt.pp ding2tei $(INSTALL_DIR) $(@D) ./ding2tei -- $< $@ .PHONY : eng-deu eng-deu : $(teidir)/eng-deu/eng-deu.tei $(teidir)/eng-deu/eng-deu.tei : $(dingdir)/de-en.txt.pp ding2tei $(INSTALL_DIR) $(@D) ./ding2tei --inverse -- $< $@ .PHONY : validate-ding validate-ding : $(dingdir)/de-en.txt.pp ding2tei ./ding2tei --validate -- $< .PHONY : preprocess preprocess : $(dingdir)/de-en.txt.pp $(dingdir)/de-en.txt.pp : $(dingdir)/de-en.txt \ $(srcdir)/preprocess/de-en/all.bash $(SRCS_SED) $(srcdir)/preprocess/de-en/all.bash < $< > $@ # This target must be specified explicitly and fails otherwise. .PHONY : download-ding-1.9 download-ding-1.9 : $(dingdir)/de-en.txt $(dingdir)/de-en.txt : ifneq (,$(filter download-ding-1.9,$(MAKECMDGOALS))) $(INSTALL_DIR) $(@D) $(DOWNLOAD) \ 'https://ftp.tu-chemnitz.de/pub/Local/urz/ding/de-en/de-en.txt.xz' > $@.xz printf '%s %s\n' \ 7373b16fccc5c167298b6cde8e4cdd78b3d48941711f10f39429e761742e3068 $@.xz \ | sha256sum -c xz -d $@.xz else $(warning Run `make download-ding-1.9` first.) $(error Refusing to implicitly download Ding source) endif tools-0.6.0/importers/ding2tei/README000066400000000000000000000067501423006221500172710ustar00rootroot00000000000000ding2tei-haskell ================ A program to convert the Ding [0] dictionary into the TEI [1] format, to be used within the FreeDict project [2]. This program was written as part of a Bachelor's thesis. See doc/thesis.pdf. Dependencies ------------ * main ding2tei program: * Haskell (Glasgow Haskell Compiler / GHC) * base * containers (Data.Map, Data.Set) * transformers (Control.Monad.Trans.{State,Writer}) * pretty (Text.PrettyPrint) * haskell-xml (Text.XML.Light) * safe (Safe.Exact) * Happy * Alex * preprocessing: * Bash * sed (with support for `-E`; e.g., GNU sed, OpenBSD's sed) * `make` (optional) * GNU make * standard UNIX utilities (provided by, e.g., GNU coreutils) * curl (`make download-ding-1.9`) * xz (`make download-ding-1.9`) On Debian-based distributions: # apt install ghc libghc-xml-dev libghc-safe-dev happy alex bash sed make \ coreutils curl xz-utils Locale ------ Use a UTF-8 locale, otherwise some things might produce unexpected results or even fail. In particular, do not use the C locale. Obtain the Ding dictionary -------------------------- $ make download-ding-1.9 Alternatively, it can be manually downloaded from: https://ftp.tu-chemnitz.de/pub/Local/urz/ding/de-en/ and stored (uncompressed) at: dict/ding/de-en.txt Build ding2tei -------------- $ make You may need to include `-dynamic` in HCFLAGS; e.g.: $ make HCFLAGS='-O2 -Wall -dynamic' Build TEI dictionaries ---------------------- $ make deu-eng $ make eng-deu Note: The main program (ding2tei) allocates a lot of memory, around 5.7 GiB, when applied to the full Ding dictionary. Debug / Run ding2tei manually ----------------------------- # TODO: make (alex, happy) $ make haskell-source-files $ ghci -i -ibuild:src -XRecursiveDo Test >> parseLine $ scan "some dictionary :: line\n" >> pretty $ parseLine $ scan (examples !! 0) >> (Just ding, log) = parse $ scan $ header ++ (concat examples) >> pretty ding >> tei = ding2tei $ enrichDirected $ enrichUndirected ding >> putStr $ prettyTEI $ tei >> :q $ ./ding2tei --help $ ./ding2tei doc/examples/test.ding - | pager See also: src/Main.hs GNUmakefile Usage with the FreeDict tools ----------------------------- To use the resulting TEI files with the FreeDict tools: $ make -C dict/tei/lg1-lg2 Notes: * Above, lg1-lg2 should be either of deu-eng and eng-deu. * Memory usage of the FreeDict tools is even higher than that of ding2tei. * Running time is quite long (~ 1 day). * See for more information. Licensing --------- ding2tei-haskell is distributed under the GNU Affero General Public License (AGPL), version 3 or later. See the license headers in the source files and the license itself for details (should be supplied with this program under the name COPYING). The Ding source is licensed under the GNU General Public License (GPL), version 2 or later. In the context of this program, version 3 of the license should be considered, since it allows combining with the AGPLv3. (Likely, later versions will also allow combining.) The combination of the Ding dictionary and this program form a combined work, the resulting TEI dictionary is an "object form" of this combined work, in terms of both the GPL and AGPL. Author ------ ding2tei-haskell is written by Einhard Leichtfuß. Feel free to contact me by e-mail to . References ---------- [0] https://www-user.tu-chemnitz.de/~fri/ding/ [1] https://tei-c.org/ [2] https://freedict.org/ tools-0.6.0/importers/ding2tei/dict/000077500000000000000000000000001423006221500173245ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/dict/tei/000077500000000000000000000000001423006221500201055ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/dict/tei/deu-eng/000077500000000000000000000000001423006221500214315ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/dict/tei/deu-eng/Makefile000066400000000000000000000001411423006221500230650ustar00rootroot00000000000000FREEDICT_TOOLS ?= ../../../../.. DISTFILES = deu-eng.tei include $(FREEDICT_TOOLS)/mk/dicts.mk tools-0.6.0/importers/ding2tei/dict/tei/eng-deu/000077500000000000000000000000001423006221500214315ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/dict/tei/eng-deu/Makefile000066400000000000000000000001411423006221500230650ustar00rootroot00000000000000FREEDICT_TOOLS ?= ../../../../.. DISTFILES = eng-deu.tei include $(FREEDICT_TOOLS)/mk/dicts.mk tools-0.6.0/importers/ding2tei/doc/000077500000000000000000000000001423006221500171465ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/doc/META000066400000000000000000000107671423006221500176320ustar00rootroot00000000000000How stuff is documented ======================= License ------- Even though not explicitly specified, all documentation is provided under the same license as the code it applies to. This is currently the Affero GNU Public License, version 3 or later. See the license headers in the source files and the license itself (file COPYING) for details. Modularisation -------------- Documentation may be found in: * Code * General * A common license header is to be placed at the head of any non-trivial source file. * Haskell * Haskell code should be enriched with Haddock documentation, ideally at least each exported function and datatype (et al.) of a module. * Modules should be split into a hierarchichal structure. * Sed * Explicit documentation files * General * Documentation should be split into several usually small files, according to the discussed topic. * Files should be named in a hierarchical manner, using dots (<.>) as separators. E.g. "syntax.slashes". * Note: This convention might change later towards using directories as a means to reflect hierarchy. * doc/ * Contains any documentation on the how and why. It contrasts to in-code documentation by adressing the more abstract issues. * Its files should be well structured, as desribed later. * It should contain only few questions and TODO annotations. * doc/partial/ * Less well structured or rather incomplete documentation. * doc/examples/ * Small (Ding) examples. * doc/thesis.pdf * Bachelor's thesis explicating this program. * todo/ * Contains documentation on steps that remain to be taken and problems that remain to be solved. It does in particular allow for the discussion of options and stating of questions. * A file's structure requires less strictness to allow for the likely frequent additions, changes and restructuring. * Files in todo/ should end up in doc/ when all is done. * If instead, it was decided not to pursue something further, either should the reasons be documented, or else the corresponding file in todo/ moved to todo/old/. * It may be a good idea to split off parts of a todo file into a file under doc/. * todo/old/ * No-more-todo, not considered worthy of conversion to proper documentation, but nonetheless not to be binned. Syntax & Semantics ------------------ * Applying to all documentation: * Things should be usually specified in hierarchical lists, like in this file. * There are valid exceptions, e.g. detailed explanations in doc/. * Lists may use any special char as bullet point. In particular, these may be mixed. * The semantic of particular bullet point characters should be specified here. * Indentation: Like in this file. * Question, uncertainty. * <.> Fact. * <-> Common. * Annotates a point that has the prevalent meaning in the context. * E.g., in todo files, this indicates a thing to be done. * May also be used as generic bullet point. * '>' Quote. * Filename. * <*> Generic. * Prefer full phrases. * Use terminating periods (<.>). * 79 character limit. * Emphasis * Use '<' and '>', or paired <'> to highlight single characters. * use <`> and <'> in this order to highlight single words, e.g. identifiers. * Use paired <"> to highlight examples of data or code (excluding single elements, e.g. identifiers). * Applying to separate documentation primarily: * Headings should be underlined using `=' and `-' for primary and secondary headings, respectively. * Third level headings are a sign of a too large documentation file (like this). * Applying to separate documentation only: * A file should begin with a primary heading, roughly corresponding to the file name. * Applying to in-code documentation mostly: * Notes should be typeset as either "Note: ", or "Notes:" followed by an indented list. See above for the format of a list. * Applying to in-code documentation only: * Documentation must not be within the same comment section as the license header. (This excludes a small statement at the top of that header.) Final notes ----------- * This is not Markdown syntax. * This is quite incomplete. * The present documentation does not adhere very closely to the above rules. tools-0.6.0/importers/ding2tei/doc/README000066400000000000000000000003151423006221500200250ustar00rootroot00000000000000README ====== This directory contains general documentation, usually not specific to single source files. For syntax, see META. See also: https://github.com/freedict/fd-dictionaries/wiki/discussion-TEI tools-0.6.0/importers/ding2tei/doc/conversion-process000066400000000000000000000026351423006221500227400ustar00rootroot00000000000000Conversion process ================== The conversion happens in several steps. Preprocessing ------------- This is done with `sed' scripts, located in `src/preprocess'. Ding -> Ding AST ---------------- Convert the textual Ding source into a Haskell data structure, staying close to the syntax of the Ding source. This is subdivided into two steps, lexing and parsing. Some information from the source may be dropped here, if it is not intended to be used. Enrichment of the Ding AST -------------------------- Enrich the Ding AST with some information, in particular annotations, inferred from other parts of the AST. Ding AST -> TEI AST ------------------- Transform the Ding AST to a data structure that more closely resembles the TEI[-Lex0] format. The TEI AST shall be limited in expressiveness in comparison to the full potential of TEI (for dictionaries), only information that is extracted from the Ding needs to be representable. The conversion shall be done towards both a deu-eng and a eng-deu target. Note that the Ding AST is symmetrical in nature, so this should not be difficult. Since several headwords per entry seem to be disencouraged in the FreeDict project, groups on the respective keyword side have to be split here, thereby creating multiple entries with different keys but identical values. TEI AST -> TEI XML ---------------------------------------- Finally convert the TEI AST to TEI XML. tools-0.6.0/importers/ding2tei/doc/debug.happy000066400000000000000000000001251423006221500212750ustar00rootroot00000000000000Happy debugging =============== $ happy -i HappyParser.y $ $EDITOR HappyParser.info tools-0.6.0/importers/ding2tei/doc/ding.annotation.brace000066400000000000000000000056151423006221500232450ustar00rootroot00000000000000Classification of brace (<{}>) annotations ========================================== * Grammar . Mostly on the German side. . Refer to units ? Or a prefix of units in a group (see todo/parsing.scope-of-annotations) * Gender: {n} {f} {m} . implies singular (unless {pl}), noun * Multiplicity: {n,f,m,sing} {pl} {no pl} {no sing} . {sing} is rare . Does not imply noun. * {pl} . Applies to units (by exemplary observation) . Counter-ex.: "Abbieger {pl}; Abbiegenden {pl}; Abbiegende" - Transferral to preceding units would not help here. * Note: Plural and non-plural forms may be in the same group, in the case where a singular form represents a multitude of elements. . Does not imply noun. . Ex.: "exit ... {sing}; exeunt ... {pl}" . Ex.: "diese; dieser; dieses {pron} | diese {pl}" . Ex.: "Wir freuen uns über euer/Ihr {pl} zahlreiches Erscheinen." . Ex.: "that; those {pl}" * One could use a heuristic (such as capitalization for german words). * Likely flawed; cases rare anyways. * {no pl} . implies singular, noun * {no sing} . implies plural, noun * Verbs: {v} {vi} {vt} {vr} (in-, transitive, reflexive) * {adj} * {adv} * {pron} . Ex.: "diese; dieser; dieses {pron} | diese {pl}" . In this case, applies to group. - Further investigation required. * {num} . Mostly annotated to numbers. . Counterex: "jedermann; alle ohne Unterschied {pron} {num}" . Counterex: "beide {pron} {num}" * {Quantifikator} . Occurs only once. * ... * Separators . {prp; +Dat.; +Gen.; +Akk.} - always separeted by <;>. . Often {prp; +(Dat|Gen|Akk).}. . {wo?, wann?, wohin?, bis wann? +(Akk|Dat|Akk).} . {vt, vi} - <,>, <;>, . . Currently transformed in preprocessor to <,>. . {m,n,f,pl} - <,> . {adj} - separated from textual info by <,> once. . all others: only occuring alone. * Inflected forms . Exclusively on the English side. . imply verb . Ex.: "to be {was, were; been}" . Form: "{simple_past; past_participle}" . Both simple_past and past_participle contain a comma separated list. . each single (comma-separated) form can be annotated with <[]> and/or <()> annotations. * Differentiation . Keyword <;> does not suffice - ex.: "{prp; +Gen.}". - Identify grammar keywords and differentiate by these. - If the whole brace expression can be parsed as a grammar annotation, it very likely is one, otherwise assume conjugated forms. Specialties ----------- * Further annotation . Ex.: "{adj, usually not used before a noun}" - TODO: More investigation required. See also -------- * tools/results/braceexps.* . Full list of all brace expressions. * todo/enrich.grammar.inferral * todo/parsing.annotations.braces tools-0.6.0/importers/ding2tei/doc/ding.annotation.parenthese000066400000000000000000000072761423006221500243340ustar00rootroot00000000000000Expressions within parentheses ============================== * in headword units * prefix location a) a collocate . Ex.: "(steuerliche) Abschreibung" . most frequent / all b) an optional prefix (not being a collocate) * Both versions (w/ and w/o prefix) share the same meaning ? Do such exist? ? Necessary to differentiate from collocates? ? Just assume a)? * suffix location a) a collocate . Ex.: "(von etw.)", "outflow (from / to)" . Ex.: "Abfangen {n} (von Sendungen, Nachrichten etc.)" - w/o "von", could be considered c) - Note keyword "von" . Ex.: "interception (of aircraft)" - Note keyword "of" . Ex.: "Lehne {f} [Süddt.] [Ös.] [Schw.] (eines Hügels) [geogr.]" - Note article in genitive * Seems to usually be indicated by a keyword like "from" * Other common keywords might also be of interest (e.g., "etw.") b) an optional suffix (not being a collocate) ? Existing? c) a description of / annotation on the whole unit () . Ex.: "Start {m} (Rakete, Raumschiff)" . Ex.: "Sinken {n} (Temperatur...)" . Ex.: "Abfalluran {n} (Kerntechnik)" . Similar to <[]>-usages (frequently the case). - TODO: Investigate on frequent initial words -- keywords - Use keywords to distinguish ? per language keywords? - include (ex.): "durch", "von", "vom", "für", "an" - include genitive articles ? include relative pronouns? - https://de.wikipedia.org/wiki/Relativpronomen * Many. - ambiguous ex.: "die", "der" (relative pronoun / nominative article) - Ex.: "(der DDR)" - usage as rel.pron. seems more frequent - Ex.: "(der wichtigste Bestandteil von etw.)" ? Change in preprocessing to "wichtigster ..."? * Work. ? Just rely on investigation? * Frequency, shortness of occuring initial words. * infix location * Infrequent. . Ex.: "loose (sharp) tongue" * Might be considered an example for tongue. . Ex.: "to get (down) to work" ? Consider a phrase otherwise? (TODO) ? Include as literal? * special: unit only contains (.*) plus other annotations . Ex.: "(Abnutzung durch Reibung) [techn.]" - Currently fixed in the preprocessor (removed <()>) ? Always just remove the parentheses? - TODO: Analyze frequency / further investigation. * prefix and suffix location (distinct annotations) . Ex.: "(obere) Abschlussleiste {f} (einer Täfelung)" ? two collocations? (TODO) * Likely confusing. * special: Two succeding <()>-exprs . Ex.: "Absetzen {n} (von etw.) (Vorgang)" ? If suffix, require different kind (a/b/c)? ? Otherwise: Error? - TODO: Investigate further. * in phrase units ? Include verbatim? * Slashes . Ex.: "(water/steam) separation system" ? Assume weak slashes to have maximal scope? ? Encode as two collocations? ? Or a single one, contatining the slash? * Semicola . Ex.: "(Wasserspeicher; Lagertank)" Problem: distinction of phrases and headwords happens after the parsing step. * When separating <()> during parsing, the spacing information is lost. * <()> must be identified in the parser, since it may contain <;> (i.a.). * Spacing must be reproduced, when an annotation is to be merged back with the text. ? Just assume single separating space (and none after <(> / before <)>)? * Likely correct in most cases. * Possibly an improvement on potentially awkward spacing in the Ding. ? Keep spacing information? See also: todo/parsing.literal.etw tools-0.6.0/importers/ding2tei/doc/ding.grammar-naming000066400000000000000000000013641423006221500227120ustar00rootroot00000000000000Grammar ------- See `src/Language/Ding/HappyParser.y` for a grammar of the Ding source. The naming chosen in the grammer shall be used in all documentation, in particular *line*, *entry*, *group*, *unit*. In short: - The dictionary is composed of lines. - A line is composed of similar entries. - An entry is composed of a two groups, one for the German and one for the English part, which may be refered to as the left and the right group, respectively. - A group is composed of units which share a common translation. - It is usual for these units to be either a) synonyous or b) equal except for (annotated) gender or multiplicity. - A unit is a single word, expression or phrase that can be considered a key in the dictionary. tools-0.6.0/importers/ding2tei/doc/ding.groups000066400000000000000000000017621423006221500213360ustar00rootroot00000000000000Groups ====== * Separated by <|>; contain units, separated by <;>. * Contained units related by sharing the same translation. * Usually synonyms. * It is assumed that they always are. * Debatable: differently gendered forms . Ex.: "Bäcker {m}; Bäckerin {f}" . Exception: "nicht können; kann nicht" . Exception: "wir waren; wir waren nicht" ? TODO: Catch (suffix "nicht" / "not")? - TODO: Investigate further. ? Is this special to inflected forms / and/or phrases? - Note also the mapped order on the RHS. . Same for "tun". (that's all) . For "sollen", "dürfen", "haben", "möchten" separated by <|>. * Do not confuse with other usages of "nicht". . Ex.: "übersieht; überliest; beachtet nicht" * Groups may be empty. . Obsolete documentation [0] (translated from German): "Several translations: similar words / word groups separated by semicolon" References ---------- [0] https://dict.tu-chemnitz.de/doc/syntax.html tools-0.6.0/importers/ding2tei/doc/ding.slashes000066400000000000000000000106111423006221500214520ustar00rootroot00000000000000Syntax & Semantic of '/' ======================== Notes: * This is all by observation and human interpretation. * There are helpful tools though (in particular grep and sed). Source files: * src/Language/Ding/Token.hs * src/Language/Ding/AlexScanner.hs Definitions ----------- word: [[:alpha:]] | abbrev "." abbrev: "etw" | "so" | "sb" | "sth" | ... special: "(" | ")" | ";" | ... lFree: "(" | "{" | "[" | "<" | " " rFree: ")" | "}" | "]" | ">" | " " | interpunctiuation rFreeExceptDots: rFree - excluding triple dots ("...") - examples: - positive: ". " - negative: "..." interpunctuation: "." | "," | ":" | "!" | "?" | ";" | ... - interpunctuation, that does not require preceding space. Notes: * In AlexScanner, instead of [[:alpha:]], a much larger character set is used. * <`- TODO * Words in a more general sense, such as usable for '<>', should usually not contain a terminating '.'. * TODO: `word' may probably contain other chars, such as '-', '_'. * TODO: Abbreviations may contain several '.', e.g. "r.-k.". Alternative for single words (strong slashes) --------------------------------------------- No spacing. single_alt: word ("/" word)+ Alternative for strong-slashed expressions (double slashes) ----------------------------------------------------------- No spacing. double_alt: single_alt ("//" single_alt)+ Note: In terms of binding sthrength, double slashes are between strong and medium slashes. Alternative for groups of words (weak slashes) ---------------------------------------------- Has spacing. group_alt: expression (" "+ "/" " "+ expression)+ expression: ? Notes: * Identifying the matching expressions is hard. * A work for linguists, probably - or a potentially existing library. * One might try to guess at least in certain cases. * <`- TODO Enclosing an abbreviation (or several) (opening and closing slashes) -------------------------------------------------------------------- No spacing. Surrounded by free context. encl_abbrev: lFree < "/" wordList "."? "/" > rFreeExceptDots | lFree < "/" wordList "."? "/" "s"? > rFree wordList: word (";" " "* word)* Notes: * A terminating "s" signifies plural. * Currently, it is only recognized for single slash-enclosed words (i.e., no <;> separators). This is to allow for catching plural abbreviations at the lexer stage. Simply lexing "/s", potentially with a certain context, does not suffice, since there are many valid other occurences of "/s". * Infrequently, a "," is used instead of ";". * I consider this as to be fixed at the preprocessor stage. * <`- TODO * Note that "," is probably easier to parse. * In the current implementation (Alex + Happy), this is not the case, since there are distinguished tokens for opening and closing slashes in this context. * Note that in other contexts (<{}>), a "," means "or" while a ";" means "and". Enclosing an abbreviation containing slashes -------------------------------------------- Spacing. Surrounded by free context. encl_abbrev_sl: lFree < "/ " swords " /" > rFree swords: sword ("," " "? sword)* sword: word ("/" word)+ - syntax equal to single_alt, semantics different. Notes: * This is parsed at the lexer stage to avoid later ambiguity. * Wherever the encl_abbrev_sl rule matches, it is hence prefered. Enclosing a special char ------------------------ Spacing. Surrounded by spacing or separators (context). encl_special: lFree < "/" " "+ special " "+ "/" > rFree Notes: * This is parsed at the lexer stage to avoid later ambiguity. * Wherever the encl_abbrev_sl rule matches, it is hence prefered. Enclosing a smiley ------------------ Spacing. Surrounded by spacing or separators (context). encl_smiley: lFree < "/" " "+ smiley " "+ "/" > rFree smiley: ... (TODO) Notes: * In the original Ding source, smileys are not enclosed in slashes. * The enclosing is added in the preprocessor. * Very similar to "Enclosing a special char". * See also there. Test cases ---------- * "Abendrot Schönwetterbot, Morgenrot Schlechtwetter droht. (Bauernregel) :: Red sky at night shepherd's [Br.]/sailor's [Am.] delight, red sky in the morning shepherd's [Br.]/sailor's [Am.] warning. (weather saying)" Exceptions ---------- * "AC/DC" -- TODO * In 2 of 3 present occurences, this can be transformed to "/ AC/DC /". * Bad occurence: "AC/DC converters" tools-0.6.0/importers/ding2tei/doc/ding.spec.source000066400000000000000000000002041423006221500222360ustar00rootroot00000000000000An outdated Ding syntax documentation: * http://dict.tu-chemnitz.de/doc/syntax.html Note: * Line continuations translate to <|>. tools-0.6.0/importers/ding2tei/doc/enrich.grammar000066400000000000000000000022631423006221500217710ustar00rootroot00000000000000Grammar enrichment ================== The German side is generally richer annotated. Similarly, individual units in a group may be richer annotated. Many of such annotations may be transferred. Also, from some annotations, we may infer others--applying to the same group. Inferral: --------- See doc/ding.annotation.brace Transferral: ------------ Transfer annotations to units within the same entry, regardless of the side, according to the following rules. Note that they are specific to the language pair (German, English)--and the concrete dictionary. * Grammar annotations * {v*} - Drop transitivity information ("{vi}"/"{vt}"). - Drop reflexivity ("{vr}") (debatable). * {n,f,m} * Only transform implicit information, as found during inferral. * {sing}, {pl} - Do not transfer. . Counterexample: "Schere" ~ "scissors" (plurale tantum) * part of speech (excl {v*}) - Transfer. * All else: Drop. * Inflected forms - Do not transfer. - Inferral should provide `verb' though---this should be transferred. * Other (incl. usages) - Do not transfer. See also -------- * todo/parsing.scope-of-annotations * doc/ding.annotation.* tools-0.6.0/importers/ding2tei/doc/examples/000077500000000000000000000000001423006221500207645ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/doc/examples/test.ding000066400000000000000000000007231423006221500226100ustar00rootroot00000000000000# Version :: 0 2020-09-12 # Copyright (c) :: Example Person, # 2020 # License :: AGPL Version 3 or later # URL :: https://example.org/ Eintrag {m} {+Gen.} ~eintragen /Eintr./ (Wörterbuch) :: entry ~enter /entr.;ent./ (dictionary) Graph {m} [math.] | Knoten {m} [math.] :: graph | node; vertex :: entry without translation essen {vt;vi} :: to eat sein :: to be {was, were; been} Barock {m,n} :: baroque Karotte {f}; Möhre {f}; Wurzel {f} [Norddt.] :: garden carrot tools-0.6.0/importers/ding2tei/doc/examples/test_example.ding000066400000000000000000000005151423006221500243220ustar00rootroot00000000000000# Version :: 0 2020-09-12 # Copyright (c) :: Example Person, # 2020 # License :: AGPL Version 3 or later # URL :: https://example.org/ some word; foo | some word in some foo phrase; foo bar; foo bar baz | gna; this some word :: x; y | z; g | a Salat | Ein Satz der ein Beispiel zu Salat ist :: salad | A sentence exemplifying salad. tools-0.6.0/importers/ding2tei/doc/examples/test_references.ding000066400000000000000000000002661423006221500250130ustar00rootroot00000000000000# Version :: 0 2022-04-15 # Copyright (c) :: Example Person, # 2022 # License :: AGPL Version 3 or later # URL :: https://example.org/ A; B ~C1 ~D1; C; D ~foo | E; F ~bar :: A2 | E2 tools-0.6.0/importers/ding2tei/doc/haskell_intro000066400000000000000000000041041423006221500217260ustar00rootroot00000000000000A very small crash course into Haskell, to allow for a basic understanding of (hopefully) most of the code. Think of everything as a mathematical function or simple value (a function with zero arguments). The whole program is one complex function, composed of smaller ones. There are also `data' definitions though, these can be considered classes in the mathematical sense and are similar to C-structs. Further notes - Nesting & indentation - nesting is (usually) achieved via indentation, like in Python. - spaces are used for indentation - line continuations require nothing but further indentation - Functions - Functions take one argument. - No parantheses are (normally) required (e.g., 'sqrt 2'). - The class of functions from A to B is written as `A -> B'. - Frequently, there are functions of type `A -> (B -> C)', also written as `A -> B -> C' (`->' is right-associative). Such functions are usually to be understood as taking two arguments (of type A and B). - This is possible due to the isomorphism between `A -> (B -> C)'` and `A x B -> C'. - This can easily be generalized for an arbitrary number of arguments. - It is quite common that arguments to functions are functions themselves. Function composition - `f . g' is the function that maps `x' to `f(g(x))'. - Type annotations - Types may be annotated as follows: `value :: type'. - Syntactic sugar - `$' - Usually, one can think of everything on the current nesting level after (and before) `$' as enclosed in parantheses. - e.g.: (2 + (sqrt (2 + 3))) == (2 + (sqrt $ 2 + 2)) - multiple `$': (f $ g $ h) = (f (g (h))) - More precisely: `$' is a right associative operator with lowest precedence. - (`$' is in fact not syntactic sugar, but the identity function) - Documentation / Troubleshooting - Use Hoogle (sic!) [0] ! - Haskell Wiki [1] - Many more resources: [2] See also: - https://wiki.haskell.org/How_to_read_Haskell References: [0] https://hoogle.haskell.org/ [1] https://wiki.haskell.org/ [2] https://www.haskell.org/documentation/ tools-0.6.0/importers/ding2tei/doc/partial/000077500000000000000000000000001423006221500206025ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/doc/partial/ding.paren-units000066400000000000000000000055731423006221500237240ustar00rootroot00000000000000Units containing only <()> and annots ===================================== Some units do not contain a main part (not enclosed by any kind of bracket). They are always starting with a <()> part. Meanings: a) suffix annotation to all former units in the group b) first <()> part contains the headword - usually (always?) in the context of botanics or zoology (latin names). * Usually (always?) last unit in group. * Any further annotations tend to apply to the whole group / list of preceding units. Resolving (for now): * Any non-initial unit of this kind is considered of type a) * Any initial unit of this kind is dropped. * See Language/Ding/HappyParser.y and Language/Ding/Partial/PartialSuffixUnit.hs Examples: Anfälligkeit {f}; Empfänglichkeit {f}; Empfindlichkeit {f}; (für etw.); Neigung (zu etw.) | Alterungsanfälligkeit {f}; Alterungsempfindlichkeit {f}; Alterungsneigung {f} | Verkokungsneigung {f} | Korrosionsanfälligkeit {f}; Korrosionsneigung {f} | Rissanfälligkeit {f} | Flockenempfindlichkeit {f}; Flockenneigung {f} (Metallurgie) | Stoßempfindlichkeit {f} | Schweißrissempfindlichkeit {f} :: susceptibility (to sth.) | susceptibility to aging | susceptiblity to coking | susceptibility to corrosion | susceptibility to cracking/fissuring | susceptiblity to flakes (metallurgy) | susceptibility to shocks | susceptibility to weld cracking Basispapier {n}; Basisinstrument {n}; Basiswert {n}; Bezugswert {n}; (eines Wertpapiers) [fin.] :: underlying asset; underlying (of a security) Duchgangshöhe {f}; (Fußgänger); Durchfahrtshöhe {f} (Brücke, Unterführung) [auto] :: headroom; headway (bridge, unterpass) Einspruch/Beschwerde einlegen; Einspruch erheben [Ös.]; berufen [Ös.]; rekurrieren [Schw.]; appellieren [Schw.]; (gegen) (bei) | Einspruch/Beschwerde einlegend; Einspruch erhebend; berufend; rekurrierend; appellierend | Einspruch/Beschwerde eingelegt; Einspruch erhoben; berufen; rekurriert; appellieren | gegen eine Entscheidung Einspruch erheben; eine Entscheidung beeinspruchen [Ös.] :: to appeal (against) (to) | appealing | appealed | to appeal against a decision Feigwarze {f}; Feuchtwarze {f}; Kondylom {n}; (Condyloma) [med.] | Feigwarzen {pl}; Feuchtwarzen {pl}; Kondylome {pl} | Feigwarzen der Harnröhre :: genital wart; venereal wart; condyloma | genital warts; venereal warts; condylomas | urethral condylomata Gänseblümchen {pl}; Marienblümchen {pl}; Tausendschönchen {pl}; Maßliebchen {pl}; (Bellis) (botanische Gattung) [bot.] :: daisies; bellis (botanical genus) Ginkgo-Gewächse {pl}; Ginko-Gewächse {pl}; (Ginkgoaceae) (botanische Familie) [bot.] (Amblypomacentrus breviceps) [zool.] :: saddle damsel (Amphiprion leucokranos) [zool.] :: white cap (anemone) clown (Amphiprion tricinctus) [zool.] :: three stripe (anemone) clown (Anthelia spp.) oder (Cespitularia spp.) [zool.] :: waving polyp -- see also the subsequent lines in the Ding. tools-0.6.0/importers/ding2tei/doc/partial/linking.lines000066400000000000000000000017541423006221500233000ustar00rootroot00000000000000Links between units in groups, and groups in lines ================================================== The basic means for this in TEI is the tag. There are several types. Unless (likely difficult) means to identify the type of relation are used, stick to the general . This should most likely be used to link groups in a line, but also to represent ~tilde references. For units in a group, (synonym) might be more appropriate, though their actual relation is that they translate to a common set of words. In particular one might argue that "Bäckerin" and "Bäcker" are not synonyms. One might also argue otherwise in this particular case. Since, most likely these words are annotated with "{f}" and "{m}", respectively, one could use that data to make an exception. Such references, except for ~tilde references should have a @target. See: * https://github.com/freedict/fd-dictionaries/wiki/FreeDict-HOWTO-%E2%80%93-Writing-Text-Encoding-Initiative-XML-Files tools-0.6.0/importers/ding2tei/doc/partial/tei.abbreviations000066400000000000000000000003011423006221500241270ustar00rootroot00000000000000Ex. from the TEI docs:
MTBF
abbreviation for mean time between failures
tools-0.6.0/importers/ding2tei/doc/sed_intro000066400000000000000000000005601423006221500210600ustar00rootroot00000000000000No, no intro here. Just links. manpage: sed(1) - Should suffice to understand the given (short) scripts. good tutorial: https://www.grymoire.com/Unix/Sed.html - Read to write fancy scripts in the Turing complete sed-language yourself. Notes: - I use and assume GNU sed. - Other sed implementations may not work with the given scripts (but likely will). tools-0.6.0/importers/ding2tei/doc/style000066400000000000000000000006451423006221500202360ustar00rootroot00000000000000Coding style ============ Basis: - https://github.com/tibbe/haskell-style-guide/blob/master/haskell-style.md - https://haskell-haddock.readthedocs.io/en/latest/markup.html Deviation / exceptions ---------------------- Line length: max. 79 chars Indentation: - generally: 2 spaces - `where', `deriving': 1 space Blank lines: - There may be more than one in succession, if it improves structure or readability. tools-0.6.0/importers/ding2tei/doc/tei.spec.sources000066400000000000000000000002621423006221500222650ustar00rootroot00000000000000https://www.tei-c.org/release/doc/tei-p5-doc/en/html/DI.html https://dariah-eric.github.io/lexicalresources/pages/TEILex0/TEILex0.html https://hal.inria.fr/hal-01757108/document tools-0.6.0/importers/ding2tei/doc/thesis.pdf000066400000000000000000030054611423006221500211510ustar00rootroot00000000000000%PDF-1.5 % 196 0 obj << /Length 426 /Filter /FlateDecode >> stream xmMo +0`%GC크YkG`f[%m%KagjCw7o%C Ҋ8TT ( ׅ8!vC[O[RO~l8_ŏ3⬤DPUfޮL~_p> stream x3PHW0Pp2Ac( endstream endobj 5 0 obj << /Type /ObjStm /N 100 /First 810 /Length 1304 /Filter /FlateDecode >> stream xڝWnF}WcRF& @QHk E +*g(^F&"4ܝ93sfBM1br I֒P$DI$1II#iH:KA$ DR"ZR i KZI'#Sxdd1#d$($Pmx/h}R./MMG+9ϏZOn]HaGk_0iʐ/︪']& mgH{t+~7v0S٣G_<{p٣3GϞTlyWXV~Xu^u|_/v嗲/j1_矋wG)tE''qi~}4G;}. <{]=ƙ}j[/O|$nV'}Q|zVT%>%D\Ta͖2AIIzP_,z@4?GԜGs-%q"3{n~ _G:=(?2ֿHhZ endstream endobj 211 0 obj << /Length 381 /Filter /FlateDecode >> stream xmR;O0+nt$jJ "eHk7"q؎=l=x(H>v#(jVC{IXEqMj3M}:rmuˏvê_\VcEˣ5rT̂4s.PeS )0ߤXw[.?8mM*Y.7ŻqAɸ'?ax"u)8K;yy>J4`FsQDSǩ(ꃣlcXZ_ƹ%Rsuʼq}#9v/ޟq7ˢBE"|9ˌ5Uadf)mY@F' JExx eL)gRaΚ;x+^~?߃ סsZk 6~- endstream endobj 217 0 obj << /Length 19 /Filter /FlateDecode >> stream x3PHW0Pp2Ac( endstream endobj 221 0 obj << /Length 892 /Filter /FlateDecode >> stream xuVϗ8 _Ѽ7C쵹C%&6e(iӓ-Y?>IJO)ӧײJ2w5OǤY)d[7d%l}mקעJ[4ϓ X-YO*ݔy&'ӭףFvxni{kS_)1^peǵtW/g!gD9z- Nge:]u ;M/?14=O7y5ey ;Y4 FtbSzğX?>uw G昄Z\_K87|FBhꐵihA)VyTJhMUpSޭg3d0T:;[<"~ /5?}0r`']LxҢk0V .X> stream x3PHW0Pp2Ac( endstream endobj 267 0 obj << /Length 1156 /Filter /FlateDecode >> stream xMsHv>9fm'TR]*ɁX*}{P6u.B 8s8.6%_D:;GP88a@|L{gN25 eB}qL9N<5ܲ'nͬTx)<Jx~uIt.qFGʁڔ0NS,I"MԬ4GV/# OSEȱ{JJ (&ʶ^l߁ńOGwm*F)&,/VU,E\EXeOgpGýH!ޚ,p:8=k`Nw|ٍ^4AFsB1U>al֦ڕ ڠԉVֿ޽5:Oy^I,qrHuVICLρzcE>!C3B |zz.c endstream endobj 288 0 obj << /Length 548 /Filter /FlateDecode >> stream xˎ0<_omʹ2M ($& R2/w -Cwxt3 9(~DGFYj@=YE7SGcf9Wڍ'0rN'R*a|L6%ՔGs|W&URFD[iDc^sc~p`SP'?&O1L_!?&@GZe4=Wn'Y^obV@lju;3!+*UaM"M ZM`Q$O"XR+|qr=u8gD{ǗJ<"aKkQWa8]'x0>GfYU&m4b5k pԸw=%Nտā:}]$צ<<|uTA+*w<6gŢL60ﲥd$T` endstream endobj 309 0 obj << /Length 2367 /Filter /FlateDecode >> stream xڅXr8+t*B cwmSs(\>"%K9(J/H$2/0ڜ6C$>>7: DǛ&U5vqzXlOg:vo8P.YpWQi[` 0V]<m:mn65Anuv/8AQnS˞m<2 uԻKkO;v%uMcGb \%puۂїhڠT1 ~05eMRLPT?#c`&l^%PiSLf7:nhvk-'u-սԲ'bGƟ]3DPrr 4&yq>hyR"X&USIGS(OOXxTpbM&)`^[ƲުfF,>s+*v 3\(Z~VxϼB(q>& `'ڣW#7`UWbQD~L*v|8Q"ЎcxEsdeJLhv>~|-ȿù160bfPmY:mn!J~죹!w&AE`#02W#~S͒D K;/JrT@rWv"gM^D7AKLjƙ--3+4г@D'TO{6D μʘt2ɞQg,ѥ@-WЭY0Sqىΐ+f[x~Q/S* )pQfo\v 1^lfN7o uE;~fP77WM*<FLHZ mDyg>щEdx?"\MSNM8ZJt%@'Yli[H4:Ĕ ?eE$`\_qd#=i Q}?x`(NCxx{bH%jqKC endstream endobj 337 0 obj << /Length 3556 /Filter /FlateDecode >> stream xڵrFm*ISh8eY52[R+r Kue֎f4M' WNq8PH / &+yd^砐xJg>+ 5MĆ`r7='"FG\+?c|K Pܶn_"< O@ -sMۚQY.k{O,aj lK!z<& jahǣڵg!pfM\먹րO,GERFee|w7yа7v/߬JA&?Df X=]C:v+sJ+XRߨtht@O)MB jPA@s Jמz F|u,+ ^ꚃ)HTo9/^d7nf[M \b@IS5l7f-R66~LPWk֢"? n $oQeHO/78@jazg9F-uxJS} K ?QdJa}:'o"Ky\Z~Ҏhj&Ia{bU͹# +\V8H~q,Jȸ02 DFɸԮT-K1c M GDM ?A/?X'[ܳvly$sgX4qJ *^oXT}[;/UjEĎ}H:M!kEw%2 o/j Џmt,ʈ#p[r3Z?0S}RBޕ>P%cMu}%ʲ`Y-;%P'!p)0ZoH5#ͩ@O,(4UfmnEK4 ^ߕ#lh`e;ݘSnb3lMKz˝^:G)xو\GkGٌs7?KnJGET.n-Ӏ /eR[~#!"=|Y+WQӍDi(|Ey98hh?㿊{M 潼 ;H3mmnd6`~`ɇ]I0q:^;S =K󦖷X]S3^\z+%ljgJ@>%F$?].dlUtnO>DXuwAѨ`Drf=7!V,y  9a0Sn~Âd zWYya&K2<bXހFGO `DaSq(@ݑ0'(d!prSט1Mh%Wn8ufCvU|^ y*  K\I< qwl;"S ~+zUU5(eamkln Z6RMuB6pxxw G,Xk>Spa @`b'8,M3&~?l^da@yu/ܳ=ٌgqŨ CSǠH[: s>nK^.'4COqDzg,gҙn&ʂL Sn C}V]BlB1՜P7iڔ}$c l>kW(p|T_8U{z45ej endstream endobj 208 0 obj << /Type /ObjStm /N 100 /First 902 /Length 2648 /Filter /FlateDecode >> stream x[]o}_Ǧ@$%/iAQ%Z ,E]CZο9\ɶ"֚1D,Ι{:.bKIpT ߋʱ:ˢ\=)%g1-xߛ׋kcu1$ @H,"xv1Lb "ŦqP*n?X)y9' 3H_hp dhtnD-99; wnx(>jN%*T9B N5EUQrV%g(-R˸!+RϹ3/9x\w.7NIu +|r*9s2>XPE]JDWZ]F[A vH4|kHVʟYMaY+FڼW|*Z853PBxeOFĻKu5e`425aV ?! b $piZBV!o_a@|nd Y Ɩ@ZAZ"`lAH1`T16L"VpzLR^J%,>],w/~vo+k4xkNvgNVNtۭ.7.gbj^`' kw+~Z@f쮰˟WW˳ K\^~}^\-{*px%D`^ g+dowS>vyi]vyi]vyi./wy]^r.ty+{yXbbnr^P\xI2CQqDF̣_$b7:x?lo*D^dYBrUQ\v8JId%iF D%k }`"3)f6X7!z+6^+JorM0j^˸RtNGXs ִX!aF]i: US7TU WfB,!k Q\?w-IʲW A(Ͱ5mB0 OcFpXi" qyX}.jC@`9 )H"(ͷt429a$+@f%2f"|Apu\+2 cQDfO∵fz"W++VrɈf2Q5^4Y"tB1Qݘ( D9&JgGAͶ"*jQ<50cHI6SA b0] kd#%,ٽpe/ah4@L#ʎ[cGp'QZ;`r_Mvc}TY-([gA=@:Kgv mpέqwe2?(l#Lm0U)#00SIV4! MI79Ejco!4j> mfp Pb̠C3u(nc@hHvsxi1y2I) F(WT<{'sO'?g:S>ˈzr' 6mfA̒ݒp7Rgsn3K ̲ eJ1Tc崡6:E.w+'t$Oi1&p"vV E+[NA 0hZ?9^^]7LŤ=@|AH6E4F> stream xڍZKs8WHUYsdojk˓$*#o@2b@_VUwo~9VeXf*[=8Lt",2ۯ8TzEpz~׷^:?~Y84JdFYgjrYCoFe5' , u g(mtsb!WZzw0kj< nYAڶvվxl]#w'sZs lp[m$,~LSP{ ՞[Wb|p+CaO> (@a^2a0YԢ@>Fz‘ UޮuT¢JAhMSi\;eo+uK+Mx?iiqq6--UF Ӫ`I;X :P,:n돗hk;_%eQM5t`=,X{p۪ns|vI2ׅ80e†0W<"%ʔMa<.T0WO Nuh P7-Wn[*ۂIpj{vDkBŠ?qz/vG d=ܓ Wz!WNn'(< 5Op|" `Wϫ0Lp;XՒC@nնhP$v*S93l+YmO޼ә7/\ FMt _;/%h¦=,)(N0YK(\]|;U܏OKG5/UbBQdEdoT=@۹Y8J3ۅ: ƫŊY)&,#x` Lp-lޓP3"ɘlYK]q?hY@JLNqzdDdy@K㮣~O8El< |nh{HA=3:4` ܸE4t};}V$͡ 'R9|>h폦_B7x^%bxqG<c|2^Q俼,]ƊA~DMoZ KP:r pD-0nq/PEI!+@f F*2~xPwB(nu fXۥr@?T,BtjޝDU1Sfy$q46 U ?مA 5"͞{f8+ieTX؜JuSj  5#'|$DVNJqdzā˰+CbN(~zH,,Ce^ґl6#ݓLe++bf(Ճ`?'ucBdf?f`Wb'؍wm7H(@0`ցiX_\_=5=SmH-^eO0޽˯HƝg&0*W|YHaEoPN%/&6TB0#A#$_.A*V𖳈f;ؽҟǗgLy|Qe0Hak8#RцҹW#H>w y%bXJg絁#x艈*Α/;N!* -s{\0ci!qBk&m8 ոU>,8Gw$NqV'ߓ2!Dվ=ISMƧ = !B۞Wdذepmܞ6R],bxɮ Ƈ8zSӻ@ݙT |޹\?K{RfSJ̘r2ر7L٪ E'baQe5k7УUf7?)Ԍp]f,|Kf0љwwoz/ZƝlzwzs)Z 5pG< XH7[.SP2X-7Rh\cE*,b=yVJ| ~_0 55. 5IZv,~ie8ģ8煝!4otnZH\Y(@U[ˢҞvTƽ~϶mM Q\I0p%p}Ui-~ cq7%XkHE;'%/#nclO\횡IeF?a%{<'_M{X;X- B;s9~$ '!pS.SB (N(î=Z㤏2Ux9~jN Ow@%uHo^(`5x pL?͕pɉ4J̀s\50#W?;Jt[Jڥ mޖƔa?U^P|%5лw$_~O<.{æx 9HGq^av^1 f〮Bz= I'7%g% 0Hi@{6-P|{HV'ay$L"g wGAįO$5(*:>-WI|jzQgs;2Eۉ&Q0z!Θ_L\c^+ !弈9X/[vk^1B 2+KoáIhZwpe2͞-NWpÒ4} 2/ V83S}ՙ<!+"- bJ@ϯ̰ H$')~H aZ))c1rtj`yhm 穽\2 ;p]s&h$9g \MfPM%B f)ұ@X"ƄYKlFs>{" |Vr7M MG;zC-$Mq27UnF P%b҄^7mr2UHЯW3>p<\I NV(/;4C.5 endstream endobj 414 0 obj << /Length 3562 /Filter /FlateDecode >> stream xڥ]sܶ]&/f$ Ht$&iN:h:uǘG^[w#)JGK`]..XmWɷW'/߄*$LVWw+_jeOpuY]{__0I0 o~zS1wϧax.ќB{F<#xiM~;=v94t͛_W۲hXwE]/,5& 4n\2SEqv[0XEQm$ˁ T{g#riWJY3&lQ^Yt]swBv#m]ӯ}\Q]V ?ئ+ָ6D^Vگ񹓑PH]0e+oۜ ,YGu-=dPfЃ-vysݎ{{O,b9UUwV6i*( x]-@b[i=ɅW7w4% &t{[Έ Lh_T͚5GQT[J-1%roedtM F'6ۼ٧DvȮ#{oAEoDw7[F{q!M4 {Oj%idp#v`5xhdZOx?ٙ#b/1Pa68'ڏlNTY~T|1䷾VYU(v¢G1K FHǼh77-#[%~ft*j@ݓhDt/hr#(`C1ngM}BfivNG&^_eĠF8X)Lml06%de-A4d8>boˉ p@ĥ"–m7k!BVzDY4p:Ig_nfG&gWugvrf䜉:=Cq:ק0Z8P=Z? :s/lgZʫ Ҭ .`rI9A?j8ѽ.JU#=M(F\_YcDsa(=8Rm_lry85Ş\:Y̠1ZB"7ar-:Ft(BO9ɒa!{mW7MFZ@=4,~&/G+I, [f>D Si! !r3מs)W9ܡʗd3o$^*or 6h\g͒R串H#;j?G"ʐF:)D#H<$*rjn3=ClgH{AsqGb/(,&|%_d1 EN3?:b*'**qmO(~)-˩o,S38^QB( sلLˤGBI b-}N"8*=,[xLjoWR޻¸n(8 H.|%-DO$|^.[kχv@>o4F%;Di6Nnii!x4=heo;ҩl9m7 L$PVseuBb눇5kS#K)}N^%RfǛ1 *w.ɳe+)&U0)dO#\)!@|}\gܒ% mq26|[ns,d|D"7TJ+=j\/EGU#w\e ~4B.]Hkg\׵h&dfk _\͢Ecbp P^- HԱ(GP5z1 g i0֚:uo{U.mc{zuݱ-H[>P\#c1L;r UPR~Ү¼[LXn{Rˇ|)ۑwx<K)T '杓ܚWik)\n)ދmsX (F=l+Pt5vɼJc?KFt|a,4 zVbb?VOHEWq<3 OQ$g'#s1cr$Dtc™Z娝8(hA8V[ʭZLx,,*5~ R0=t#pPQ{@BO0C^Aj/>`#YwQI'tdrRLFnjC.oLɩa3oO÷S`eNyB}mJ: K@(Hbm-51@CKp5- %U1~p螝E HZ^(ں-q*Y &Xy$59T`=@E TZǾZAh pӁ'"k|-eB7Qªt?8R1⧍"yt endstream endobj 435 0 obj << /Length 2873 /Filter /FlateDecode >> stream x\Ko9Wz/bTdE f؍dshKmYNK~Ŗr,ushc|0_>VȖY1-X?/^W0Ua9( 8#IiApd\0|BQHuqƚ'lHz֘b$,)c*'Ñ4lM].r3[N: `z=vߕCw(C}s*^Uv6nۯfw׳q:6} ˼c5^-ulN+D.F\W Q@YH^qH5/c}es\# Rgk ׫(>jhwPW7uM5eyy}0s.ξYe/"Yƀb8炁 *dzTTVT T8)nÈx#(%*k*gTo!̠[V3MF8!/"Jh$&@ GNI\R9_wK8s$ME8.DDFAG0QG3jC5vG=-:`O2%B7;OQ7Cz2KE&*0L35cժv1XmdHnHʸV՛!#@2P*B7C86CuC̩,[^"&1X+KO͐a(!Xgm/!T@AuxNi+;e@p.EvƃTX.lQyp3>G~TΩz͏'aL[lD&GUANNd':ut ɛYTzORsMEnqWwAs\*B7ś VQc2rbTNfnjkg@,Rr]kkt$;;^Ģsd0P鿨`@E ,pz͏v}NPйkÁIʹj,Z9"'HF9x}TZj>v LN@͟x^&G.mݛ?$#?)H@f$P IDFHV>MA9grғdnf1EDJs9Ɨ Ɏ >lN蘌M y{dηLۛ%. -*i}XD/7piDt296!U=Ep7ZNL8SۺH$V:Z/?Ħr[nx:ф }춉tJ}yw =hZ s\ڗC9ZQ% irdy J)a@&y悳}AC7켵4?% !~憺=k+W4YwhFAo2wCǽ<WPo>4lAa6 #N%Pc>_39́V"~H<8$O+Fa}E5Hap-QMbby݊*+xNp\p}fg2.u 50Z:-u-L}Oe,ezja#&H]-y 4I%NvAv4Dmm{3.g|:z! yšg܂͟w;MRZЩR̂bRXp.X~r_i$4\z.'KOF8 B CSrI ##I'r6r0m7m:Jp)JTnLbFcG(( X*5 uNRpI AzhUK[j*pYa2 Bz2P0詰~D=m&%tC7zyç|.zhA7jͽF% l$#T3:ܽwTj q/ 1e9H2BkQuWӦ72e+=3Gz &s6_z(yh"ʴGJ$p=YZ ,U(Y%#tBXiԊS]xN ơdTNHN2MǏ"93v{Zd^½b=.wX֋e;O?㱞~ Vrd Iy97+SK!g8vy >t` FZGGk t nʂk 4k*IbXL*z g_ -P> stream xZnG}WcT!X'^'v聖F2##ޯsC]llKsjzoQ;e%+or(1pJ>Ψ8fe 1HVYk⍍|u$ Oʕ,'> Fb+g8=$嬄Y9N(ʉq4lǂW$(ʕpO7D8)6M7E05%8E%Uh(TKV!S-HFI kW!| )%zrE+LJ R *`bA 9IlU4+K9cT'qĩ tn4Q%$$OJ!IQ)gDJ«bgV5/ˮ5GWnuڮ~7pO{xyӞwEz jyqx8м5z~x냦 mM&.4|͆-C!٘uty>̗r:I:#YI:1-ņ謁|]wK|'bQoT,Έ˫㇉@ "_q6:*boFjC̊GV"_RI.>-0:h:o[]OZ7s^]-cQ?[̚Wݲ ߆rrxCDe {xC8"o .=]  햌 6|kz՝n|Wy~]y=?ogȲobXnV'zã?uHa!f!8 K(̆|p56'#Kz1F$zюHe8q׉:q\'8/8/HGޏ|>PN0P APYخ)+`zc7DZOmGsՑ;o9)Fྞ!A< c s xN a\3Jzi./ !Ē4z!OZYBBKKw |fp,T0~ W =rLMDdlpBT -b}s{`G)yw9(`ꧬli0(~<(n9syOhS࿞5(a;o` PQf@}pdœdQFPtD;w0jzcN@i!J) eG=}"B$fGb(=c#[&t0GaXr ͡ p.h[i3|)fQ' _ )PKNPs[K@IZl8"ڄ{ {;j[~>kdw(`3eݘ;v PERMb@(,ƀMҖݴH+ OF+Xd s-"hg0Du8PNb0ey3E0 Cn3CQ9S5BԈ ڤ=ya(s[ yG[NRFwr2ZK.Ȁ<-n`/9[> +gw! ~, @hZu4KجPol4mĮmv<)n\uCvn.y=xDwe)_i?e/UYDשׂQ'6vr*"6ds-r;W endstream endobj 498 0 obj << /Length 2313 /Filter /FlateDecode >> stream x[[۶~_I_1Lۙ6k;$uԾyW\-PJn2>IΕ˦\}|ЙoƷgx/_+i(PWoGRʜZ_8~;.ߍ1\v*8A[Yမf~WM]vO-f/C8G,1>-dF&YysOG%>(WZ ՏY ØqYƁq!%0#&bToZh] hD΅^^/5oɂM,^ݻ+e/=fL\! F QqB! OF(nMTo6|+ H8o>I=R&PzQi9JL2VbEI}${Or\7\D6%175=\Q`u's^ϰRYRC.+bgEi& _v怘QR C䡵 Ч8ї у>rLvZnD;&CS)V_NQihN3*~ᒭU@|k0%"ha0X@aw*Dp RkR􂾭-s0j1(Xl]ʸH wm= V:ElkQ;F2nhE`6(BwGËqaFz:୯+X5 xcúy'9")W d]D? eqyi*uЋAPڗP>0eYg{lW]|]vrûf3`-AAg+xWv7 endstream endobj 545 0 obj << /Length 3108 /Filter /FlateDecode >> stream xڝYKF0rF3lb 03\Ñt7%z&n( H&tݛdy&&U~Mg~ݥi6̼|7?H)?UPߤ$qy0I:*{ ά6¢'ly?Iu sќk2˥!TQ~2CntDL9>E4 rla r-A\2OyϊߪO`+zB2N͹^b!縐&swr' ?,LAM7=3pKZ n1ݩ< olL~A\sf.DcpmAgY _œW%~e|Y owq$\ϭ.S)Qp*?2G%.KUTA/?N.Nəh'@'✇;Yi(ӭ%.!:]:ѱ+C; ٥!Сۘ7a4Nuiz{:qNȾk(Ujʢܥz| `Hb_侗#/gzs[cc Zsz|[5^ /,$}(FL\ ]o } zSW#>yFN/G> }8V^&Qcd*dL6Qs(N4="vH4I9_SE] N0brl~!7!_碧灠%<5"GPƴ\c)><%LW{,Ĵ9WIi$'^ 5'l3œb''ɜ`W_&wmmV0̋ltv2D%%|_Cm0P1فd+"q_ۗJRg89s hMGt!aθN]{a?20uTTO)Ԋ޹pgWƋ/BѲe)rh{D1z'? gm>zMCĿVm@l\LZ@ _:s  Y"IQr.21e\ÎR|pMLS0E|A 漗&Mޕ)*$`}a} AkI^.'b/֨56t5aIX/$>35܅q~ MK[+@ MTehjB]wD߸K촫<5Rv[ ¬+7'y^$P299嘆 qEŷm' '~Dr){'i\mz(5]59Ը`[5$p.%ց+]rNGcO%CwO OS3L虄LH䠯b4vFې QF /G{7AMABnC<~d@ \ m^5WrD<; yj{ 7 V6ͨ8nqf }gJ"KOA S"h*a RrѴsچ ( `Ћ>Z;FͿM9^3ro҄(LOO?F;}&p陷i9UQԣV(IA(53%@pf!jb O e~`k}[./_cE/ݜH{P*fSN@ alGbX"1U_d$cbөS"BSQN9TG6Xh%EfK@b).%NH4*tT wO$cS45J%ɢeFӞ)*0BNs=c_!}2cSKSX(/ikD `ђSܰ]#M]إb|^w`.}Gf\DTۊ e_=ʚ\1;қ5ެ>b(> stream xڭXێ7}cv|VH\2)C>l!Z͠aTre%“է:eJ*I*NDifh-1 5q-Iih{FaVx*lɸd 瘟JT֞Za#56k 45M:r`Ejk 8b6D̈:M&kK$KóY,}h.]<:ۈVȖ=?mt`|n>L?>fٯ778!>oow7JYL_\=ؾK>qul"}\b\?I2$χ$SB˥9XKA`#f/ \8[1pR%[ Qf"=6%VˍT-vhQA+^vd )9qE[W0 'Z[ iQ!CD3E9hde4.A0E y!B_d[!0(ƈ OCcXRR -.8 .FAJG G[:#z&QS#=s$a V5~.nQ]mb*ߢM/.o9܉dO?/7vb[FݔKEhU(QՁ:J_{Fxrc5=W%6gІ% k_O\ZZ$EBI"}"klXUNFbfsn2NLFE>JH`:q9 \n{+zb8%b2mrOb|m6 endstream endobj 557 0 obj << /Length 1479 /Filter /FlateDecode >> stream xڍWKs8 W(Ī(A͞v=4{XDyD*ȩ8 I,vd՛4_TqUb]E.ebUu?obv0-8U GJJx*Kݿ_\*zv}\%e3yI*-)I,6#pflX KU ,ȫHOov{̷rF׾#eZ嚙~)#$|a*6}Wxuѽ'~&mQ'AM{$sh1~덍"yo[D-2CtG`|G6_Qit,հH56}whZSq"+䙅{k8 %@}?5% ̷1ܘb0V?,G[OZӅ;^vL^]7Di[pɑdt_݂+@q&˨ty,jYuAp N,b&fi'Me3~Yyʰ~-&#<3 zC7o,jIE %1ġ0"yǁv}n<O mdmެzG~&h9 eLBQI"Ŏ۳,攋8Ĝ2,u3MY?I盍=g$zx/֔K)E8%<&ͣeEịu,21{жqխݵ̰+xLRƅg  I;3t')9piBu9Zd/!E`@w10$ y3Ʌ83Y/'yX`$g+L$#(5 Kp/-cUqU>pێ(d" κP8yZ:ϐ S:GXłi9ah:=4mag|("Ap59o:N(0LǔBٔP ܠ_"81`:c~ ?ŷ W9_*U> stream xڍXK ϯQj3|1RJ́mmʒ#Rݒ`o^7|7<˯l$gy.i7ə(6?ߎ<~Sdr"la.⛝.Y!4-gU"mdڿյUzqP$(Y< 6ܐ_=*tџcݾgڡqcd{dq mdJKqŽ K#A 4q{zvA>"Rƻ>zǓل`llZgǰR;ڇ(/kcZKuKϳzi\DHR\3D9ID!"/,4+zG<}{Hͥ*bArtd?*S\Nn#}F"ޏu׺C8!#.xPW(2.hv$3M%jb*C 𕓚ȟ͆B0803.aWUUH8 ENA' J&jX<H$_]  ɪ!!;{L^Gk2ׂj$w{T3bاo!a΢ʏn~m)+=%)n=vґ}!H9\̐ccLCx&hlK8ҹDLBNI󈱒 H7!MO0x})+WQ|dK `)I2 ]l!7N'> stream xZKsFWʄ1c6-yldcU)}߾_O@(iUAWOOOO`Y_xױW7 $"R?*_|Dxߩx '~4X}՛0 =/WIz}wLC%@Z3Cljo~Z^4dM%W& <S2GE;-Z)u4+* F#}%0ɔ!10j*h9j*S_߂PUIᎰLT<~ Xu&GGPAAa8Xj!O oWЩP򛤑ݹNUC!ե%5M_Sxu4}kQe4Hz@)\ΎD H `\Ӽmu79S Vr!i%..k[ƤO'q/ ƜH" w%MhD H9$#:uHR$8'qv&2m<ALZa-tkB1$˘a?EC,R(4vJywa$1߁ <5sASģS]j+I{adM!saM!ʸ(0Ec e#Z6.LH=T2H8EΈ9Ţ#l1t9c$THaNf"dc:Eѷo. "A\D2aŇ""8:]Y4Z-MPVk"Ǟ@gR })|?D) |/m0#|QOWIZ[oc݄Gg(:iX_QbSQ"H:Q::Vs?b&q4(sA+oLb6HZHCi|Tg_r?A*%ssc\ :Qd.hp#lfG<ʡoZV5tcKYژq̧0~|e:)vtcv>Xg8[@:+iO(\gK#`OGkGH5ƖPbJ[X.+*gбZ.!M ielJgv.lzN[5c7Et;jnZݾs[ӣf۬<8D2{tsB?"!J)—6Kg_M5.p|sօsWbf(Ee8fX5f IbG'*Gx/Ws8:b?8͖r@~LuQ u23yqjGjLjIaDQ͜Dʴa4\ vEpfkZtD(1|s>;ejn]Ҝ%G'af:_T<ǰW/ B>[3 cc#"; p=D En/7M* ~vp(13Irxby-NdG$ }5t֐c0v"pQ!Ԡ_I.m m9Fmoְ: SQ/B=+(!,^(84.Zm] Qy&;bu*c5ż`T? X2zpUP5n^2~I|1hZe+-'Yqglr YٕJ ś­H|YD x4QK"Aȧg1(.=¨G_i؈8n8ӻ1?,vՉ3|0"Rj.Q0?"s5ẬnqoHgkWP[h!ZeBE+ 0г51@JxrۡZOd%LJx_ Z` endstream endobj 602 0 obj << /Length 3259 /Filter /FlateDecode >> stream xZݏܶb%QEAUCpH}q|5A>-ERp>~3T-/./N%eHEʙjjL"ŻH045^oFE_T7Kaͫ?}*NX* gqs.bb%3R+;f}42j,x j\/aVԔ5S_ߏeq3Y7c_ԭˢ^_Ⓦڑơq!-h:8lqA=8~ZJrd(.X~O},ハ}h0,ƹ!`wLensmЌeI?BoB@8D%c\z6u ~_vtb2S̈l~dVy]nu  oWͦ aF 2 R5}9Ls.$2w=pRʩ] ~dγ?d AkvsLǃ.|`C15m`WA F1sIO#Okq{6&jq8DV-Zh2SEv<8&)Op3620bHovB7eifn8yon8/B 3!0',4%j9YV>}2O`Q73+ƔDvM0{Z>TO3~?|g )z#6%g>E-,Ckg\#?46wMqў&lʮ/#u$)ٹ# XfF𦩪" 0'Dp= 9=scv#]vcu:]JPxs}19P禳EaW`Pu;zHh{l> &R 6&qj&+S1DQS1G2H N<늺+\aO;?>{fVQ>ptIjj>dv/PsY {z|~(@L*HlKh+C!Lr_b"3| a5{tRD(m^~JN>3J wOOw=p,9=]}{$xg~^7I3$Bzݕ{Cr?N80us;G![q>mڻniɯ0XS Q睠.I TbfԠoE|'FSr$Ր_*~y)Жt w:[L&Y 8oMY'uC<2S'θ0sǃ{„ 3]Qݺ2ٙ:2+|fm-Lj?OG/A@MD|M-axd_%붹ʯN9mnCŀ~=?_ԉQv>8?1Fâ"sϮu'QVAI_U1A tv渋} _*UWЀ6xS/%f\h:گmzRnnP _:n-:L cx endstream endobj 626 0 obj << /Length 2880 /Filter /FlateDecode >> stream xڽYKsܸW-*WRwJ9XΖ4$rH-'>Qsa@m6懋߽VɦTMeaě,Y;'d+|Vn_y{ˤVvٲ:[u/p(7$3 L8^46}UΕL]AfkLRP}F.̄ihydsfvU0so.W’lҰt,Y(Maeaf1RjerУZrEbdH %V0ό鱌,LX~R5I># X nex*3eUՙ$= ifᢋ`~ehO~w@.Kg NXiif*-%?>Sfx8 8}^3ʹB슄$L~LBQ ߬#7Y{Kʹ_ˀ]5voWN8$k1dS|cKaN)STtcN* A2иqf`1\ q8erev 92bl9IjzF<Zn/ ch C7! S톊f8)5B(L:ܶc#qCms|]HxaC0OCz1Lb@NVAPD' jU?X˟NJ`UcFK6[8AO/֬=dD#R,~m( r e<,] )RS1N%1:w_F7J pn:KXAB{9 mߊh{@`q=ŠyAeՎX,mBYz}GV &EVNyv)Uf~.W G`Bܧ;䛛N_'H54$ nЉ[YA8C%/)-q/<(gKCBA,3`\7 ni9g!K8Ɩ@ E\ߟ#]}zQ&槮}$f'yRL#2մ2[ X@} 4(ƲrQ}afX~1*_ܬⷉHp orI@Kd~ 1XHpOy!״6"\# ]}L|hEflHw_0BGw*E@]Mզg|U}\;E Y  ,h2ޜYW\.Z$;71˾k3:K3mk_va$GQ&;wVK0sՎ2,XNu *= XNs]ޭhSP,D\ӫ*vHK|\೬jOx bjQM龉ad+!iř/ +7dNOrY&QLOptɦК*vt3q- TNIsL:HD#ù>t;{Y`0Δ[<^RVctHX98V2;@D ) OE s j9DXcָ=ݣ#秐]b^w;Ѿ`N,$D,Jz$}tF` R:"鏲W_`l6^N6ŇOѦ18N|88MÔ^bͻiI dt (@oGIɀ_P,T5 B@/"Q`M_[_ pSiև/>DXD|0BR6@:]2’-ll/Uj~ӟr4Zћ]T^tq6]/m7 }PI^?0*@pQZh=RP{)0Yn v;r_YMn'pI4l_859I1hNA#xn4"[$MBSM1B+ٲ:d> stream xڍr6]_CGo'r\KIJEB3XsH%/px"h4}7z;/~7qAFw}e*ueIi]ލ:l,~wc_}-^(Y4Ȣ":J#E({[] coeG3멷觡?lܯ0@P7hGe3t<#|Ai70PǦf$3Δo۪kQ0F.ۚleUqPp%"IJ3^>L?K d_ 2&A#حk16ܓa]˰}92. GswS#Qv--<yW@W&ʮ+.)Ma䅾D2h{[2^ GAۍᤎg # $pl}fCy-!.b]7 |9n"AF͖@Hwz!ie9̤;2',>A'@׋ [8S02nDڮ&19A;IԈ"dᓰp`3db&ҹU0mOF=h*jc1FV=^ W\-8&y;)j1IıD7-rc+#g0$ oɟ-yH~ăCie偔 ,V`M urAB Јx <0 uT*WS=(:Z[UT;~!M8h!*lozc^SJ@=orMwyL;_0qogǃik gioF0OlbL.~(qmc[6\ҩ@%=ݴۿXf6/DF& ġy`VϩϙbRcq (OȠMC wxO2F%EH bE0*[)y2pGAaAh1kx& QLsO+b@\t@6 rɍgdaAӇr'hU8M h߸v"R`ԹKG HTޫН5o5wǎ5=l3 QS<凪c_ZrQǖ '33zecKE ~jr8sso@pjnC/;acwErd&;f7Ԯ:L㊾Vtk m Vp]'SY~o9+%%n]T@ h8?*|2{IQԱƩT+(Zz`}ד;>5Bga" X䢹+OZ8ewV f"}Η ;4xjE`7 m8ݪŀ-^EB X#˫c5)VSaY9OR^(ja͸(J jc%$ᗟ顧iV?`~t1""ī7Wph}yTiø.Hi%PdI(H LQړ$2 ,*Q.\IF" ŲMjyօKV8N:Ȓ9({T<×H\kjR$=jPnh 0;v5տ98h'ڭ=V?\C:' fIDRz[&'$:KXDUC1bO4{%4%.?gWP$ʜN`^m2 endstream endobj 669 0 obj << /Length 3866 /Filter /FlateDecode >> stream x]۶ݿBԌ! 84isפ߯^PJI|t*x/L\8/ntKE$ "0!fqgH,HP//81}Tq5AKE,x?G@+ Zo兎ӠߔC]˦~ -e_ L[sn#,[FK"5W0S`]tv%-ֲگ]-pg83cx/ݎʼKSt"lUu[0ea]\:s/>X5xBnb!sɫJ>o{ů5nL- o=5=u22v|h\2ehU}êJ@n'H۸E)kJRۥ|UơP-P>b/8tʓ52̤*tV>.uV="OJgY Ha rRԄ'ySV(!{Z3\|,[ۦ.[ٶfD۵MB nؽo-bzQYy$;>\U҅w&zI9q]5פ_ mE[ nMw8 0fc_`tx2kD7GY`n%cn1-#|-ꢅv?g#c {/A eEet@.Z1,ڕtj)e0ҧoA;Z4z#kCw[DE}~[w%w}s]dt21E1^ԩiloV#w1>ܕ^"0Z ajh⎺n jqG$ &~xf(pXN1htr: 8 : {#1}bT? K W]?g\ڙw=( LS<џ)9D̦ l3n}gq"\>" [e4KLlPo '\pYbO0GL25umN>C'>u1 k#i! ׍Ĝc-oA '3bLz,vsY0u$[|w"_S cF ?8WR YKEs?rzMn) ȈCvWif,%$ۮb4Q|o0J diB!^YB/:I}AY $23xϩ#@ڜ]nPEGZ8}fLSOB{ϟg$Ӈ;N8ILj@Ӓk>^"92-fk^Xf0]ZKp![I54%zFҜHiq`luE;'*rMc"x3({dTSop }yuh3fP2K4nU)qlK_zpWH] `BiPs$h ` ^Q =+i&93ڑI_9e=47B1-)]Οf;_@&yb饕syRVZ̈=lFǸpHgϣE`q)UrCLj|'4ԤjD愃zmhbd #!V\ty~-zS%W (+}Y7Q endstream endobj 553 0 obj << /Type /ObjStm /N 100 /First 878 /Length 2381 /Filter /FlateDecode >> stream xڽZs7~_ǻP'ӹ97s7ө^&M4u$O>։W,fZ$PI .%u3,.4(M%ՆɕZfIG#¤8Q)]#X)FI``(G%,';dI8c"*XR':L ,l$Fdփ5Xz*&j=8 ĖѓKi v QI.F)PvQP):NB Qm )D&|E% &ΨD_koHI M(  Q!2=1&*?8S=G`)beeST1CTMX8; ZlmjH(3) H6m XKhWl iՆ,vv&ĜtTAA أ W"QE"aMJ1ZhŕlAd%_ "@J&[{%ȡC+4{|߮nVWpv]w.Q1[cYBOwΦ24$ C#kb5p=tVlt??4 Hj6a_vw[z!|mM7n@ò}>mVnӛ?՝{e; CQ|S@eGrŒz3f,܏lfoVnݯ@5 HtaavT@u6n_]-^,<6Ϯ|n^Wfp{ a2S2pז77ڶXx7'eOyLDkC}HhCOPzizh;L9l{źɰ' 00>pg5P DxTGa$h9PF+'ng6Zy977Ruz==T==KM8jx{X +T]Xj,< ߙ}BG j|mqvnf}%f~L<cHGD ƣ86Q9FLї(ě;D 'ǐڈ#VSp<G_s\6`9% ؍Q!Fݜ[|8*}HP D,HJ@%=Oj}[mgjowֻ8we2uJ[Nޢ.-5@bG蒔|ЂvwO1q`5zfVh:VJ1HYV]&Zw`xtت88҄`qFe-3 `JpIݾ6t,8IsGYnjǴ\ͷm8xѲ8-#B>iI}<"Y17#GS-#!CzbөP8ڍ08C+`O.$\VA%%%I%a<ư-I g5 <.PiĀM5YfK#q:IqY LSci-Tlr{y7Wh~BuTܻ٦٢ց,=}6&NPA$gXLznn)`& .&!gLg]!3=! Pڈ-Si#SH >Z_}Po aH7u%}"j?{ڕ2i(TjddMbTj5jlaJf3X\۶[]\w7e73RӞ3G#_j{fte<[m=6v,gVд#ehٖ+- t2[+-jC))"ZMy&9m1LŖtL pX68ë;yH{mv#19?ƿ19?&G'sfg> stream x]۶ݿBof|I>cIMSxwPԅ|wwI'gA >ήgͳ?_>kef9#jqe2kr9{"l]έJ^k~!J>69<W_ ATat$z->Mc_20 yEy[z ( ax7]We}5,Ŧd]ͽo.jJ"ȓ *Uȗ7ESzfiͶ +mkmS.||25gN)ilH12yfֲ_džպ=FTpD[E)ǾwN.qW˗L=yV9))lگpXnh]w!".~Ҏpi*'UJCX@6<?ݶ;\oԆruB!k, ۺ AXK 64YDRO lm^>khqN6?H `MQ/#52MF3 e6Ss1 vex7WF#, T ) zXIlO\Cf+>:6XTjf@ i& fbDAá{/F|‚! |x`F!tOҏ))`T-?5\`6Bz ш>̄%-VìYUPͤ5m`!BnEX$Z*YjEdՆuސPEiI4R> ;7M,9Fݕ~,{3OHZ[oU|y'8ګuU ܵOmO'C8l?tL3]ۆ.,ej~FK ] LqxFùKO>L`DBic a %5T7?v)vo;J᪳Wvp2.bЗ@)vۋܶ #fef[y{x d|A{ .E~ Clc^(JƂۅ )߫*Q[ F<3^h~K?jfvw G AoegMHN~2e)wcsy3zX~ɱ_L Kn(Ѕx5ԂAY%k ELΒn i)* xaL,=vAVA ڢ2pK}@:ܑqGO?'ߑ1;3HOģ/I痓A"z~YғKN&x~!SS_?吲H ]*b d(xwb1!}7,gT8hTTnʂʜ%wTlBDj9`3tV(7Ȼ={4K=ѧ8р|v`hȇD\|Oz]J5=V/V0 LKDe 7 &zq*ji47\? q`+*{ozj08*39-BB{˝E= ҽ 蝹 68s}%k{T~*|t?Oq&?!|!Mz#_U4sNxdy{6̥Or;dN!+0K~wxG6{xpV  آw//;[PaxW &Qi~0 La.z "Ypypi`6^&Q R[~[l? `[)I.թiF3>xF[sְ5 +?=ZYs̝ 2g DrntxzPso#_E&םߴgl%=! '8S@RA@Qt2-esDLQ %i-z^rE-s cVu|2-`ƅ$.a4~;`_H ,='ewkʎ5!e CD >.0JgqQ#qB_6Gx? ʋ6r=f|w< SPovavNRwsTk?|[rBuhmݥECU?=Ji''g;q svS~VvWV!eB<sfZy12uZYQ2|8R b& 6z066> rqx@^%8! >Iz|"TA)ʫ )90Ϯy}Lq(`"Nz(>*Nnn$+Jѐ7U'y|}\[l|!W6Xh(Pg-y}0ϙfnNlW@:~vYD{C.G/N<ٔ ?3"0, 3y$_%)yq1r'R%!uC2OH:w!~X3M e 88xPyZ}Ro6d^Ӱ'n$܍uE?hlgޑ(-M/xQ ֹ.80 mXt7xN(+Ҁ!}.AV3nB%epv*d ȍcZ31 qN&q@m1me&$ endstream endobj 725 0 obj << /Length 3657 /Filter /FlateDecode >> stream x[[s~[ bX>:IXMQ+ 0hYiJ"͗Ab/gwEo_$iPDEd]bT6ͣp^=p]\< 'qW+[xS\^VB~`WՊK%xPscH7iiW?-.#Q$/'n\и.yٻΏ!mEb%wb۲lv)$iЋK:}W~ˠ8E#-#Y 9F`v:)p߀a~ ~yXϒ8 7X`؛val;$ppDKC6lڡY]I;mFh®Y;9q]5m_yFe`<&~,iL3;>j" ٪al 0SB39 `SdQV{S;)͆8(t#0pmGPKŦlj]l;i.;+;'U#% wW sw[zM8M5hn'#j>޸O%p=RiG|IbM1v P%# ^{ 2 )RPSQrO< v&@ VMf˲!ᧈ ^M3iCZ2`+ҫq#Q,FZ)&wDd2mEu;nV,fѮwMݡfC[r!-R(.ВmIHV:`PId 벛6$S~ yv!NSakhĬ©AP jԋU-䨠4*ZWKAF$)КKS=X|P>s=xqFyV`"Kư@V3(CTUG 90^x}"< @Ͷm+Y `@$5Dg\PEqqz(kǬݵ B 5J!ZAIyoGf3;xE]RE 9[(2,ـC'tp9q$WjvK/P/C=u q+mGAo$&f)YAfu3Cܬ!2h@!69фaa~r2zsm<˝{>ýZ6;Et6-C R$E(Ǵb6MIHv)qtbc&ܶH塥`n^5 phX5+е Đ n`^VOh'Ѻ1Rz"}r(8pnJs,G5p$H9b^"-];UZ0ӻ!TM6k v^ LK0Uk`@ a?c1l"}@ 偳&MIo?=GY9D:fU>~pQ~.|Ҭ\pKBsoo<@ba$#vsl*6 ~1?~)P-#w܁tD.MLnƭd͜ɥFurDBa2y΁N:Ɍw`@JXl񫅫C0 V\#eBR.(A`œ[<`[o~官"|vǸ]FATx.򨈓]Ri&1 <9^&P oj _`6Zh/K:8Rf\32AzC TVNt}DI*&p}o;؆!_Yէ 7߸ڰc'-&OFs';%$ʜ%K $CByX(`ۜNp.,Ke`c|şIY4 ˎZ&9I#>J0˒sT(`s*>OUr>?q/NGYYӅ!&6OQ:fZ(J/, 3>B<Z|36]t3XWbTYt,Rښ&>eSd!-s$AfI\ENFdC ,~^d|nu ~lC%!F߇IO8c]6x,19qO;0"yv,2kBm]8X@GؽM@cT#r7j ikwW/Kon>.xmqʷku| ]Z7c゚r/㖣WhO AvR[`D%Fl M*^|"[%fHu ",SC_j+}4~u-[Jh>Dz@Z+F'm$Ayt endstream endobj 746 0 obj << /Length 3701 /Filter /FlateDecode >> stream x]ݿBofr4\~%if&I'Hk)G9\gp], `,)7'\?yB'ϓ0\nRHo8$\6*M3?~f{*my?=q~z<}:H4<"RyssF}}pʼi[۝5 ɴoZd-0o0oL%+{ yV( Sw5ܷOjsA5PZJ)?cG,R aUw{z`hW,\sbTem?ei[ivē戳&"hd=cjږ&m*dkڝm(^ [* 2 +$p˻k- NnbXw[u8B.^P\CV l^v 8ȦE-%S}EZP0z=nRwAFh4BϚD3GബȟMTU_ߞ(IS?T&ɴe٦8>y&` <ۼ'#E~jcxpBS (PаY#й#ずXFMc_)NeJbdu_L?fO@x\. EF I )ρD|0\--qIk1W/ zc*WY[kxiIX{oZ]F} p9xp.5&1DQP$SɶRP>r+ %蘒B=34p@ynB BZ QCn+kFr@-v!&inC@NP~' ~e׹~<#]&}xYha+i:v?r0w# (SAҊ+lz㝀1D&!)͢E<4M6~1 PGd+?jpVI5D$"+JxQj1N<%vTGPNua,˜ Վ Mno6'<>g@?M- N9pLib $xC{2?P&P J!.+^0U4H ‹;ն-o{^$,Ma:sE Tdy|Cp]G˕ g`A9o-4^ΩtV v u) |5<-^bML@;sERJ~7T^ob޳k2w7j&RǻޗP 4{0Fpۨ.hhm/' OJokKE*RZ[RzcXgmͣR>0$ K{}c 3t>&oֶ% " 5*ы-.xG*E|TZ:@;@ Ad/3i!*bX+ ;M+[ P-H4 MQ4ǣNb΁k::.9Mij`46$k z~3 SM1OIu[h9] IhI&G_2n[{KÕS[pUA$՚8ρ!]x>C9w g QJ%Pn7&vhݏH_ 'ѽp J:G ;{wퟪ,-^1&3z#ᓘ'@ǟ1))j/Ɩ*[ :i4A^ /&榲sj{x3Nzך#Тy|0zq{½˾NR54#32+ҒD^a\I w@q"l mKbdp'5XL 5Ҹ|AF(HvGsٹ*Syŏ0^AB I ry(a/߇ˀ3TYr3eKOx\MqDz8 ODAڡ1^pv%*ț' ȏԑV#@] |~Vt$e"഍#ӊk#,XaeWx NnE?B6|H6uZFB1YTM' *񨠯^`1UbYIٕ/'nU irsU5)a8;@(~NCC D,`r懣SeHYT;3T'vdCZ*|bݛSsR V^J,C`I H',cfY4tG<Hs9 UJJaa%;#Wdtdz_ѐjr0cg%qG:rtO;d'Y٠Jȧt|E@YW,a #M0RP-z{gjbĨ-2E=0oį"gHu:ZAUq(8ˊڝ ~8R_)ŵ{+קejNGS=QrQ4gְ 9GZȅ|xfG$?NʷC8;d2{C;ͼbn1fa!Dnnl܍5>Ύn.5E(P|1[KM0 9>(߬=ʁ5}M~ „Y:*-2^`: KIǗ)zb{@LpMC'*d\7~).az 0M!:t J,6^0ǧ,Obbh2p,7s2XxX92nqvOt̩R 6I,1臆eOFHLFl6] )~kŗqF s'Y9U){#&; 5?z,Iu1JBUö=vlwj1wLe퍐6К L82мG|A)UyO&pҏ+8^I4/_ii54*2oC$qlr SdJ"剄`}&W;jTmJ-j-&=c2  -OzWgwQ2'XzѤS6 t>Y!*+:6  T+ `VzD7 :'HsvP#߰4ЋFy뗾  Dʏm®ďz<{KcLQGuq__hrHIo(;8 ƇT[%Kݻ,>@]"f`w-uDZ=F:9a q dv]h%~H s–;D3A_iMjFB-;Z02%q_'R؅x(i-=1kzs9&d0Jw(0=}NO! 7GoeY;>vCFu K?/i endstream endobj 758 0 obj << /Length 3858 /Filter /FlateDecode >> stream xڽێ6=_aee xQ[`ڤ,̠}PlXX[r%ABǎ}|HQF\fͳoo^Y&FLD6Z̬v3rv}vf8P 1߽ T .ܩg\G%~zw_{@ s\_]"OB0BZTLU1 U n)BGQp֫tHŅrgE0\lml6i >U.O n^_1N]z=G>8LEh4PVy +$:.'2M^ig55 /lCojlo,|UZeHN P6qp]n1]H1ry+rUU^ 'G@2@ mQJ%{Q9*!DT6  2ծǛܽ-C嶳ـ8Y=EM&_쐏⮞0h>aEZDPJ,B'{$oco8TݩO fCD1A3B ѶT!` 5sK5-)]7vu^7 -LǗJP7Got }Έtv#/G|}gh&@J$g:1lyhB= / ag=BM8E4tL*Ml$ZGɂi\;o_A:_(x >P\!lOȘ`П]o֊^54!xKKx 悧5"t`Y ̀]N<͂B^0q↝Dm@3gͪcYU(0㊰f 4c*EBz bot(aB4[w}l}s9J'ϲSQ5aCۜNPHۚ"41$CMr3[3Sz.=f[~BFh濗V\d`v71[:Ju%&NEq\Dv:&=㕣"~rb\==;z',7BY:;E"G:Hz*鹤op1sgqy*\*ҀDhc&d}ݭC8z1T}Л~ ~r8B65ϵ}UǧZ%'(؄b$u+h^0NyL"xw+v\>tC+Tlw%mC_88+?x Nv^,b k-ƑJSr'HC>|D |ߓ &rOA"|]?0\e}8|L ݏ7 ߅a2HƐ1=g: ꢅ<">&mh|Aldkc,[,?6[;oX3ȍkILVcč0^KSV#{kiC?Kޙ.Z˪em5!)itQE 푎Fݧ,dL4^Oyą;xސ|]Mﮊ>IZOH}va \UZ)AKw9X)O_a|ӣ0˒ʞFOIYRLg R;M̒l^MUmp:эj([@Cg#|:`dǬ̓\xnpinC;Lޒxj'Cz"x 8=cty4 '%-UbyZZpxN8![X|_u*(åAsBwa-*(eKDw";疞>JKPI-Qhh#B&Bҗ͔78UUpĮTt 49 7z -3*8Oe0=,b*N~NXgTsRtD!Tk. |;"ctg!kQ7n*$?aQ׺%)RI%V'NLi8꿰_ؤۚ]74SvPp_VT-'N8eb:4(a(忺U hދM:owW7eՕzCırX/"_Ps5'^L؊Bjj·c%5-B՗\(DQEmDĺas+d5ZhV _fEå"!LoTb~9椐bbkh3jmvq1Z`^d+>`eѤyQs/7FÛє<FI~,(!v,zKEqmoZxo'U!t tm״̑}k,PL n;ЬBwEYծo*"=)Ag$NFtMu҃ m蹦Kz歗,&}{,HFU;9V0bT4d+znЂMB?(ƞ(3kOi|VEܴ԰! gMtVdGªPrR2e/+H#[?t2 i.NpňqP4i(sl#p8M#C~8nƟyqDsG( &`?)IKcp1RFlqʍةUq@Bʳ7䈽 #!p(C?tO1@(btUwb}jfZJT6jUHj/dlڲUZk=җn%6nw'Mw1s:˰ћGVVwzaJ,ň> 7+YO9g}0wp 0UJɑ6F%hCu_cЧzt:|zJ7oԄB-c^2 d cAE1,.Ͷ7| !T 0zw!YjA> stream xYn7}W-P.9ΐ4:)F[uR`ɨ=^GtHù"'".29HՉǑ\Oit9൒lsv<;I6'FJkq5Օh+]eH)9W+.ȌRL#)?.*d)%' '0F&P0в*zɵbPLAS|{K1W %  f1q vc˫KRp0LKwS:N) ԞTnj/+,aX9,ϦsJ@˒X$ Y՞@r >#_fA S+ d+quœ1ь52s# \Lex`=%f|SQc% -0fFNf0 lŞ@ T-}L1 x 8U 'R`퍺?L\l:-FջE?|~=]N.R2w_Gpuo4nq9i=Ae5$7(8q`Gp6^z7`|6ad,糫˓ɼy&k~OP0"ƗXIK7 a*XC?_؃Qc}0ǿk_xeQ5#jMк<$mv T`F=л" +C QXJ`>i 0e츭'/v<7FwhQ>+}.{K}&@TotE&Q<*ɷm c1mUL`eaEYh@$قK+$Wodnw(8 Q\BH0y&QBI{ Yaۺ&d2eִRnil &VM}o&c8RqD(c=_z gZn5k;c2 N!Oӳ'1 kSBzFs$@3Ksd/\g?NvG$gd.)]Qn[)>Ve`)#UQ"*&N_1;=ZYVwyp| : :!AK[`5an%zr8!)mϰv& ^57&^m q65mj|:tmk5j;m4&CiHiH_]oCss Cv̛$ A>6MCCXF!55aSa4?{,2xtO~mHr'$|MHH9~TM< 3GX޿T>nWF0e\EZj#;%7"ppD#bLÅd 2`&鼾&|4"UNp6N7' endstream endobj 788 0 obj << /Length 3794 /Filter /FlateDecode >> stream xڭێ6=_aId^EE6MiؚX[r%;ɠP${8)^y.<<|W4$!Sfdj8Sl.QI/.ɛ_~&yKMӅ/^_H)_x{O_*ۃhD:a=&y$Wx$*&r\Tdѻd]?LD|Kdfyd>BN_~|Ĥ"H5*XMԇxO_fa $OP9FXge>)..C@iTIʚZr%_w/ yZď-SY:ęR(얙` $~y'~y'~^KNWwA/v Pɻ --Ҷ^Q7{Cj8mx"R˳*3/%0YjEzw*c^KGzc4\2彌7 {> |0^C`jBegATR *X᪳Yտ@,U.܏Nw{H|/ x7ek@0ާzĎ'Rb|}10yJoҷ^H+퓏bE)j#Z! wn*6yX?ifʍOˢ B&?^:'m]]lYC:_4M; _(p@KB6^B?VokWyժqWj`vB%7MժPseõ5(USEΝ7OMY;9 `D.iZdZs$,64E/tDVΫeuae2_b$S=N" 2d܁TR3bv\J .!hlk,-0͠R0ulfؔ@{lɒZKC7K>ֈ#",)nq\*P}e+GƦ8@%;F\[7IfM8Tt#˺b㴪&QV3xۈ"`/x|(D\kY`ӈ΀ AYkdZD}9င V> 9+T?Q) ˃Iĭ֋Pݴ (95@k<žQ,$ 2p܈:\T[R_u5QpY!r )*E vMnu{>6(FxDbQE>x\VpP2ACIGTK0 ث2d_o_Fe_EѓᆆvJX7v}AU:;0Sj<iuk<= ׆[I5?6 >V4jƭ$} a*(-fA5m8d*Z=jM,ƻDSJ$y9rA M~gY887kMĻȣ-8=R `>C-/ڒ熟2*WKf:vlfS XR'ON0F8{Y V.@+ Fb LYP_P<()52ja !<-Y4 ½T2D;C;gl LߧRx ! A̵у~Rkv.)S0սp8,o(7>9qJ8YgPuO:;+cLzb d(ګRdcCD ZU;3u1tE Vx%]L].'Kޏt4$P: A((9\+n*YRy~G'78WnZ_2 'Q+^A"%gzh*z5 rgYO, | é UZK%UOBL.]^6$ TRJaORa9$fL(5(~sr 1ϻn$pТ4Ѡ@ @nJ}ip_8ӥ T(SML+RgFGj^؆Lۣ%bDz]\*@P3(pN7@%uDxE9 B%ATv>S·$|1$ԑē> zt41'~RYZCS!'^Re_zy:8ܚݧЂNpQ!( }@a/t'X!6d=JzMK$U*dG/de=g|]ž^C} B'uI9?l N+ n]f?žwiH'a}؋r;B57Œ&;S7y:ie|]+hcYfmPf|1uFw˪ۧS;w N-ܮteej x@leSڂ*W0~_X3:?mdͮw;V*!b@oP=_V5|hu^~8/V털 |:~?xgE%3 G5> ӠTz-].m@||12 izYƷy?++jh~ICbl endstream endobj 820 0 obj << /Length 3455 /Filter /FlateDecode >> stream x[[6~_a2Qx/mNɴ"  $',%c[,6Ho$qK ;g&W.1o.eYcfwK/uNTۉ IvM.WNgq.cIy{ ,6Lѱ!z2[~K&sÄG]\I1yu-PK/b! .?٣AƔST*I`c< n`54&V "RWJ`-n&K?(=j N) `:i?ViAi@u>z%'0NOO R|6>BIQhkWsQ6\c8)iDW>ղp+x`H CO1;TŚjbl}˓X%2X?Lgc ^s|VLdwu! "/t:J@8v pdMжp#Q[ K,9gIp,FA$xPi$N.NˆNNO'L{ CKΉ-pީ͡shPt 8ac0Th p RD}qLm$!bk] #u-+P~[YZa,Ɔ ziuY-Sw 6Q,]&m4k˲:[խ}B:њ^dV s]=MϹ`_/u1wa[MOw7 E(V~u7+Yacm=\4kM& kHVz:z g%oL݊p*EmUKh,|E=#Zk6kݴ6[7rqRNy`摏e׾J;Sp^_eߪE9Z@pzuiVk͟lXP׮9Ly~YL|XǝYސtQRa}wEul֗=.lOYBAԢUPi.h\zedCF7 r)sۛ >c.A7l`}lZvݻ0bmcXKX@I3p?kM ǻ*zSno)S )[Fq'h9DNո\GAE`N볰RLD;VDQ‭p-5Dny \ccK 7=o =XsLJeN,1$n+JejEmQ3T"PI,;.KLq̫Tֶ`T1Fۖnw `xKFihR\J ᷭTN: Ck70oO 1[X n0q6q6wԵ6yT1mx.eڼ33|%f`ܡjwgN+7s5ٱ#f3j8 fTHT,ÕfA"ME L+`cy!|H1g7í2wGc揥7̟E( TȜpĀ[t:@G'w]z_{O`-˺q!B}s!;wF=_cP7 yz$ENB ASŮ6$%זZd<Hx4'ǽqXqj7ǽq#qmqõ(` c]I ضyuW^wq\6w {q@ᮍBeQC{<]ȴ*\\nt8@=h0mH$[2H"st_#10PhƙC1DrьK1FFyK>4#>1n9|.ݨ/=R܂].A1: 3` g&ɗ9\;{ׁYA3) a J", 0IN߁ I$ C!^tSG{j{ehzCA٘p|x e86oς-Y]ײfa2|6pr{:0wf>mznU|\DUVB- yRIrք=øـ F9z2H}ԽʷpX]?q!@cEBU~?,0YNd۳ʙƦ&u)54viebPr&K"-v? !hY<ܗzSXk?+9ܓu!z݃=|+;DP)X/ ؒ/*4;ohTÀ$1w1#3<,;ωV3vQMزZ- qg٥m8g[PXsFQ+*"yxv^!cͭS˭c!C?Wܻ=>nPIkUϡ9selCi?ARUyH*i-tQ endstream endobj 857 0 obj << /Length 3514 /Filter /FlateDecode >> stream xڭZIϯ-`Elэ-:)L*eWJA4II` dk&|wod<~jeݤq$UAbݥi l3}.gs0Qi <\cPoޥlZ~ u}Bا.mPtFi0:p}#=Cn 8ZM_WubR0Ľ4L{' \Y\pzyHY4M;`;UǼyY]wH,\!QuÍwoo_1BZ0Nyn

3e/E703`yXQW[JBE ;y xgA7'y / sp+ ,&%tE9ԥ#pl86Ulp su;X'P([]z= tj`3F b1$ Fr"gczn{]@x*u\ZG47<<c`r8SPܲbһb(Zn;A G1HםkY y0]WY^1"Yx&pya~=|$H ~ctT9J)X}(s&9o4Dӑҙ?ݢzqY0xl!c@{W& m튠TpuJ_ǑJ`#< +˿FiH8K5: ޼H{p'E9_t@^-nn\\/=x-\_ZAp;k>m#uhƧD♎"ւ~ٯ^\$J5kă0c:z]b@ӎЫO+o&6J0VSX5uc{eUKz|nQ]{66(TVS.1,˰e23 GFl8Udri$a+w_DJٴ ]^] azo} nJT4)^VxW?Ӕ5eKp"KT#Ou::qtG/b⊱M?qkh2!F%bd0xslVduv>=f@Cd|;å#8GD5#s7uPs&yTq؇.k/^Qk]^NdyTED( KDv̜ 9I,z`-lsDH%]ܮm~ wQv&Mzq(:XGn" V=" R-:WC&a܅c_Nrt\&s*gM1c`inFXpa?)o8[vL"3/*zi< W·*IM)sB'˄/閐 S vkp QQƭY] bP>"o&WgH +~}Qʸ H%+~:1:48VNZhY >GrCYX[:7cn1]DSE_lbQXKw3|f Z̽[ڎ~3 K9;G*ɒC S(ICCY$,\$T!vv2 ͸ٝpַe:WH賘DvkB]HXE/rP)\I5A!P;wHqIyM9,*`&Úlr ` ##>בQ:2s3!nghC-Ig CL@Cbo$5,I@G A ul|/4D%;?<,l>Mi"q[wexUjMJ0'q?8V~Y= BPm 3P*$s`sء~Ĕd╣+.wDHg5ˊj&FqvďjMCcF%` gT1V4N繊ltޢSLH0{s:3KA0Ky6߅/I@|V9yۢ I}Y7"= &?v՞.^rD*L `G> stream xڍWK6Qb$a!Eٴ[IuI\k EK{Ar zp^h[D߮~^]NEy/8*8Ey֋OA,l~SμEZۛ͟˕"H(ǷZnqo&o޾E ͛: >ޡʫ Ӊՠ|@lިot,[|[֡Xs+E4 ijU5m+^e@BH{9J"V\i"LD>J=ñMY)1,te,ip;XWz^؏\&Ep޵hroP3D1@OY*?yg<ȊkvQR3fI("1*Va痶5]VMh]؀W|n~vtoK n*鴡Ȗb# m=4scG4,8Ptq2 kL9tvпv SMÓNw+g"D)VUg"aU푷:gch#U֝ }H//u=4S?TA0PxM,,J%ɥ"PV hzѝ}n*P2~,WT`vئiNҼJq3U@[C8\*"Kte*>s3c gGaO b|ӄ;>g')TкXiй zN؞F/9́ߥH-qN\gsŊGk^'?խJW#4gi9|DZ e? ږ΂,{Yh ZfДmm ?|x앬 &330 lO:$<6Ѩr~#* joB%aQ/a GCYh$,H5(KLT`I5a2ӦFW0Hc@pduAA NixG+$Ҝ2L!ЕϸC( 6vCɦS˱zg^0| NZ bރ *2(ӺYK(xm棡!Qb> stream xڝWK6 W3"Rl$NN;Wjd%& lNŦ@G0YV' }udWۇU),VE^ĢZmmrN&-(]DJ&Jdñ=S9qEpBׅ,xM.*iZV5l$#WG $UFPeI3h|+Y`95_o`$futZD;hVZؾ{$5 yA6h3UmAVUB#h5/F/18!n\AVYdl ctW0z|gs^<4wYtP`F 9ЭmgIR R}4GBl*@UR 0}4?;?T+PL’x4+$jly揷oߢڶþDO=D0,N;O0 ҧk F[룃rS~L {|a0٠weYh`Feg To%6Wwr]oy `%[ʢ4M~lO ĭe-t)ؔꫜm̼Ȩ9ԟ-3٤n`~Ztվ1FA$T1 "-ȕBg6 3TMȬz5Nj|#"wW̗Apc;^F|YZo$K T0 $^];&EFb}~_2ZNN㳃v\i/3~H:Sz}r3 endstream endobj 782 0 obj << /Type /ObjStm /N 100 /First 884 /Length 1784 /Filter /FlateDecode >> stream xYo[7 qERTP kץö4]8/XƎGߣ(?Z%P[Ȇ1>D[T1 %T6|$4R<~RLi B|VBijeR[ S c Rpb 8*pX8Wn !4[q 8O MVƮS&{7D́2cQh2aYp1섄!W#dV:UF>cIg*Ag$P ,QL 03 CLva YNπ@ଌ|πs->n7{2XP1W8s>MRrY7΋ 3Nkfߠ#uĂ$I5Ė@ZA L0u=+~؉k&% ϴa[x3 krPnNHPeӂ&Li =L/ cZo_L~N0L%2[ % dC1zg.2_5ߒ9LZ],OޝBYlfyÓl[.OfC8|>nw3l ͇|'^L].3oWgé ,M"ޗXl ͼ?x:Fgɣ깒wK?\7l9_LNtZI/\sl0Bs*][BkD0}x8X XmKUuArJ"ez 9KYI(PJ^K˞SB =-+KX6Q ܃, z! 31krV#wb)WaO^aS>l)QuaY-#Njy17nB&=HUTJ(ںIBEZK ..}`aE.68aa-N}vo7RFҾfRFfiWnLb׬Q~|G#rrV> stream xڵr]_1yTP^ƫS%KR38,ke=},G!F' ox.~sq6:o*P(8ԛ|sh&A@7?ow;EQ?^ƾssWY;^TV#s:7"KFf @=7`*C d[4lMZdU^g\NmNLK,}]}Ϝ՛6`F)1ٙux?6$Ns_ pP6Ž.6ǶN)qD-@'N,ڴpݣS JysFX()L(䁣DbVM&S65j㻡ʙ׀&̀\A7A }Wo8%<ú})&BH D6h3e~JJze ]agdT;Bwm1u%%_=+&N' p=rr@C'qkwA@<@́ 98eA`^Q{D}A@pXo\swHQt'd1A'MX9 ,=Ģj/{ ))3J!d`dx2?!\N8ِ=ҡC{bѧ.9O1@|^`WUY e FeiGVBWvv; 9:#^9Y|,†'h';NhWu#"2Ŭ~[E4h3ЀXS 2--س" ~zdN<~h^\(ٓ@NPp m?p;km@R *td,n;_IƋ=5:+E+rK.*;^V]3qS{l%]J*(љޣzv"0]v}Wb77H" ` # JV>c^ ځ%}p'/{~gbL 7ԼC .B8-%;"L&:$ϨGJ<<p#-9z$PܒfGCw h^9]AZN9躪4+z" A$R@Z4!9 #@,GIJ $ .Cђ\i>`0RJt4 ]OUKq=haن?9 3/ G!m[A  |eWF$ܙ^UXz=s:g 7횣P2t~{ȸ?ќ[qXz͖FbI.t4f1*moK3zӛEn㘵mqP2랏b_qIIO%9'fis}$X8.m۴P A>Kz5B?u2U&EɉU{u 2];a*؀>/=֠5-@# YF (?;?aЀC2QE " BslϹ[Ƈ\2If j)&T3j)2isYP40/V˲nj_' \d_Ĉ?i\:rMs=O,8:zG𷳫MvZj*@O3e4;Wvjjqj#i=V& 뉜r;?q`t7Lu ~}~Je]p`PQ蚱8;ms鐮]BcKe$Ny5|u3ԘhhS퀠P@Z(X#kU>ǥ]ޏ_#NPFu(R\cDʪN^* .GntJYcߔ?yr]!8[ȟ0n=c)`T(P2iŧLS*.7l8 Ƙ}K[H8g3t o:yB&!5Z̀g.$UL̎Ry+?|Fs^֟/>ɝ1Nj8_B##6_рwulC7sP3`d?9%VFJ ?G%|* (6&X rH? 4'l: F!M\b',A=bCYzk#zTrUbԷ]fbWqɧkCs4ɿŒPg~Ӆ:/WrD 2Ȑ@1͎78C@{JQ}ǒhrX,g=&Q<e[\VdAIxk 1T ᡷ=|C[$Lm[f \t$cl/=VY珨5\1H4=wjw)} endstream endobj 928 0 obj << /Length 3307 /Filter /FlateDecode >> stream xڝZ]sF}#0͓&7ɖsnj?` [( qjk_ AOO-UշtyǕ +d~F3k3]^^3~J#SLA(r,+JhU `dƾiMD"̨zQݔVֵW4;y6Wt=U ȷ6Y_[`QGyn^NkȯS?#땵v;O]qv_ U@wy"ʝ~\gi;ݫ )C7}95 H#|/1[u)fb<,uaaPgˎ l<ѱgRbcu}dvx C i7z'qkW#B;G8;w F`A wBcתq8DۮJW/?ISٌ*sէ.%8"P[ rYc;V^tCѬqDi2&t*>ht0^,# 1ش\?*fM ]өxazտo8Q8 .($f^5Q<T)N{Hu)AJNJݮb=Hnk; ]pQBS9H||]Z9 FqPo ߮IF5ږܗ6>NCO^ph qNo" zsI@LK4#U³-Iu@ܽéƜaFI+.jdB`. Iq(w dAޜgK$[$JO[0Fisa0;ӚPnk<8^P5dO髫!̝x+CQϡryzڳQO58b=`B,sV=r)wUۃ"vjg Z=[v0t{LxۺQsU*eDy(zDy05۶?L{4I Ǝm4:wؑT3ϭ#^,2-x{ʎK*[jLj^d0VR;{n$5K&?ID)]΢m4ׂ0$HSui#wיi xZ%Ck;"#nWHeI%MqsozJKH sd7xџ@и/|cT\IJf{KkJx'JbV{$X6NXgXL`TS1f5qXv#aiAyRmL96l::YKĻ쬑2!Y ͍DUt[ݙ} Pli9[X^ 3*_JLGm -EScKu&; $Ko&4Ť1rw.4] VJ}E#ޞW! a9ҿ»ǸY@DIg8GZ֡IYO!*exʱ#ɳz'dྫྷ}";x'ϒE?S\ͧaaѴf1i)jiKu2lBn`0mdWAD 2{F#ۛg F-YJ\Z0|zup;Dot xa֏EsY K'({U]jN{RO1D:Uq֥it~4+ʃt-!z]|v{Yx.n53KqէfRP;%xL)8W)8էsUP|[y˙'BpM] &* ~@#^x9mۍٍ,LZH־89Ӛfi;atL>\dOh(Fm|7\DHm*8/Ae×r#{ULs^ZO *ϩ]ݫ<䱘@qQ6pY:"ƃWiM$z9{7bFIv$V=XE4lfFVFXJI$`z; a9q%}U] .$[*vJOu{/-?!&A O< y˹^}UXY##)DEҲI\aq&~I@Yhg? ́z7[>oGGUǬ}$r-Z*}pp0ߡkĞ:b:̿%­hAhj ;5q_R";|z1@ KFqfAb"UEv'H'JRJ3i>K>/抹X/\nf4gFEpL#W^|ۆOE6P_ endstream endobj 951 0 obj << /Length 3523 /Filter /FlateDecode >> stream xڭr6[8UtNލnDBn8LT}9\I* nvhgz7*ݕalwu5+詄 OUcwmFOLYB^ Z:p>Lt$y27N3yG6r&:JV̘6fP4ʐy CQPN|q7& !v:}S=*b:2R4Y sNrM֓UG,%GfetGA*6rYepךJiz99M+ |{^ S˞XeQ=ouy|Oc:z2U85CVY,pYAǥҡ-vQpVUUgg08RNgH{'Pr8D@&8k+NظfSU0G1LC3,D;x:#CxyOmöa1^Lpo`ñWBiCfߌh`'6<`NwP4 UVzxe>nhȽ9qj[)@V8#T s\~7ؚvK |>Cqp˄U6Lе}=.M[kj5˜M%sR>x W(_B JꎹAqBn9Yl@t1v'aI-LkܑC]F:#}V>K _Kpy b$[0fv.Ё߂ʶ:azI.9hɾ΂l[ۡ;.)8mͪ%\` -gh?54Xy{yHyr-Og2$|׬q (xͯo+%4byc#`$^! @Ae`ɕمزfaܣxzNћG3& J 4nQq7.Z?4o3C7J,$ KGm 05KZG~#b>,-Tw!,.\I#N8 |'Ǯ)ђdIWR's򀮩f`bZU/LgHЃ@Tx) "?Awj;VA΄y|9H8YD,R@cc ctzhH(L̩XWhTe FFNp3XTGTM" -#`DP?D ǑX CcD@o샘E4(v?A@' ,2F/ $s7;As6Uk&qڸB,{<@H`iEw9-1^n/rF@s9z .9\@*IgC?9_R>nA ҇ *-'`+@>LnAxsmpo>o,$J&n _ܥ}_]=,awqiv:N޽v5= ,ٵ{BSEH?̣7 _>jya ~{rG 0`XRY!,<^l^JcpH_0H2 鷡ߎ~oqcQH΅'uOJWJQLea, RͺzPj.qTI: 1} CVa]Hd 5l?NtҐ%G",@(Lӹ`}b$<'Pdg_m rOY|~8LsBʳ.ݯw~H)> j%KV'QY1-C3]t)6nK. {hNO8n*NA(*QohM%~"!!;S?e(ڟz+1򒙛*ɤܘ'v|hԔ&Id~JM@,p,+^4ay݃~RES8j(]ܕrjpQIBhN'37IhnFs }5֍i:Y\3YMw)}'iE_n1w<<Ҝ7pؖ;Ĉ|S*G^n =u@%U2耈$0YDZ/nS6^q˒\x..O?MnIêlD)5?fQɍ@Raq5H]"x=.k'/nz6e&& M9|SΨKg5 :h 868#7]tTqXti9M?^phΪJdɲzϴg,yWO6n|Y6T{Xb|Vwnr !*\ŝXHJR*QFLi 62zn\.6՜y[b $泵? OC66=β0˱ҝKA璇(a+QѢM`O@Ijyi\^GmW{'r3>\=D#rI-%T;EKF/Q1?ɟo⪈Q._ȤⶼΐFW;37z[*J1F˹:\pYБZοt endstream endobj 964 0 obj << /Length 3736 /Filter /FlateDecode >> stream x[Ys~ׯ7cDsp*KUJp$!. ('=}`K{I偃9{z{YFY4{g߾vvvq5E& +go U8?$x?|z~ ^+g>O9~ӷԑ ctDKY.}|& ,C),VQlQ>hfQdz%l5;m P@A4?BG($QgF($ L`B"T#%+g84V?Ա$zBkg$d4Y$Pt:f+;FD㉘!bgqƑ@*DfQQڹIX* 逜4Ɠ{1?K "7 *wic[d"x5VMlE=#!UJτAEatk $'iH /twgK-l2^>zDމI@0ҜһǴIkkwt&"NRű$&"ƹQFlQm@蠘¦*"ģILM G΢Y ~6f.(hBO: &1Qt2Zy2|*:y@%1Q S}"PM5$PM5$PRSMwpN& XkPZ#O&ܗ DM$& .|Ibɓ%?Sn|"LHDDD?%սBN/\xI׭vB=$pj"cILԩNBcVTdb:8 B0kO0~t`5)]ѠYaCZ}[waru>p}BoUUVKj0*.ۼ (I}$f&[\&6ꊋ}tedwdMF9Nc@raVN  /#2Z+%j݀F,$NKXZYD!Enw#s*DH}aQWm׬]8>yŌ'476V뜋iKc`BC_^f]~]7_GKt> {da(-,\n+ | .&늺⎴h%JW79T \" c=@PN8ȹ͓q] fk2G$Qf >2Q ALwjs.@WspF]|ξ/m[^~"t|/9oe ˛?AMM9Hyp#3Bjl&KFeM,;ϝoи6Zɚ%&%+내ݹU%;ɹ矇]^dtJR7vMwn8Y (ֽ̓&o)'#y*<[d6{ƻ^->R]Ke.p#u]+Ƕ%2 t~A=iiDb^eMGW80G|e-`}z D#QmNʱw"`%UYW\.31wB]\5nO="[ %p7-yV)9uDcv/ݗp6@)kwm h8cqD]ZVŭsXרPGi,Iwn >kqf[#xQZ; 5a{wWו6p?ȷڑH? @Lxۂt{pi8V+v ,/h[U>.&L;p+I(=:޺2PKPR@_P+J YUCNу|ߧbpܱs܅^Y4GV_T#YV\tMߓT]k#);£3_zteلfsǟ+nui}O>W}?+~EH/RDsGjYq#R̵[ĬZJo\Sԭć.4E7îv4sEu\?3ґ? RĶi;3-6Xf:%OLV CT!y\W{,!X]29p( ׻I"9N1$sp5:CT~p.Ĕ-pߩ v~ ]+LaZK_U#EEu݄YoU.Ƃ Gz>=M8TJlt!87]WKc8_4)/svzֆ2-{U5z 9 S7uQQ4*xw cg535't{e.F%Q<8m=ts.88e 7-2}4I$3iQJ\! Z0AOԴﺌB 4A/E)S A=Je}aXa;OsUc͚ Fh,Z7VRQ)sxyvCr0QT=YaP0bf.jʆ+3 #1%i#SВ{JN>X΂?^$ h'hӠiPIPq(|{hFH~lݒ#Q[rL FPd3Q-S9S|LOXUch+ >goܷ.ؒ)@cx+^"E@/[LTm7XġL.3,xF\1q8(?ȓg"򁢌 w>٫Ho%+Z2+ s䋜IVo"'!p6\{F8Gd[DO#[~ )ŕ7FK/@)=@2_sA\ Ȁ ĊLG7.T3I-r El)^'W_֭#`3s@ ,w:s@I}PȌ~>;W.6M^=>mT anH4{rlUqoU8Rnp?0]٧^+ ^?IPfo֯I9aEG]p>u0~wGKb/p2z܏&1 aR5xK(j[甯wYA-sWǯHa{֯ l{=53!"e&l3c%D? endstream endobj 990 0 obj << /Length 4303 /Filter /FlateDecode >> stream x<ێȕ 1,d'dLq`-Ռ)CRcwR(5բEAdxԩSVEË޼cpӱ^.DdB(Z/n6wAlnz2VqhP׫?߼qJ$H|n6 ^- n"C'XuW„;ˠDM$H~-WW\B{Is:l7|KF;@@?uU6m_U]&+6;tK6kӵ:-tLb?aC OB-4wGeCab5hV 2X*+!BF)ҖSeK_F<ޥunᲴ0G o:pf,eO"e_opEmEZ-L :)wHRre"Lņe8̙ gҏOњ{!]ű(oU^5#TuW$5 wϋ4R:F D)ѬҒN/V#^ YZȸ1 -@MEZilM5mU{=jp\&. KvյcDMBSΔK nAm]uĖ^߼ \h!aġaj@q,݋w*Fgj_&,9ZFOA:L!@c *NB+9Pz\2ˏ{ gu-~P!`b.X1b(ZFOǜ 8L$ЭH:W˯nͯG@ MAoRe.WN&t sJo)]ӳ=/txn+CkO(]^+ xNhNDPصfS,LĎzq`w[## %dL ei-1dH9B/$Rxi~x0R F*2k q~^ORQ:~p|$ wAMeUpW.v󺀶S#dtWfle~Ys{=k}_ 'α^ a6C$ZӾ{0ycrI8bpr핸^ӗ6juoʦ  NUQԩ5ڗj2PඪG}(z`ITʏLw+'@ UԷ~ :9 ,۔ՎլMH$a #NdVk@ JpLxv@Eӊ02vi3 H:M :Xgfz,Iba (QXB >Ⱦ%sFxtEIiqS;uJQ hc`Vl.Qeh>$Mu6A_Z $ HaI@\fI3Yո/I Ps!o=rG|\92szn&gɯ|zlz>*._\h3zr-=;'enk n)@>| !@\/^\/Z;3dLa|bg.J=ɛ3V))GltXP20SwC8mU[m3 p%,]`>qD nu߃1tݚd ?ǎtiB&:ogA(FZZ(1J֬No:Al?ۺd;;>nGyJݡ1HnLWJ{ȾG6^<y(h}hO8L2Af 㒡ٺvzjx`*Ͼjz(+E7 w< _-s9~y@iVX/tqԽPs 5Y݀D45uJ*m9|v&9ѷ)7q 8 X,& ja )Kwc29%{ ԇ{T˕iy_K[yсrd$G*-xxo8g)C[ 3cc'^ܾ{%HĎ{ORt,_~߭]v0:^@:>ɛC}7Ba[Ђ.q;9sew49T)'-jgZŞNvi^&V_|'oJnV,~,aAww~O|uWX(iWD¹n3*a50lB۾q5]nD}Z|34=g5Ài ޙg)M'aڟ@#@"$EVXKl,M;R1RPti`Q;83aDF698bqtã.CNP,C|xI'A;wbJBzC|q)'V*7ʓr}OqYߘ1q\˦Dph@lsL9#ltGFGo ]+!;J^PIkƇk~C]cKW/D? *]^x`蹛n0Z3z5Tn)Û1D+}_*l3LfskcChi d=輤D.?هb;~8zbH#K#X0>ޒ^H%}[/r$ 1j,{fN=%d bp 7r}j IU \f{fٻdp݋5GC8 0'|=2 tqx:amgY0@x_qZ$+oOe ¯>=[ K 3 FW}{tׇ Ҫ0w@\ސCOWbܱ3?nq ?98& '6q& c+AEC/'vExc> stream xZM7W{0E50:Y`2>$;A;44XUwkV5j#]]|,^ǻdfSx h9)؈t ʆ\R!"L'C,2 `P!v,^!’)O8RY;bXm-Yz$cPX;:#D$ (a՜Б0Ld(,YЁLV&&97W1ГJ$t2ފlIz2Q_jVF*Q;ΤE2_lum]*>X0&Dlvm,-^^W6X~<5 j YfL@AR`$ۂ5r, . yK&d [rDD_ *,01Xerc#*Z5*1T>/ kB h1ߋ o%Oڢ/OppAr hqH7|nFZqumwSֵoocߺ1S ;!xkcQXO}~2&sZBVʸXdĆFDvDm7S9k4NO#~z޵=Gr< sˌHu1-L ,fL $ud3{vS_$!e%OG Z21V> stream xk # -+C@/i. 'KImydkpVho._|J',.gFZ陉0Mr9{Psc/Jq)-/ꕌuF0y9w6XŽ/ob.\oEWTkk۬,7|q!Q1jP"R; ϑW\X]t+B.j55|xxooζNL0= KTAwcyE4 iPW9™͕0)O.&1B+g?j I(b{H*/' *90_b,TJW*X.,v Deȫ"ǫ|0Uk; ei?fSZ7:G>p5\u ]| f43cB),PY_{͖, U~pR3+gB$DSxx B}p8n磠3*Xdؕ < 1 uGT=R!PlX|$IgpS%iJfSňszd3v@h:)O;Q(ͅgL BzCiAiGCL*OA߄O-h|#RILj!^ lȷ|Nµam>lvKxTgD*Ci*gl]$v57opB &݄/:)7Swvs|b nti ̔Ǥ %9 ֔~O^g|p| ^mYw*30R`>bm_Ɠ&'R2fg*Rj)ݗv(:G<;ę Bg8vs ~JJsJinM(9CEJ$dX=МJ]]6#ș܉֡MCG, W!Ң]qk޸Z`)ӶӹujM{buC6'{kjkE ڢބu ,6kP@ص],WdkنВu)gUyK&F&#[%cLo5^|D lMcv ,&Z%;j^}cuu͵U]+{Wk1u6K@ƨD p8)n>3T mmߺ,ݾiM̱|el͢_"4-cr0;!n&h"]%2˂MjW tYrk@/ջX^%j̲oUwPe[h `X *~ /7KlD1` k򘭆fJy&$Y~f`꿼BȰ 2uk,nN d.R^6oʂ4U&` ڮb?/{R ;V'=.@ @;-76.&KĢ5㙓LŖ=}y,rɹE($J> đms%Z.*٢EN 7AsZ45/d à?a_Zr1R>nkPB8IBBN*{AÞXa3X @.{t HxdJt"8w2LX*Rf]).#^̀$<Ѿjr7}i/g{u1_Px?wE^:[|i 0%+iXDl}KR::;z+9x*3c6g<SӿZb# X5/dO]k#X4#+M#BK㏐Gbu'Rx5MBI%۬QؾAQ ku nM WUoR#=A=+5p*8=9I{ۀS\"ȓK%+BPUtui.H>wEM#u]RS;Vnc?Z]jIp%S,jAux؅ev́/ӠܭrPq 3Zr6MϊbnS !ŅL]`\n7Ex|,\)p97fuq)b)XFCGr˙RHpuIMV}wKh?q!^s w&ib^8\.`d~pgcI]ȱ R]a[VnƮ6^g:NL4$%I,?7&e&vYҮ!>ZS{kŇ\좞sbi~B.ۼi^x{_ðw7o}k/dThFi.1x^]<M2 <+Td 2[c A+ta^>]Kǧ&"YG)ҷ%\Ҋ(9>VYr®)_Pk@|-%:Gfi=JZf|>|:';gNH >ABzK#Ҩsվ&x'xn?(`~:@VMftH4"^ t'GJ tԺR5XMNwXQ܎FO=Xz6ZA>hS6cu7PZE4WƒBQ3Ͱ~R%Lz }-ܛǤWLЂT98I`M܌Yn&zQͨ%U9zTBc@k_*vԊ8n@cD Q8.jz}؉7YAwjr6pϥ7L)2uSΠSृ&gc٘a7|oӔڲE2aur[VRʒJz "3(LʤH01$3A}0,e0F`FjW2$mT q4*MlI@J~^%i5  cƢ,p#pCgE1mz> stream x]o}Q:H+=])(u+K>I$W3,9Jl})ЇDԈMZngbWz72aj]"a(f6Nr!PFAOJx|}sT‹Iw~ݛ.Htp/#WIzuޡK @Eȿm>P2%>{ 8Zͣ$tE&Ejo͏B9]9\JjT5}e9_nb#ۼv44E޶Ol;5UDaǴ6J`iuU6Q/Ғ$L)4;&0?oU-W""6.汌M1Qi@:dFs-dpr5]X+$WOLV =]|Gq׊EE>k0S7Uݡ]H\UM1fQ$T3cm=[_}(fKU|5P%vV޿ `ID9 4^L`F4xI,Uhy1hPl`IBQ6IN _`h$Rd0հ6 #)IMp0mIg` )ͯN@40(ɱQP>GũTO7^ <9èS& e[<հl,HUv;cyL~O"{ #_GX 2s,=bFqx9rȋ}!$&0oopy>"JeE>ljt]{ݺYޡ"]0/7۶C~Jwq4@ ޕ T7]XNY "7Б:*v?Ցr\G⊂Zlɋ@AyTHSΙtb=znQݖ/&55GG|*0&q .-oiQm1g[6I͘O^hEO#IJj$i=˪VYK^6GW=:p]5';ߏt`MO!06EblZg"QmOۦ%zSXnyI_PJxy, > 8D31QX 9ϼle3$703hTO,3vVg40qFL&{k5>71Yr=^ rx-n E~@wl\mСYdM;Z+u >KiO;ЙJٍe觋Gh Ҝjݖ`+ ^#DkxC-?)e=o%?IP/IA0H$E#1`%ڇ\S|`vypu 'Ők4 Olþ [.|?{YiV.G]* b9.^rv5w! lvCUd-_I%+Y_mվXpɆdk ODY>wTuj)hiT :tvĀ\_\bd7#HIHLV&+HǽZ%Y\x#: i^4`'@ 4dr(&&2ք wv>$=u|_>rv@0 4-:tXj%o<(gP7t6$u?:*밒OgKhū~^x9* NF6ڔC5H6Ј.4fs qHIFt `oO"> я`Y}Ȋ#VնXRa*:ἸcTV:>T XP FI2zt 蟵6U# >1(`#È_N71b v},]N>=ihc _:Ni6gG8ƞA'pKCڪM|Fd t KnuAZ2O vKsx1!VJL13*08՜[+pFM^1@9 #Tt}*#0BQcOp*设x]S;3JaSV")Ci X09" >~p~Wݯ|,$N#ڶ?93wDG i=xѢ- ;MA('g^S޵.@Y nZ.?tl-:`GD@D5H((xVI傾N@ƺ;J];[P%$j *N3nq?P۲3EO =\( A}W@qUjԮ|QuG/TM.1}'{`̳S ߉=sr \;Qي`1:r2Xh6|Pin jeӺlj+ġu䋧!abS{aw%G W_XR&QtVh+||޷ ½ʽX-l0(-~w=IYM!q 03=:&BhW)5 v]_ّ&ccQE`yxr:z?Wvd,B.|/b߁ɟp::.@J6x]9j8زmQd!1N_Q?tVs wxBWiС"4HL JBd>gihT! QIn *2>Uch5mUw;$ι^+Bvr::M;G> stream x[~Q8D-6 A\I lz-,m$3Ce{R@>,MRp8FY4şo_z-, jY(iiBrcC̵6w_￾s΃/e(}_~XH:60Ű͋3~u ьʹc4B$ŏ?G%|v<1Gj?Y>{c @A|>B((1!/ 9BIw_1 +wGogIT)o)_2(KLO=X[&4ꠢ|Fw͟PJ֒~$Ƞ|F˷_À 0eiIM9I/0Q  sGܑ`[|EK}|C'R=jP>Œm-gXRI7zΑS%<0{tֹ4K+]\C5"oezrZڟ".Ք+߷m7зȲ KbC#yg_Cno{[pR˶FvHC- q~~ZgծpO+ؘ)b ӧ jN'I6>%lU58CV{sS̭&skVo;|6[Jx)q<"N|wMyǛw_ORKILTZB (Ȳ3{R9҅zQ4Wac)V(1q~vz&zuP!C#~_6ۥ MlGWxD??,K[TvQVKw2+ʺP*+̨q`CCRfܑpyx8 a3F4mO.E3W\[ٺ!i:uQ;gCa?_I 5]qЬ׋t_ba^bcVC^ T@x[,-0Ug4\T52nW6籢SP.4on Y~׶>0nB}7s0:<6o}:R\k@i64X#n(Ѝa0KY-H?ƦE'is5o&VEE,pPl6Vp,%b %F ݦ>"K|(JڕdZt a$VmJ AͼtJSNήH0_w#y-:ޕ>Ri ;K|1"G";&YwcL. A:mE^N8T 7䶪`+ml^؏YGhv?_Cv3z-C;Z@lOZhPܟҍP 岩[Ё}V >lukl-qom E?qTc$|`<x"W<ΐv䯅0!_)r^F_/q=*tYhD-5]X=L`:I`q@xAU3_M.9Y=(֦?A:IU/Vn&"o._7]`la>  iTxo_!M6,>hG׌FI-|w[DF|1 6,%mF`0.8r7e(ĭ`6ki6$:G9@/N|f׭ j1;0,~ݛ՟{&? :D\> stream xr_vN6qX3}=DI@Kjs[( N^Ϟ=]EI4WO ]$0NIЦjrQLZM?^7&T 3o/^4iNgYf?\L^N .^=L,2 :5o(N4|}o'uJVjfU_@ktl7?OU57z c~f>7+6X48/T̓YlBg ơK>>*uAEA\AD2DE7]W<O2 V}^-;IueeA8V$ G6$7؅쇓 %(&jllUHgY9? Bj[XH=[{Axc<[0qi Ӧ9Whͺ&{|3,FIG+$:( @mfKFƧe{:^Iv3p.|yA$dMUp7 E`u ddl0/QSeys+ ojvf+؁L[!\r`(*]B ~vPkT˦+bfEӉ`elO BZf7Hl<оӧR:pgm=  < vyWp[g$ɺHq;@Z;tP3zQ4ؼ.!xa[:]U՗WIfY8huKˮkPd: * Pj $x;1XԶqa3^mŊ5G2SqF6 ͜ ]ai Ox$n50,CHtqJ^1ZbqHNa]^{Aő'CFzXiy%ۮ7~ Y0{ A&{$`$чVx?l*oM_E7Mig7#bU_ȎFx!)d("?e7o+ vQA<Lj7Km)WCRkY;jr{e?ޕ$x$\J8E-; ʢ<`J?׋f)b΢(r9 Pv`*Y8=*JyD1̪KtuVF4js׸kp⧬@SYJw#=2.A2n"IZ&4z w7] N"2,qX-/]W~{FݚSlRyRSWGQ"!۴ IJbe g1v~COa&@~ScTEU0t5tͰ+;B=_Ir7 b1qZ,~2P-޼jQ%ё* %w,t2>q(xNk._ٓ(hW~2[%^jWyXYѱ+4LC`qX Yc\3|>v>Mн偸$IC*>m2eI 1ػǑ !\X(64B5 Q0h7(^<輺x˓$Mnb -hzc4)`jg'4ul{=pR0JӇ 4&L%$J <>Oe =a S! H@-k&OgE4f4T8xIC`T S7BE1V zq^h,wMϲ, ap`1T6Tn\SCȞ5>R*6q'm>&G*=GI?Gq>v<=} +Wb'Qp6m691aE62zPzȴPgs]Pڄ4>UԳlW؉ɇkaO_xz$I8d $Kv3Plv [vpMŋjDŽ ȞEw-c q *;,cDadRm{q-=SI INpY^69/߭@`dp1^R3X;kcep_ps3].ip8f\Lgp{NHC9BS Z#dB8[RE2 p&pdŚ( 8 ԯ|GeI4c!a $>CNp4%c~DkFfW(ņv!ۡS5s*T޽"1al9p*%J/#0j}vAxe099ٔY$U(9!ƻG=GU6im4 OC2T^QOK+Gyu]CD^&}YQ*u<4dVRSDRbs*Hwhx:!!qsTg+Eln5$J}E:|ΑRЃOEDG u$9R\P^k (EETTsDB9G NpD.ɱОsY?tQ΅7?w|plX%T JgDCBk!4d2 a"%7ҳvX@[B.!@f#J%}iN6R7CVet @W972'6iX,% 8y,4w2)wjʙ9qvbeXV :^Tdtw^,$ q2Cg䰨[s>S̹=S|D4C=4:q>gȍQގs\MɈTødט/Ad))>,&˨''$dnZ4R6r%[+&g i{Cd\,a7M'> stream xڽYMo7 W K$EIEQK7`mAinA;H]{H8ڸ|G$EQ|׽BN))Cg }Cn>"GJ#8H >҂2 sM. @l>250NcVC'tH $Kjᇚ1UHnn&VW 1tm9 1G&!YLK65 pz*ռ`zVȤA|Ef[jzf5KX "=pI6.R0*:ǯFewF#/Pc*b!IlDaNb͆VHj9h! i#j[*A|^l&|9/YU?>[:[^mC8e@FK;e8/dELZaWK|v2}z=r/''4ϰV̆bB!Z":*DG4ŋE@|b s({ AF.v? fp@2-f"-ڢ-2-2S;S뚠ZtPB 8KYbOv Bƶ(j&Z}A֔v4Ϫ5)}KT 4FɲGme_H!ŮWtGn ?o>z9\+l|")s9s #1oǥ &Kd%Gͷ2hp1]vj1KsD}M[5G=aP:Эk ZK;D:%5h4@ l(:=?0 endstream endobj 1117 0 obj << /Length 3829 /Filter /FlateDecode >> stream x[obDnCH.iz8܃Ki%GҞm;VZv) +"p':^,w/r.2Ye׋T "MpV-.׋wZ\,Eo_~wZG~Å/~߼*4r0E^b4JEclڼx>^X-7./>K  <>~B>^,TI{x|*Op*Ls'&fQJ]-mQ} "<) vH@@qsuz3'D}_begRfSTy98@&KUWT43,[XEu/ֿ:Vx B%a. ;F^  %$y/oacLlR5 rٖb6xF=TZ-ɉyU]qGl]nꊤˊz,@S*2SIpSoj-. ~Xe䍍/vpIiWT~<ƛ#vux]jf2ö:TT$iE e^OEMM!a۩[/)nnR{Q&Y4a(n_4|7ʷ-j6`-WGepha!gLP6zJJb0/9Bݭ컼Dd7xؿ ϛZylmA ;+ŗE˭Um,C'T$2{Ra͞H8kO,L-iGN (8> 9;ab9RS[m ||]0q0xu հch.USypLNF B?ܕŪ` @_r#0ʱ,@ɛɹu=]k;ķXoUĦcj_HnFڙ ZDO\Yi6ֻpsXP524;zBڟNHv7\`Dt@&( ᴝ* "ZېElaPRNLT-O/uO}V5)_u/v-MzF }FiQ/5sMh-\fתv{UJ!?m!oA8T^c],3vXB9ϾuASФ{:aFx}9xp-.З Ӻ7`oS\%;n ػ<#Mn-ET޲fQ4WOXA<9RX7i8PLwpĆ҅E[놴_lv 1]D-XMћurRS%"4t z.KVm3pdmb㏧g0ѧ'K(XLvL<+Z`L &uζJmQC"o:~jG[ kCMء&:ƃ1jXgG9dNc砲V,8SY `QqTA'쌏 W.?=O4p=Ox ;8nyB,%Hۜspp=>318?6 P=FI  rU_[D rbb̖P ]CKeJwuX0j"uH#GyP5&NVx%Spei1c2ƒ WC@9^psϰlddz2`*CN?bR{#7a^ع]۴q1:R.OqX6+7sۆ1=)]hi&GUSPDE,X{YXmw-uӵaP?a3Cj{ysPlFþW=ZGavcOR9&V &^wwM=+m^\:RG]mY۶Ae +P&\Bқ yf[ts92!OƆ/ZbсmJύ p ϜafVhؘs,'0wyQnC]>Ъ@Yі!iîFӨCնqVC܁>8Wn?}lq!$]5uAOQ/ hCC /.B q Lm,ӟZiO09'a4@02 Rb=Gq_Fq }#C1;i\S$ȄqhKR9a LXKy)M778~MЭ W1GT墐J>Ő{HG^G}@W‰S0H=  [%Dɗl[OaʘblTw* '߮V_h;GA ޳(|&')Ycx=#l@@ew"$QRsqxU`S1j[MiKHKU1xt}t$ 6;8yHT @w򭺄Rzr=;WO&q@σÍ-'bBЋ Նʯݲ?؞7= ?ę{_>w%h#Wt[)O:ڟǞ?n LI 2a);;c0vGR4soR!S8|8QTZBȝNo8p8x;p_\?ߨA cKNgH8ߪmW#g7 'ZaS3\9),͒xn%8.9)nx(]U]ܘmݗpI,)t'_EDŽ0Z_F\]6|b"AԳ 7߰f|5CKɠm5̀ktP < | >']{rދ B{b18Xm˼{J?}7\{&V>%+u|4Ɨ ݁XT@c1QS0E*(ԵM4Rӱ7[2T5^/ۆ X#mܒAo#>!SgcmsUo6ys61&Qm DGJun֘'Al#6>o#@D!0ȊzBJ⦪P=lC1ȷfK endstream endobj 1147 0 obj << /Length 4298 /Filter /FlateDecode >> stream xZY6~_ r%"X,3m1VdUj[)u߸TWEQ`0/m6W<ջn0Ods~QQ*6,7͇@|$4@7;:,M_ۿ|63{W Fm<7,7Շf/Bg4?,ԛ^D #x>ZJO!a@j).03KqF8xcm&&12\ S!HK~f4ۘ0ϔfan`3bƩaS'!1j%0O#4`+2,AC׊ S*9Q Rɑ w !ipI:&AKϩR]0Dl<ՉpD/:׭R)K^4ʄm; DCޤ )y@֥jlSLȵ:٧8zHs-NRip AMב=]kGׂ"ۚ.:}hn>:3C%/Rxj]+q_7qS69!mMɽ3 Mjeb @CsPMa-ļ&51gz-]W4b=}MrFF@OMz6̼5S(< (Ě>Y8}ѣ`?Imh;k O@)^vj#)/(;W lRgϠ׵[#pOla> 4հoA7[`ibFfGbene$Dӽou e_oLEߏ*t88/9δI!3D@V'p'9/8P//&#D;ߓfܙB;r 7`g@ ʲɋ\*@*Zdҵ+ H$6n>n ~%>&wBoH@ `΀gJGjsyڇEyFaLC: ,ڹ)C^wS`ƀg{Ih~}Ł8Nk?w vt;\{4/a]8ZM|I߹+ʲt&j.@Lmaw~pȎX9Gf>݇j/KR#-t60>Ao;ȱPFnP|[5HށuH_{WZ]})TAao%ĶOq:U"L X!f&>AW#-+D7tHiGx$D FC&ຢ~u2 ;NpGp?p~/چ>Jf%N,  F< ?cE}bn籺>fBC. YxA\/bByV\æI  Vҷlzgk-.LLb?薔v!2$p:[&) t<&m-9}bbY{;fi3rfc#'oYYi@]{!$qƒU 3N0qm!y8+ 맄LkkW kِPtMpp7/e,?& *Ic&kv1_wT:cWaq8^%}uQtN2C(2t#!ae1 $,'ɻ@ O9}J޺JIO._ A PX{Px#C-YAbH&?҉R^羪ahVҀpnl'~4.K ՉwjIH6,zGYi/6.|ZJZߢyHr @k7 Ӝ,ӭ'~J[Y>u@7fbUV{*q'0eW{[5%H5bs-ZdE} N0Bn暌*}KiK`UD Eg*rt_D!QG!My{&JcJĎ0Ȭ*'bpd6L2,lt,˟ϞtjIT]I嘞DKjg/:l/VQ_ێo# |s|~Nh^N|Kz6Ar*Je$)`56;NXe/xdt͎hC ԾEʖ43i3i\m@ H@~C? ށ&dtL.2'qD]+Ymf]XA"x^{{,0|J[p0:Yj{2>{hc)hkN#6B|dG?qq/` |@c/{OE#7 |޷uܷp-0N;pk>`l1.+@v^^WGjűlBnlR^Bnh1ҟRڪD«c(䥮 6H>O[ 3 :\8Hk ~qD_EkSY6[3<'5tr5+%tz]uXd>+ "JT㮊Ws[=*]-28څZP|ٍ$'ñufu''%--rY ;I'NNc^ U쉬1X!vr,StNmf}tߠ{B x;ٟuwP@^8:z@'#ɥh}YLMӆX8y7Վdbl/9&ZxD{rlwTlDz{pW^B,RLÏAh8m3h>Bsq~ :+~@:_>I'9a;:9fC5@y͝2eY\󰆽57?ꎰ sNnWQW-k*=Ϧ84j:=zY` K> stream xkD|8 t16i,Aw Mz=wՎ<2;Q]]]].',}￲٬Lg˫S5v<3=\ξLl⋹sy~'scL򋏸Eo_-?\~+$a:b ?| dfYi:Qq̖,Mn~El3;̾y1<ְS$cdŅKQE*w3g8/ ^b^بguT\Rj</O4+G[ΰ vTn\ѳ+BP+$e.5ʀFE{us_`]KܯZr7-7 K`ಕyu[\qߒ_\̡{Ju_5u)*.U/k$MUKz]o>erE__CݵТhL)x4Ewrl6L-,b~2G=ˋ( ~ϵ9I')\$99qEYqsϝ{}J3<.zofɯp0j.mE/+dM]VmsA_`}(\"I(@^~Ữl+Ar(ۺul1`3MH5&8S2D Օ(bFbeǔ@cu'>^oftԗ(&#ylriI"W,D~t)OdH,ct/ O}'bLw"*- mYݡ@6$sX4]bѴK@~|IVE_:' ]毥=jw/c#Z,=e¼Lwm:"[nA[t -R}`TK!M+y(郻H#.dKn _ƙS߽n!:L`b!2hzRf[,my]0 {iznv\d`R{FB twFrnI,hhI0b O 8 (PTG =vkEej Kߗզ-o' d%@$,n-`bmM&z+̬"C2Yv[yF!ڛɡ!\Vgk# ]} V]5$Y0DQ*D/%7(Ψb em͆aCn,+wu*DHPk2rd< tT'6rߣ|gX1|\fUeС|À{&Ô]q 9 v`@i0KU7;N[J'(/  7:GPvPS=6lUQ 6vTJsŁ/W6v7}CIBm޷۪{g"IXʑ@bCޔiF%me(^r/>37b? s'HQ_t +K7t&tX104(-BUCV($qb< Py_\i{p{@} 7n!5z iB):1O>NHS,n^r \C=Pj9 ~/… =:puT^.Cb]4{$E.lF8Dd`1T+\v`:Baغ͆4a(zĻrn>j38UtGk(4΁.wfoap&F8VM6Dp 5\iGbz;S0to1·HvSp?T ?|\#9/?@"()m4۳AgC0\ao43F [9/oG)SS,GuD&1ѿ?x`79w t23̐[M sd]tE#Я\ ssG  lON1KrJ:Nq&U@pة9P?r`ṣgGOҳ'mmMgCϻhsS.rgp DY !Y>?ggAi? !l?te=φ\ZO4 R]Xk a;h.{}389 z\P(QZ oTk כ]wټSٞvʙpr:F%o9%'Ez( .:uKLh;+YA$u1D驪D.)e42d՗\}\cCE!Y?1xpXSwFV wBL#<,v1W U*q HIx @xlG߆˧&}c$s|3zZB:n5-2؊c!+eI>C=έW)vL%&β~W^ )Kj )C!HR S[EW6)+ .abXla9 +e6)b7N>L@ut2| l‹eӋdbM<tg`r'N%Q-!8.hp"O5$o0KQ;rTfJP?X!R-Qi(w%roO_@(Լ=Y6wr9[p4 .+<| 3=F+Fփ[epHٱTdif'i`r r'?"MbagsMڇ ͡Ga[m8J K>naAyd `-_ؘmldq>)]RRR>9\L%#$zHoRt\K5spJ;L֞kJpm)e%`x6U %q4v8nk feBPf-{*NG.h!3:αK-5ߑ[ "I-s.|@2;%ZIrYJwt\JGv(wҚgG\> stream x]ݿBcK2@ 8S_'}@Eމ/;%ђxD ApϥX-/y*Zaj].CE%ab&_|]~o~0vSE*:˫+G:.+@5tOm!#B9[09;, 6гEu"#+ΚH[PnHjEu^l}U:6%5OSl;eS"}șU}vQqFX㠨Ko1KLSmmVmUw`4mwG r9Fg oAag@V ,nJXc@'C L7 uK/qN3]@ҫCvG2Z=Ecfl(1k[p~[> -'3Y*=ǬM0׬E E!g$}Ӗ$7mhDD,%1m#ЊEV,/'W Ԓ%5 ]TZSX163\3惖R%ܒ:9I r2c=gL0{t WnzMbN~L%}Ep7 o+3.}`×}T"B&Bқ)M"A4A HT eF4. ab̜N0{]ᩚ<ѵsmnx`RK4! "L>ʹ-JQg5n2X͑fJ\Ape_y_RϧuBd2S1fALiHzNS\T38l.¸k<c⏔ʎU۾ݷR+T t&\c[f  ͋ow| "bsVw*٫+0 by#/GdEJYީf/H vEW;ktՏ>kɱH'KD%M/(.MSHrDf#ߊy~^lYN v`C%쌴0b0" :5 6Ƃ]8rR0_TV|oUrY&T/v,ruo+K J;4+-EL`qʞ[ǣBhgX6A*cnxaծzc hїkZgt5?4}F x[I ^!`0^q[7#ї`<^E><@J\N*'a CAj=!Q՗8 H=@ YͯqGȔHKGQ8a+=Zm@sr:S7nbt$OhAA jLlU 1i(wGHp93[-/bt^l}*$};"+c٭{״)H^aMQt^ j7n]kfU)X:o+Q"XܹT:$ǿ)>U dv_pxڣ4vSx5Aė7w20MьcV& #(֪s`[mwfi9%/-:^|"_,nٵ߄mW7N%cQdzlJd%xhFt?^FȄ[Ӟquwϲh8Wp˰6 ei 4(XmQ 1! x~?ڣ&k?ў &>h": nePS}z5i z%usg8r Pwgɼ8<֊ξ~LF'T{G1:|ϜCֶKM[Sxo}< KXH߼`ݹM/i`Y/YŰYNaCY(LhdKƸȆy7 * *^л.e+U*ߥ`>xwY-LM592<;} m;#aNW\ȯ~ʧ0 {78t1XypX a0:W?U K9j :Z̋2L0׽Q&_1w~pl/t3k<+}G7W1}pfKaAGaNİxX).*#: 1$v?₝|:M6"l5D'sƌo&$.wN?8vlХL 7 oNN%=_]:y@^%"C ٘k*w1%Jo7a]e Gc#!!񾳑 >'7h;}z@.~[@Ʒ-e҃CH˻u˪cq.͡ڡ.zS 'OK-h{c |>) P`\z[SJUp- < a۹6pTg"px`FT_PaokDicgngs[t,"zt{obU^~.m>;(:NHkC^[N1pREb`Jv+}A*9bǎKx'{)b[.yXe}KGoGaݧǿ 7b9Osc)hCGXR'x\eQv;}pDV8κ,>cWmGA%q endstream endobj 1110 0 obj << /Type /ObjStm /N 100 /First 986 /Length 1881 /Filter /FlateDecode >> stream xڽY[[5~ϯ#8lKE }V]J󍓔qQUi9YH!T_CS!sFAj#fJS9P/:)\}?ԉcvk 6ܜH Tec8ΈL|R%Ǣ] (Y~{PAExm $|i=I zR"w'LO$5t]9 kr])p* %KEUг*T{awtF@z.^ }{/Voݑ_sz{iϫ͛.Wyy\dm >_%ڮD:^7 z˷.VϷS~ Xv|r۟xk|}uorvoqVDCЊq=܌td1Yn˰|y 0l`"_摄ZlHZ5z|#J! ħ%ڬ#Ex|Xsn^*8ըQ E,Eujd B$\ u]$K[›18[QAWP9nCPHhx ld ii#H$>&dOe58~hKYzL-GE3 1JCf#&5"JtP]( =8JvP׳X>P\aq;]eON=e_wE/$((rXYKcBӐwŢ-g.h튔hŦj( iE )f4D ;7gݑ0AӀ12e})3JRJ2a(6^[㊸b?Og|2O a=YNk10fN10RD{G]>w7M~CH%CՔ;A2E cS)xyX֔A!lh[ BVS`XJyn+o/G7G{/(JTKJղ틤_z౯Ľ!ďA)gHk"6TK n+i) z&=E*Y 5ZdAU~4B(z?e( ڐ$g ~2Ri{qC1&QtQޫt=q endstream endobj 1230 0 obj << /Length 3626 /Filter /FlateDecode >> stream x˒6aJ}T%k'$*ZX+cRd* $)y!SMh4Ѝ~{g3>ŗ/>JY#nfJl3rvZwonr:}wWn+kna/|&f2)*\o}3L%nH=O1l5{߇PA3{0L3^s ˋ;0h瘀/Љ`1r'to%5ǘ0mDE`¿~~!Hu3r&q:ͤ^đ!]@W&YX.,AW׉ xцsC-4׎s|Oxfda˫k#.GsP.bc7'X:(5}GEVEaE*Z =LXbpѨ[؎$z$pdwZȄV KNpi;hT14ǟ4.'9cwXiM8Mٜ?U_2oy):zv߼NXd?q7WHj j⋹:8zPst 4sC2oFf ]ll2e ӘSիcpmה gLnAiiAHGL482 ɥΆ$cQLv V~I^*֙50745 zU˳kJG/140ذ9)DG;abf珝Az&48Și=oL1V2L00?#wbc xfn*$vz];42TtnZLNo<< &_ND1 ) ؇(&-|zLhN {mQK~j'[st~Ao OzJpI8h 'h-9H<4L%=iyAzޟv[*G5}I._A=#Xzbׅ8jE$t\Ƣx\X+.r`7˖z?dےqj$,i i',/f}"18Fe#eݽ1>':\N(&PL#LD52hK)oO?6FPSc܄7١3ۑS)o9 p<}DYG~T'Sy7AnrJχ 21XC&|7Sj-JidǛ|UYk܋M-Z%94`ִIQY,"ڭiݚϮěٴB '#>A)xjtx PFSJp e6OwP] Oش)MbԿV:?kb݁5x8p5evno{$}|y[W|l/R( O}Oa^8&u?/fѮ |e X LBu,=xQIaUtIJFhpoW/x Э{ύtu(0%31(OUp\ۃi_0fC Z <q\%̸&C)%4'xI0c'v @ϡc|8LACˑ[NytA,2CfȔ*v<*{ztұ묄3NLDE=΋k ,~ؒUcK"-|>rQIћ|SE'.pހOy J$6pOcrMS9^ߠ{̫|i{K(-u So%œ>zRB6Hp滀ƥhY4t{Io$A¨7k1.}hTM@a"e܂oct;J$C''黇8̚l=b;R .N זUK=ߋ,C~~Xs;l1a ] >XS:_ep+ kޓN N|x#"`=sxcnVǢ.XȆ=_9>J;<ɭ{}o.TA?I?pF@$xM2(w 8"ZY=Ӯ=n,M&jTtr([7C%[)m;=#QLdg,Y"CBS^N+>u-RuzJ=ڠ33a"w)qȌd\qmR689ښtaBW+ ~U".S8cpT9F;)Vմ\7U&>ks/eVV[] GEk/ݪUo(1i)i4&.8>>Wwpo*(Ņ+y@A6._SQyb|]8*'uBdJ6u=|X<}C@MaWmp̓BZW7RYԕhxu-\ Eo6::`5x2W,0xߓE! IhR ?B&Mm>߭R*p<cC/JNxt:_n| UP8+88+%CE K j-O_kDnX:jEx %&,RB~,s*+CU#VM]*ʽfd]$ZWĶj~w! endstream endobj 1258 0 obj << /Length 4028 /Filter /FlateDecode >> stream x[B 1c6I6&WC<#LQg}Ek)JǓNGpA8;3;;٥]g|&eadvi:ֳ4vKr15˿|u Z؄@|/_tva lxv.xݏ{ݫ/Ϝ ^(|H06 4D Voό iC?&cQi %L0_󢩋/Nu7Ҵk`-W%G EH6AMy *7]df/9qv]8lwXV*VeKݰ,weˣ$4cj YX3 mW⢴x9vۥ(հޒkpov@f)6R ˺6CjSAK}R1:oٹ]i\ִ; d> >. 6nHI!rB h0LIF@<Ɔq"ހb؈[vmϦifW{.m, y2~ѢAYɷ0\A;PDz0%%DWŢDkA~rn-Ţd: V~]c SX*D[CO\ *޳pʮJL[t-t_)a{حC=eծ Z. ?7idg㫠yIR"  ?כ6X/pbwqP}85 &aϼ3\hiGO0~I#4ft w>"󝏘YY8&v|QEjs\0܄=%,z^G@g̒0 biߴ=,^\i!m4QJ/bUxFրF?'0]!%6?$ $[p=ިr;IрnMq-3 妦YL)Pv켼9 ^,9Z95 Un:6s_`M &3 vFeB_U OY-:w`,AUfc8{cl=C˲)`EXz]Kp#F}iW 'v88hZ𺈵}fs\7EoPc`[חhҬ=J(< uRt[vۈ`*ՠY߅R@c(|ڏ(<xzQЎ%0 6=o##cWUǿo yMoNxPafXЩpԆݏ 0@,(j1>,05G)|& =}.#Ӌ)lӉzB+5׳a'tρOD"B@ y"[H?}s?XGI~Hi\ !HI&QHv(t@pJaPѵX sz89I@ߓ@f?ѱŅU" fH\c p.d30 d SCk9N. QdN>A@G=A@R, Ј6GZPO:'K( 64[މ=$#0aLث@gSG8WOתӦb~k6iYxÚg̫"o(r}HRr=MdW#BcBP Mԫkǐ5 z'=B-‘Nڢ' sdKJ94WI'F.a+-\A0mw> ^""1].#F1(Ƃ_!#Ht󅸏IAe><ǤFzUtcg*Jp he؇[ָwDر]?w0m7XXϷ~'!| lN6Th MPƂ᎝bM4x#Թ8e,jLrUg TT U%Q h vtC1N "0c~#H&ѫsq:n4I&NlK}Xǻyi@LR78KGi~F!{ Pf҂2u0Wti? Bj{T.C2#v/A!\ {T%kD;Euۣ݃qOoż k2틍Âӿ74U\.v_txPu r)K~@mPȫ|Z.!]bP/8hf=Ëp6Ҕ>r?={ewHybBO!xGM+x,X#P3I,?E*_gۏ8 61aN t>q> stream xk (P-:4.>RE?\jkFoo )Y^Y4VK䐜΃3b<[ٷ/t%,1nf3%jǜ]$b7sk]tW7NEo?н*6 }ۼxy\X#}Won"QV5ok0QeUV,nxm>|A V*g? ^x*1BIeRL=,"We- 7Y?X"%DGok٧nObT6'8s=O/%Ϭe88 dz%YdfrIgg/! 1a|-S=A9\LB%IZHLhmJAR'5D7)K__2P(c&p8 1OP@@٘{In;}3OT'0x"gߨCIς@T^>8@2Io^J~¸ H.q$@"iحL<4 ˯Sͧ3׬ sTy1P|BxLb &21, qߎ@;z' 0;= ~u Д֛/{0{ZP( Fxlp^Maφ:F`1E'Du' #1q3d|odr'g N.1Dh9AiȂ-;y6-+Q/UDC*XTvo'6(T ܄yS؛M rs/0$ °\ϰ_Ocر8oD ɌtXu#ʘ#F=P L}N騣 ZG.Չ g*T3r|0`^ڃa$ b"pJO~ԕt ML`Kv$I cALHN-m8M;%kyM8`:O%\HJJ:Ѱt9JALNm]xgy1٨ʾ`!.bsNL05t"Xզϛ&cmk eohg6*?t]eɿlf.ykC̗Aq2V=V"عY:^5; 6Cg۪?fߔCg圉QƷ}ۀ Ο725M< >d\ 9eÁ>(L?}Ǔn߷uhQ.Cޞ͛pZͶpOwL)w:=u$=5gwp-M$52D5/PK@T55cRI b" _k 'T8GJ1΢3 &b MSY;9>7. 'ywAL$xw=aS#> y~.ZAUUnj_|pE΋?慯dxr܉(uLoۨ GXuHN/},e7 cs:GÇYLH9p)ĚÓLԀߗ:`:,*K_"$J7Èv(:Rg.˜@!azy^j2j0MiE6ȅ@YS)[mgǰF}Od^v>Dmp(h~5?4,ň^KiFDpGǪ=UX3]p{lLxR"hۀ#`R%M; b"ƴ`iTܞ; $QyGWS F&b?2;FMO>d@]7"eH5tO?"Iro]$ +36\SY 9)$8ì &r0ܖ.> z*? mkv*Ԓ u*tpigLk9_ӎFaNp,_UL%ƽ3GW5*Щ=jޣ-tdVK]r%l! }]Itm,&.Y;J$Z T!W, F6&Vs ̢,|نR LlxL)ň&T+\cQxJUgE eCAS ͅ#G ~^_t-@'s!NVAƜ!t_03.5t_s=>].pb˜lNGTihxgp.xDQicOgP PGE"KUJHtO4 ld̋XPۥtk ToaOYTNX;>VXeu#NҥaO$G 12z WHKcIӎE/EhyPӊ# (c}6,Ѽsם$#oVMפH{GJk{?;;eg'[[T@TxQȓ(78sBFxj/jh~/0@'^p4j?ߢ_i3/>B/lGpo6Uǘg]A> ]0the,4G~!i*|6coY1t,ֻJC"svE/~o >6Lھln<, %m%Q_}ڔcjwSCwEz&$áX".ff.e'o5۪ f4MCiaΨI·9sA7𨬪,|R˰< KSPť.Y31M æ&`o2=iQٸ;Km5GYn+ȪvѴ=o$U]OdR0)wK q{ͼIM9;3Z阃hgO:|(nkOd]1FӸ7xTR+2|U a~SR'APnebX =] endstream endobj 1320 0 obj << /Length 2535 /Filter /FlateDecode >> stream xڕYKsWJbRݭJz1zXҿOfp\!6f, vGKVEe' *,Ӕ+8IFQR/Dq0'a T :{^zcȋZcttpނ!~冬G+UGv<9) 8ܰf$TqTH &}>6~|4Qu]?V#:.wV)( F8`(nS8i+FI}<oɂJn}jXIgkO(P-LF[CVV ,5{ZYX囼Ny3t.X 8Ygf 6Ѩīhi,K>v}za<,"NtJRSRRŕ^0yǃ=FaEhT'A,Y[*%]I[(c%8~KN87'Jjk]ES-Wa( INLybQR{56;rzI+99ƻFf 횱uCN0}l&$ ; @#b8?؞ˈZs!^OPFi,'wP<0sgD0 y3XZ}钞%i˹mo9S]5P7`tF[;BGg\0. ZGbNq'35WeR?͘OХ/ܥ$Ju}\c.UKE07TV\66K徺\f8y,q㭔N!zõ晊TП~z:xt%_}$%[^#P>wL]Qwݟ`ͿxR.%`pW\4m(HmrرpK5 V\B4 L<7*F\hu0ֺdJ/=\W64k W|0VjIIIǞj$(wF?;< >t4ǜr|oV"mb9,IvU4DV&*,ϼ+yeEu%eIQͺgN4+SSgkڦz$t&(#I]2ӰR2GT؂V? ף'읺f*21,H Uz1qſ|3 endstream endobj 1226 0 obj << /Type /ObjStm /N 100 /First 980 /Length 1666 /Filter /FlateDecode >> stream xڵYIo[7W^(r.4Kh Ρ㨩@ lH}$NJj;K>HgRȔ9Ho [Cf61nD TՈ2JK5"<AAԖM$$ҠD% EL!#e[`դ080C:=ljb {2 e+MOQӢ kJ3 2*w:/~wx @DR)xd*VA$Op.#0MK 3p.p.tBTT)dpnl\ Z5i+d9ԇ 63eRi9Jɭ DS3.`466t򅕢m GMũ1vT oԞȇ(mŻYXyq\ήV?}X>?Y/g!b GGaz&ahQv-V)7~lLhC,í^~fƾMr6_^[ L/g׋؏w\?Y|BSVId_ vzLOn^/&'7!#M2}zj3SNHL R"0xI>_Zw7r1%梻! _ jly_o ,){9Th ŷt+@÷t" ULiES*ĞV \{ԈV'vkE}`X3PbO.\P4ե&ؔ]|S6:(6WbT]H>]1Fn.w$[c|yl-Blb߬X~]AnWU]g>_tѫ |؇.D !4Bo&./7/IV'ڛяCzy}pصx(ҽ!ȣf${$4b/8L|yGo6n5ҲUT.-vKKK8ʟ &CZh6bP ]%͢K5Iad^pr.C\qq ҏotp+l4AڽaMfo;y_0oS+w[lB\ӖdWe $ endstream endobj 1333 0 obj << /Length 2717 /Filter /FlateDecode >> stream xnF_!e) 3[H7E&Z@Z,P¡j=(Em}09<3sf"Y8{u6*Ot2[^HYcgi,ѳe9"M/ro~d  .ᚳP-B*: 9`XLp5iϢm]Z&pYe?vCyt/6bWE xWMs#'KVH"DZR cLpE"rkaVF[^-zNLk; tnp8.D dztQ)m_G pn G@ޓ6p݆[HxuBksC݆0QX^BR,j1[r/sіq] *Zd 5yۘ%8]K?+mL1&K7y6Eƈ  C@>8Y|]%!*, -x`hτܓ1g Ejq8Ç"-D'*M ;|bᨧ[7u@a9n怿 Kkl #gr.*@b09h7=8 O}T9ֱHg8MVvLKc8>EXS@-.+cI TNC6'LGQ'!==Aii_ ;0{Dɼ}: 7= Q_U/Qw4$Z9l ,XP[Y9R3Q 7TG,W!4ԣ97F l`tt 鞇qa$T&=JX"7P݂!]OJA1\Z1uB8)(mXM$ RymAjnS<5j?1'r'&^k/J뿾}L?[+D6%*OMFRhcA&>!L NV}wP"/Ͼg,3e*IjspVLͮhre;kf1Ő) 1@ϠZOH9l3y(eǠ Y*'DE0Eu('H#2YW=JsMpMς \7x)6n ;DYE)EIx}Jr1P ԧ0AComƨe_O)hUVmo?RfŽbۇ%F 9@72;0}d"#H~(I6P%џ)tՐwC1 N?\;cx. W$AWe*n˃"=71(\%M#1]7`kKVn)/l Hk+c Ͷ už0ُ'(T*ǯkJ;5| ul{ݺ%ԌRF'*Ɵ(O|n׌?`%(†D{A~fPqA+!clMHXJ|8)rVAH48Au `eWҷ)om]w2͂M׿8$ij+\7ir? >Mn$t+7u[&=ˆ ﳿ;m-лEMv#.n?EMɺ#D 533{S ]wSvG?L> stream xZK6ϯО© NkU!Ɂ eo@is@4 Eŗ/dzldсmūc'TBx8ѪcCqo@g3x}MF rJ}"ğa&LoOP2OB䙼D+а?4 b@0FavLM|".À V$uks1ӗ>pv 3uGKZk;Y҆DkHL3EQ\JK%m$.pgM>~GXA*$X?SbFɨ@]leG{ȧͱRْTŹ`sޭ=cعMA<*}`Qlg:F^𾋔lŹ_>i,kSK `\ǎܗ:y5tʞ_cs8H1nݦ9}=o@7C]Rh}'AOl1j1a˩O#hE|˲&je=PGb-٢eŘ{D܃+\JO9O&D*?KhE̅o1?,$/qu|ַPYzGƓzL{*9 ߞX+fK=pt2,rJc"is|P9'T6TdAYg+}}1p{o;h̉[ߗ>mf> "y_r oB{OR8A/%l3+(w1\"j5@D>>'urUFsɠnB>6`zE٢;Z ,%29ڟ?<]WMfoH?]Qws=_7CPYW\99‹?|ێ_ y5Q GM.;6c||3uswY} :X#5s_Wq0 6xSyzja~Hǻ8*L"#D>fLZqst/?'rWW d0lCqD9od8SWչ|.,JeXk-cLpM!_ùJ}'72BSSec 4cL/ߵqZpܤ#"QtWf$?xg%o68mH X7p7Խ:_ endstream endobj 1375 0 obj << /Length 3585 /Filter /FlateDecode >> stream xZ[۸~ϯ[e ֒"%R[;M }b3Ҭdgf}ύd{.Q6򑔚7~;8ϒlv9[cg.ϒu98 |t]Z.@FY}(*0 ZQA?V)cz[|auJ<$'&aTbs h4R̙y020;3cu<peU/k Ƶc T Wu2M r-v{[u< O]q qźn- L+XhitOa&\q(+; ׶+9bveɔ-OEyDE_=afQ"U(R y>* A&R \<>1"] Qs7)QNGm/ZeIrWۮ^Sb9즘'>zB۬%fCh} 7]Y9Dj)1*y q\ܶ2i gokT G  c_>섏bݷ"F~uZ.&<\TKboYԱ哱`J-/yۄsŲX9Wpn*ijX! ֣L]+ܸ)j¶M%~ ӘC N@z`514!ݶ^օPt u\ʊGahG?Sǯ,1UǺ*^4'=Ѫ 9Hb*z!Oq#0'ܹ''pUYx OZ&B Dْ[@JFhT5KV}& VIXfMB4v nnb*}C y|-w"[F'!Fm|bP>1S NXh SHaoXEU1egqC%Q u)^b -8}VVp8 mM#$G;ݩlG4[6;ik2ӑ#ʮdmLĕE;Xa1X[=)ୖYk $*DM > |?OtVA0@lԑBdd$v |"[&J(t:IȌ[ffvyRZN&iDBsjE ނ~8$%;dgBgVҪrcϹlF :[K (YL9{TOT \l GJW\e+f#t[I_eќ0^Nذ˧0:GYP _;R̎-:3{>L [v3{@߯dQk*]o[>"qS'# Z n{1A 8vƣoM38h91'o ؕFw b6nNjpW{_vœ ӑ$〺p4\ߥۮZkII1t梃C6$aBށ T+8Fy ԖBo;9(ǻ|gl{FQH6oٳEE_G^Tn Ga]Y pe{ QIتQn3A)e$C}WJii8-qI5qD u~d8 bfܰ8ț:.khݥwKCLKf _Ս=aPPO! ラʕ'WWox~53D'3Xj \n|f%4:&{긁2ֳo9%pNyWGk[b'(Xc -0asZy8|j:lgS34OAZnf \YH NY"h<x䲙cU_b#\G;IR d\%ilpMu5_h- /Q -'Kt}aC.Hqz??=MK|GCZ6PEw.K5O:˳SɅHh璸p\N!<{@yI$oDNG?c͢s#VճVc.8ă$ܐ?&Ź$. !lzCK-Y'EZ/鿦?+A;1+zOc.4@!Q1_i)CƟL?bzb~a0L0ݙѨ^ Yuu lPծ-p6,ʊmkɊ|t./ataTNϱ:w.>r;|#^k.QLoW̗ǿB1A1ϕċh[]]~?ą._%qhEn#²hkW5jG5` 'X}.2pE\6_+&9쨶)ߙ$.S>X4Pç_X"0Xç~[t6^-R-迡-;|FCh Jhi}sxL 6q$/# !ly,h`Ȓ27KA9jcF;'NS5"8a[$1 }G.QCE5S~ݰ|ܨF.vҀbUѫ㷢ځ%-Na[t7U*79ra(]Oג*4&[c%Uo4_S~`B~a᫁11(-Dn.Z7p"->>F>{ endstream endobj 1393 0 obj << /Length 2771 /Filter /FlateDecode >> stream x]۸};yHmKѤyHR@y%Gw )l+^kS$MEp,? =s%"]\xl|fe6}I%B f84ǟ_rqvRF_qܪ? 8~X@) V6jӦu^ޮͬ*jߑoY}mS0ߑ{Mu)tRd٪Sa Ι|ye_*]67/`(Xe^ml`W) !h5@ "A;m7a\8$L;`r]Z벬|wdl7'٬gزy蛪m0.o{ɖM.v9u<>ӓH%X~dHKG@y7= `gjIC3-d8ǣD*R%/-ѮlJ Қz]*f_2" Q)bu?SEw]#PϽUoN_~sr[U+y{A Hvvz~1^#~YnQJH=6pBζ.#Į]謁ui܎Ya^%t9#-As tr&YNfR| C+iutq uTL%'wqvC$q.`b֙x?1쎆O2il=_{ ,??S@(h!%ؿk8ҁT_Ej,) jo fcgp\k0Zpm{]@B-^{M2U 8ڞF/\.*}W*NpB))WTfT2[,?{Ǘ:qADN9A{X uIӉeAAbXq]"Yc!L؄[.;+'lu4[.T*|IJ'J_7 юʒZjbĪVd4CWuW 'Pq0/ɬr=B, "[c!L-Z5Flձo5f .ŴU45JL+(`yҝ3V4K\'(&$:zN+WTԳc$oRQ P?] w)6dY3 ,?_I?C[A'C܏) cޭ W џ p p3%j~ ~c4㺡-8zr0Y;06fVcNc!Lު,8stm&սr S@q>N0!oyH OXEK Xs?pq+fwgMmTR&xD&DvJG& HZS8~UGaC=[txis t0Ƭ,LϷ BoG0ӿlϡ^٠Ve-`iǨ:zلWUxl6!fIex쪝zy߆uû s tla\^ wO#lOlQ|*OaO*{~duJJrliQL=l <@zObO L`?dJ@4ϝkva}DOv&T)q4ɓ%FuLx mn7O81|$2T}z`L )l'*+*_|ʐWl.MǹBO9rk鳗.dܐ@mu֍i=X:)-%A6䳰~HѽkZrQ;4/w6Qj IC*{< endstream endobj 1422 0 obj << /Length 1975 /Filter /FlateDecode >> stream xڥXK6ϯ-T[YoS[IDb$)gv I՞4M_\Wr?nomU,Ej{ʔ0ڬ$26mtΒȬ?mɻL.ژBi7tmٜk'ee6i앓L+$z>Jrč;m6 OƩB)6>a_=h`83-mP*VvxskiGGc߭7[ 4<Ѫ?D_D]+.SH8J&M0mZEދwO؝y\vE:;–XEFIoDX{n_oiu3%Yjۊ p>U8un@e]~qxcݯpi&:r|&1Hh$AU0sWəek7|Ǣ}zb[ 'N2 ;{ %z Ԇ %G: (Xʅ1髂\Ƥd{L,~RRBReO:`3|2d mf+"l,#]H"Kl]0 4(粄덁zC؉ l?幱=:]<6G!O(Ŷ,*rAl"/?u` 5ᗟ= ImŷbaocS<ofa&|=VеY<ʾ>eBa=@}Ckнp+`yҩ[:r:yQlcK)Óٺؤye`оQ&:`cA}PcyaP;([{;qIE|ewixm~,F̾ QPϐ*LBἛZhz B͒ҩ޵mBE.?]X bA-Y_{{<_mo(ʕ cTǛ䪂5|uOGPf!47biEZdA ݠpUbN& bV/r[{IL7лW?|j\@w}E) F`䩛'. endstream endobj 1325 0 obj << /Type /ObjStm /N 100 /First 982 /Length 2043 /Filter /FlateDecode >> stream xY[o[~ׯcBùA-:~p%֕ VXt$xq8gÓK֐B.BfA9PBD J`JƪArGrqdAr)Ш9ߓIQrxAsNIRץ915Sjk%V[k(u).[ʹMd5SJ Nq )"W8~U|/TDҸ MJ/ ŧ;i&3T`w((;,@ھ#^?`.3,QZa9p25.]fZsb TKt.+Eݲf5\%3K#v8c+ksKs3q-)04ե6⺂E̥j5(t.SΨZ-8~ 2#'~+1(F\)9;`59#vB--\`Kh5Ap!r?{~]z>__foG_߿xHO)Q›Ɔc1/k# ϟY~7`r97kyO(4.DKO^a."?j"Aj80@bҘvb#hh=b SpC"R DEȌsp0~}>rfϫiZ\m|;p6]L #fԭaHDǽbsx[rl.0 4u<[M ŋazq;]{ua1ثinltf`7ח7k[[^| 'I$fe:8۫k0bP778;׻ Qve X=7K"g>&>wRbFBE $c8"~p"PB.IE͢Ww^A^2M o2=4RB)r #Lv*d k$rE@ <9j圣JL2菢CCs(B+xFI!0*3VNsLIOYvJnw_>xw=:b=s "§̈}$Ty&Hܨ&B{tKP71J>-a4$h>1|0"J7hT$d" [}q^4BKﴻY Ol x{sW'/K]lf5T4X2^ T+M؞m5%5U 57l@#~>e9j߄Ib'lΙmb lz)[0?(w!E%&?A1kj h[B=RWZAIc{}|V;%~m8ycܘoLsk$Z+5faV d\}\=Eo{I^3x=9Ւ(n #R7Phнhd2t/UF>Р)%!?UʣwhpŀSg1<27]_t%xKȖ-a[2(T~]SR0iVmPz``beiE~>t[%G/Љ_]kG' 8rz0jƊ*c_*[t!=}pC>WӂveyVv)'aJͭainoO$5 gQ-2tI GDEE_ߢ6hPi>V '9w^ 6R *خZ~g endstream endobj 1432 0 obj << /Length 3676 /Filter /FlateDecode >> stream x}ZYsF~ׯBU"`pOc'JZ[UI bDb  __ q놂~lys(~Fq&&Kr?O}ŋ8]̌2?n1^n,~}#mpWgn4hd-mپl@so6<{3Zjw4AA+{wtY+ݍ,ľKq&ʩz75eϻetj*5S!]( z#q{D}M /N3 {C^wATpU9`iDވ<{b=[xbX˲p3Q'InDuvzⶣrZσiKhLOaoq0R92vUqqqzz83$܊s.2LV,s7PW5"+w`) ##@\ɲa 1Xz[k hDIhى'l5?ak>:rQn_\bv^gtm+V[u"gcWgwtv?j3iQ $@v$ծ8 Q?Ašfi,8Z4؊"*ȶ\R1Vzj 0E^k駊]?Zš= E.3i0a ѢKW@ _8$MAD(#Y}UwR qx"M~4ӻtө Jq3bU!Pdؑ"( c e2#*/,t f(A鋀 "KHA4+jm*go{)Z:;P{HFFłt^RҜ]wtg=Ya4S#}&*Jc@N0W[@/^%=Sg %i QLP?hh[7|3+kcBV$X$zwKD  K ^=K!adâD%̺JQ'9@{:nrZN%9B##yT}i80 cQ٪z< ,>kzՈ/ j+ 2" W7c V&XDz<̨j`c]n)XDy֨C.vX\C'13$%BuZҳZ3kUm4obhRgC\ΐ1lQa:JA:D3L~#|^&Fb3Yd乑?/jAA螓 n T:8!&\oNhmBw y7I?bo2Wu "REQ~, G33Ξ: 0L~%ٟ?~l TC7;I< 'fj\/JI(u[:/|Q إIC4`>]*,aNj}ho_4Q"1)mO'39QKjv`Em Pz%rֵpĹ+tQaOEPapk$'2] /P%?+f+m]V9ֻaE܏tc\m (\S(,e"k }x{-5r?X㭔਽BQLC B4$FT R=$Bw>Ym)2o8|=E#Il@t 9`+c7IAғx"8`Ge( 9Ts7L&z|C#ڝ.\Mڛ=3_w4#'WR93ͻo( 6;ao~-T}S'zĔO3җfg_4$~eun3h^Zl2L Bچ3#Pms}T}gCo lYeW\.ܥU>-Ub;bҼ endstream endobj 1443 0 obj << /Length 2961 /Filter /FlateDecode >> stream xڝYK6ϯ-Tl=U{HrP  E)tjk/#ݍ~| ~ .2xx TY2 vafT~{ݛlYo>$?~{bA9HD`oSg2eMqM8&w+I|0&~KBcyO?8'5g@X7MN i3Cif'7f@?g"$b"|ZGEMp7: v۬$.mDuQ>ӳzOL;us bt;wMBݽ(k|?:o_豆q$8YF!5h͋9OQԵ7 MkU'xl?4K5Ť9mewHLVB1+/[\&+OxCB~L'Ր_.(}.DGgfT0$jpm WTD]pp/jP3JxrIPոa.Q#i%ExYq"J@%9+G+ۭeƄMG~Kޤ%:/g AϚjZgzI.HٵtM5N]eC*dl X`8wUGeR[n?@MqRQ$A b={xUL;*Q|c F))ev-Qݕ Xâ>*4j;u^κET)u?Ӊsg>/f|fҷN2 " =2^Ca.7ADp!s7*/dpaayh-h},9ˣͮf>ysp`Y %fk@/y+[d34_iJ(G<KQVZ!m^^j!%T bI7%' / er|#ںN.i1ќ0`tgXLDF F'84_&HX>%}vM]hBoK\\B#O'a 8fZa{{>l'pE؁An EJ/YuVQK(|dUF+|\WqҲSc~3ElMX5T%QY>;B1 Ow[q8zN" HP V= GM4_%u8`pEnRqSSi+tRK(eY^4VTZ ͧo;#;cPKKMW}fly^`,b!7YkW_XyfE{̲8rsO[چ\ V suEi-e!^n'61ڝL9tQTsq-|cSۘ\`7''ԧƥc   I*.:̖ؼT@@ {aRIb4B@#^ua}gȝB0ˍSlpG%%Fd;;j X.b3qxDDDK9{ַjւ񹓜)u'd _@@ `4#g$-:H9m ŭq0,qyE~ι@D鎢  4 Np}MKgM^Hi3;j^>xTƱqEh-?BaܲzѹU&eLE2)˨TWRPm*EٲJ&Yq*@8VR$PxEUE9R&PZ{vo!@$}, o endstream endobj 1455 0 obj << /Length 3663 /Filter /FlateDecode >> stream xڥr6_PUM$@nldU'e3Wtu 'WUeȆސx<1}lwAZ:sؼdx;Y#ƃ%@E)*tc<RxH6Edxov;'x$|Ozh7ܑop2ߢǓg\aWtv Mbv[xmy5އ>U%XqJ6ffraxr`ى 7ѡxa76&¦KFa.af.3|kY2 RCkxiᚆ = 4a]n-hyO  qd}=$ ՙ Sc?J(Ed>2'B"&V³H3 QO}ƱM=yETvEρx⃰vomZf%Dn>fG5'Iӵg;7/>kQ<σ&rV6oa,Qڠ_!F5v +俈!Lk DT*%eJ ׵d(cg FJtp"Fof펞-f,`l 9i8K㑞 AehOe ARJPvitQ-m1{w7ECO+$KC9JWQh@%+,'JmI4ԗyI#wtUGΥ fIH7K`K[#:SLRҥVM-LɬT< \CJPXWJn4>A}RhC!2.%5raF0q)60* äsx tssEG ,wȾ:_K@E*$ W,ܟkK5CUłϯ^tc)ֵۺ}%A=;p GmBb{Wt|7~J%thGc {$J+JKCG+38nL*rlG} lR-F-ԃĈG0it=JghH\B}7U0u,9WSGXjI!9B@ -q틩 ,J|t[ GYMoy"܌ݹf$ָZjrsvR1&9B ʂctzjhYXk_!|@`СHh,ZbabNjSO 3 W2lAl*B߶+1T#GiJL p]yMGb3x( YμeC0O\ E.8M3Hz:3cdn(V ZcsdyLnX s_c:.^jNF+/}2/.U{ZYn>po )ADc\I <gJIJ7#_ sjHˮXWkJis|8ѓOp0uc9d9.^o) 5rmp#>$qJY2/qC !&Y<=n#=92i՞Ga3OqE;n C)8 )/ smef›UIO)w5;h⚍C_*y*ZeAٍ!poG'EaoMΈ7,+V}}Ҙ[@xnp/1N>O~Y=4d_9eLMI:nR$<\ʕn> stream xڕˎ6_ R$ @Ҧ=MiY,o g$ovΛHFyZQ)<ɣ.*Щ'f}Hj]&~aݫ:M*1wl~:΄h"1K4Z:)'%75E\ ]Tq菫5hrC@u{lvMW#+;mVٱ;t;xԌLLjjG5E[JWIo`}JRGJ2ȣX Zt`yY|ԵLVd4qn^k/@t{l] n lfm5۲{aO奏lV;0<*F[aྶ@ٓ J)m@t SxUC n9=Th)I&ة? ?@IlLrlH\λj\$:A@?u" v L}GpCDy] m[ 5jCulACK\V=ծ'"W1/9qG'!|4* &\3ા1QCH*;yHMCǦb !$9H_tJs@I,Ze!t Pxܓ 50j\Iq`.PY 6%SokyԆd5\Tu$eإ ꬩ\mpNԄྦ8gD%1Drvjfy0f:vA[ C4hiF EHn9vA_\OCӱHf%Qe#Pgt[_m.ȃ * z> stream x3PHW0Pp2Ac( endstream endobj 1487 0 obj << /Length 1021 /Filter /FlateDecode >> stream x}V9s:+X3S$'U2ʼKR@$$"ఢh+#!{a &>HO7_?nn=eqUWiUy4N:tCaYp<QN(&!+&n]% ɚlzeU1pO+3w`th*om/lί &Vi7e?"MAY0p솎%Ue,|<Xײ3+yzD`Zf-oauzQZF9aLU(5?1v&j +`("#NMQyө֪yZ7~K58x^h5)25eM( P"RۼZX$P8(tTXhB#Eiʅf֘@ W*j/.[ݩֲA")+y.Ӡ-';f׶Ҙx/N T|:U|4?'{ B3D`3G%VĂ/[5@@If鵔럘tgoypx5'GvZN{z0i|Ղ'&3+:nxיQ~p\69 @#Uڪ BӴYXx$砡EzUցN.& z뭾-Mg7bNdq,yϛ/c\d2O'3sk%VY NRA> 'v`̥QMNJȹNuZN򫚭fyR&{HefZ @Efz3Ώ=5TyFIaWTH螕5YW~:. ^Jq9c~T4> 5̀%D(کQy#4aLKoWOOpƑAcz- vͬ~E(9FdIk'/Ҹ,|Bezu+{v endstream endobj 1491 0 obj << /Length 19 /Filter /FlateDecode >> stream x3PHW0Pp2Ac( endstream endobj 1510 0 obj << /Length 2607 /Filter /FlateDecode >> stream xڭYr8}Wm* ĕ\:3٪$D[\KMR ;x\@'>ywˋ6uVL.&V0$1M.rUTuYN/> cħLR&2NLfF1_39΋3#m2&jn |tQV.9̚Jz|]"c8\| }WrlͫzjD?b~QT}c|ѫ\߸2-9?Zд-d4L&38.`14#km^74}_if̠LaɛZ"wDV%h+F֫WeR1{0m7th:Ku:7WumzkS5;v@F-P=BvLhJEy =$TH @2GӣwHLJ,l& Ku(f3Ls$|"|)n NoSmLÒ1GS aN<1 V"Jh-#˼ZZo|BFex"߸?hy* vetLtHղ2W%X ФU7MN5SzК*aj3(t*̰Z(1/24T~&u ɕ9ٹgqIuֲ2ov7"nY3 MbJJ-҄i.;EGqMTt>uofAgJy z-\Z\.=U}G>p>*e TsLOhɒČ*[O!\C``xfY?ijrGPI"P@ε]uAZ)WR4epk3F ŵP!QՄxC .Tu[l!R ;:$'^XDž4qyAal ^62e҆74id T܀}'aFB|zFN]e4 oj,~,ي?MyPZJI.`5.?g  )ʋ)K*pZht:`n?p;&Ů5ml3mhY(@0=˄qфZp[  SlIu 9u`F]n/ޙjo0Vx^Zwhj9~gOw%[wOQG,D{΁U]f=olNU7*5NFbxPoD@䓩rPDj5yMz-H`S-pǡiݫܜ[.QGtH{D|A*QsɊ;dl57jEWfqJv[6ID p…yqS,EUXղwg.}xw^@'S>`40]Tt *1%p Kuy=VW@l{ A?;"d6Z e 1Bc2?9vG/REUF7 ( D|.'[ajJO{RLO<'!%J8K2HQJ!7V%Pa\f`^ݐ-?pW\7~-JB*N+ 7{'$S!k::kˮN l(h5VI g)YcK#(ق^5HN;!jN /j ;5A;Q#DzȝC\f4nwZ!} zhw5 Š赿T CrXޔXhۦwtt:ITY)S <P{ݽ@! qk_!7i< wE<y׻}oA%H]ccS&-nx1Z<H{59}g0Jt#iFiO# 0=[l*TWw}?o =<guŠ_[W&eNAm?|s5<X65gr0$"ce~hH1@-Z6_6,^]) d_+O h~(x7s endstream endobj 1425 0 obj << /Type /ObjStm /N 100 /First 980 /Length 2631 /Filter /FlateDecode >> stream xZ]o8}bC43fAv7b+e4s.evĊt[)J<&'# W mq:*.(a X-m5"?@J[JBk9\xu 6曎scQ3 סHϧM(lgy)n9a7tBtG3縄zsGs.UDND2lU|7 k2BRZ#-,)n>!Ahщ1d(l2M^+{Wyi,JJDI`pPܬE4xo߂|.)n"ŎvA:B)p r:E2'cG!G^:9F Pʘc8 p o ?P48h&8,}dOdD!*S~ @ܵ"ܵoFEjND ayPJ"zgb'HiSuȖ݄KC0 8V_ϥ,Pr4FSʩl Ċ] @'s c&V0ܬ^*⹜l*_O#ߊ/"9ςS]য়5&A?$9{`$3KZkkƢxUOV¶Q6M5 ^i~=mʦ+I=m(NkN@lFw.,.hy鯗>A^B=AYqܠgkֵwǼV8_NDi]*ŇjMj,mjdg5?7+($Bɘޣr0tZg-3LB +6_ /M}<8(~jqoś;HgCt*%\ [lIi 6Z#-&g5jcTwʀK;j{s9snَlGo7QS9$Nc5zЧ !;\Z%JRA Lv^B4=p0 1V)7LlCc2Xj59ñ\`$/R p\9ceKRAb[3^Bź3U1ԾLg 7(,zhd5*O'ZDKvou +H'd$|6b,.M@-2LbS>$a [uſ"q26I0` |ϻG@cy{:iۛ{F)mݺbqˮ5Av!Ȕ"qzcf*@/*vI:ZC$gz[cz [kF諓 9W<+ ;~ wrw@v86N@]8وv;RGFAeHhZ#)KUKୈ0Ԇeo*óY׶0`}[ ͍1q;ªvڔ76dr1ytf<6F(SH_G>|_':.Nb {QD5=ovŰ7FegbS|_YI2t:KËy Uu/B"?|L;pF7]~LZ"d>pjøV$ens.֝q'+RVܲ wvM(VW#Pr镝{(RL+> el-^y-N}lEQ|IrMƋf!bV/KUӋI;k/GttTuVGjSNa#\OHg *bތN;e\b$6OǥBa^}a}U̐\Ey^_7tD28^{MOA˦^Vpx3+ըhdD671քFxk 5p%(VY߮4#@#m0Gqbwhj4?6VKCqyyTCa\^q]L;ϑ(޽~{u_y\M/mL5w0||:Q\d҆ zQa.bwL|GZyaoiw:VT}ʚI s#L=";(W4Ur1_/2oԲ`_? E>V;,z-fm]O}b/Tԭj;P鸗T񝼸/-0ix1^,Py؛ oo@hgzmXJW0OyzJ񑞒59 bqo1Adn3(5*~>ѻw oIWA yȺ07{%Ϙ|zZIbm:ḑ_j2 endstream endobj 1541 0 obj << /Length 3321 /Filter /FlateDecode >> stream xڭZmo8_/jZ|"ͶIIv-Vlmm+Mwq~3I*-I9pި`q>zD$oxlhl"FZJwJiX0/Wgƛ+v=04[yq=?2G?p 6Uy86٬a4GvVYYwHИ:0vk9K8b4}C(fuwZ {30/\6ͦ>pi^LwkK)FMY-W .ǼΛl@CJ2s洦y<.lW[afN&tU&)]g`+~Z`215uެ2_\W^iWh2)@YUtL2B5[n]VfTƿ$Z"C]V+Da5PBá7DƲ1IPRʈ QձU5 !(ۆ(i!lڦ*bŜ:i,FJGOgDu< AD)/4rID_ڮ7;ˊXXjc&Zohjԇ;P`!1s%EǺH+h=ɑ2Dy"A P(0GCЧ:Ege(<`7xl`v(/n+ GYM< ,F',ŖM:ĺtvEWq(21qJEy$:pbhErW{uObU?tt3sBb^/% };n=P@mnp޾m ƮC8  -agɐ,7&oP~6z @?qaį B aB[\e묩|ƙU LE` c+[(L،OXdW" N'ϊK_\ rv`&B1 Vz*A$*Y{|A\>/⺎S^IρN˾3/0{Q3mMewMbM8qM(Wy⻂ٝ\oI% ʃ1`:~p١ZB}δ׷B8.ۇZ1i#eӬz bcF&y(岚̚VjγOTGUZ#it=:.'lR0Oz[tqBR*2-9:GQ/U\aV@Z9 %DvNw/"æ7xTm >Q6hr[*|:d-"/SM`/Uu'Jh٤hb7٧zm]FH83ɖnl):Il!S FՐ/*bybkHMou>v~P?Fg@kyb4 endstream endobj 1572 0 obj << /Length 2962 /Filter /FlateDecode >> stream xڽmo8S2P|&U (&e].BG#|i}PЩЊRt={slNۣVڣ#'VϼG߳7o~:_?NIkeNz0X6x|D[tVJ1?},8ٻW٪?FC5I])W#ݎ#7bhgNZ_zMY%^O(Ǵsm {ُ=$&G^;lZvL%Y~/MsE4$A2 %͆1v +&< rcuySW_lVNfd}_7v$53Z# wr6+eMO$LX8. cX[WAgwzRs. 0iS-buO?! ˑnWRd-nRa RؐfF* 3h&I.l~ Ì0v^|drww7yYü6*ժ`>'كȀȐ"?æ8rHGmĪXͨ< K]xe".(Y7ŚlES-}7Ţ5RM(f5-VUٕZz'`(/ji gĜ[xm+K_838/x{G TKHR(%3y'>eR2(q߽LI&#BD>gZ!%Iƍĥ1@(z LqZ ALhd7޳r=^OAmPU\:}$(s!'0>@{uS~&UM]Y].*jEwrR %9[7=P_'AxT9!_P?JK·J5a]aAIU/z6Y7+4dޛńPfn݌Q+H{} X Œ}_~Чue^js*:E#Qnϳ_KʜmY 9Ele6% u!NiPV2uIz(QHZiQ58PBaF,TV3wuZ=ƣ]պ 1S|Oˣ@t%Wz&.{EW3!}Nl8\pZN#ڃ .@ kZhPB8\%?< dӓV0z&y&0a`J/V6|QPlQosE!Zai.UAK$Rhmr{j^>r7i"Lz tbQWt)N1H*f)V ;궋ф,5M&W0 endstream endobj 1519 0 obj << /Type /ObjStm /N 100 /First 976 /Length 3062 /Filter /FlateDecode >> stream x[Y~_rv0Gu*NNU,, |=pW>uvSeA1}Lc,Be( g4ƶ6*XWhm k"~]aF+܋Yee(b,-/bīRx4Gr.a$i`} 4G|T޹uyn!21ENLcс]K`YTe $h(^5$M aCP <$Pgw^RSg!B`jpS zsب}g("Aj"{XRn'A-uR/*)Y$[+fa2X^Dl:Awi kRm-MR-x%@,#pa6Q#aʌ !#öbXIO$x[ xEEb:gQ 9 Bj/3b`'"6^ n$тe$F㈍(fPU|خl]Mly^ C5^>~7<=.T8)Pܿ_Om9&'Z R =e0ϻvzVK,IQ~W=/߽_׌h^i3гJo>)_}uizn/%&lBcR"WXЫ4-^r@V < glĈLL7gb yfOi(=M1g2od&nvTtR~vKUg7C\beLFAB؂fMНmӲyS>?P>˶)ʿd1 |tm7/?qE+1b"tjlp"2fN0k6OŴ\(}.V妯~2,gu9_U};ƣõX BG!̣w%"+pHjB@.,?`gwbX5,UųeUtVE;۬IS]l9yࠇJHHd;Cd B6.<CgC'ٖtscASHqʨ=c`T8%_H޾U"i9tU;tz Sm*kЅ.5vDVbF02P {__Tf;&“†+\g:ge"vyZvz{Ssoo'b:q<`1w VMȩ ~YjoLإmF0H!n󺻨 j~;gjix¾=$ŨE ? 9K- 1$eɤ?gp5B0הHʉ@\ٵ?`A̐ Mb>648ZB'* q/$L8WE^T|oӊk(>{n%f+\2ё!2a "[*C,_Cf=Ka|ImDJFhO5\j]u}==N\B+ #P|l*z?ǩ^aN6bĆ;'bse$~?ӎd;m~){>`xU|*emDxSˀe@GqZ3R-5IFRc pm PM43ԑ j$uVxcCGRS I (c2c<׎c%lX ?FUX԰XT3cbXR㻺~-6N!~[&?/o^|3s*䶕_4sW3-2]nS{. q #Vqy@uBP 6[9MUŰ9.f9Ȝ}3T&+$a%ˎ}"z{*%vq#uL͡hrWjvA5ol頑HIi}TkPϗg=U$c\>3=wx>7pOՆElVU3CwGbpoj nS%l; ~Uiq?s1'HPF$Q3t)W˦+W""c&uؖZIJi|8UJ?OAd:=fɸf6m8p09,Ղ;^k\hx=azKٺץ%vgs>:a%F!}[Øq߿bgn?רv7^s&vLŠ p-mċ[T΀H9^U:мBHZ_jMt2H6;BW~25!nFy]5A9V+pfv3xmHlw;v~Ag/K7ɟ֋̅{+Bn$=S3.IWW0D? &ۿ"ϯ+-ȅq[Gҏ}32G2hD?u>$1%GmW[;u endstream endobj 1595 0 obj << /Length 1329 /Filter /FlateDecode >> stream xڽmOF)*rfH:JEUE:JF&JBS}gwfo;ٵ=̀>O8`m!LQXeD$RCmB.ޝxz98.s>\*xp9fSW͊r6 ˲]5fUy[ ^AEVbUXR]ˊ͟og/ky}?BȘlDDj0_ܵVյr$4WG}SNHʘf`._-:vfpl|&MC^\p,א^'Ǣ y'|` B& &.i"m](7fQ$U8^`uK; ̋:w, h'Y΍uIQ"\/Tѿu)`S!DќBi:6X@l"ǬRKtSwgXAJ=]պXхYD? @W7,z :AWJ]znU 9dDFvU^?GCBp '-+rȲNpU\xV7Y$2e;D}J^Z% +%J5<;󏨨Q,V?~Th/DB+l?pY6UC)]xt\b ny  H+< 6FNa< H)ؓ`ʀqO,H'ؓ` bSHx…TRIg=Ƶ]%@jUE^;9nШnn?6QY4E TA|APU[hmXAţ\$ m4D\: V 8 x?Γ ԅ!/.i 4݁mm{Y[ {*-罕=X~uӣTJw?1 |_MTS*8k:jSu艵9",Z`HX牅Z`pL F:!cDmUj&o2ޒU+LUV) BK e)l,HOMJCTG.6&Mx!my^Td $ "ʷy2GI0 1QNQM;[?.捊1#I8#ɗT-lߊ֠Qquʼnsv{'z3bhJuh&oB_> stream xڍtT6 -9twJw C0ҍH+]t#]"Z߷fg뾯`54P 8J((,PS EEH88La(w~s(B 0>5 C|@QPB()#, @@0@Opzp"<0g߯n0(--;E 8@rz`:AE )#$'D x~0  EB!_+A? pL]`LN(? 8a`(@Lw. p@AU  x pC(?ܽ|/r~h(@ F70 Ig`HȮʷ6qlXd>?)3xr×(ԹO>\wۛ c:Ҍ:σ.OntM-Unb"#`tdێ{G° Mʏog76$tH/*mʧ9fPLDԇ7ONQf$x/I m"|6Xm*xњ $'Ze+芲id'kMES0O.8!uFVJƞ B8}9PS6_jU=hN[+S4o5+@dfr_PYDѡ3lB-Zrt>Ȍal 2Jt:H0vF{bRsght ?>TO{[id:ز{bW 㞥-b7dڙ!q Z~Rðk }/tS~r " >_UJg=ҦOK]0ij]B1I `& 1~C+œXf_Ӧpil8j Ě41ϓE? BYb|Ąkv| bƍkraJ/:2| aVǽ.Z;Щk]r#GgY$? NxJWks_Hճ ~kTaӳ{u6pI:+Rk Iutlikx遳}HRj(QEM0í43?|Ι-{%2.Tn{]YBerX-]np_kXEB?.tmq#%^5:MsH PڎMmy&HBV`s2N}TTO\O*K*=pnA⋭cp_Ѻ/-[..Z2.;%,x;9{x=6 )JFC^rtd%17:n*hv5m o8N#7|dLfmA{\i+qNUJ9}>U_Y_ֹ4E S /W ʺ2*ܯKtHNz!duvVC4ŸfI]MSϖ&T[`q{^E8Okhީy"og%I͛<0Z-DT:c@ov6{$;.FVmt݀R*] \DE~S QdQ (--iWLڦn&0 ~~S:jv*;gֻ8*tUQ.y%t4C~86@,}3k!3_v.O؝iBr &ӌIщ|k*<ݢ`ro]L@=5 LLlZ\r̓O3EH6I 54~.7 CM(*Sio^*` ĒrRHD Id -~M1Ъ*Sʸl4KIܸc7qؗ ټ|SǬ| TG+̦΢{Zby&: any|mUKKTA N-}C/{bS+%38DG|>{29 mx$gŗ2"MI:n֬ mIsIz]"èy[HoqgJdBm$Ny8cٍ#"v,h%^9^ŖM֓){v.7g'&4o ~DZ84%7|J'9|57ٳR^`E j%ǚ2JAsM~r3״G/1Q6'l:iE@Y $h<8EȞxc^9jr2ˀU]nAѮH>qx5/povo[ ?ZItsz? >,)1!T=B=ȋ1ЫY]Iy\7AOje|ښꞌ#Y 7Xo9 0vd&eH'd!F8~AVmLVO-o%λOݙٖnK̺-9Kc}juJAJ ?k[m].6abI[=|il T2,u9zS^Ajm.kkc4Mw Kb_['zeJ#1Ț._.= mYsPgOkϡ鲏R Yo5 ²J-&'ŽM Ԁ‘ 2w"59j{sm+| uWx1n@$b!`ޭCl:DaQA}^ǹ)Vr|2&=,aEͭiMV܆/+-+N%zQMb-`Ѫ]nwRѬAƯ|s4i"^2xk OQz :RuOsɼ8.l^FX[X*]ɷ.ADuvA ssBC#]k +01GRyƓўIACF I.o› X~vLgLCVYY9l/#4݌Όoq>:\z5vrmۍP!R*p9*} K1hʮ g)=اo^ d0G{ #-àGm̩wN鷚%-PUWIǧAfjKÞNBZr@vk!m2nwb:RP1 }|X6_5HzfSVWV[A^<Ž.>@u#K.Wnc0U-qy| -u]vɐ*u=T6pC愂XkR,Kb?эbPqW[1GgEs#C抂&F{G,14X'r;f>՘3 > stream xڍwT6Ҥ8$4W R $$ޔ*;R*)RTzZ߷V3g<ϻp޾g(`UQHX$ P $Di9Sp1X )% ۔!8t`1I]I I Da$7-@GB±J('aß %$ (1 Ё.ag! <[CK c/pGp,\ Ѕ&H 0r@`; Qv8w08yc=4s9 _D_$B C`0 D vg8@OU[@@3A(t@UAw?, @㰂XE4kVA*\\H>e߻'p(w;; ۇh17QfDAwEpW8 _=zQh /B!?Q[ HgǛvc ~`%a(F 43Q1򿜊(@@HDw A?b5v(EzwnHG!<&E p@  /LT:;s?~ O݇8 tPx1 j ]-{5pxJ EA"*n{9oH=qG@ë ``?PAP@0'~(-@A$ {ء0v\[@4~^(_{E|) (>`RAUA K$)fByVdNa8̌ b`U+V]E׃ދl5"{l+lИK,T LoIb: FRwrRU)ޝHfpnK[EQ6=ɷ/Fhђ__\.D=OMcגn9s/홢`}xi#G"â,rdB4ZouuΗg.ysw𨩐xClDPU7DJ5`{^"gϷ[Z&\#ѴQ^sS>iyώ6w5(%/w=ryKnToW4OPkA M쥓 aJcl ts^пB5SD{t)5^$nH ڋu?$S)Y_SEj \7KiVf>piP'aQOW${WF5+4fGT2~\$@&(k=3v56ks9Ʀ"IQu[l 2AVQUz^@ǭ):ELVWspj^B/DS`D?ϓt'XOrqi^}dʾ`kE҅$S]G KTLkzѨ ㌗D-vի,,T8D)i)^fSq誝L&SulES}_8rz;5.5xYT9U'Xc=md^"V:qE޴{5ߠP&[9#6qbCDϛ!D=&jζ+O3eG9%/\PKgɛ_Ed#h#.[+lwluACVsrd+a)FV+DCZek ٓw'F!<^ sPZbO"~*HF=09xAR Uцq o+m/]yc$b&U M&DQUAIo=;}K hB U&hzjxo/$oL.9*ڮȔy}h3k^j=9u9C#1WtLL^7VV ye '6Ák;m^AӖ#4Xy2nZéUDW|V~ߪQS@;{]Dz2w~dNx$b*xpEj/G [*/JN^[ , zSFB67̑XLM*٠H Ɯ5<~(P$5sWҋdrn*V 85jJrʟ9~u%rZ~:OmFh9 .DmhͲpJRxSqϻ_3m[tSHZг{7 ~Ȯx1#кCMavfQHIx~S3N#1'b{זEqKۺVq|DYfhV(q~g&vż)19Ń8uRKWx•Tg-k>oa'a܅R~gi`| I )P)SUv!9'LR5XB;s8=n*JK~x=${۫}j2V/>87"š#Xj炱!dT z!i#8/M5d28%}zs*/QY nD+ns]_MM!Ƽ~0Y/p[|&ZhYyݘ ㍦${m'= 9lܟ-u?PSMX%V:O~a)U `8{>s2@?}IPR!|د0ʵ"w#0{}&1sA^"v ꁼv<(Gʫ/&7R!*CLKWF/ӭ'Zd_~pnϟ{g+d-M_3d3 /+_ޔ !d5ΗfU-L럮*YiƪZy/  Ogm&g-Qũ}#rၚ x^Z=Vr״ݍm%n g4r)GS} #ˢEXnf)uUrέ5U0:3{;dk8O@&`w,T:$1~=K:Jȹ}·|q5Np@z#Gӷ0CufqYMĈ/GK]yl|K̚mV!c<&`Zpeehkfz|y8wBپ"⮚Ɣw̒T2v3p֋8`voKVv TR fub+ /J_t/{ZΎ?Ș 8Uw'彻k(:-Bs-Nsso4Td -,16߫M?4Of,s5?8lӂ"b,Գ i(FR}@kHOٽםܫ^3Ck.nx S+(pN3k"u$EWW1]"R. :F(O q#(6*¡ ;·el&$q+KKU;Vt~X:ұ>=VQk=i ^;JI1g>rNf3dEZ-d9ݚ gZIU~+FRZEl]AcsMFk\NpY9=o;<Ccc-BYk I-/Mb_4XË^a9TGmKF!…<C>zI(KqDd{of 2>]Fcw5Pf]Lj QC&euƵϠBXߛFz+ ! ]=Z5gRO}^ϒ\1bZO 7:kݹ5Nj%^;4k=%]u[r*w~ LghR? f&Ov3SKڰK?j3̪ڇn7$iF+:{i"@V;26bL]$$ĝzo!JC3/Q,d3ݒN6著8]C +#iwYmAJ$D?mM;Uph[WpڡC0[<UqIzل2UM$W?X/wʜS%I,(3<{@9iE/a1ɫmB'傾.)&ex㩜i8h$<ǝFf@53= \˞y $% &lKB↘(=(wymZK0ݰzF'DmͰGIO*mLw\= o{Pd V7 rh%-GtVRrU|U,6 ?-4di5WK .4D+ʥ `) U[du6ńJZ3YVz,I8!+o> 65qN1M/Œsf#j|*g#Zdx{s'ex98ۍ: orXs;FCxe|pQxm'ȹ~=Vڤɝ<*cV@Qd!#/h`Oj"jGǽ\t?v|c/w1u'EE g̖XDxm]k?_xn%DvlKckuZhSx6(q+zt}b HF]Fϡ?/'&0,KggӒoV9DV,"!!LaXH?fN>cykK{$:IMYvOEM5 }Rm=^^.(0*yrXHE߹zk vTr~},ٙgZ -We]$fw+ EWA'Wh艫`YdkЛ &»FW8^LPCid̘k_M=AJLtڏUnu9',RiQ,EJpO\7.et!!r:*Vv(C87x,zwAcx#/r-jT?{RJCv畬i:{|ty02xriHLû1Kx -+z\-q6LAo-pƐk nT"Pw.C<+С\v7BMW̑/¾Mh(Ϊ]$}KHݭ sd> stream xڍtTS.`)EB {b!*Ez H^K.rsZgygy΢g/oE`8~XPV`a0XC!MX$-S8m @a"* !0X_@$D+b\}ܑ81zDBBw: pGah@sDOP!D|QKs`02| wOk`@3;`bq^0wwp@!အ@k@]oBHdqq}hB*Z8oCPX > C`xa>w<, `_# *ee"a~tG#ghaDUt@+]d9 p,.&,. 7QWy#W ?+G#d~X'{ w!8D:ލc܃_YeA|_AU y# ?;D~!(@ `@ 2z06UGc 8_apwe:ፀMcRaN/?Hog (PO2|=Y2QvjQp;4TsGʪmۄ ~M%K;b(ZHe: sx7qLEL#Q,[Qm2;gK|btLVMc$*=]3L1!"pR]J"$U&3zndR٪R+jБu)ަ"1s~LAG K#&(X*;g'=bz"gpxg ue+tin`@aS3ib: rZ.Q.i?JXv)CyR35#0v/Wx1br*\e~U:щWGI+oH^cg w{<(䎘KVt=]ä;?T"3x6s+L|/gln\2AX 'hz-ؖ|V$@b1Wr(8/s]Ryh5rAXջC` fބ:dS`G5S%cjsZ@Jb\-Ol`U(V%SVbm/ܿdsE ߠξvSO 2{c~i>: j&)*N{@vҍbiXS=ɬS[Bތ@mL%+bi½N!M؋Iuld |4V~~-K꫗ ƣ-/LW+73muQzqn-2p0/[:s}p%}&{_ :改*HjvH-+1j>E4tC bkq>9{InN8},*g3&3Nj jP Ҫjܢ^;Y6za|ms㓞m:ܦPj8e"-DI+'d/hZ p E|j-5 ^}%=Cs}F A7߂07-ܽ]evԍ'(ˍйsnM3iI>l]+UС$:^շjG6x#~*Nw6*f.K}͠%X),Tؾ'!&'٘*}賗>Ͼ< 떱vg  67:>Us`z^Oб ֏C/v{j IHqPZ$rj_q6䊺p*Zpbmd a⭒}wK|[]gYr;M@3X>p~n%]LE6Kbas*秪C:?oG'I'*| bO~Y|rrFLWS:qҋ\VA[VE (guC4l#&o:sz73KSw;?:׆[ho{ >:A9jWhNw348Bws\e "iy~/8gc[)t-iʎ/.V1i+2{.'_v8V 48D(L'C}5$K}B+qӳre-$ _lBif=Cߛ&9uD?\`70b*fGpM}}ƙf+Cxh0a2 ͖qy31wiͥl}"GՆńmb6&[$ϦMWIw 535W.X%a%|w|'c}^݄BݩMȼ!n\,kt7xgeJB2y}gUCvZsJtN0'O4}MTS"&,i{jfg%|> @P_RIK#<%q^NF{zSb1Z˭ڽgڄW}W:Ymdci@A~*uNĆ/D˸(·-ʮfD}Zy;`)G{҅y弝{,M MpKˇS}73(rjA8S{%YQx'BuOdUR[a[j._XrIdOxNIT]P.JmI9xX|mAf~ l|/_o5K]JY{ӔEo(IYک!H+S=Y{ojH+ۍqWv >@vM_Q*Vb3 k9Mnf/9W| IEvzw<߷2wf^AE #iyYzOg!b#/RR_ٮ$ ba8Cv|+Ӱbo2hg& mT\5JBUGmpNw%R~[^zV*)G+rBwFyUЕfOz|;V_Fm'bߚ_'S4`gMAs*$6!Hs޺/4V'U>=tHfs)_;{-~n[J/Gw ၯ M%Fe_K; D}sᩰ-] _l"X}G =z~Q=xQN:'i'r٩~uU-14۳%kq(f'5gh⥐ؕ4ċM@V;vPdRPؓ;4_#yl>֫ŠX}S`DiC޽c'ّqK4EV;wo\0L2.$0=gi=k/j9,P`o zUǥrb?PԢD?-N,()>ɽI{p)Y T>7*On=!c*6zs46K5XfsZV]/+|²y-Gw-O&ؒ}:3=a5G>rk3J;&1UnE[i"؞OFN<ǰVcNSkdaF $SeCE4{ 9x}eZ I%76#w `oN LNA+ur-* rMBoXҞa kQ[V?B< qXEq6r՗bHݴҩI-rmvUH_Y(}]fB6[kyH^^R%)Tyx/Y;N*G r癐S.Mmo\v튵wAͽ'AwOȫyAP31{0sa|<ߠZP"0="VzgsŠB޻b|݉AezgXt _trs6agv{ꁗm.R3{::S5g5J1ahV$Y፹{"뽡g߂QJRE fgTm7|Fѩjk]Ec8?Sz\gyN*\s~&qcs(y26{õkw ~ Uޣ1+^p=][E+aU+?eH ܜ %qV86;3(ke%91rb<IW5N[sq 8?$v3_EutqOf jFetXw#;n9,K S\N|fո!e7v%,Q3"}J~x?-[1_ ݷ\('W/(q[QW_zۑa ۔<=}]!U=d]lhjc^`~Ih3C /?B&pKXdRy:!WorK~JݾXUTQTm};ɥ~ #Ma*Y{Gl3憇QH\~rj`Wmg"N(f\I]2 QJ=c fSYNf[cS宣@jfWRk[ƗvfXml=.*zuD[Kꛤ8Tfǜ.$=)ST`rлb*S{Rk)u4CfakuATեJ;f_?N7"8:0ӦIﳞ݀#Q W.7H̒.zën'?|a6C֨p[NNQVlwZ(;`8àI7D!.&O}ڛ ʟQu{Dryj磶cQwن2-=i7>LD R$=5Zȸwj;N^ST 7*2f(]N;RH3i%:kF˻Z"Ր*GTnd7T3ϯ$sopL|JdzF+cȎf=PbLd!!?s8J+囃W^1g@x6zM[4xvuL(@MVw /PѦ"dv >wm]$x毨.~־`(|nԯx\ttÊ|FYRP)x? T|z0 zk ~N3Үq9uе6gж@懥zf+?]NZ|\CՙyʧP*E\wL=2CeMnxW9}ܑ#:#Ugt6ZN_6352ReUɓu`v2o):Jd/|OQ_?9w[{ ZlCɳᒊyrF ֽaǏ ;HS(uϣ[t0~[{lH[@pg+D0b/A& .ipNp939z\VpWsu)yNrso4cfʤxiµIŔ\Cpֶ-d>DH^NmmΑ*Xl_ɔy̳H&:fKZF J6s#!).lK%#ՠȐJ_e=JgN&%y *KZu@ioL4qwm?{(s&Şv0+MϜ@?c7C ,Z>vK HQEe_"Mn@UЩ w|si:-.2<:S(b=J=omLTXYv2_9}#QmeR+F=R?K,cXg%ӱt3za.ghpwacҖ74)͟sI/HtZWZ-wM`ft?AT(y9iI&͌ Sv;: y? \+\픅z$ +Hi$}3%?'q2{b{!ܥ轤ή \d3 ֯YR01@,[pw=1n@EKFaxPt9E :ƒKAo{nfqqY,9T?G^S endstream endobj 1641 0 obj << /Length1 1430 /Length2 8158 /Length3 0 /Length 9125 /Filter /FlateDecode >> stream xڍwT6ݡt CJHww  C4"% ҥ04%)!ݥR͏{k֚9gﳟ}VF}Y{W[+ kx7j{@\a qt< (" ( M^+ * 8:!a rqȺ; B8]OAv0!8$7q>>ooo^+Q A8``Wex'_( كm70/_nwo߁ ? ;;W7s8@`/ AP{d(@]@;-+]]\0 pg0Wown|0'XUo= n?Fo@7W7}@ @iC[#0}9ߟ,e |J\UoG# "at@0W__ӿ2['\U pGv_oq;K'A.߄{z"@~ `M55`{[UA9B}%^sK-ᆿ u\= }kv?Zv{I1'G*\||gG>^+p_^ O|v?vĿ{ݿ0(7W~oD}y `̔D*YZoo8yg-D)KSٔk'2 7?ju6G)zF ~}c p` k"|s.eȹȩ JukW8×!'Xl&Vt2ϠKD"ڌdgR\c"$C]W ͞`v> *@uy77e]֝紃Bj'AEH yaHr ^_U_,EBʝ*c,B}/ x  twbykC"NWOKKȿ]{GZ1 {Ѫ桝,lmזy@΋ѥ.Pۻkon+{ьfwɨiN`'uZ9P2,bܜCgn3uK#Gop췌㗱5h{ztth"㷵GKO9^:/z>Θ/ b1cI K35Wd.VxX|X$;(}jfʲ mwtZ$%竏r.m :uu61< j׊s2t0 ű~&9N!>bc8xC˷Djĕ3]ՆQHNQj<ѶS*G|%DJ_{ҏf qՕN $E/ Il_Sw1tT=*CFH4'`.yJI!$Sȹ ,1HzC} \]ꭡ< &-E x/m=Œ~Ec㦐yjc~kMX%CM Sx/%S4x{[&Ag诳 ΫSlTPE˛)gqŨGv hU갰@_$S.QǞP~c`u|m[u6HY&E虔5!mwcwb _SqR}9[8pAI:anIlfɵײ(>)2a~Ψabg:ЈeS]Fvufk'˚)um ݤ 9&][.:rry[V7/mK^Js8̂( rimJ")IٲKgYP /m̍&0%$&X7(O]wPr[>Wpķ&O3~t(w]sN$4ڪ fBQ/=sQnstFJϱpIȋR5 0ƴ\ܣ-#y95w~}=z&^tdNvX9;*ka臔GǺ:w'^ax$Y0sdO_ /m48g`ݳkJA%טuUYY)1r72^-8R9R+Hmd$1<=NDHRͫ n~t>l321e&Kus<۫7F!PNJYۯq /+7 !] e`Tlݨ` n _b;vyV#l?jWp|dBk-YvڑvERÍWҮ=T3TJVj+2Wh>VƷaэ0|u-5[>.d'm6M<ޓ.?YAT<_C\PֳE&;k.T?-`()P9=R͔H^*%fQCMbg X14LN(z!Ul{0,tlh3ēȓkomHJ_+kSRzЇS}$@%%Kb\Iñ,&Kf n^Ytżp9,zSryei/|VeVDzjDAۏdh%Kw+EeWI7HKY& 'E{U'W0Jm1ۻ &w9CƝH,"_VFMUzP۶[hڄF|jTK1e\tŞMF\ +*ݾ<Wk$VyV LWfXufZ~f^)z4 "n>1d|b)f KAWȣHe-^yR܁ճ ؏ ]K9PajCu4|fP1 sRݞG YG!l qzӒ 2[D)|FyyXJ;^fv7)a˶n:oYx,N+'24$>cCI 2jl_w~r- cX W1y, 5ь[T * d ^\ #6ιeЯ d\,/Fס K{%K Ps`\5sSdLI厅g7WT,Kͱ΁vZbH7)Q&Z>O%>D)'C! z,b"dlї.Zű]zrVtzr >sf|_7GMysarLc뽟 lƏS& ,X*K﷈͸&ygLSXC:p[Z<4iLE +9Gڠlx8bupקyz<Ϻn49"?e]I`SX5#0Ԫe~~ SWn$uG{U{AlwZg!L6Ѥ'~rZ";j{T  j@9~ײ7u7\ܴEQo1sӋeJ!8][Xd1/);?P.'PUq&L7sY+w2 ZcGu#5<A[_0@STdrT^ #%F) Elpp yf~VgX]sT5r8y|*&oTeeȳYh[j%۪Cpغ_M]ot [fs 4$K)ŚQ5RT.ۼ&k,ێ35vDv CRj/zטKRo;hzaO0^՛vYO|cueTiݏVz`})Vb;Uz<!x,3H9vy'tp}/Ud˩p߂jyeD~hrcQ3|;@Ѽ*lU7ϗ7jNu:v_c)V:ڶv &n/c< 0u(!νK7fr2mȐ˹ W9mՎ㨰k|S2juŢ*^Q^bfo5݆BO8fEE.Η5X`S .djIn֞[},xHMƹB7:OߢSOe^2aYvOfKVʑz@X=Rͮ60%Oap#4L|xk;|Fۋjy.FQ@>5]jVv/wtG0Ƽr8Aov=-A&PNI

nFs<6H*Vy;UCݣ QQΞ£tXB6Zcq(wH=I>čfwƻgb&I%cw#7W\3Lwާ>6XmN_m֔6!m5 M8$$OOR6XD>>|U XR9Ь ta[yJ؊I!%1Z*Cǣ m2J[Zw& ՒeMNSX!-B J uNs 81x#`ށ7fZǕgqzZx+6JA>qa&IQ2ĆSDT-÷O&߉=V`&4﹮>8SqT<8ڰ(қ4 3sQCqlݤ< >|%P@+ I)-|N(xlakaTS8տJĠwcdE_qQBin x>Ε@wIϲ7.pe]ɲ/ rAumef6vPOr傠@ ;ȂPnw4wⷝ?1p5(H΂9/0-CV&"DB :Պd%V3\tY|tً]؍}5sY:%:\ɵ$m)/*}[9AK/+D4#z#զ6_kvDxvw:.rEBZk," U`'{-BOߔ QpeIJhOg>x.V,WYaYsAe(O%ӍarL(;[/*MdpO3B{2x+2`Q@;NLW+D5(J*գNtg*GC%Clq8oծ=)y6F,漝¢hȎVŏƷ/S%I<H"?]$Ct~7$P>UF;c5@%g'ڕc%nLp ''=ȾQ[+0ۮoE~"cѯO嘿W`or`$T c4'Y.Ep}Vry;m{ dT(o\,{0Hۭlp0s%J6|Z`I(dScH$e*Żu-t:3X6$=@C6!8P=*Sƌ{M48AߖyAs) K& ¶wy_$29ۨܛ vְڙvPZ @g,LI̲vY oO4F|vl5ee(%ޢ1a+UêMpnGr iԴu#cTĐDX/`{|CbәD@nd]hw;d I^ !aP<%JxgmjIxs3-v ::+7bePKP[~ŋcS=a)ؤ嚱lػvx3y)S+=cF sy'IOiiZUe=ĂY}udt;Of߽1kMw(Һ~")mPײ Q6%Tw[;"hԗ_4/."E]M[Wm[T5ؕYt`s2Q }YRx]iH O |Ls0.T+cTQ|_T}Ȕ#0J^@v5pW0@l/f;'u~4< 6yGuj:\sMExlsIl˵"]{1j&T !߁ vU޵GYdQQ~iRo>/NnD*/3ҴoCϚ-ĮEj&w_V3`Ԉ<RD`@2X=$|S 65S_C}((`Fh %nG,%NKj?`90ʗ=gߋ,$;弔p0[: 5)N@XL5<)5G]Yǜ-4yu[!|㋦O NΏEN/ֺCK* > <] 3 +J#3548NiFH0T5zm_Gqi 1r ^[cknkFȍDRsc-ʠ2u# FfN6Sh슊:';^ʉܴ%{?x+hk/eOUrfYuDH+<@witZ2^<+qbfHx1<Ũ7^G|Pi7]z~]@\~+YM^v3I,TE!h$̓Ň y_2lUA-9'YeHjCmArɍ4VD< -j*Gw>|Z'|6r#Tb+M2N@`dJϒGTPV+ž/bSbbgc _GGGRɄ׈{k;)8p-%QӄThI\?J`ƽ$)tqoh*$78uJ+ {%6Fjԩ3C-zb"Fgn,Cb:'(yaT3{ylG̐q&l|#Ӹ7W^3=e >Sor{\puYQ;``Dj t6 =)~i5?{C<^_~/sݛbMo <~)MV$>9IQ>b;L7pxҜY``nnj endstream endobj 1643 0 obj << /Length1 1618 /Length2 7579 /Length3 0 /Length 8664 /Filter /FlateDecode >> stream xڍ4[6.5z>DC-zEc0DwѢ AR% $7}oZ3g}>>3kؘu e(hBllP 7f qGBpb(C@(4BZ8@ @1 AAD@ hpMupDsbr.w(hPtF00@ϿBpJ9P^^^ $?/ rCwOWm OilCG(/r h0PB5"4@Pogpq}p=(kQC" OE~oP vH(W ۬S@@($)B!`t}3{e*Uu)!`@DP\TT `G_ }\!at ~W= HC|E*C!vP/7E nCvF"H4 w^%8ak؄DD ww>+=vb( ]c` tAW1^v0[:տa Zo 3B  A` b_E+f0q`_JuC\(?: uEL{a_{V<_a7K9뼬4d6%b_|稉2&ocW{o4Sw2bzr B1_WF;äXfQ Tp>矟]w#.s :T\Af3j1IE5f,4B3φEBȇo2O5Dk#i(|ՙ FSh([" I,4.+mV~v$ {4;h+y2шY.[95&@xeJ#o}&<[HQ0#OYDyŅ&ۖ8'V]$,ӥa>ȋ]GM^Iiunk3;yZ26eJm|9y`=d N2{ܪ&r>Gj̭$jXDXs%$V4_bI4&.2$]43 %!"w>dxFttKAOMRO$iظ *X@JѦ\C ڲt-o6|,R(chM^q}[gGrQ贞 1.ީ.-owmGT$XJa-" ),Ag/q[ξ-.$lZm7krWs, oj!2T$4"po߄h*޳`}C]y{/vi? ꈾC4>۲4qS jÎ2{Z~,q~TWXZhZnm[%B5H=CC1A߁ Ѭ5ohq3U=lKNR 1k9NmXי|!#1f8x`HWOcrINp8nVYt *qҢKM6"ٽlЈT2]w󼃬M,FZ2g{2k abDtȀ Tg)"VцڷGh}"Ctškf?ϰㅩ<[B~zj9eELj/;{]>}tw!{rRȞCgezsYK08G([QWune)atJ+ԟ1粹pn[e޿:=bĒ!,=qJ i}݊+ )ZTUpKdvA4I4bOl ĆNJrKqa X(ѭJ0h>ni_veƑ#mqMU㷛C=˳my^̃[T,5ˑD4Ngu9Xxޫ$6wыW=K> Tͪ)>ËT'`ޔı~e͇\[_ȇR/톌qR}mϭm?渆/mqkk!x#!@7`Ce4u&޳xi$b&&WSt]iKWWw`3[Zcq6|8dtgy S(gv2L)sp6i6=P^ږhqn|׏v~JhDSz%#nx}_FS;emXO>j[O&#'I _)5^,N[=gC-߾RD,*4xjnX6䨓zx97ی翬ƨTB4L(|W qT=0qOo8xز;oxq5AזZs/O"kp߬,Lk{Յu7L^Y`{#'uB ee^o#1^p+,SG! K7M22hӭ=~:7 }\q+sPI>M>ZmϹ%0Ja%P5I{@VV9 S?KMRGsV(} k@2Sć446s/ 0c1NqU*֘ܢ[@&z;la_->-F.3{?z"3PJs!)u_V/+Fs12c1۳6u ='ۏRΘ)U.SpXy%z@ :Q ~Z{ &? H7)dsP=$[߰xWn&P|]rG 뱸Rq;U4~ z; ΚXDTaA|f\E5ZۋQbޕ81ϒu՜. mtb?uVnᕧGg=*? Jw'&VPVN7$H߫}eL}{[` uvN_B.~mv5̂%L߸CNL~+n_?sOƒ Vt aJ_G^tSֺ$l1(,cL8^m0q ^w:/:1,qrO6E>R*h'Xܕٺ\M2\5GDNMdSaGE2ˊAڗ᷺l ~nԶfXrov;W_"c.1NU7.-t%Q=SJ) ^Y&g4^rE::;Z>ثs YCILW&^Twl*(\Oo5>7LuV~xf^4G7E#WRj;d*66IմW򅃛4%E7|"+S)BU0:iVw^|ssΆ_I9sc ;rջ~Kf=Hi5AG>^; i ڏ!y2v`.K* yy 6nU~I,aw.z u2)-IO8ɀJ?PXWxu&Ϛ)a{ni]-v0BAO O-F/C]d*dgdnA,_~meQlfWK]n`5g9.Ԏ.GIG_RCIyʭrSMϟ2S;T^z[M?eUPU\>1q=l]aS>_>/RXul|dڈnT!ʌ=lFZU-oe%:GV}M6{"z#]lpajNνr9P~uzR0f0^hn* Ey+t^X$6ȜGlg?`ԈrS9r\6.ϰ& 0:gaXfC1"/5(=Cq-[ZOy0OR HY;L̴M OMyKBm`3$rRp(X#(siqeiGC2)j"!I"dߧ10MQk8Ͷ{7`yҖ/Isw IAbe'MfY •l'9[n k']bZW1q˺ gVz:z 7z;n ,3eڄS . *Yz54;S|dqeLqNnx }}_Ud^CzM36dBTY'vhc6ۍ-ceA;LZ/Ռq,L0#lZB9Sޤg QMy[=CzH)knlF.a8m3»2hgbNHt{BGkňt{/O XD4륎ȪfWT` Ɗp(էon7 J,7#E\g^F η/{6d#)Y~/W+D24n xPT1c}qZA4%w2p+XOX6RGH! vmyaQ4u_9FyzKx%SyXS0cI*za!u!jBGQQU}.}qhn8F+b9au\ϙ:[?\>*y-\_ɔy;`;yL;}}΁pY$i,G=Q'm~f_ C`c)y[J3wb1leQ:.]ѝw]11?d^pxS2?z'C)jy=l>VĶzB$}Moppϼ; ; rY`羒m Yj>^FW8kbA{ gGQ_1pm ],VjR\^GpR4eFV4pH Y ZOX(&ljJiwb,4C*E V|&1Ck,400jM* qIPu|_:ӪدV/-M'Jn:|s+'Q[UIȓ^#!^7œ~H2~dvY08z^uiAzӑWq%?Ϻo =Z β ⎝dPs7rj6Τ gp/\ЁNe|s0>7Y . Ȱh̿,ŷn6i[/A&W}ZkCRb~gI56py{:d[3,]TG4-뚾nZI0')FC`⒨ா=QwKSDnXՄP!D IUCDՐm'uXLBrVH> Ȇ"c}ez;RP2:O`\a'nb }>=؝Qk -7: :(vm2Tl:ZL D|"KxdIy:~P,1~i"@j♲³oB6pPge0uUڿ|uA@(4.E>4>宐b֪(}b}`R?:s3l~nA[6L?NVM R zN)bfr D!;1D,Ec1D] JY,`>~ޥ/d\JIJh]ޝ_mԀTa 5q6(O$]]?O,ϣN7­W>ʼ;nȩtQP0)0ҁ-t[ZgY}eim+?!>r0fJo]P;*}妈3[x&_A7 +_aRa>rl3~o/|$N0 '!JH7{vzWP[`;?(hb\.v}5ǐ>vqÙ0 'n筭T{:Ⱦ6,a)Bͻ3b~;] m޴929ϙ9eQ밈/jax1&WHb5Am_gCWؘ%h>dry#{'x($S9;)U'voTANLSӘ`UEB8ec$ ]VzD'Pz^D'}@ƈnғ h'}}ʽ%jҍ+8!iOD+D4ӹ#NW6_N]ʸ&a1B%YֱE+If'Q%Caݼ$ SbÕF/4U$gB8mro|һ ze@Zڰ-7DT_<m}TpNT07M|E+vQqu1x]Y!MzKaG<ޕOJZU,ToX*>~EܞU_Po7yh/ǝYO,Y!y~ BmxESM_ZJm1 S&}u]?F<7x.Z^ m:2_. endstream endobj 1645 0 obj << /Length1 1377 /Length2 5985 /Length3 0 /Length 6929 /Filter /FlateDecode >> stream xڍuT6RiPFni`ݝ" H HI !<9s]d52T5QHHXf`j%  89X_3 PHPC!XM PH PD (")+"%+, C<0B1N5ŕ# TqPh:]q) c}GuA\1 AW:M8x 3 4sD`MQX/\P8@h 8TGhG P$t%B CP@:.p>B_@ xB.8!@Mc 70P4 a.FwHbSGPܵ٬3{G ay #p  ݁poЯf>nN_f~n(7=nxa p ߎ""@>; dǙθ@0{"@_?C!]|_!U#;&OU  HHED%RR21 !X= ([5cϿ+^?sAX 6Pܗ7+ݐo7oᆸ"\|p`PKa`!8! pd #0o8:/ p#݂/N_Pgo'@BQ_:Bh@G'QܾDp3(B n= V11. gY8h4Ngi+oQp(`f wztT%=/-1IV]Xg~WUoMg<i O~ZT;ms߆c)"&s?[x5<_= XP$.EyM!rv?c9\cV 7'BGgc)fi5֩^=;#29dn\ZtZϧ/Zu#vH- ݬ51C.!hn7lBk UhGjyH>'` !X͛5LADZ݀$yTe3OXKV^KSAt\;+;JA6 Gzx&֟bz}O:X|lU^{ܽDa%G2[\R[5,D$p/v͒ÖCTd6V?L4fj%`yɔ?fL^׽hi1m:[BsůYėBVlȹcfiFͿ|йֿpY 8ۨ6xyUxt]"yjhF܂¥?8XEhGLj3=(0˷=}BxFȗ)­\_ 4Q ]rO6l?L6qe8EUv,:xPXk`uKISԖ9oz>?{1%ʑ{&ELTwJ(Pb`b+M5$t@]Trtѧ F67b5*M}(Gʾ΍טipq . R]optci!2 b6WWݶQEH˰Л=Ђ\M7$/OU NV[|J=7[N5crlyKMsS2+{LC$\Y3 S k;Nb䅱0VD^_;Z) #[H@3-[F-#%̷rx+ܬSap(K,7l'S^*%iޘɶ}mߛԡa/{-_sɰEa ԟ#8&O9L!hG2pStirztҁ;+9y Fk!.wQKӊw=ǖ^V-{[7pA}c2:z<#m{\.#aMj#<և'_e)S2X+> b$%nI:61bדRS{W7 譲Mag5L?F6XKv xh=ix8t P [+90%ߙ.1O֮z]zfE@x W9|2,RzNQ{Z@aSRP۔'|]~/9wP~VdvRx;`it#g0e<9Ik\}3BB1jh(?Dw"_rgFDfaWISKe #KuWq`>y' =f@Q^kqj%::88/*op02q#eMlyA F 4\e~TmL=ec<5:7l*;pĨyWQd&v?n9q]J_z4xDc?;_`L^EŊ9ߵPnjVՑ;R:yrtrû?A|Tܣ_%ɕ3Vk7gI\e yY+CĂPJ]g 2HˎZ&#gMuzD𙘉DrcZ3f[ O{6~䲑 ߪɹrY8uDb$O5ԟͳlcpqNzx^jӆ Lg5?t)(}ҸHI.^=f5hkt Bԇ%ԧ>H4eITXuC*IȍJ;tY{9W(yr6tY(1S=;J]wr=Ie#g o\ϒ-F47F\'\Ѝ_驃;4DBx[)8ׅvx0lMt@+#^hk n]ՠҮ.DedO!~eA{^[VyU ɡiyIb(gsv˄a(u ms#|SOO*[ݶTV4XIjN/ŭ2>s )]O_#;t"ahU盓Fε-P@~pCJA50ޢuzR =*s-6I24Ӫ/MA!B~4To(T%'C򒓧e-v|SNq|ZOl>qVp*p#9X֚$6c)nf/;u#Q&S%Q%W1\0)|p J$̧VƻPiuPޚ>d{\Xω$k48\LZu@!%[nlSuotx}nL깖k12pd)4R"KkJޑT\`wY'Gً H0=֙箄fh&$QLʝn&'xY>ѓz,k_y@aSȘT3l\b5zQ\ +xy$ג:+(I1rS"B>K@fyD_K)|)W$e)8:9h+ "=Li.D&Q 91&*9c%mö=n!VjbbjW %(x[fwV,פ{y^5>d@|^~]}Z>.sΠxP|q;/ت7UTMXǡd Е-Cς05ei=j1]nO0rH[ƚ"F6|tE$ڍ4>|/vʗ㩖{bKS|H$}ى1N|#m(G Uxə@:+ra0clU/_?2ߜkXrµ=1 |"Rw4)sHNZ u'ZSemO7F$XWk ]=&սUNIX>iC5^G7PTS(K2w#sq#h J 0*G'1{[t>كyS\c\71iq樶>]頍 #$y9m3_^XyXIGZ &npk@lAFQ~Qba{n(H~t{0nQ忣[p|uJkn *c%sX! ygY;{NBz dLٸax(h #FMEZ`d"Ko8fR% i^åF,4|E$@};#b^exܭ;*Z?7JT2Vxj /[ h7]cŬ X_s6)ǚ!<_#F3h~L]-tc(T a [[8,, %6W`חӴkkiZU+vp]9KaT`xl pe͗ruo:-)ٲJk-F?8634F`x| S&9|dZv%1?U?B{cLRی҃ok.1,CcZ5]e7|*,\E^nc endstream endobj 1647 0 obj << /Length1 2566 /Length2 30014 /Length3 0 /Length 31498 /Filter /FlateDecode >> stream xڴeT\]5S;[ %nAK @'*OI`@15: %PŃ]l"d degF-<]]-<>;8dg@] ` PzXh9uWwK wbkdHm<~ba]w$+@`b PdUazzW%jtd4rj:Z Znn"# PՖur:Zڿj]m`>*22lgp wm-5p z;7A66oooV[OwVW-?ޮ Gt#5XN; >;I_Ng$?Bxp;_m,UVWW8[ػx],\lo5ݿR *v%];ZxYxmn@7{gfMEBUAVFKEx.,*`u\X=|<]OBZY TZ|`<\AlՎ..kwOłMHl@;cMmmKprH^@ .6L!e?֮.Nk xy3( g{'=o>@ku{+/x%\lǤ9r/,ұ}gX8yE+G;_i@,o6uUm=5Yd\\]l< <<*[}Y8q!>H^oӿ/MIA6?H&`8lMS p??A?;hA6?O |A`''iamƟ \o ;x X4x06 'O.p}?]=AJrul Eq Gt 9`6JS]kD=ī=8qk őxpn}Lfu߬H]Pt|BpsVkȸ߃%QY)=>EϊOqFT(d4qXb'5_Pj{H6 kGOO'|kI4cZ3)q\PjH@.b3]+ł JMQ\$T8ˀD[XŖI}+%!Pq=5V BZYű o\ N/7ZTA~g`܎TD$gs:NYCopu@.-jz95#> Qom|Bo&F%xc3?Ys,=fIS4Rj,O.bWN;LXti{qa>2wnjLh$+پCV? >>xܚNC% [)g9*T)ם"5Ps3nK#ݮޱEa!\ Cs`+J[sedqEbEhST$Ss-q/5|ϙmV:phRg[ʨ_d#JQ1dYC/(I̔⭧Ħ(oᾎȤ&}qʡIje~|ԍ`F4Tu4nWVObPc7s2l8eE>,=y1)-hK"L"*5^U*v.享i1~4`/q/p+sB:t6-r-,H\3#Zj&LN"GA:<κb!}Vyj;x}DoL=?nz-^.T.$T K/ˈCfDڑZ:cWԔ2݅ 0*:*\[Rʙsi0; GMe*u`geBZl$%UFZ-9&.zQԨ4 @xHt]c\0A1]γ.)ƏZ"d>\/:5NV8z;t0c}3!,N]0UVoA}7ARqe7 S!B=Iͼvv7kyR5Tb羊08hsQ ՗/x fn*&"1B&pĎhޭš:v' TAݖ {J[ZO4tQA-p{*b|(AJ hp_f#1T 0G(遶JH젾ud6JW#(q1G`Y牡麯%ü{2Hw˃,ZAŸZazEq)&CMۚ (eoũzFTΤ;\J{A ruG@ZM=)ǥJ%R9VNiA>,~Ňkh^g QĶL`m^+JtKmVẎstæٲ|LyĬT,ƺ)dwPt$Ēw΢hv(n!'?T{!^KZJ&jˆhȓ#s?OTC.߉^2,nepG5ŭIRFBvz-qz:r5>; cek!rh{U3EN =F6&lxݛEX52]/勁{2t(h{ʰNub;vP:ܮJG)qyͯ3n}G|$:s.qnTqjFxC~NJx9_ҧr%Źq0^Ƣ핢]Pם-ql\6Ȍ!"$E݉MWY^ncfTzk@yђK ӇO'1"PXmƸoYp}(qC2ѹ~S"NȗP̉liRf]'8ߙx*W3 cC,3_GS8Y.ňc$x|_ٗA.W~iNMU82oM»(fNE~$aZhs@Fc '5148;Pf>+p[ "\n ?Ca.Gtz_IF-7Բ:6_HNaX@9;6NEy UF@:)be}Av;Z!qN-{$od'n* UQ],^MG7ÄDSehNn Cvűhì6>`= zĩݯzׄ) ߱݇9/:Wߊ-V w$ mՔ!NI8n0OdrAb2`7QQ?_#'<@w@i{8cC_M>N!tc~OeJk&L1u2Gf^TxK5c){6R\sQ~H]44*S蘿[DD~iQ*rOyS$<~Zk CP$6g*PJjhgE~[EB;!>RѝPWf gh?a{Vʋ#bQ!3 TL/^"ĴF7?Ä@Z>F-׳2 !󨰊\ kf jT,`MY}ދswCXZ? QתbGM]FЩB-PUXѐ 5g']ث+=|Pk2ոY҄;qRMjT6DANlew_4XDmհÏOMB(DTLyWi~ԓ \ketA7oA`L}JX "J}6NJQs N)} )X ??%j?6p8Hބ\3}+b^T;pUɵ^؀6NL!T#(-n0H'́@tFzA[mwsɓ]D5R7 b}6 ǏFfx掟S!'Ɖ9~9Uj{@<$)^35d7j5d{O^0Yu(Xfaɢ wMѷ~ɠtb> E/svs$p|=nEH? mL<]Cӧyd;@H`be^|)(dF!6'\*,+{1*rMd47!}ey۲&J/B! i u]w,̰:G %.0vG3q5K"I!xi=i 4y4V xmU=`&>)qqh7K%m~=JK+;Xwa<]{*pevx[Ni"WIB||3k P7U&y~7G%wnՔI+y_|751YkY:ݍ.5U|֘cYJt_v3=,Q4R^lusyTAƬzێGߝ(1j%(?c$ JrrkHeZqo.lƐv`B*@Q z{Rୢ5i6[5`2ltPB[2XG 8#Q_h3zc\@gI _>ln֍|Sy_@NM)[?!Ӯk8]/]=҇.9&cBQ]SL<9&//۱7m"0s܎)OoP̬mJ k=Th21/YUmw+1 >[S_R슈;5IÔa/J %^Lj Tnk.Tm#SFR 1RҼq?d' `үyX fD~,X*; V~?XwA:^T=Uw(%d*R93qCYu&iC "Šn9L w{iJ=Hc.;hMG(\x+xM6\Jre6rwT/LckS|Nk̅bU3-2f!Fw,މ0N 3J:GlP506C^2bubO]E8DâGշNP19a_4V;=A^]h)v}!ewMY%"x\DL͂^uD }XkHha)c1h 0LՊyyk..Z]b}P-9XOb`Ώ6qL:Y#=ԒUϽɦqgMSv]{9THX*l6ͨ4 rwwk[5"ٯ|FkE&!&AB֝d>yHbDYhJޯz] zOZŃy薱/RAc6!сݱȚ oL2͝ Wirq< S9GpAKQlv>սF)`5isy숷"ͅ Oh>׽]KJ/Z bRy{.X=J `/[RxW +כժDB2 "'h0{!>2`i5<ܕ /6)͓8|Zɮ@ GIz3 FNH<%)ZATygz"鈶M:mϖ^oL_Cx1'DExZ|OeL3nÕxyL1BJɣwhńY|x5R1?a=Nb|ii mo D$T.2S>qt4I-a޿a⠖$P>M>Խ;1lU]̋CKY0:&A`ؙTXA{Π)y,g{'(Ut,/^6vB.ׇ{8qhrkh" Qj=xW/ k&Ö; ο4 %IoB6%v:kT;Voxr$/=P"w~o$-X=:{y,G,ġs RD xn%-ɳECsH!A[}@n`D)UἯc"N tdpdMOɶv$UKPvrI $7\b3cOƵJ si-Qgk# J_[rY=yQƜV qweL[/6w1E-|҃ Ng+|=!;]˫$q$m"0z Jۖ}~r P$5?F,yxFL8v#{~(vS>B\K`1íiA|[N>lA$˴ 2kiю5$)^7 v}2 ' SkgƧvBXSɰ P4V֛Zamtl:% Js2)ˉS?f.g|:HO=9_?B%nsJ[( `]zx_+.}<=>lKבf+@ɒ)=[2I޽O}8v2b1ݴĉ%/8{M\9,w XV0@z 37Z3N٦Xbi eKH`%vs~ZPZ"O`xIQlMЈFRuq(J^ {{e4knYsTKl|ϵ ۫~Ou)F'?\9!ͬ~1mYς:kvf?QM;}`r(Ljb㊑ Lԭt% )=)pfLϐDPY#!vǵ2q̥d[9+[/ӴXtX5ޞ(%qgNM(qb` RM;[{xa+Z Ԏ=_noXSaA8"؂KH88G ol DXBbL;>@U+q8GO/n2J,|khJjQ9Hja]"9jzL,y AOh Di#O|W@o)}:{ 5etLGT%CL$՝-)#&Ӗw@d[(,¿^mL; ţ"Ggu}RJ!Jvyx*+wXWlۼ[AR6\T€cb_B*(mzA^ˏ6DceX_47޵+8\o(D!/6q#9new}z9+7%"R=p@+kAoGad.+o$R1<u>Su֌f{G3cG W=JR-/Q1DG?Qwu}-q,Be.$}뻓DaRN؛{^0UHCڝ0JqÀis0CYTbMw7SZ:;~0%E>~5gmU9t/ ~E@d1ofbIHb02TKUo'K A9iW1yl' ,_N0|)k0TjNF.'$M@섞[}!irr,ǯ!qƺDgT];7Ԭd"X2sH$ƙH0 &c;BR9{Fr~АMmp許'f=/o\mN0+pڮCL2r 3$p4b+xK: l^0#>L;/+ϻ^=[,(\z7"e440D?ZAzoOCMUkݞqKYL)!"+3D/ ?-%ƞph 6Eï =ZҬ3oe ڡ~F,i N- Vʈ5ʟ~pWoj{^PO[sBYC-&8_ULGcCt]jQgQĬ)S$T7Ek>g"p5"vς|b_<N"?QxU1{vu?:c9HJTrɄ] YZ5wҾy'#ANUT {wxg@`󣊡xpH:^~KʔD {M'~tgTmx~6dHjtwV2lirֵyw'ePgw?5꬜aB_o&Qn\~B3@E8D,a gJIX]2#m=5~_ɏϴb3kDJ+J oP=n8bAh|!PIyAzHk( o1]ǿ>j(s!AXC7 f̭Ƕ 3rq6` L=Zɫxf91uď͐ HΐV ˆЧ^G5:lGZ"3k5h6V3A/'yrq̔$@WF1}!Z.-M/PA0M,}a*b.|$hʆ MJ3,B.ɐ'ۈFXBo{wfU*C6OK_yqM λ$|"x "6tMj^!^R Hk]0։hO%fs߫B$xt!#=4Iu?90dz[ hZ{̳.mE6eu?IJ!-kF1 ڍmD!nfϘ1 WcD twO\O#7f;c0] />-y=T l~z-)lWVہϟo="3|S{ʉQ^sB}XϐNG3LrV~.EFFV(x`CYvn*@PkC?T qx&&_Fd]0n@9.G.("gbm\$et_?%XH>F&+h,F4Hi6\gլNUU Ž!8im6+:(#QD5Ƕsaz] _ Gɚz!1?*\3ef4 /]%^Ej*QZ"3x]ةxBVo`ބ~vJLvײP@mR Dؠwoݦ*=+)V5v˾ioqKCT,$>QcA%YERb. x1 (2E[ȣ}5r PF)<͟H7{\$۟H-V26YT5xhY2WaVNo#[F/ia${F2&C,-:CUA, +gAI7[c+%0F>"!yaJBġ'[ycgC ۚsػi7PK(0wH7cE=O~C`/bS_zRaecw~m9~):-dv7N&z?ҩK#\x$* ^+ lh!]20E}E7PRF,qq0"Wt"xVWY;h'i=#=9M3ՌSVޖp 33r5VYntheRun7s/Zw G/(n_ P톼7$楴7J=^cݙolM@1B{_4c7lӡkȪT, ^[X3ROi~d[H7{~dKj탹l|''Qom*UM;D)& MⰒ&C㹧̆MН7qheC]:hڲn\I#]NoF `덷N g_E5{lMKy$O.%BDР2\EAD5]ꨕag5MP2p+ KP+>LB?rnM;wC^Y&. dVwpy۠Wފqr{Um0PO8T s;a KjQRh|,E8Yz[UpRJ̒,ȮL3C{./]/IZVcêY݋ypf9V-ۮ[fl<}tR]\Lч)^Žc:Vԋ;xt/ROLGζY3W2BGQ*k)+,|BhHCF87K#n,(XPz1@c@1 N>ca2,SS|Sd*B,uYm(jb;Fn3k TސVHnj'QErF aTgVyAN:T_M@;G2T@*++_aibȜ:+v4q/ GًFoĊvs\Q792 ja;ílQrް8hɲ0,? H-Exfi msNs/u*=@p z"U -n FvA m,,w-M͹pa)UgSW4e=LY$%,-#r^xW-v[P//|6%WpsMN-W FTӌUϑkrf:Pgshءm#}CĒLM#^XyJ"nK@D\-k˲',0gXқ2H ,HYd03sv({4o+YM7P}}r4 gt2Z}'ﲔaV;u1)oF1&κIlDs|J=WeIrFH(~8!J-M'eZv [ uOۯbaS r#/H݄($o9bߍoBچ-Ky:byRϩRT<0EO$,iv"3Nɉ6& ɇ! O"`q= 煌V/"aBi$o4]ԙyf*A}xr.i+;Fn(ʇ ӫa%=g OWsTW 9bh&)oU0p ~9 #M_f<皘$rߟyFT25XUYrY'_+ r񩤷Lf8[|1U34[)&'wՃoԗ4_v ~jcyZZ]€j-lm5#踏Gbq"9HF%usnҋ>: X(:M%IzͥeiQoZb\?yO~reYkTI$,0.#OH|*'Va8{ Z.sW6dPƔPQ1/xq[T+= 9|1o` ܑwf>)HfZc Yo,jpV>pk>xS1|PYY1m!Mȡv˯`jB}9{/I CSę1\ dEUK?xm.q R{UK!ҏ\sj҄tӶm۶mL۶mi۶m۶mk8{q~BEUD>C:θGTdm`xJ 62XlUI\V^Q]YǪGsyxjoVjNpBb&Íڤ(RLeEܼ+)@4"0C2ɶW>gb>x/hG:3Y6$k !v :U*_6z+MN)u 9a-nӤ̒j'Րxdehh-|XEEGrrmT=C z+bЅz}p|oI8A!bGy.ԜY du [(A2o)֟ - #cRtN"G䮃A$8Idĥ Y]!0'UhخZ"SeT5gP F`sD;@(lyY3+>"z>*%.«m skd%.:-97g`snqןW3|[K ͇t^R5D"~(Nq::O|t5ĵ]&9Ol 6VjB+ZXK"f)lCs}hf`^iHNU|#[FS0"UyEսI\y@ۀlɑ3S kptAW$^C}:u]2? a.9|ٚ9b{#5х_Ȝx85k{"`Y3$Bo4(lgZ"%YΆqSUEfS {_aU^\vk|qҘA>)\;knYpN#5R^B(Tm0!#N@av$A˿ɯQ2OF}^rqSҾnbǪU3ޮ ѐBKzFJk;yi-S} qI伅[3>{7l1i(ӟǴw=vTylWXS @ψ0L6I[`}pI+dalC96szJm]ЊN*sjyU&]5-y`XT7ִrPv~;I p} w"IY.}rB&PlwFy}kI(Uɏ:1e\ǎ;ZJbLV5rdOo/9Ѷ|&4+ @OԼ4v |_4`imfħ@NW;D9XwkQwLzVŎB|f8UnnPd2󛉀CG͕)bStܭֶ,Q[?ɓK7֒bQn(x֝|e.ǎ^+7.[2/^d]SճZe{\фl;hX">wsB ¯eA__> .jPh25!ےJ"ޓ8`I3IhT2&z `HD~MK^ĿxZF^pXsͣL:Iv ]0b>!9H$VkZHW<-Qog~B}@&t._a僋6\[-"Bʙߵ&Ѧ,͛Vp/襻S4> XSwԤ1?z;.W ,+.g m>3ԣ] 6Q5>8~)se@fT8K|>(:1}ƺ5<\]HHRBA5*+\"V(JˮmI`s. ds%80EEm}a˅oFb+jp :/ܧ7mhW$aJosy"J?9(q+vGöv A`8`cr/Gc=ѳajxAB:K+;$Lѫt"c߲ [:3dCOj3jA Aȸˆ wb /Ե8mw{!~ƳRy\:`ʹmJiZFJ.3 vI.::w2L"HPze'J $JUco|T9{[ a/)HBEFAirޖ'P\ 7ε3 iXwAm,!Xfc('kF[g]=qFkƤ]1wΤpN~5Mn}ܝ)UVtSkF̝{OPX,G1jK~4B;#~`ɦ|sH`ޅT~qx5`J#/ʼq}Gu #*⯸A2sTm8h?S^1~D%Kt`[Nl #< )6{ؓV[5T2˷shgвZKȏ7ɯoץ.Iqm+aYPmir .\U8bXhEnѶ-gh4 ]I`&AiEޠf4Jl Z:.nݽ~ a~oB z,Xpx01BOk,\W_l+ *񲖥bJEP!+`/~!l$nƨդ^`>ɋL @7?Adʜ*#]麡3a{ 2A|o\UOc*qa{xZbVnZ#+|%B6L Ħ͵^)ҍYI":F?ڿ.43h5[k l$pQhP2óVCV8 &vGX-r|y܌?8PƊ^m2my yB*%kjdxtXjJ/%KZi|YB>V͒l{&6tPuŻUFC;L4TȇqH;92iO/0 :F+[PY.̡?EƓBa)̖cC,8f koۧ5xnyN#ul69?=C0E#ΝnXAwK2ok|wt*6A>ٳ]?'rbyu_{ZKBiow-=~IY颺aDonHwmSbzȣu1d)B՝黭(ȯOx kfx Χu kL, 3 '+!*'W5@ Hǯr[+Hs1LK(B3hVUyLnَd ޝ05T&ѫMSzT ё-I瘞O5p;5Y=2'߭z"J:kq:De6snaP߾T `k|f\qn)]®td=!ĭI)SF\o̭U|7MXmk%4K# d9:BLj;60~Y?C!8J}ƅQg1+T-5_} |N=^fA|'fv1X˓s-~Eޱs _̌Q\9-krrvtT52V;5vfA)]>sC6I_J gOU;{^eY]gs˱?rٷK|7|" uU>50;sy] F쓧wd RU߳;F#ayy[2VU50 tp}.Pc>{֬ 6ckD[M3*_/? u~s~P̠| D 5I87* /f(_LRU'41(~G-3JŸԚrQ2J-:kH/6x\<AxA,g9Rgf׳|"%)8Y p~j@דP(PLT3:Ӵk IP-)iz3: -J- $"s"VWD8/Wd]l#",gPC a$u$]E$ʖm*nz:_~ 8Pt.?z-_3s4A$%в?{TnC-<M]a.Ea)=!⺁I+m-񄅾aC.kRڈ[فڛaIDsxrڒY@cJs9k`r Dz~Sv~6=5"uQNKY; i_@A#~z@jbboK^lS Se0U- @pğL=ّt'zgV[?$>Ⱂ{Eѩntj#1o/Up봐2v2KSt:oXxU*wm%SG Md;@VDK`vF~%bE<@btzk96k6BXM:ra)R߆im>?9YTDdH~3^;>'T'dzO@Qtqll.DydGhǕ(v}5o`< k{9bI'rLBtA^xqfz<(ƓVM67l#8iEi}q#@bG@I(R37)8p QOE!ޱY>dӸEY8y9P#C\?+]gܧNNӵT~XL4BUhS= Z'a1LbG4vʣ5m ސ$̬(y@L5A#;,@1eG36?XꎙRh/XJSjW:MޢvՕfa>%-ֈcW,+Ri7FZy8U^fL[NX M_^NU|eyk浮ez]?ɉuPpd$.NKeiR)fJH7K}+=eg!}B,{ S 8ͤͫy؊\5ٚ M/|Vb'W2,< ݢ;R-,WWXqS09KF Y.狆KH(|/Y/͔S%{,>=*P?e-߉v7;]_}L{d.,(/95%<ʞ[,sόLaQ2i{/lWn34Q]vȗ4r t-xNR8vF7.ֶa H>rB1pb'೉utӟHmwߛd5]236 1Wx_~b1ViR?_Bݯ/uGz2>6)Ӥ&SIضHi$8H`{;^DvcµT|A ]փړ0^dc@<"FD $_kz/ Zl8wO낮/R[a a'4S < x$9AI=%]:{Ib'5HS %\3)  /垢nQ4׺u+(d&*n),R39(R;y%i!|Y6(a%W8veSMfivAɘ2\p'VPOs+F7i†EV0$ I F+<0L^W1BlZ'`Trf)LBHS+##3/]H$L:Us8(F^])@{ rXi&nj; ?Y{8-GJޫ7tɃJaecY[Q ]fgɷEJȆ6ukhj@;pb#| APhWwYVuph3K@޹pǥQDzūLִ6o@<B{rP ao @h$M]vϬZ H#Tz p >C᫥| >z^bTם^4z$O{]arBF7>7,#yhҹMN]  B8|"'bn ĝh]4H.ƐN:Z<8iE_Mp^WAr٨ ͒;xwV^R2uX 4O`Y!a4 Żb- ŻxG3(ITΦF:=_#:CmF%MضLɔ,Z&9^c5H,*Lla;`i.,}Tv떀DFubUA%ګhΌz!TX͵}xgMBAͽט3izU3Χ͏KG%!4GJFܝ%5,TZ4.,â'>@B8ޜMMno$MH\5gX5 f-b5"J`G _Gs 1+^8[B;y;9/#gid3n Kc"˥!P8c~0W OJrǘmN/C(,ʥ6Tn )F/Yߪ;$h jm!}&:S6ct !ZE^b?ShXD&Y Y{,5kR9 sV q3RM)13T{ .@xX`07E^4ta2c6y8!Qa'Pb5*2!#X'3]#,sV"K){TÇJXlXGxxB 򁖅A/Lℑl8>澋!k~簚íE^==N5QH$/ۥ6~j6Z,L#{Z欓s,nߴ0^W??lj $F*ݏjh~KqVu3;lvO/ʼO[*w% is%HeA_=׻"MvKMw'?nΡ:b8vC:2=cN@3Rl[JWa{Vi)LKwfzyZs^ao++movcHXHD)$v\nԘwQO~!J?g7 ;m$4Jv 37 #?]K3c5b-FrmT̥4#VɈWq@1HID㖔Y/+\y- Og{ftޛ(uŀGE8HpG$6"I]z+)%/Y3^υui6ՏW Cu|ܲMk x>q %ߚgb4ur+jFƥ>T(}"vkBtΝD5Al^<  $I0Enسr"+['eZ 6ϊm5"tÓBp0H9 L NfDH掇+YӘ$ciϗL >Cڕ( 3}ބK;Xngy5i@68 e/7S[{^Znғ w)^ӛvEvwC}8.MύU8|yBrv{Fo( @>R"PXP#%ےi;ty^{"?,Q3[x ZPQKmxPy`Y 7RPjn*n,kl'UxekkY5}L|Fv0I/ʺq{ ~䗘3jJ&0k)g?Ǹ#ZJ(AJ k h14Go%k ?>H@d9\r#6+1F |VX175ftHԅXZZX&?+ %,̈́yF*'N`s+@σ)>F#şf/bL@ϩH߸9 gv@M:DŽКxX_=P.NfRr00;-|bEP-^W+Z;zW;7\V$+B~rr,E5 E$jK/ՓSZfv5v>x|XqR a[ۥ%XYpR{dfH_OU2T]Df(o [!>\2yLI| )#k"ȍ+1 xt?zgV=P6hPM*E*X!D\Ͼ&O1 Pqk{6WƏLŕJ(3cSz[m/&~zjnVx-PK ۥ>'#%V#!P>eu7WA,: YEW\ڐ8gqI3gP8ǿ_VDS֎Qx 2ZdceRǂ$`ðgR=";f2JKƿ t(-j ]قȝx=Pi1e__\Ϛ= r̾/> Űb9chj`EWtB1BZۛ+ 6 Gll;3TV @l=7s5JӞR1&5A_0^`x"طI^u]x:prJLVx%t 2?,@)W׽wϣEB tRw `u5]-NZ wHMkrW/P uYƙ_b%@{5V.Nli'.owȳ 3Wm#jа~O<3: @<<$grLڞ\2j`?$oźV.b;q,˟ۈnd_N$g$ߕ')=z̚; j;%-Yىb=/.KGРl-'8, ,71qh13,{3 &xxaxlȰѠƮ2He/D2WڛKIoGK|MZ64s\id,ڷrՔ+5>%+;r%|fQe^W56ZS]6, FW%{qP]#[A=7hsD7?uc}j{iW'txK.nj0& P1ES\m~jb~m% 8bqCNe`(V4Z>z4ds)!brb^Hf?kG&c35qysmo{lJ4FNX2 _J{_j~]12C2^h,1n`ꉋ5U;)z2$6@kPgOU&%xW;Q~qd-{0[ HA!ayӴM6Pqָ z:+IDx%wU=b+ya솔 S{c5T,_58 k?Q]f`H?? noЃ[7(+ԫ>~s-, "Fb yDZ*_}?wx1t;`)Jidaƌpba=!i޲Cnr*? ,mzd77NQEw' UR0.9fڢaMR0Cq\3PszKȈ8>Y DlMYɗmb!Ǫ[KF ۝󑋤AT/3ʻgl#2(TS5 CdkJT'_ûGUf^Fj׶LTIX~%R'=t-X09D|\TRprtFxZ7D#'!XŇgO(PZ;xӷR2<-zMՑ/:@6,[̃X*Q;Or+5BL7} KsZ 8b]\*毫)N}Pi[L{w1EUYi#]axzcķY;僦ŨP<*,fC%H}> stream xڴeT^6LwtR;94CKBF$CBo3:bם{o *2e5FQ3 P dWP۳1llL,,HTTN@c+ b P2u;:XXx@{Xi0(]=2م[Xi. O'+ K1G-36;XrL LE;XhLƶ9@ PTUH*i(2:84@BTQ]dHk`( +H(K2 p:9[N?ܨ]͝@v$X8133Y:0,lni p9ߝjon%_~ oe wvKin% ,w1p#\~Ǵ94++쌭]`CcWg?2'ݿNNs([4.W'flWolS"V:Ƹ_tnƨ( gLΡC"t,}$a _[r"÷5)&7R]tmkvܱQ^:QpAJ|_N?4`k.lH:>~A exByN~vK9 1vڪލNȍ}!ДԱ}k{?'l^VM>%I;bH2@t-sllV9AN=5ñ6>KIu~Gbh)9v#(V5WbXElmGvF$Rϴnyg}.TSFG! k'|'iz~PC}_8yAAQs~N%ޙ⟼ۨj:tW] Z edD8 b:34s[Kn3iq0uA+|ӔvU?l6߮UH2.od 0_ghۢxx @%76{[Y 3l JfW2ʗ}~/?"&sYXaUmYrv3/KQɽ'9>h= 骃5O=k[,'TȾ'bOz ~ϙfbߤ5v̗PO|2VmofjwÆ5MUw N!64 9_B Zp1]%rstq}!ݪټ倩)i orGLUĘF뗡Cx]4 7_17r>êe" ~1CmU08, םXhv,mx\I ;Crתs"!cF5 ˏhgnJl>`̝{{YG| Bj2ʝ]me#ft!QkQF{9S]_7k\yRN5\ed̍w՗ ^H$//tD_cWD3P Kۉ`/1F3O,yg"%mM:D#@W'Bʉ ?"w1i.I'éʍs,%D-yTn^oQ /,Lh)0LE.7wYmYn*s Qd΍;@<+~])r29.{j0:u?SE'Mltxz(6DuQuFOlC2JQΗՃ3П&nԢ2r_<HIySml !pI5b> @4?ܵ*;D#̾fg̋ئ^4UĈav€d*VY~|sE'&i1D$N>~8T~iuTJ 2YdK;(a]-"kH75we-tb\ kr~^Z6ߦǸ`E=$$}V E,InBsf\N3D8ÖL{92^ʜIK;ul*F ȊpCKk Ϲ2@CL뼜pRpP/l_lG1_mGF+Y!>`Q&8LiH+%x|(a ^:q9 (?F!x0j|3mVUG/ZGNDGŒST?&Nim*"1Kv۞#̄/W3X8n”sA=fbњ?l>d<$V|PIy{,7Yke~[ xWus^2^e{WM b]ܦC쨛KWK7j%x?h}#LؿA%Ӭ6MBR:wJ)#8cgڴ~]ʥt?lyF.2\"cDs\CԕZ¿mbyʵ`|[O=3__E=gкo[woylLsTrQ(3g<<zyqQ$L"ٷsd =*Æ(Q+!c8Ҳa/)pv;X[Q^"SQNx>Űc2k!IYCYUvp}Va E _Bk}]gog -/ё޼/,zs'y{ÎaχzMrsT$+pY igrAfgBR%l8P2X鎩 LjmǧI;WtMWt|$LA=sc}*Y1(Fǻp.wvb, ^fYɫ}>\ŌfQl鼱 "_IPxЦIB~EN 4 'hQWϩ^87x9ol z &L2h}ID\Gu_cpB#?$ӒKsA~[-i b4+l椕WV@ ' Cza`;[;~aOnAv!vT18^{qٽ*" e>$ !tR-ɓOSCN-ynJW=@f;-`z}1[TD:_q]|m]=ҙ+$ʸ@u#}czk{Ώ)G7&Ė.AQ{Ml1Ĺha0XVC :{X?._%M˚?'C̒{FLiꂞEh:yhF8q.:$XƢ9n]OsBу7Ov[ѱ~hbwK< y _xPގU9Fio92lF¯ EMv_ N[sވuI&fBI}!s&Ks ӫ2>0.k|6S<}mETT[A .Lgp DXF "EjRB@dȃ!a,&$XC+5+W\||<=ʏBq7ɷ0,evSn)w֭KMiDWJ w=1.gROcS2鞯r|2 G(ƃ:S~[+'E2Pt&$c)/}եyFhFE9z\1erfȮM]$\uPeӣPIj\N?C^(R^rkDAh>hߩ$naxx= PhE܎~X//HYӑ4ݶҳ,P~g63KpXsib'5%22yO48v>+6&ck*/#S#&;̉%c~w93it<21e$DwȆ 7BNsgc {[O@q ~ɝ-Ýstǔhkb_\Wث[F^C`ǽ/wP}SMW(^*!.3b\ϴJTNuG|}. yu4ggR1`m]RSTZ&³%>whvWpiu!wf.AS=d,k;-bNd@:0s-=$]BvBOB։YPVfNĜFH׺_>/l0*TLKaN\Ó[,R,Ju0Rfأ#hM$ms12p*YmfτnO]mT@KQN w1OCbrUU=9ic~tԈkC5xK J_1UP›jz| W1d n.o)^E^͉2OM7~|ƗN}9#n\ǯw'`ߣć+, E3֤mE6r -TcJkY|/)- #?١Iף.(C8j['&,P?g;|*rA!ϋKk AHkg>ODYڔEQg~9\O 1Qe𫊝]i1"3WMdj^߂ 9;fiAŹAKAeÈʺ;5RQZ#17m[zXy֟R%nJ޽ҙ[kno$뜅\M?Vi\髳Q56#mD9rĖᣌE` 3cNټFrzd0%nQxQ:iϥ_EX/&jQC@3a plEt{"L(ɒ2MrhЬʙyVip!<y†iVՖX91nS1T&:lL2Μ!El6S2aKv \gҹ^Yc<-KO d(&hwLAF^>@8dֿC 2\2:nP*asb;bMj˵6gW4qqC^!VɱlT8x?StxAn{$?,g:PF#|鼛t= ,3K~W Nyx˫GV!m9 -ַ1vdb䋑.xwir8d1 F1JEQͩԖ8BOL17M h_hF6/+b٪A0;c#~nNUrkMmLcaz_ jxgq~ PC1F ,ՑYh2ݬ$bNz*pK%,@W38K[V:mʆp!(s6 hɓBz77EM4m?x2cNa?`RZ$Eۗ&E,ߟ&0 &v1Ɉ-)il7'] B:5t>f_;&^aa,0|R ]"yV@ js}7]?\_pm-nfG3jB-/;UF},FѶ&SmrLmŨf Ǩa`FUɍ+iR'3hf0n~cȩP?L:9T=+Ӓ>]=#[y&b2GUXJ36>dg`yHQl! G!hONjv saл|aǧ!E},P 헡jGy\$ .HӒwi%TDnd.y1vP$cBo$J7 &g%V"-lo"]-E3Ec2;Gx\!%TArJ},s\U'n S"} !S6xAh(^zKBBв,: T8殧,g!kI9"锉Lif-#5KCtPcFlUkA}/M5z6j;-o誡AI)x (ڲݣԐwȦAE!i2sE^(ktB3q3-1'[3NA\F/ ?; El, hM a:@z۹oNVZT(~}Y6ÓRBur:{ev\b Mקh=HK͗ϤegfSjt}bϘꐪS6Z?wp˯MlibUònCIjGaQ1R^t-kpi2R.Ymiy]"h㸐I x6q q1Y0'$ ] <6,ndQ5)C^+)m;ZM<gwokCrw5ؓl.%q<}D @rqk\'`1ڝ9`{_`ZpSlRD'w,9 M>+9&nA~AHMürRYQ:!E%à6E7R\" G#^W /_D ҵ9^"̨G?ND}#^W(qR98| 8jB@Иh %CNkwe$hljg[$A;PH=w{$Wxv=Ă}eR?5,0ʋu4:} Tq&A1*yah=LȍUwLĐ]j9mw~svVu^iuHrt@ &_p-T)wGVLNpL9~4D*u۲YQ9"6'DWuJWd cHJ[ㇲYw_|VmƳnEB\]E)o(̐3_GHٯ%TQgukDCA0qt̷}%ڃTtFB֡3n^|~DCI<|3`Rn=!k-D'ÞS? 5Wϰk~!o>W(1;!oN'UyC;Ҡމ, hG| 釹s? oM=jaw_}?Qrny?~~]%6%7Ik8l$ ϟK`tmcw = Z.z>5.&bF# cWֈ;us-l髚Y3UaVd )_bmf;+# _ ?H-c v @Mʲ{AR'za6xVr5&x*-A!Uti"v>}l z E饾)6sExl6v 9IHde$A?=; LB@9=XEoYwߴL^݋p%BGr//_R ;@Xo_Bk?i_QyTp ;bѵ9|ٰlLWW͉SŻU,VU\|* X>5Lܤ3I?R4\ [=[KO*?⃚A$CIj^ `IQIJ7JC\™x d*&Ij-n żVlp9'{WOF.d_YY۱ 7s膆A'|Q2;xP\T38UFPUA}C҅Pׯӑ 1$RxskCˢs^cD,[a-5f>ǰRtZWs* K߆I u)W4V(BW=x_ H~E1Q?N$mUl+[24hV ~!ᷴVoV[%7"5R|ާEM7SA弾G:BPM gLVSq>n5S/4GoE~yPOM|([a/#K9jThLt ,98؞C! X5bVEDQ U}3|WkZI3c} M\xzwOTtK\' ;{ɟ33.谉ޛF~y4-MԒ%%o\៕ZTGX}dYbj/Y#`q@=V"'աB}^SW̘%R]gqw&b6~[ ;&>sV'%tTpϺk"zvK XaEh4Q.W$M(QJ_3~*8W]M3~+Z5Z}An=W 0[..5"N8-75A~ *lm±ȋZsF᧟JwBa! ߺ%0Vs!H)=xY;V1^j+X yk##$l^6Č -rE_툄h\`3)*MR$M^!3炘9h(v)cmIn_wMf=KK-)3wo߱Y;omъmo"l0hXZ}@1oGgtѠcAH(%!}XX#װݖ[S`g(rZy6)aci~""ڝ 4/#nrЀJ(g.g{o65` NSв u7>5 5Y,9PBc{2 h.^Ig$.^ N[7.<;N;Gv]JzCrڳں–ۀѝZZ]-S@ i.U@mn޷uknFǘfپIQ/ņ+D^tU {%^: d*Y|QB.uq._(ɘb>֞&Xp ̩D173]q~-s!TF2g'5Xɾ[nKĖJL Mc 2[u?Ykئ%+ZA{wDl}q!9maTKm(:GOI6= [NI͙P ~!VS} )ۚn-2DOYge1#GlaG92DK'1-[,͞]" guxlQDɵ3\d/d2,73bH2)пA1Oj㛌q1q3:ad3PII|=o&P Wtp).j>̳K 2I!~Z5ΩmOsP7jj>s0y#*56pB1~*'`1(g&GuCԳ&pȑ)ʐqEm7Gƴz=gq}E g,L4C FٙC^°)ǖyW_tT1~Τfir-sfriFv&SNo+1ȵEeFĂZC cif#g)Wt'R9yX A>^Qz.;; z$@>J%qr2Gͣ"{T(!ezGݵ>u%;ߟrK* _E*'Gj_#]`Xt蚾MejҸbh$|&ݍhS܈bn ¨}E?O='ڀηn۴ .iIOB eyv:,]ܶa?RP}}Ff<&_ ߀RL1KxgU|Ne"F)g-ƸKU6,_dP5*:1#c^b+'?-[ V4(y\ݳw< 1X2Y79ӄʄE4%afZca[8bZRD9O-?yV[}хXeb-E.Nz5-8ؽ״1M oT;ݽXq|@N"xh1`%bQ.s>7F+`VI &j4!JQH[hSgWFnKNz//UޱCJ &d$h9- bJa8g(Hfd^D_I=SqX %0B% F*zo/FP UW$VhǹS{`V,X~X C)/^zUǙ#y ꍬe#).=hhJouTʕMDQ=(HΗ 8=P̵ %.IQ{[sTg`mCR^1@V\ql[g¿-GpӲy#20lJܹ!z$QD%>!?k ~/uR,l!$8$ګ$==5a7n|E{˹&Hn7YV+ʡ$i[&TS(61n=I7J1(U B VH1QWnGYW]Hεؙ¡0 UzleUۙ73ׁvI6FZuBk##XT {B +0_? Ƭ (|07=-aUg- <<u~)r /@׼I(@RAB&Ӎh*0\1'zWǥU7O)y6jB´!0ar.&e-R U˦Msk~D 'U7MI`D CN~l}N)D&}y)?c{:tN”XCjek `BY|=A79l/gBY\bFL}7OndIbxN (M!nOĝė-STL_E1.MƳ$|Š6xE)3L詎Bi4}s"WG~=PXwO/"Sq(& ,ޡe\@q$p9<qOZoeň!U3Ά)pDDY#I_޼l6j ,`"G/C2 Fd;n"" W>wOiCtXq9\A3͛ZGs]`:],\ rS싹8 \:nY ~)nĸ64VaB4]eۮ%3 i0KbZ/ZYf?1ĐBb0W܄r"e (јfj)Yud'MVAV ܪ(S1{9v- $bVUeBq6 *Ӧuh9xŵ z.&=/ Ε Z)14iJ(et O?8{Fv#$8n_ cXmZ~o}X. KGMVBK:~xc` nI *ꥮ#[~ɦ!I O>KUR:;nʁdVEILR,#Tv | jGai0=߮O$,(2)TfVbsckMrD"ԩ%Kf%Lï/{U+O/*c(lt%%R WX :m@W6{#T-+4wB1KSR1.S / 9ڐP)"R5=r%NzdRQ~Y(s^`/i)/nmЃIbB*G%69V;"yEе:fYCS/@qz$M㹌 u4Gƣl 3ɣW̬ l`#TLDm?ɼ/G_U߽H_Z)@B~زHIbY/jYA`!67u4nБnvȲ:q,mCU]Se!XH{3Jjt`*DⒽ l9oǚ,CŨNWVx3a N5P H1EyކrX~ q,lیgMpk҈9$d,6!cs4#m9vP_yQSld#8Nh`TȔH#bܱq0`苉O+qpH:.t B1YE6 cI6=mSPyp('dQ,+Tժ ![I!X<ZIь~Q@b \ZQ ljc:^uCQ( -{fc)&,g@αVb4[#;? `^]lL;P&Muz%P@w~`Ci$dtZ}o k$`kgj.zK+\Ć}4l*BN`H LOC߳@pZ(R5O@uDU;^2w۟Q4C&W>- ]5w:PӍCecl@CGa),RY,~=OiP$>ڪT%-w{^}v4P/@|?ꆠ %l|;<E"ami6z3NNO0Ђ2reUcDItOq߳x 7M$)6bw@P£W*eFzI}gʼ޾Z3x'_?H[T{lKҀFƜb +{L}h ]0b/E i uM;{1`Om5m bZJNB*Ue8%`@֏p'q­{JE8k1z&i>MnmI|%7QuGY]+z;z1=&Y2.1CV-d9/Ⓥ<e8OcQ%5>hʾWL2}VeJ뵟't*칇?|t5F&i"EyYyU/=7Psgˌ.^I#׉P*FWz E~~_Z9Vw. }1Ev:THПG*6bfPt:,t/jH-(U[ؕeZ% }3aNִ䃢: Y.Ly88"]$)>Ur^Hə.Zp[5LCCyȸ˖7yq(9<ԺIpL$e*=ʧ3R1jS} [!!/Lrjَ SQyC3 F4TH47Cš(в#Dm^OMr@[o8L3׻690}lŅԨ쩺uW㧦hH:cX~ؤ{ܞ7~0Νx2r(UXfdZGQ{ޝ"b[qw=NJ~ g]a`wb _ySDpks`%Ucϔ27Fހ9 =+8ӿ.:gE}M\@T`@w,~Cx:G\. i*?Y-h`sdPp9q?:hs0V@U Hol~#Fm;"k~$=Q u5eH_ד:tr%4405caN' x?϶?? ).݋9lM5u{ݿ%zTKmv"-ũ)l7&T).x `P,EE\we`5R`t poTpc2~\'SСinNJt&[?}S_״Y,9;wv=1g oE%/J-QpAg8@@[KxPiDdcm' L5D#aR+QSj֟ uD<<6@# !+!}%j$@$j!%{kor/J/s *T_d2d(/ Ж#^P [JegUce H{<kQ{Q&;{=G͜@6pWP^NS7>7OΏ23,ab;g;)[nn\@=!Q hg1(-)h.o /ђ _ݽ,{__7RIp㱂c|4{^ ?HUH`!Ez.=d["Ogcq0H v[wiwK Xy6f–*̀"\p bKX?_eT}7DOw>@a2xl|Lulbmntg(~)$%| e;`Y~D#|7&͞D,֐qtB-ϧ#8l_x}ѽUۃmqԐjD DUH!1<8EFeaߧX [ȝfVkR1bZ>K% oJ0[.mIG$eV ULӛwwa7fJлUih[a! Nl?Z0nAg˅B8 1B +|XųJ y -'sO_7H.-߮2ST[C**qU 5 O^) 7\Tg\cq0]ҪĀܾGQm LѨڊ"Rz|RzWiY4-H`|ƮnRd5h>8DrJ+#B"__ bz#=?EwRK ިckJ^}ܙ#x~F uN#:}ܨ`)&29G}9 g%{]P$T%.315qʩ T08_|1;Odo_ Ut%;5 N :U3o|=JټT<$CRY:m$x=4YKenw+@j¼9iX i]cr16d܊hEt:'5莠P #}? RJo^\2t s ~2w;h#nԦ;={A_G82Bmj"Qydo^jwOCay%lԜpeJD GϽ{٘1tZ_^1}Vg&L)n\du0 [917K Uܶj& žWuQU]TSpP=E75ߧC#:&@nQu,'B>m.9!@e ezRԜ!t*IbfUHaoyWyԊD%@ud=^*m6DT CWE6&ƭZ4ifj=< *i k:iC硚{,hpRԡQ)yFn,Lh[SS5ht?PQ_3)ETT-Ǭ+ )b*f5=`o~'d0 1ղgoS5ᙲNu5'K^Lv dem}D-bF߁'UT=0=aZʬ)sȥvAOMGyZtKYS"adx@rpEFYD*_ϴ固u3HDQsc! 8 ["t;?`0`v .F8Pyc^ھVI]oh8T c[kKRZ1L1STH ߆ qV; 6N)aQa׹(N8YB>%Fyž+=rŅa\5 ?+Ҋo*B^apV:GY#{ʯ>KFMn3=DɒhZf>3U5;UeLf^E8t :SBƬNN4ZLe 10&)vdS:Ljm]aR1bZ>K% oJ0X|bN2k(.eeeAH2lרfS(0E8^䍝}}Xـi`H(PWI پk0Mp'Zl.'ڍ[vjГ,x|!y})-6@Җѷe25_q2 zPR*b yEoG7&ڗq PQn1+}CzKфHVy y=oqA4f 7f0ZOIVn78Ƙ}eSGv k6h3)#H[$n7B GAȖ`hA잰;,Dq "LٱӐzZNR~vTI]0Cx{z: 1g9PKքc ݮ a\]rQI)CJ+MZ KDХ iB J*DԖQ_ȺDmFmPV9DlCef@1t}5Dy{*:u5_yidyOl}FB MZx!!4;>̳pO"@% b;.,U׶R=!ŭ=}:?/3׉UhmNn1WUf Vq+E3L\:zB滾/^Vzt&=# s%ĸ9&LVMV^ͫ@OP(~WV^D&.%kAvPtJrꆳiJ,7w^ vR{Ek QOӱj|ˏn#:dK54(n nl dwpDվTLS왬^#S%ljj8Ro㞃䭑RAv竖 o7b6hz _.r5鱺SΝ烾6+A @f}D4"Pj}'oR>y),pE1eK,ؙ9uMA&lX-˄]7UbZneJWt,?h x.36[py̙j4'# s߲Q 7*0zŦ6`69Ermp9ۙ&pٚZ|qKM\2(P Q$eģ.]g5ڿy̵m`1ZvEDKL8CX^B1%i\qoZ֒a uƴw>PSiZcr4{:>ѹz|CD,WndmQºͦQ1, L-oZ-1+4FQ[`bL/.4}k].0c]O'Is[md*O0V7^f]I+pXq ;k\U>Y&vaZPqz QWq޻Ŭ6 Gw: z*AE!cTff\K(S?=dA"`kV-" Øo*+H?@r=3_T~#nL/· @BrOܜrfaiulIk-p.w/MaP0bz䒽=JZ apwMIsB(?u?P%H^9̘rv-uG}`Y>}׮SWOtͥ=ԯ1(t*ƣBoF~cQ(;D&hJ۲;W۶>83a>5 HE+XsM ~6:P_` v] V5ˤǷyBY̴GT]-q;HWg3hN]q0XBk#mW/ZPޙq SI_?[Z:S_I*4'JC(iMh?Mem:څŮVk63[ ]5]ɵ'F0G$ 8#a Y#Wn ( eE82GĞ::txT;'l}ö&@JΤL[[_ l=m9 Nju9.kGb@BLNh'c&WL?1g.J76*%jJCl9ݯ+6 ? oϮe r:5k'{~gP c̫=mW6.[q3 fOl W4K9Z _(g1 |0]*@ sA Bp ךWMśCܦ&y:E҇0Ir׽ IvTvӋRpTܱԸ:JV?\ }~\%hKQ Vm_de$ሢQIBY<_jM,xߣ(0f\zc>`KGZ\D ߁d*SW/6Ndt'8M4 ыNBev VEWPBkk4Juke{[Kvh)$1)`5V)4A#ZP+Ov'= W%5[wͮN҆!06@Ӆhd:M3ISvf+UU=3 #̖\ua2\ur}( !*Z~EhNG"7]hCњpDag5#ʶ *lP$8A:ٙ̒卥CvZ;/>.0_:1*/:0"jP٥S{-N)žl"AV^:Q7_nj1y94lM =2}ي=5$N=Y@/Ťw_εP.%leofnf;'귴hK7r {q$AFBLl8IQG \'yv궻sH t/{/aXauz"Ń- ;mVyLέzMv 7 =й:6@Yp~YZj׏o!|r$h۴r7 ^/WtPr1 g3Hh@x$Porz:0E1:;NnZ:!6 .nPU"3 /ݘ`d@=މsl w}Ri浼9~~ 5Dw!_?u$/f:DR1ZldAd<2홄߯Ɍe䎡zJXo;7}'Q:l NAoA<w>gA`򷥘!!^"fg%LZ^ƽ4I)S ^&zCu$0& <'ݬUa(=& W^>L^c#.I'=*5ؓKAMC u,aИ(UF3kiE Q,VIL E6}@6lݻY0P}8dJ} m%LGK[8z݇r4{/xWe4[G>,sCv~vU@7\>[!Q XAaVci qHi2mˁb_ ALzM$- 졙kxn&y>u5?NeVH=r>'{{cEF>Gݝ<+n.S' ෻ MHa (]7W:B7Wn$j~ߡssShj4X+Q9G,$Ri2UlRZ+:g0=7A G$baa>tWq_Se 竳oі|BSN+#JLBm>R̮X{`dV <·Jg \"9asAf'Mn{T-"cL cV[7 ƣ=, S9(YBg@֔&rK+q{~>'U-LyEkMIZ˃`f|n!lB'=*U(=thT|a[\rt|O]0Soℂ#)28u/ MW +Tzʓ8@^ &\3u92ݴ8Ţ6j}( IeRdJ CX*8tŝ= >=  ; xTط^ͥߛcL<#k  6gK2tOd,y^IMi(]0 ~դYAf P \_=[2|)ץ<ߴ GC\(ɵ;n0> *<+| p V>Q7Go;cuל }lor[!H=WV"!c^f~ܞES-W&1Dڻ}~#GF-^\t,b2ḾdYJɍ۶-_Y =G c.c&D)Rpqơ1A !)߾'RVaOSud| Ff 0Oێ!>$d -&'SNܲD(F7YSћ63d'qa*Hp`"hz[]B xE'+Pj1#WS҂a B'nR%xh_Z֞Kf#G/-e><*n>l>ЏoS <AaVjvCHt:\VkX.ҹ\F-ۺ oE[pAŵ;&=8.\;Pz;#=W` (Jn\[BNX[d|tYPѬ JIK:*4"Q]r }cF|{EespAKV1ճV6 4ru t,oG'lW8!M֦z/OvwvܜcwuǁiH0TL[."ۅxyy#)mr1FЇvE0Y2t f-ѕ#>5]spiI`t89%'m}YGf:޳VƥI3~X!F=!eKͶ7=fF#(_=<ռ4c!Cv3;r*$fG/)6&Lzt[l} }LG:*0m}Vۙ+' P#! _-IUg~L{sᬱ@cj;7 8"['^%TCXTԓ{ Rc!W>d35-}\C%qyDRH# ޵E" p >a?;;]5[|s{x>6#X5Gj endstream endobj 1651 0 obj << /Length1 1661 /Length2 18853 /Length3 0 /Length 19921 /Filter /FlateDecode >> stream xڴeTݶ5.CpwwwӍ4;-H>{{7ztW\k5k]Td*ꌢ`SPPTۛxv6&**q'5$apX\l,,Ti 4z.&@V?'*`gFS0di ҽ<-\wbL93[5dcRd(Ak-0ZY @S]RM N>鿸khJ3$D4$@-O 蝿%@I={rEI Q ]IV`nC lO3; ɒ~Vw-#+]N+;#k3 H E#.1p? ** {k d2{Ot1qqu4":9!濩߯?g6]5"`amYEd$5ލbTbrp'x |.+/ݤ sq;kgIXvdwSۂ -A57wu`Y;e%+B`@3+濍_/.`ab M܀'W+7eA`w&ɧgҽOs0`@`V[\L쁴&[y[dh?j=*.fV_ɻEAvXݯػlA@gg'?!A]d̺bjr$Af`sk% `dn6NN7뻋́8\]|`'`65qpv `v[,U.?k\ l Զ6߯W>˻qX3TU-&fd0pXY8\VkӾ.!,mBK$>P2VMwI [ҩ 2|~AE:TvS7"{&~~(cZL_ʺt9fHc'ݏloWIem Y0<: ;!]b]4g3&ۍfqlEw-Ǩً ӧ0EfjϮc9U.m6W\qg!%IUv׎\Ga^>uX m]aMiIRoNLy$˶w>~wGDvSyJ)_>,ʝ{ <1)[Z%jnuW̘ƀ*cf *}1E2VRP?vjոkh^V"i߫k`^cPpMbݩeDf|@cycnY:6_66c\&͝r,TMZu=e1=dȝ&ܪft@5;`0s-/J ?WW5Hg4'dPDOȻXn C9u0 i_=Y p O\S!Jˑ.D-vTϯqV\b\RT0kg҈^RfGZƾ*rJ j$Ƞg6M a/"jlhqKiZ?Z"J['At~:4@0Yd8#v8䖀CAf)VN߷WӪs˨TKl2#(~j@K#|sF6:P@j"Hu+27i9a*%2t/]QE@a`*n V }Ezs]kHOӈ֗Dz])-߇}jCC :bmkN6ceXY8"Wp#LV^0DҪەܤ6sv<KB)J Oߍ` zRRv{zdૅ[6]dg]LG)wAOY;xl}2$Jt=CiC>tU *ڷߦxW+|bI^y5 鉅tٝ ^e6>IoUep!s;Di}w=!l\h/Gρp3R:! N7CQQi (\^~(?u|)]z`9$Y JczV!F4J" @c^̟zs_6dU"[ ~LJJ/ZKP`_\DM̽1J0$6"XúBK9~Ě?S9wU9<4̔qƩ:p"Q~k%4,٨]\}j jcdᜪBD*crikJ7`~ZN@B?8wRĻYL?_3 ~-8m>4fAL,[ 7{~M5ՖkG8uA 3rngvNf7*M#շ+Ry2u?f0*S %nK&nXz0.Ynײ75ӛ78kqLTn. :P9>4$1rM'otuH 9&XgmѪ_@U* )\MRAt {5m92ȷy= g)"FZY`K_Kŀ܂Ma0 D8x=G'A$L`;ٶOBpq/ 5.i♱GEQׯ]&Z^5}X淥~_nYxfY ŇǐK]?_|]$='B|4|XLHiIݖ B Njߠԏ4cG8XR'fY!k>8bϬF{ۣM'B2<CMxoBop}Gd-("l>ӫ%6Q$"WS`{@ṕMɆ>oՠׁ? _\E p |‘ (PN(RXk#K} )S˕vfmIe Lf_" QaɐxfF n*G<~\q̲ooȫiUb/ϻi܀Q6䖤xz}KkP0kB]re敬+7.4#˰SZo5oHPW.ṡ;ϾboMҋ|E\w ጓ+]11bzTt-hlܺOUHa{){m:}))ԚS\k Avu:oLFNP 0RH,U|ET  =iz_ɏZ= ϟn*:L_lso Nli:b~ D5vJUR҆v/w`1G}$SD CԥNRqm_o阸~5ԼhK+=N/ܣ<V*k8reD;(NIZW1 ]pAh ҳ3V!Zk7p+QoYbCXBUϣIs ?Igb:ieBeء &Ÿ^_&dyȟX?Mm~XLԶB9IDK1|9ΙL;H_ʖ<Wy3s ps)љZ^Ċ`iU9};G+T(%6ͣIl(:zսg'&O٫V9"ښ-TgCkԲm\T `z231(6En`I_7B:iZ3k*WPx?^zY#wɼAb!yrW׋ X 4gT"hIE7 IzJaso]ֺtVlQA5'I!388%DčY0;7sTK<FO[S͕(n=XЅ 3 }:YP{4$/XlJŹ>z~pUf!S:I* XnlO0J\eM>ʪ2F-+ C.^ΑgX ws|"}N?7:tMVc{Np!Ņ$hrv&j$Ph| nn^Sb&Ѣzjr~rЏCE2>(xbŴKMMT| $xu8#/9yKs9GF%. O>\:$-q>a>(=_C*=(E0e{KK:Q\Ⱦo`pO88W(:~N~yUR-Ʃ9CSEYLn G5/azO{u)}]lb;pdQC0_SFCV}r vUqsA-k.tUm•(g4a:tش  @Iqj}(~'Ŵ?rg,mulJ;MPRq¹Bq0̭P6.A*0l)NgڹɏdSSK`R/ʇ4AES> IR̽.m%}+_+/):}0O֩ +#|od#]EX 3I_\Wh6^I3PRtVtN,CZLzYu۝s _p-&볝DICJu㡄]FkO 0FwBșdW^P0gGEZvW)kZxe׻^dU3%WC$I!_-9qr_H`$̤}H_gumC2n< Zrogn0HAZȋ@['*FB ܢֳ+, TI=oC:NA+lk2ňR˰(r^@M%Ř0TqR ==/v}ׯlHsXA0&B+ppdl{O!cxff%J)z,SzR!vǏVm# +k"lj11͸ּewpC"% 5..Kd+%2k5YkEyr;tk+ʅ(~Mm}PYcT|Zf>/j:k}Z)EDgL]Ł]u}dqECۄuF)%*Ԉ:Ju#0J!;s58guICw˸; RVv`7:z _[ӨvNu^R {?;Eٚ@#Ðj Ia`QíN4I_j2Q'^حcto]~Y38 , 栐&>vh_TO]E|JC>V{"Ə1},[ 6'Y.`:bu?=v?R`\i6:{W8\zv ? rCIפb?,ߒԏ`oLC{{t0L2լ%Mw*]d nHC5G1aꤟW[5N(]w`VvU9H%rL&p6]֏Y<FqG~T;.rZ bWp-|oQŸ-K2<}vi_ GGD\Ҁ!a by.H po_u$q̓c?I8M;m=1.C29=H ,ҎϬT=_VAaѶÄ8Y ʶvQ wWW=g7gM pAmvJL-x{EOdkt5#71)M$kakGtO]ky=}w0 C[G~b<_<$G=>7LcU 0 %h\M=6NaNJP Nd,Z_tW Ta, 얾>hL` 1(+83dx z ,l a/ [G]ВQa`{E>BD=aͩ,/.t}!dl.\fpsħyS'C^)"c'1?<>m l5aM)D tXA{g1rxUd^ڨ׋AD [݈#}rT: 5GA,8n(qR y7\y /oK{rLa {qi(*1uVr 7?I:1| >>#^O k=6_u5a6r9"(`(m=XFC ՔUnDV\/c"/$wh4+y XE{(la86U \mrz9 z1'rYh{Zt7P. 2cr\Mk(GLѿ~VYo uCt ý'_IJxXi'RXw{H+5|:¢\! ˲Rhq-v g:|\˜%aF"MgS "rDvL+V[o+6zP.jrW'mT\Bsw!%R0w`@*30r .`1Gðe(;,RS @7BM_>>/nP0cF> )۰9;\'M$.4Xl"ҽ?6J 2G݉A1t"[U/āoXCZ=PX*Ӽufmj_={|Gm\e>cQnq9񭳡Pr=w᪱BB)%tԁ8?wb8M.f2KJV[Ȑ[fvK)m[PB@cD2`zls' o3ƈ>>cx*gFFGKW;.L5Y]'{JșFE΢}<,i)hK~h{Uq PfھvKjg_i[zLjg{3>n۷Y8Ѷ9.|==[ QM| >6.1Wr/yh;]4l<꠶گ': IY2 g[3}n.,K- VFNEL^>dHcgUcPxv_>Q% 4 *\>;BԤS ~]1 ӕV9m&pڪ$]RJnnq6i\k2Gf0tàb= W]#l YޏxS)GԝKdN֞#Y1Vp8[qvoNVtLI1l@ʮ:n`۱nbE7)r+-v$٢5`DJ8iέ<|2ğJ)-[k4X*̡NMhYG<$dP~i~4iV8N5>B#䞊Nq\BS_3 >?{Yˏb㷑YS&Fbt%h'2Э6 Oކ]8BCl, d7^G%:U'^FUֻg֌_-y$,G\ܒktF (A4o5饐nFnoմ"j[2ZDt|drD#6aqT+)aowbB%mvޣs\kAC\";oJv?]~8.:;z vpJyGV:%:q.q+)%Xoc!|7LZ JOW(%Ov$CR'ҕA純t,e B3;6boE~TzYtre[(_aa/[$2SZI3[gcGǨ#ߍVAڠk5hQ̈́ė-Ѯ؝n|oZͫKo'oЮGv_oS@"$Rȃ(; 0mlTVErUH?1#v8pz`ev3o1v BVGU3"*ʡ7r{:`mMI F\UިaLj:x tg7)Bf5Sdk~iq8.C e{`p]7O1X,nZ7H&9Tgo7qꖰPm[P)ǒ ">} #{X<2JhR#hB^KS}>ڥ3jM~'JU1}FRgn`e +wueS bށU众o6 O¶=W{D/'|>?mΫ *;/O1}y4^+&}AMyކ֮l (1;czC E*CʁkCIy,0Rb ] 8Sy#b{bǨ̅~?ha%P"1?mM ·xU~؝c%p~Mp_K ƛ?3eKƓV &IPb*a]\,W_8⭌늁(lӡ, 4B{x|GrW@4 -pLY_*OqA) l`ȯo' D;1P~~\[,OV4}·s'TEٵR/mGLH(dtǟ8DGYS}|^LyjZ%CLd?.4iNy|1i CsM&^2]Ka MCjgd*4ikDw`J(ݵ(0딃xsϻ^8O;)*w2TA ҫcݣ0دA>?% p)ѱ>ܣ$5ogN*\sqyfgi7tm;ϤD/@Mݘ&Fjjj&s\ue\;U2}iM1HTHǴ¬-=LMӣ+ Y(µL}d| wMBB],`4,4oϱC\|0nl:lD/iI̿%9τ7vzwӦJu G ۈcKԳ?3N~Uy%)ND1^?o\Լm iNA7eaZuT`, gAN'"8yTel;֏%hj=81eqA&N?rpv|hBzY1q%+ oLֽ:xb$yx.7;t[?_ueԊu#J2fE70!ko_6cWV{ ,(rUPvl,\zԘ)ޕV:gÑ u_D5vp(\;_R%`lG˗ A0+-0\ݟ#!&CRM4?7еlF|,$|l.زN!Ik%M;zd|0nZ]S-gVҽ{_zF|& Z>mV ~]nieSP_"AL=/e^YǬ~7VRg OtSѨ|*v\-dٗ;?<헻vXC˞s`=S7jWaF̧~>Ϻ3 $zd~D_´غ^}X+~W~QGPPΦlVWq÷%+IJWEhwM!~K] l9Z%mp{z %EUqLTP5 ]FlոkWtTHYt%"A%ev۱tSLIm>)NkX|6)̋l)oRyoIb nu^ť|;Nf7/'йJ])Ksk$H~o{7׺ pp%C& @30d}#4PVB5̃_V;w M8 q "A9o=?;kȺć'T\oއ.YtӲ͵*NB3+Te y˥hbD߳fKׁF`vX~:twNn҃[)JV,]2>LTEsn:% uF/N{noJ>z7=4m2è"8^ n,ozD9;hJXA4tR,:v|y.Pp*U/mSn;x\;Gܱ0^+OUo Vsֺk&; y:m̿i )2 -~k|bVܕu~Bɸ5X|vE}v\Pp,Lju S6ܒWݴ@R{ DZ~ϥ_qw?F|*f p9Y Fk)G HnqI#i|tm/ݩpU5#9=w)A\X['ts\!/ d(Զ>7od'{*5p9E汀`巎4u_Pу15̘!De6E#` EC37P> m["z|c~3-L~Peiކҫ%-|Jq*ҟbkFIn9f)&9oPVJ U] cb,sz|dGinnsQjaoP 6;9 =uF:Yc8Π"j ۣzVU聴pvC{")ąмzk̍iե9}vPK  1Qfjz/iuؼZ] 1Q,,'X:P#7EfdoPn61SZi.6b&Kΐz=y"KkXzX:|6cu/Po>7(Qm!yb*ukb]Ԍ^_{v vWFUu, sP'I, |m nrChf:S@V{iǢxzee=U:Α+{>BJbJO^կŠcy? ug;!@K),T5I5jG4 =GC.$8dYQ *=d'-KEAQPy'e@ DsGoߜ28A TzJ|+Y"/U+|GX+E^sŝd0g&Ԍdŏ=q񙰓,R0DU>]+1Zuԃ Y##O1-R,,:֙â,6e}3hM no3AHx^H;qrw.l#b˩=nTĽbbڌh[E ү+iWƫ.mN(~L\i/$ 3!ћ'yEZΨd *-$ ͊qS.5XoE|="r Cδ<&DxG=)埜?0(W(sy{zjKrz&Sd? -N ?c.R>aBFR/ dWx!L}>5jQG I↟`Ȇ^etQ%Z5-V<_5ѐ#8ͬ8%P֡F_ٖC:tk0nIUE|+8Xӗ>wmN-K 1)?|=w=̀ͷGh xE6B+US|<Ӛ5rp>DžKz1_}1O\T䑿퉠33w"|]_)^].k6!ZutvC1y:,W\l#rY*Uirv:m"( ~5s+³^fO 9rk%y@<<%sCdVL1I Ƅ;V1ڰG@G].l8,TC4ˎNeR1~;yʂKzWD4Uғ; d']+8!!GX-oϨN`Lb/~{VkʪG N,`ƌNTN_k)w[#%&QRBc\_u_2PF #F<+Js5.e6:,xfwgW̟Xto7ʫߑQǷ(OIb0f rKld؏ Zlp;k53HN@nw][sb^Bz_]VMcqOBzn!EenU-m 3kb-KUnމo>@vrֳ' 60+ؗ<}s!\m'|^cPwM?0`3W*Ჹ4aOCē=| fE|J#)N_C.7nʯފLp8R )A e6o?L.ޭw.w ~`" (+ݞa7•UbΌ",Ȅ *ɺ"Il"dΟR|I%\׉+/w3%Dn\2[C-IeWDا ;^Ԕ"-Y}4D$H;}{; <"fI1H9X/xs+|LyШ%u笳& :&ʘ0=楑 [W,YB:$1?38$̈ !V"[.,=C~͖D`9ӧHfM ڰF/{ )}V= b3 aiL_bL}hm'g4ּg5ca?]-;@j)cZ $jZ!ԆᧆdvEQmm(+UR,9K:TAY5dkBO1\Np"̈Tz<|Zs͍9F SW|q؛iB tLx]7`LJ\YsZ$X?hoП g'4lwJ>ȏ\c5mɸ, TFp茅MMd6it;kɗHnnXD3 7h4B6fQUZrp.F[|`k}h"Nը}rȂy _A(DT#Ft%bb81H2hqGFA.<[mֽ,vvG #w*K".Ȣ+νq8.uE^X"jc7AvxwE:8L_fTlħ}( \XV0MH 4&GȪ42\=`:zXOR|I4I)M+h&pG8G$>N2UNR錐y5EZyKjG JZبkăJZxDN4~3(o mxa&G\07 7Dj_Si*1F|y$$)Yؙ cAq  _DY7o@9=lP-U@pIz#b$tJ5 ;Hm$lUz}d>JˬH-X?GFuu8vnG8ze~W@E87o?mU81O~* 8R n8uʪ;M-4k&b> stream xڵvuT6ݍJ )0 0 0CwtHRHwK)!]R{y[f}{_^ "y0G K`:@@~n^^A&&0A8@a xB܇yyp*`(hh@}O'0GІ\@ @l! 0'O-!ChyndsC@@[sWB0(l t=%]=sm=6zNN0aQ7P(i+=_}0 '@KP!\SI_NX[ >y(lCv9)`E yxݹm\n |8b o'W4 0~Reto}нx;W[ O"P t"\?/؊/`C \̿Oωp!p`503NSNKUYIOKxP.M}w|rQ^a JP+=j8C!}B\An^a,ݓda?&}C~ H"|%ҽI||?La/'#!\``#M z|ϿWoD!a" kH-?@!vua%~JycLb{ȥ_šͥ5S>V^634%B _09~k|W>zj#$P|om5m[}qDzS\ `pGA28Uib{_#DO[( #.F;'-r0:ڈ^)xI1] 8׳Jb.͔}/3+~i;V|9:M v=sUIt&|`&`@0O$ x3h=Ӽ됏2oA6ˤ3stEI0cOjl T}?ި9K=Hl̢ lMg wzZa){W@eXGu6ϙL(-o?'atnjp:Ŗܜ_Yٌ~Cmzs 57, {>O#ˇYLҒh Q –LP&Ε0?]oW|Z6m`padvޱf ss[x:-pw<ҏ1ueB} fhfՒ H%1Mg28r3?7e6.KXMg#jMom%Rx6).-x>`T[vGQ¨ c)gS UW!sj'F+1'vj, E]w!s< 0b ֦DOrr.O?>Y=r\mXV&C[hY * |KULܬuFT2\@ANXmFلؗ>"I2;NжRM6'Zjp,ua#:u8FDHY|;M-  X'5!4©|YL G_Z ;H TqHx]yR1"l˅E 0=XTOt Ϯ 1=ûCURZtDQd?&^4RΦQdݯQ8Tl,2vΎ܅j鞧( ?YyȖvSprP7WkVxaDw|ڥc3i\<53)"P嬹&k2ѥQKDLp/U -U1S [T9d#.yj/gDb'_Uz]z&qN5;{Qi}̷]xMxC9/2uه~=CM`U|w3u&N2XLsBVR>T, lݞI򾢊=,1AN޺3AGN$7: *  _  ! 1agv{%}Ƅhm0`:t5@C_S^҄@WVd0Y`KLIqrvB&%5Ӊ}#3'{$gdnGcj "s4=LT["Dkri?7fҷ ޏ~r=*vXm\Z.I8KmePW7jaoj_p#- il&z}uW0RR,[SN} )N~>p={nB2e{h.ˤ2:&;T&yXi>UC[moh/!뻘Q>7W7dZ?FRNEm1pلY^6pýy_uw*xe:UO Ȋ||;ML򊿡j'b.r6 m`Rm*qN{6Rvն^E˂L(sېڶ+uES弟ڌi+N0,hˇ%htN%-_ssRZ庚Y,'?(Lf|˃Fܚq}|D?R'9O5s3J6nT׾,o[@MXI42jFj:eKsx>q5o4B9 /7h-*]S R*xu6q[?ymUZ)2Y.aNc-, D_]=6=Nt~ -y^rۍOm`Z8<*HU]tF~yQ(iۚ~B[wxŔ8A;ɘr c3(pZeEz?uͦ lo''Nh4P8PqwjH=YD!jO'#0A2~n>B}C7qf]v;Ӛ_]:o{WGѮJ&H>J61ղSDИ6#|3ByuREMآbê+nIB.IZÚ Vwʅ vcsaMĘé]$z吏5:Z838؛hOkwJZX=#mЕЉ~m-"BOԠ-l"\s`ҺկoݜC!ϕPtr9(0~+YhYȏׯQ-*KTPH, X#h਒5zSSgpWZG=?۳,Zf.{"Z.%]jDh#khRɾYݣ[9̫#%zJf<:es$嘡ȑUU-ZeNLڪRVcÌH_֞p#L&/=?ͪ*J6 )>u4mRgp5[w'4ծ]PƠk+a 24<{"z)#fD.V6Lk~Ǭ #MΑ!ӮJЕٯV@QUߥza19c eBl&̵G{˅7&taSZ)(orRfh"V)bl\M]BD9 $28Of9b`kd}8+ .$\ 0..xҟb#,@'i3GoXKװZl rџ}էE5Zdw 21ua*Ep;b[ V"ʷvy2ށ3FKHrX*k& P`v\0L#x +^uM ˰y7o=Y5ip-Swo !Ux>y^͕Ђ;k̥G׷;/R6IUZLF3~a;,IEi?v? 8o6<[)bAT̀֫/Lg?]~/ZL-͑V ?gkL:,[KcjŊ ŧ*+d&HN R>}T݋.ڸe./B9,> cdkG݋?kPc+s݈Jy&!%18bqL MwD˧dݖM2Dհ"KwZ|+ƙv$P#MEۛ FV۬f/)&-q03vUx~Z((JÑl^A\ZsԎD:CDQwTbYG(=)io${>61]X‚&& ɂkYz7"J{#`{DB9ĸc\6I`2u{TO'lRW.[":qhnD''hc*T[am> j"y "3@{Nw#I4V&VJBT-!)IS|ڗG>+T~:~y8RS< *zrIGk;ȱTdQzm4y 3Щ0fRbfiWܒrHL;_Df8NhFio{2:Ψ9lδVno(m)*>էͫ 8qa#?=Sʹ[Ǧ&*V9Xw7o逽#38h'IS,.ݵ炆|O]ILK6)$-FXҐ6_k40Y?{5oE i|lKzu>g ^f/gDm֎U *RVj5( 55Cy ſ2؟vsP!rdXNʏXڌi$wZp[K}+rKF>u|SW.v"SPf=WLo"Kޚ@B<>rE?rGmzPU~;]z VvShOQ!_ ă̺n%ߊx-Q 9raMzpKaTdQøg@{)MH\8UUpw,K=F{V9ΦToz ,%0$NڒfڰNˆ d-P ڵdZY[~T_qF~gڑ^T1G[YSۉ b6Vqմk}i̳b+;ߍ[V =0O$S_gL$4Q !Wwob/!`?<σ*{]ˬgͩ sk\Lgn+Pa_3JCd1wv#QIZjS.چx-3k_%Eſj <֥σ}k(ka7j?󊳤8+e7ugM=f:6h.c*Y&9n@лRuєJKغeeū;|7cǫ/F_Sy] ^<9|q\>-> pj 1qVnxi)݅ ZgLTl:pO_OOZo]wz6N\&sX{vnx1|{0j@=y~5uǼh׾fGڰQ9wٸ p`\)r|^ŵv*C@'%^2"=gw{5u33Wxeb4ab*;^?CB UGTq vE**i\cZ^@O3"b;e^/]]Aӟ㋧L4m3UXD^#VFi_|_i# 2!~ioԷy=/EOTxêxY(mT;S'"9%Fk<'jB78LH.C:GɁ endstream endobj 1655 0 obj << /Length1 2065 /Length2 23058 /Length3 0 /Length 24268 /Filter /FlateDecode >> stream xڴeTk[pwwn 4w @%{g?>_UղVSj0M@.,lE%u^Ƭp:8Xظ%@@+$uY#Qd@ 7rjz:t*م[Xك\$NV.bp23[ 4;Xfy%2MhL@@[s ҐVȨhjгpup;I M-&4 oP|f]IZS\SWU5n 'g?i3;Jtqq`euwwgpuva;Y8Ov=;lA쭜.9)=o[)ߜ.&V?1m68@+%/_EUUEd7}3t:Af$]PiE]ve޾@>1?j/llwD3K$,^ZCY왕oձgqpO\<{kSS{3o7[A) `Փ_`3+{ 7Db{ nn7[вnzePg[B`k+7׷Ұ/_ӿW_ 㭙Q2EZ^ [6IOR󳜔|OYNj#—ݦ6de >@kޜ\>ym&S#Bf PZ (頠?[5ђB )y1yBaP#{#-a;T;=N\4G|ak(ިؚNАwžJ Ӑ^~0$* `΍v_eq}C16{Uhx0[_whSB9qNU of(!G~i*\ aQjWe ww{JBY]`|w l!>z*/6Tm8>\ֺ߆cY͐^0vŒ6Q) @k^ZQRh鱗1K+ X8ӂ9}00YaID2i1y$goW/ldc ȆVTi׬;4'u G4h}QfyZ}nVRwD`!?oH| !Sp,/` FKEGۜ458!`RGsD(Uuؖ8xa .Q<ʑ\(|1Dt L 9$fQ8E[bK6mҷfVpxe;(swW5gv,4؜cpQ~ɪ3R:2jUc)x WX50[!yY!ӏ-bSto_>X׌* AnHC\zŬ4u%;qaפc~2#ީrsJ@%'7G8IITV֊‰ih G)kTAy;>a^i1ƪN>f=Vdca*D"c%fR(P >^(W\Fi(_$n /ȳsN䄘]QYE?Q:舋]D૝Vx"BCx4o'* 5Z -iqrMjGB#Fa–jYKft ))Z mŀ,&Cը<BR0`\QLVˬtOtu(F9D L.tu3" ֏)Nb ᇱxߜ% Y ƔC/^)Q"u} d*I 6vCe >Mi k#\GV7uO4:HوYOg F6-%7 M8O,7FLpJSv9D-~#IX%>I FaF)]abzNJh92ʋ䌄q xϟ z)k@wX:z#'@%fW%otG5Yùb6➬ jÎu~g5kL՞u}^ZdUEЈ@DeYރRm$bF~3u½r9$dFW&,y<;"2L[s~kKY7e_X5D "4cP՗쭢^X.} ycT[Ū=tF+vmt3Ֆ+ /XltG%.Xاwq^ta{ؒ@ڶ6/v^CD s <=Q3Hhɬ<{agM@#\qFoP,o+?Sz~4Z]ru03#O~k ] $} #ÁXb8 "pTUyR]%ӳɈѺq436lp8 DcQ>c)h E b^2U Ѳ.pB1Prɚ3>urv]/m#Fŧ0tssX)o,X&ٶ.ڢ?xA\qmg7i! gdЯ;i$hKv=5XzHbː{`mHLnMG̓"<|ů(}n /Ǝ`tg͙غ*^M[X2gD?u6N{T먢Al+a44y,M˴>ìy0zuQ|y-8|UGtT ~g۟)xG)Fz #%d|;)LZQ p⽊f P毘L|k#%ҫO\(ͪSG `Aҫщ-n%u2 ON=b2'Zv?=l[*Be},d.^] 5ΏnJ&ޥ{qn;+/,0Y#/|t楾ulKI}2ԊR{79鐕nY63 xs`Oui$]'Nb0yHrp99m򊔵|P,\hl$U2)U`|S'amnu? Y_||TEPnVJVEX"[j}s#-Өf6 zL#Czߝow?pk"[fiZc\﫜Mn }\GxnZHΙ%c^$HRϽHͰ0Vˤ ×AyWVSY8sRjVcӕVI^%ou{\ni ; U ?x1H-x0L!b_V )fTCyWr鑃#e]yՃt%Mc#?!bRkb4??>{bKТi0}9Sq #lUd"tO !ZQS1&}4Ѡ #6\`\wI:LΔJCГ>{aM`FUyeiP5HjP#uh˙G=20˃'WBOY|vQEgluҹ[f-i;}p_9]kpAя yy3-t[w[#+OHӫ|Wo]̙klpnlotH$CxP/#q E/=OvIfτO{ksxcCi+˙&kH[g^z7FX56dd! L Z$5&xUXJx <]|zp@=KJ{APb?#'@#>9] W7 iy::ZݒX^0cAt^ߡ s,z匊ޒwsb䶳&QsdNx4fif_1WbfsKf{)L@K3>*Wl ݇)CY9=-!6|m[|;>\k0OH<&I`JhΔi_ǜx{IC1Jh_sRޑ{"5S\-"r]!]}vr*ƌ?+䊾eb@?"KȢn uas[Yٓ\02mJwUػ 6+ ajNysK]K)nZI@~N",ҡ}05ѹ5hWy_-@ Sj/&+&E2t7u'jz箉{}+m3QcHCc.[[]'G^䕻si Q)l["_KM^4˴2 :Gj)۵porzxRl'5Gם96QwvSZA VYjaLmKKb墤HFOa͗tfJ-IAax-e 9T߯S3vdgTDDt`?}3+?,Ӈ T$S?B"qK }8`9ȫF5["#cc$ Q\# Q\ii.ޏNR91.I݌0kF0ֻ*lJ2_ AsY>u4|?2 \p&+bz:SZ$ | v+nYy#><(8zτ+ޏI |![><޳ _**o-9ĔTUԀw5&mIg4"}܊Q3;/}"C<`*;⑁m {ͼ[fy'o[䖼Q\A`.vsp^QG^J`v<'Q!+'HEVрq? M-^溆ᴲE`=F(W%+;v)#þ׌8ytEfk -s-"{f5!Za3#aT1Q)Bpo89\'s##1>XwQROZ=},^n:pzU3|Y܊-N.ۨfk)I ;_l w `Ie@˾=;zFS"t$%+J-y 4?[3>cjBOl1@q}d|Cy'K(.v?SLjjh[9߹-)bi} :/A:­bZF5_& 3S<ON12Dmت~s4Җ5=T˫_Q- EiՏ8KEN:MU!0On+\ _[aSQW,ѢYX "!3@ jD +Lc%#>S BqDx񊾒;qeS^2ƭ#!c4.&+KUljȸ1;Xn.͉K?ɩ *C)8wpL8@&d%K\FXK_wM]DKzn4eIs3zG/`7:ڴir.8 p"BDyQs1ܭMZd!HJ 5R!֢QT 'z\[V-SQ%[+O5π182xa$(lҊ+`D}]. #ߍpĮMMζOB &f(7<Ô7-T9;?q3yv~'L= o !p 8 QŸVX}Z3'_ȝ-?F$_1{X(` JCU&hBvk<"9y#F"?/+C+R5 p،9`SexeH֦]Z/lhi>KT`F1ڌ*~&+cN߳LZsaMy\-'GBS HQWPNK'[gJ(9L"~Nz |J>n/BbB)I]Ȟ}ב=K 9=u`2[ U6߶D>_S[1E*RcH>b8c6vؒ߃:J"? ɶnn50{ @f Rھx\t^IZI[H˘E&FJ!԰$5'J* bV11..g@w[c%Ѽ#د|rZ^zVJj !pGN_Bv "bSxe+b۞uLd[R:]K ױxƩ2+2}G"eL]=i!lN>8oc&I'UM=9#o4UF*RQu+^Ho6!IJ7n``r=V2䝾 qq/m 74 &厅T˭QsqJ6Zh fj[qUNP#7q51fBLgn!l#5eM"`3\o ҕ'wC0 V&˙8@~"u jCZ MF~|(s[ $5¥'k+r߻A'M w{Ru iQ5l\@@:zNx*M'Pn z Jk~s+~λ3dr % */\eafKgK|dTYRWʤ<>3kSK82<3!;绘ڙ}"Zèg,fNg'ːy6y#㨬rNN޾> sK.͘sڵ}jTj&#NC(ƃo=|VM:0SMN3VTDM~n \JϒV4񃭏 o]Z7rVņAJt>Vft˲)bimr[r|s@KMMwVb[킺ũ`˜ޡA3ĿƞZ: y[kDh|ӫdіAS9A5ЄY '-PhB\nxwB%GjSA3Vv1զN|d%ŒEl災ًTn*FҊ~F7Xݖ~Y@F;ccٮGjP[P+a i Ĩ{1Z`F6NEBE/̣k^ɄhliK%!USy'&l/ )(1zl b9.YSsY;sXB6&>ۏ(SWƊ*;䊳|θBw2l2VYUL<ӆ?4lH 6bF~[POc:-,2|67E㐐| fIŬX tǽ=vQ,}"c Xh j3<ݕ*,$IO _NX%匶, La{<ʎ2Ѡp'oN˧f]GF7:Sm=[_9 ںwOfBģc>6E)^?D<~ˊ,qMu]e0e÷M.Pad5PyeK{Cߔ[unC Ի[)%ccӻ,l Y,8ccD2v~c`0PE !5.by(g[o q 6Ѷ>gg"$b.=a^n^:JSB?͒Huak0U!EV\]}d:Њ_ `G'k%:cNv: `@vP H/<XՂќ ]i"|.@ 8X^NA&|h@6nTeﮄyg؛q H E!:i >.l&\Uc>ʹ 4V{Pw^n1V09MzzbMO΀u0R[ojac5}]J-_/brF¯nj>I" [Β XYrmԨ#rVq,q$}Ȫ$wF9rlZt 4rؾB"QJvG>i["TF*\tg{\Mjd *=MTwS"X]BZ68I2ExUܶ[FC%kd2v.^UQR\iy7_:\d#C6 (=&}p7p)R 3}]š<!jqoYM@U~+8F<(5o,ŮFp#L(V+[2EqٰY4ij7I@g2 VZ7cdƏ7'w;2 ៯~jNr>:-)cNh ťΜ MCRv &pT o(|Vi,ʡL^t$rM㥿M;/pͅ}=E;X+-)H,АKA(@Er0ҋ2o5 ;JܐwgZ"UL/lAuO9%p;X_-Q3yIvHsͶ& Z!mbS-lmO?IvN2]K nx>|Ym`7C&nXjxz Im v" >.jj|yIGۏMUWs>#F=Rh7ސ)&ؙv Vt(U.kR1P(I"0:\)X܂|MY#?9iq|J<4P-ҭˁNrm*3<,68kW\޺b|?JiZz^Eɖ؎^B Tu2PDphAu3Н/J3iz涔Yd{~gxwiYat }u[e2p >$%)@My` }cj|緱pH{S:aNֳ8P`{;pv.6o{tď}emV5]*b{aH{@1Æ%$l"HN])x{n8tk$|QSwr`:L2J){0=YȏS #p_'AWCiMրގyů /O (?/~)}fn9x BF99]|l4v2۟97{%i_x@xФy#WOY\1Fњф:C ڽ|)`lywi9ʹ86b+qie탃7>ѱyA<"Ȍ>\.1%#:!7͕ 8M0*I^m F^|t%@AZh?0KSv~RkцD}*:3Uj~ݰ \Ud&5xaM@3%V6w1U!7G9ˎVi9OnUxl)AbA"`MiUaP ftaQvqwFHd@S=z{RF^R_/* DA"gHwGw8L ve2 2SM:"$׊~# `2&o:vJt7Eӏa6ǎO֕J7=]"7VPT#+Il&K¡z"MȢJZOx|32{{+17z9l.ޖ %-lDmn묡n%*Qȗik8[' c#wpyxu-R9=3,@A#ٔ +ڀ>Ӓ@"ZNɶ ڬNn׶⬓i: UD$8Hbcyq_gŶ!)d ́|XJYrck\n<c2_.;TiK Sfa gSJ*5)*w04v]MC~] nTl_q9ބoux.4^Vɩ 1el:~M@4ԇ(CJ:8$ufE qYm Ÿ<>:ʹӺ^=*yVs?#a}1MJqn!)t)QK+m{xm4ɂO;ZmXnqӌ2_C&pW}P4{Qx(!wO%.< 4z[a6*pa>x'FG\`?,${y;1߬Uo.&ä]>r7"kT\䍌aM6B|Q))G7s!-,<]"T/a|nm^꯻@V3Q?Jitqҿz-IJŽ 3+zn! x,v&7mM̰.rnPVtfZ V9%5Q%'َ I1zVF>. A_XЊə+ mRUp9S40Q+:X{E952&v 0R 2~O-qeSr2؃2:l0n#0.quYwAɐ i*@HDh \Yw3. <%u0H41O<#0Nh uЃ}~v@O"rSdf@',u'Ъhr9os6gO^y9߯ōҌ/D9y۬*t4J1Pjqv)L'pFHY7vw<9<k(Vx9ѯm^PrҜ-^-s׉362Cq5ks[h2;A̫6݌}ؔy}+;t&g1!9 (.M= Z#_c5X2е؀<)9T]'2`Fo!SJg | Rs;ڡp/<)gefeDߓ |L#Tɥ5Kc=ltz/m.DhtZpƛ%PPBTc@Q~c4XCa942V#R3^ޝl]ET;V6C蚂Đ#*۱[ϗwx>3&-#_ _E[cm߫]Hv^]I! BaLe,~fqDeEzRIda1 * ,BC.A~. sZU7{PяalJnwGmWV "";EZhlȠ'RɎYI=o]<40˫Zq5{u_12p8|%YսHJɞq$6"T1#Vn>vwD7fiuSESB~ }㫽sTb,wx?DRTW`HH"3\X޷]%|pݫ8Yd094\kgVs_7'A؃<%V/u>~_wQz* }V盉mMe \,z9Eqk)4Dؐ9Bv^E,p?-=E0pJz+o.lc`鈏֜t}Ŭ΅#@;~Wsۄ`FWQuqA;s{_ ͎Mz8}!$m8SϦ +6 w0Rbp]uEt9kD2&&jg`]>fdصEyXc*(,̐܉"k!-,4r x?4'awˤ,ćFL<>㉝ҫ"GG|&Җx+/XcJwá5))"l.(pʊm-\.cêPXeqdkSTF8j'C]CMŜэgf&QL):H^ OǀsTpv ^ӣ! ">z ٚNoE^POۆ SNQ7~=ſV,0amgEkR+YDoRof15Q3#n%fӉmɊz*@SBYÒ2i&iD#"ҽf䵻J8VX{q`&F"Gxe%VkaУe }Z$,5eDX`.~BuPgd)0:S1S7i7ѐ;̷,P4:i mc]r8W_Hp`W_%(vG(qB}/;k99D8T @r: SJd=7-jO2. _i{@wP%utu5e_%#o0iqUa!F u Ĕ:GըCRYS K]1:(w CKs#mf8SҌ:Jm+\ƳmzFuBv)>dUr2mӵd+/D/olSbd]$3I Ewႊ빓ps0B R2WXM'$>zo6Wz~R `zXQ/-/u>vZqW}UCUiP1:*Ͼ\4 ms=׫%0ㄹ-n.JjA82 ٺғO+$&w^&B)(O t4 j Bo1> 3(.S-Z<3RĈ\#tw٬x b$b@D&GrESK{ARRl5sߥLFToƕȲb1®= 2Y4Ɇ(kd>֦]} "4r̟ze@ʉaq,< r1}[ކ!'Fc]Gan| Z@uWIIѺőCvZEUSp˴,v~"SXo(F|+r\y;F]$4|*UoY,O<gγn0&6qn$oL) fe;IhTd\ )~E?C@*`+Ca{@OW5;VХIk4%¥SB!=BձP{oӇd׮oK{gZZ{ ވ0uE鐝K}s`5'ΦΉ҈6 jܕ _(AhKf|x}QeY3cczH3>G[!f2vǽ!iVt14:YA~~~ Oe] \U|/@۵L|T魎.n-0.P \{@x㳰; IZC?6cZΝrJG`^،SO5ПS]G%(9mD,vu@X-")rB*$|ԍvg1_ITѽ-\gHz3$}55"u>ur1P0"y0IubCy)vo7+aY}^ݔ`6Db9.T=l/7W<)b7,<(֟hV rv%2f9RUf Vb^IFV)h{]GQkM6H"Wޑ?'S96ʼnNAS1s6w.mׯٲ?7 _l\ \J"Hbl(qF8D\+Lg3s_!vN=z;E NQSuS2Z`)&娡Wnu9Evg^0?HW q TȀ%xRXMwEDUҏф^/9,VgF|/A;EЯfPMx"U(Vɬn]?.9wEN W,3IS) /hΐRQƚWhn%-2t MB73l;fB6Uu|pذa:H@MSQ^o[ع"sF"#'x1>‡B% 4՟$^qLlKW|p+6cHgzp<;&4JUӂizCYխ83Hr%;14DqKG~W(Ƶ#QŮcѬ]Jc`oK^+UԤDFLƨ5҆)mQm@CΝ$22t5(˫WL,%)J- A̵jPn\V6sf6c}Aa|N=H*+LXz ǂRNrnix endstream endobj 1657 0 obj << /Length1 3062 /Length2 35243 /Length3 0 /Length 36937 /Filter /FlateDecode >> stream xڴeT6LHw7,Z4,b! ҡtK RR) )-.>g>;םs怖R]Ud Y98* g NVM;VhH[B~@ Xpp".@w`P-}\:jiQ]l])W nV_~yK-A^ k" @@.K dtd4rj:ZlZ p֑cHKj,9-_j] mY<_*22jp^=ntf 6 `W!vv///6[O0ݖ/~v/#1.֐v5IJD_16xJcg񗯲2 tp-d@k RrWORA*3v߉Yxz7. a;"`c]H*hi*CυU xB> 2.R ggk_퓶 rar(l]m~ӕ] spn/1/1 Wa {}T!sU俢+؀!L0uT! `O.YO''U g Z8;L2ܝ-Gg!k V[ڿ ` K:!cKH9Avr<| ҈a /v5y m6ɸX]l\| ww d.p|9!m kYl. 0 ؀ܑ .K7KFvH. B9첿']7Fv߈A(F.ʿoA \#E7p \#E7p \A솿1!W?b_RgWȑu+𰲇,5wHo)rt`' ߇WV ^H0+dI+%qv͚R8 )dZXy WVN_Nv q!{/d@6w`_Lܐ6N ~<qc r]-<0dv>v@?, 2? F? dЎ@H_2CNߑx qB¹@?| F!ARC w$p|J%F@.'vك~Ϙ%W'? XHku!G2œ p'4wmvKpx&8/5X=x72#=OD(_&Gk>5G%&O0b\+4u(vCAySBۋEd?޻pq(" ֳm[7se$O d%n=>?Dl8 dWw|yV(/# 1 L/8uf|)5;uE: *մA M})CׇBW|[g%+{Ȱ~&qֺHqqQdk4iׁ}Ir]Q_>6uÉ]>s!3"Q,#er\X3ZpQu g MȨ ) rW`iLZ65z~=SG_H#{`әD{JKٕ:Yeu¶|c?uCHVz! dz4NQ|7>k [I04Ppʦ*:g3UHVt,22N#Y:W%&gOt‰g6Z' 5g~=4" me'opU ĠstF J{_AJT"cr*KwW%{,PyXaUq cmMc8̅,闤͌jtF*q :rN)|MWTIM(Tx3c%!|+Aj\oص&֞ :*cI3TSaV$'UAhk\Z | tA$g~)mQ^U*آ$ gS0;澥y*GklK+Vjt1(>r>e!,M}x1S޻#l19 ;3񓮇~1OaozwErr0<ΔCZ*KbMbfz͈Y|wK9~<)ի}I`6 ~ ?#p6= A~:^Zz̕.H řEy(ƂqELK8OeM!e_twmadzEX8eM5[{|aVBhɿ_,(lts+r]/u*CvZ|6PlV;Hai4s@JTW:O mv`iзRޕ ԢޡRj*h&IT5 pw#6/Kj-jnX3,z$,t9!|𐏬'uSv<6;<? 6UXh*Y;"=50cS1{sL- yVt(5k u{C_fn&՗em6+Ҭ \R_9 5_yI}3֦]S4a3Ȼ>˲==DH)RD<_-ϗw|P|=S̽&Zc0gĝxBJ.s,/*ڮŊ}aN~rmc[O -;g^j5a6Aw}U5Ƀ5 铁_]/ւw}_Y f2|7ɋۚ.+ &7[.ãJp<̊V`?v-wFN;p$3@WN[l!R,zSO}}~XQT{,,q:&h 2S`&;o)Lze1"M?]~(tIzwxZ\ uA jR˾s~Jdh?ë̲lIޔ;օe!}ĉݼI)id3Rab<q#{@E(WKq(J̥f`C;GZ 4g~7s}X?ʥ.t)S5?|h]d_ёIKV;qfod3jń5|?ͶZ!b4xklF16Eܩe&k/ZУFa^|gLx)RS)xk.7 XBi W>vo+,Hr7Kʪ/Y>,F 9 ^ sL%&v!NR,ٛQmK8HthLI\ Q-aA)&OS"~P{𥖳tЧq`L|S\3="ɫn%XwWc(Lm0_U,˻qos%P fp~~l||{~pW%0IWMbӭ%7r<|O,|%4zYC/!DȚ;&Y+ΰ;E{;hŞPfC8kd)%^ÏeuY!k ;R Ȥu[5rmY2țz$!!2[ K} =8u$z׌Mp\0|M8SMz]HՓסvaؕwKKA5k'噘WBhB N}KYMhdu%r{~*a2zwᠬ:i?.2~{ڞǀC.S:z#iX5TOl YWj !أ^-.a_vWJ Bڀ àcOk}LĴ4PO7Kߎ3^S)?VN$b iVo~-](-%ݧ_sY`VX9:BQW2XIXI~FP,e6V8y!o^124ahՏ^cknu CkV{7l3a m_L6>J"MuSǣ40)"p.xi¯o >BHn eh@tS$yoaneNgQ3) $_S_y8#e }] fï{0ړJۼ̨9 tbt8QN4c?/3,i7>ٚ+rr(76,rY@iŏIHH^ ?|8<2 QG9!Iz$.);)*HkOL.9/y-;]Nmۙh)!mPQ,w)S&pgKfr$49nKc< tModcUYěxrise6!~SmrÓ"naF`T[pk*ltX|(yn[K)ip򌢣)Õ yX_wqҬ<7VrK Bj)w|`hIn8c5'0@ Y|>0T*Cȼ֣pK.~Vd %']eTUvgeT"IϳJ,Vgy|A=Qxo t":B'ʎ%~h5p3Q=tv 6ԯ>gyVe:TdOg\/nF[U,ЉbPլVc.uu˛"5ƮpB㇆SY09:U]3zniHQ +xE? ^^Gv_l(^ 5(І 7p,pU/ b{ЄApqiӿsy{9_[=җg&oN`2)i[kxG0I?^kN >u3i8x+^l!6 k1Mr^ C&`Z[kBwgfx6+(XH$(1ԕ-MtENlMD֛Y[B9 8ʨmΜ{[MY/Ӡ=䏘 (C̅dKh=/k7MU1r;ۖ-j%ՠFo嵤}q;ݳHJf7CdUv K`c< TY*@&蔍g/r#e3]9S=h +XMmǦnhXJ̖2lP^[Ef-urf1iNY81ѣ9!$-󒚆A?ϠWAArq_}jy;z.`R.8)"XJ0keRvurҾ-(E;S6Si=tь$4[g,UP]æW@[ia k]I_e z}QMAR%%ϯPShdz鰏s[eʄL;\26w^֌:dN6QF-[U#Ƹ$|%HDZ#"[!qy$Q.slCewQG3O*w(_k&5Ů=brsK@#.aĈjoQs&+xO闂ɽ^oeh)6hJcl-jY_ƫ:LF@) vT۫2"S3u6\>G Y~|w||=ph}ttcأ'"R1-"Y鰈Xiph&aI>Ɓb1; /ew&:#ctbt^*3Dnfn8ey{jkcʢ)Cש(+1a 6{1ũr5 Z$ǢJԊrۛ/;Q'\z`WLV{N`l]ی}Iu^q`Ӣ]9&jxsFqzuMD E80!pi=ĎA)S3:>l<b-h7՟83SznAqJC}c~9lQܻ%U)OƫGg)Ǩ*#ir{P3+rU~8 \1gnFΊ2d뛄Ow?κ(|/P-FӠ\?N Rfsb4/EZ$tlR [k!^a ׺R,),,bIY'MĚ>z 4vOA9! *q+/ȦqdmoҌGh^mX }-㊋l,Dկ1U##:G-֭"{˒%;ܸ:'/dY_oveyF'ɕ.gfϤ^K.u0ᢊfNB|iIV}E~@OdotXFWk46Eݳ"s|»+00qFr.L>h)'tf\Dm(F@3L%WV3+dؑ nv: j&E'bBր֮˃&Z苨P+` "fI Gi^i76Aw >2=d bQ'8Cz@6d,=ZG6N8j~xܨצG/}١0;s'<1m,zP5O~y8v{ĬՂ2g+^^ݽ:,{+I}˚cR-C\мqpuC$˲x J?̲NCQM⋧Z^ˉe(Oh ~Ia{cW8&r /ak28 i&`@<<éB(n+>ݕDKhQ ;M>)̈b8jKb "S$rޣm{e-V4v3˟R0G$LlZl k򠬋9kJrsRt>c;5؀)xdB1(.D8T&l Z;>{|$DSo!/lHn:A v롲,?$DS8ǩ}=r[?sDE~2 7KUq-/)Ql{ԨJ9>ƩvGGeև'R_TgHj=Ec<,:pvdnsW$1?2pŰAEjr(?.!kpU*߰H4vϻFm\sk߶WR ?|h.mI|96eji#.lyiw% 3R/$ MmkAe'ORRbI0>q!C a_ƨ>{Vrno}>&~!cKoG5yަkrkb]Z zqP l|okO"'npTD1 %[:tI3{+8.dǐy:rWۢbbr&`oQ*iJrf,l\t~Qo gd/̺ nm6ۣKKXttz]vKn'" .ZMuT,I{A RcZڢS P놂,vcSC!NiJ*0W #ZŒZuPZ\0 =P6b㟌~uY9AD[2zyF O2rXcd0.H;|O+M: $3|M4͞ XZB&bp ۔/I5U@'tOgUxP{(j>Vk\H |O?e_8D &YߚPr#a كGT^i٥sd`%K0B &*R9&d%#F X1h4iʠփWꙈzߔJ3h^ pCmGN3f]葹W^\Cgm]c&+yOD™yt(f䢖I٩> ð^4]1ܳ\]ʳ*Nm2[E>U}hN-T`0PRۼ)/JB@hv,]$Vȼ KiסQzTpV\XBK^i׋褲KwB\p %I|8?hp'/ě,ܻ`ELeƹ rLVX k_ͣ+ hI5hO ?F)\vp9|\E<%GyƟW3.x8Gq}Zd ?ɿ*] Cm l-^x>Sy;GnzE%~u2/8#6NX]YCX} P׎@ [cw:cTwޘܭ[i*,COzh=Kox-Qt!U/XLN&2/AUQ36> 7XfrbgN"TYW aP3r.mN>ˆ-'b +CS:Q]+(3 p.Ddt]5Ѯ]n3k`  o׮NxTņ0WBF1kHԞg2o0jӦhX^ f3pu{#- p v%7 i<Kn;4Ef |r=y6 &츛45OlkY$JD[CXØF/ [7f\M6K|}GF9O "!9_fYe ҵ S"9|ly}jaTOlF{Sˁ#I)z 5;XpצGCj_z߼M1xc"\Rߧz1ƻʆN/.; ?Ml[x$ᔽ? 2m@ #zU ]Gç#{7E19DψT4zJ*erOh\>uz9fe8&(MJIk)/n}Rt pkT@H.9hYh3ƑD92[O.f zC_dOMbnu%bwG"?NH!:>ZNK8BдV(<{>gSxܝnlW5NYd}A}$g#8/y6x aRȸR 8([»u /vQ'L+;V<5מ1",өJ4{$74=RqǪ^KE}@X jqҀy#)V̗pu+jY2𗰢G+XqNhۭC9,#񜵧 'd6jSPč5)\ bxBB'C{3r`/F0p/7 V0mJhT' !>=_ͧ"r'-XU ܑ_oiR , Xo<dh/q -{6ةkz =d6rųܖHB5,PݨUmG !hUh$ {K0Ǹc5˜eU]dpYr/Ʒ=HuH-QM _"(;bk6 {miBc. D{O{d0q^wU.Δٛ=,]բh$U}2sa;ŏ8zsHs_E^ZE3 vN!? $Ɉ-X ~B;د`v.y~rz5ŰIIa_LӺZ8Ҡc?O:s>aq/CT5BrBw[zO.r.5<7n'sߐF13,?N][Rc3iUDeІtY GUG:ѯ!iS;߅2Y~ IC_{`}?'꿡-,ѵ=u_ƞHD9F0vM74ܼ@zHWI]=јdc֑?~vv2P={uu*՗.(}5Q>>_MsRBarC7^J}]%Y=/A#}̪^d|۾1g(AVÜKnq|ƮQlf,Ͷ>y?-iYܫǓdB>^s~Әg>4+s(F=lHQ=.ybr!C颱5م9]5s?bWN%ɇHz]%Pj, &}QyLf[5%/Z j>(^EQpU4Ҥp%<$*3e]4OYV,@^)L. 6.=m)̢%4`2p[h_ԢτjpXa^!TUwҀ«^Z~C|TofrDNu]FO߈X9fͰcjI<ړ0ya{$LYuz#EQWPL]tqt+bNB/>.V L 2Kh־>ʣ3x|eާ|O]s/du5j-{g@taKxZ=cd (-I|)*G!7:6deSE[Ҹq0?(+ڂ}t%x\8`A͕mf9bg1C)ݝl%SE箥\^31Rok'K}_:i,<&|h?Eoʁ,K6ZfG?eN9[EF -*ʰ}y״e72C)DKo.6uR4Uz?0VYf{}rdv!dѠ6ZATj8kye(&ԆS-ki-a̍ȇڞx9 J}XKs!RF[<.yUw}tyt 32;25w}SPvٌ&gq&TJ Wkء-l5"bU8•|1w~4-+]*9Gٹ9kݏ\&'>"u"T{MQtD:Cj%0! ͱ7,Zw,mN~*TvSݨ4umvmBd5*i-`+'l&D h@~>p!.h7v5zY6kM߻ iqDp1I &b~P 1[ݶ]}V)lT6Z؞AL=J '8nF0Ŷ ⹾#kl[fJЃ҂B yG3\J!%?1"1p&qj,L~<τ3x@1Bc^[Â~=i0uݾ'Ocl|7HDŽ!r k6BJM  kݴV3r4pRLZЭ d{s|ɺ+ euU˽uЉ;=lQ-FVR''$}? ~uZ6cV4QpdTV5Qoeof1UCCv4_84.dȲ)%:{4wNq\v0=ħm;Qr} aɑf3o߽|]'\`~?v)"?2Mt[P4OPoh_$mesI*aT| 򬜩/1@vt*~;1}eE}>Lҭ8gD~'B|nBs+3}#&98؜an-q7o6:> bPGTRsJzUq]^I&^btcIURbahx?=O"`x"."u7zs"fbom}"-:$43ަb2cz?m8k n%Գ%zm<{#7|MYzy7.0GJ>.nڍ/856I LWV|6b+wǗEV6buE1:Pz*\Œoo t:*s2ќ|a ;MI(^'qFQ}Ff\3јB,C^xiY(Dkړ;b Gyp+9yKMD&06w7OP7) ^ nڙ( }V[!FLL8ֻҶ4,\t"5:!(D L}&\HWK1Jð4Ynn1dnKK׵({ QͪwmPb4M2fQq\qeZ*i]CѸd@pWdyZoDHZh=AoC|}!Osg&$\淭Xrה'"ԩG0ԽOSb f``Vg\]Vnrz/;tl}MIwt<_sܧhk.iQ,1p9Y'%E.;V)g ill-7hAjk6JY%"8>w\˒D+j6gY3>I`!FPSR /$\0{ n؂Rغ2%]7yY}}vc~/~ =^{Pڶm۶m۶}ڶm۶m۶힛ܷJUvWjvQp}vśL36MRVYѧኒPmGT:WM_uN,00ƙY[dsͩ1T|R2VPҍ_}2^qzCks]|ck?/C=ϕZM# A&*M(19ݽ] TNls`t*W3 MgUС&?['2$hfôD1\ߵ ^CFUF(@w˨clI1NmL4o36ʑ'Iހ⽓_oZxVLD|mHU" 2l|.|hQ  ﲕӠRVlpl#X)FcGL;Qk6qIJ& 3N V",b/p@85E$We!*]|Bσҭ86 |= )+.1-i2UƥS.vJ򯴥K >~7c T#zd8mMxemГC]9/$‡? 2#QZ aclIBۍ<?D,&Gx@SfBgi !{) 2-^Jo]_y<| 76:ZTm:Cb9rq_w;>t\ck#8 J;9AÂ]dqC ؙ47'oxhʤY出p{[rYWD2ڪ}jpfsh9iM8,/(E@b2d*^[5 -8g0J]`Gm5Ubmr0n#Xfd}GlE06]֓dFF\ً:rF;T Icn~Hv7Xm::.>_:<-y|͓T7]>e.Cu^{ P'*˷\팞4!7b,YКo!^%&bk7ݛĐMFqA9G"&f[墶*D)(vhdU^mjF&&ja$Z6w`JXT(u9ioiN@ R%X;i)ꬒa*J3ͻ)DvZd*UU*rd6%u)T|+SsnslA,IۚhqD~TkTa(G%hy%(g`€=PPu;`@d.z ׬ɶ>2[pnw!D-ͧ&[qG: amBj?@3WAK@PQ@(FrʥIo2CŗFd]B^&A8d9eUըݲd D)V+C -oƊ\DD] aip5U8O )~7)ρ|%>Q'K`O tl%ڜ& lQol*f;Y#WKj`Y|V]bƫN`|N,g5H\wimibdN P)aK= *&IґT;7JQHҝTBbgk㦜F7oT_`zQ,5[hJU-dب wKtU!,>ӄ .tdB?Vj5g1l2|| ߹A͊9L.6&eWE<6JBH7MRlTwjƘ4Md F S'Nw vBn0׺)SX!g@S*$?w Jxgs`Ś*t6-1V"#ƺw5Қ `Y~`[B !֒@zEs{E{FE_R% u]S`'[n4b/MzcWӉ"۸ËISE3al 6jZ4]W(OY8g0&3ʸ4AT3PQӈE03fW .uVusz2N4=2yPJsBΥMhY BlҌ=s +lh7?{O$|V|HbUR4lVs@Vh;*~-HˇpU)'Wg LFK; (>Er !nJ%TXaGȘfރ~_8+}g Rh܏ֿ \rG^.*:P1wOT,|L!AhZ%%H۳|ަ|TU~%ě~q|q|2.m U캓rK2c׉֤CkV#NS +sVj }ILR1 Յ"՜[3Ż{fz2 0Z*cJ ]ZN]^^ #15}F2=B9QUa<ÿ@L5K\7{yX,# /NnqTBQ{]w$-4P _u}KbbDc'sUZDYmHw"OTKL!7+"nh WUxo_Pv­4} 6!F=A/~cpGFk6mW8nTiwxe&KWIES=kWɅ r1ѤWa䗋djw/y\YKII{" :xzyklRpewE6f6 (ႢE[I]k>r#|CT Ym&D=k$@l3:JGu[XRj|,\]eaoL:[~J)m 'k@4L . ƳR(Zq"8OUVY, ? WGx ]6M/9n0@:ϧ?+\#^Գq1kB p#+">c$#Y0s #n~869_g8LD tB,_XĶ iZz'QXV,jHyd$tu& >Rz8]:sk_A5Zx40#YNEMjGU֥fѝ8|q[~nl^D%kԔ/ k׮.؁SX>5! YB GBSe.h! r"nmUrzP Jڲ[$3`D}"R|.Eῌ &1db$i@ž&TbNaAP.,Κ O:[TV@5ȡ"/p~4:#z' m_)}up/nЍnQ"$ÝP=:hgC% < *[ Ňbsф a'9An'`h{r_~;+,'` F"/<'t"޳>3LvPpMKް/Gt`trYn.Q+4] 5A[*&qhji#'P_LݽӟT'-ȩ 2c,o<:1Lx#EX,:G/Pbbs~6_%$Ȃ׳Sõo 6isÿ|n2>WJFU?7eZ^dqFCpa A{hxInĭVB`Y!D %+&ٻrZfTj )7>o$Zb!@s>(ϔ"fXЎWu~|&Q_8GX ŕ5ՇW{* &Hإ )ķoeDT,z Ɓh@ 拘+~FOMftu14 sL h2C<#Wf(&ΫHVQ:-e~u"Jem}T h=Еگ6"[m "0SrMA3kEUr!肸j,">6m`W&B=U4\=chfwt"S$ܬCkݜ=aH<|(=PtRTR7 = -ϻw}i)P$NCWb_yV落p[[1,GK Eޯ $CYGOoEIT$ix$IۖGf- N=BMLt &\VJxEb:;(wK*#I2¸`T{0mCykT:~^וVTsmWM|:kt3ra 7v[Ybd۲K"!|HA^ͩ0a]9?_[ uLߢPI80cJcVBXqh4( Qx:r;.2e.O |R(G}s.;6Pw\qm+QHEHj9 :?<+ x|mS+\҃g.kZox84rs=`ZHgLbikPI{uV$B7c_6tn@#G I-Nˠk&7C.{Jm`H@ ={ zͿ ['z;A<{q?励 dZz>PkH#LI)Uu`q8,Wr n~ַK;QNu "=AJ~$[Hߦ]{d! 2a4)9%l[x,(8C\S.J&|쥺!a, >i!] bLWa1ɭV6pͮ!Nn5"X-%@`˴@@hʕKՒZJЬCB;K> >i%E1͚Oۼ,k潩2n\)m"0VʸM|G !NQLD,ˬ)NIĚ'{0HΔH\a6T /?Z4Yumo9/nNx? W'6tr֞$6XSRB+v?lRI9Q xfs֨V/թz}M8YϽ}8ᵌ+CQ_s}Hq4{6(18PѦɵ˜SXX>J>z/YJ3DK3&Ra*& y/vx|c#M/Ix8ȴS ?'Zc U%*i$eО́(6ۑ {Ňxm~xaЧ? n6F)FC޸KnY5EQs;5'#c6D)>T\MG`/lg{qN\ymhjB!>i*+G,ܪ_Nyɶ} S(PDRg~8:+d+:=H*`qVP9g vg]W1K{;AK(0Yr6xԙ)R*U&=gl3Sr2~z[_e6P2V]=s,]ij/Ⅴx' 62F[ܱxo3(]`}jIOG}tzF7An{Nd:?۳[ ٻ2ܯ.>Z'm3cE,wF ;󕲕'ZD:ԹPz 9)DZ̳HNN^ m'%\C8#2z0.96H>22:S  Ƨ + L!yGV d`4g)ʁ a)"w#VǝRJsZU ^CчEby\̺g3 T\6;"[Ut** Nl%eWY A4)>OJjI: >1!J 3]Oaӎ `y$L&vmV#:QC:'hN6eܶja"V vo)T^ ZVPCwX5N[I0-sNg(Gj88Ճ݄f'ɸ^R;jʠ:d0Ն@CZֶT-iAgYd-dDrm)IoȮ^/Y6MęŸbi}(P}E?y/}PEСb<\Xs^q&:h'1[xorYV:V7vd-^ 䨃 !uE!)>a-UZ DhtE=="g2,@bi7vУ\;As⃑AWcr %Tm^IVcX5p>M7HߧgZ)"kb,unD8*by燢zxԋ WzX-? 9tD׼(逻~( q!"6^umG/nm v5ǣJco<\¦]й&.%8D9ITʉ}fB\ۆws7qJ'by)b$YHpfIxJ ? ~xf'KojS)2yտ٥f6ޏ_k} $}C[``1zVjQ>уNC&" V;g\jrdd8$1*t^͂tKxYg@?^Y.j,lݤ٤cjюW=sE9 1CQușkj!C\'P4ϐ.`;˱wԄHU*LtUNPnл J#ߩk4 #6˿OFlrYíњ~`f@ÈQRUo"AaŶl485]; A' ᢾ C;DDvVD:|%ή4e'8hdU0A . spy!U_Wk.O FZΙxj N}|41hi ~pLtV?pO=ZRzݴꩋ:z!pn4C]HJ^}<18{l[)6vEk'[N0*BluLKFE» Ƅ `{s,D!#φ)ZAsz>3yUv?/xTm5 ={@5 tkqe'>1&SvgXS5RH7ͭ'/npR>[WFЅZ<,QLu0ۇKlɃ>Xgbo1>I& UlUzPg(Rp7%.j5VU|WS;1vv:_uME{R7:IZ}P@yE`oY֣Њv@vZs m-卅;H?2n,k.C?54`l}살>+og%xe4ŧJMg3Oɐ Fz2hFHjBn?Q hTřa\Ñg/C\fh 'o_/·7vd<[6@_P1X&HNȱQnjn'BgTGAh*<(ـu?8F:We|' iU_ 3=YFMW)6hC6O:KVҍdY9 ,TH!~w!~gܲWndpwOJIOk4Inp>(>wTBY걇 r!&[|;Zr ;$q(wI`i6fy`)drwJ(ea+ykP:j3GYskv#6=! Oa1 ?5(Gv:V['oYIAE `(X0e`Y9Bg`߱j,M~C#~XLqTk=$SHB!n :2+_T>QȶaII.l,\f sX9,~@7V2 nވ*Uuq;~I( fmuye)=GfT0ƅ~(JdC!C[ՆGgK: ]ġP.Vf]FVZ垍U'xr-@z;NWf}NQ1(9n?mR#!* ؂PLV?{eoZ[MբB'_И&QBrhA|`\9!Yulr[0ы7{(Ÿ]زD DQ1{`|m[z.2;\IM!8ČdgS ƭuRvdOo_T_bajq[>4F;j*9I>xBC=do 0J`F/|潓QUG%\?DD5b)P4,›XHӟxaϐԣBG4:sL^ȹsm4}(oK0ԩI+2Bo`lQ>]Md=lx%Okf .E+dRS/F 0Fw6@99ҟqSE[v&T Ad~F5@aa_d@=JZ.6[۪>mљ)j3R]UHm*c=| b| bz,*nh,Xx3\+ح%=(䃻BZ|dNHʸ>ab4ь]d{bo^OkߙdUg V]xq:-yA60&2?mv9^9GUlvjSQxNb{}Vغ r7헂w%\&~]0oǀ"_ov}Fܼ)qj|pļ[T,Tn17#wf-#rixb 0P~%'iܿZܸ Ǫш%S(׃iK`YJA viLP:_\+#K8k.D`u؋Ͷ_NvC`fXg-|&g|k.r ՆʳrcFW;":D!]{De oo deL!W p Yq>N-RҜL-T>ކa%aj#0k+孟rNP-Uxʉ~!huLѷ7mu5IY^ 0$=Ody467/QV öNL}E_6BKjC1)T& C l-m?iGQvB"$eQ2Ni@`@uRWeMY%bO}&qvÐjWvP]s. rcR<LPAkZdO<7[ z|8fk3 󜴏SCy_;~Ԏ@*ث8xk'kUJr?PGĊN2 &I̞@Sψ*zwV)6 n#oiXexOpAǏ }Rbԩg{67۾6I:46vbKnm ^fcA%$F>o1atyY2ϛ1Z˶s^݉8(# d [`wz}s[ b%F?Q@DE.$ x}X1,F0XΧS=C_"C&%v 59e(:\|MAxinA7gY0[JlNh"FP9]im٩=yX:<=+&Z'.rTQzghD'Ia`8c?D:Z/ }luؐ3PzLoCP\5Dj5ou/;,R"5c/HEv"7lF ?üm,ۨ@韗 [? ^1\g,f&y+>IOj 8)nX@sSp!7dRk]0j-qyKzM4J*nXS~R΂O} pZ67TQE%NH?UEp!k_ 5XAmCR~O"h/<@$hۨ-:b(%=CC^.) v.zUKV{ܜy^J" Iͧv`4D~Fіl):"0z'DG`-Mс}c@#}"C̔!mzك'8H2<~"&Wi/]b,o'c!m"`%Y7ゟgYo]>>L_edȣ%ϵ"/<8.ٚ\+M'3|Ȓ3T%:~8rvKdsFCo۳zg2KDgg_(?^Y{w07N+`%r\{Ksa(,He@=|)o8K1H.þ 76DBKO_a-; U9j[³ys`GDbfL`݆i(Fa.dl , dǎ :DOu6F0rT]\_sRmWI>~̜㵤4vqGh(\zrNhǫ7u1JCXfC|fnF ےJTyv atQoLܕ.b,aWU95-vL1)KC[Gv.%4c =gAzE8 K7m/U4zw$FဝmU>ÁOWf~_NzI'^f$t?N K4]r0\4EW9⅟;AE`uM b̨Q$Ҍ}5f6A[#L,C;5Af/_ZGNЫq0%j&Ä_& 4z<Dg`-X?[Xh\4{-NԖ\JpnԭUz**5ỖFwaRWOi6i6Y<*H^n| sgu w5 G],楼^= &,GL"8]p$\>%EQl۹NMҷR`2XCoA@,. l K03h(J QaT|cb%5;}|yđs56UwzY`?: ZzrK$*3j?;/,?x^gɐwH,N/8x]Tg*fUIdɼ&f](yinsKI#MVAMf> q# Av$u}W+N?yH}׏ :jm\h`4ez:qN7o\7f)`\) |ox +WIF'4nVNUk_׽N)8zRw~bxېvsb*Ǖdս/Bi=2ЎJ!Oۍ[i+btZq4 K? _hinHh7KϞ?Ɣ)=t4mTU ur'I" 5yq.IOAAjji u59߿N R4RIYFp+ HFߚ04\w2lo94iAdl,1z=vE,ō`2EOs3>':78rܴglEQ?= 2O(nC{6?벮 hzݙWfKUN;ꫡs,͞u@ȁ;A*{Y; Jlg Lp=k ؉ 7υFOٌ܈4C=V3YX7}嶎t4~U_`^q_֤'JlM*[dqmS_$΄MG 3(Pq38\ʠ9׿"Frpu#+5meҥ6݈ lF'M6)!IrG=1{Q[ 3xj˶eh"u{4Ha'Jt8rW)zQ / B|茡{1<<k0Ȝ#ݜ4^9CJv|wC6w=OP v܀c71-5ա+"\Cp'oUW.}31Lڪ.xڅLcK̑ɍtph ߮MW ֑W#O0-ՃmOE|P;)eJ(YПds8T]`aj#L`,E٘Άw6y]G\i N)`dQD. 9o> endstream endobj 1659 0 obj << /Length1 2123 /Length2 22739 /Length3 0 /Length 24053 /Filter /FlateDecode >> stream xڴeT˺5KpkݝƝ!݂'h.}^|t֬ѣɉTL zf&=3 GN. 4Y9؋<N%@ GߝfO<ddP\@&.n==E+=JE2Ʀ6.6Vc{3 <@hr-m5@]U\E J^XKĄ@ :ڟj@wtw>Մմę p:Xi_(ޙj`W%`bpp`p  %ٻ K )'I_Nw)ߓ{.@jciW77}\]Ff":;!ol}{nj]]^ _s+[./;c`zRq{3Q;w.pz 073#+PZMp, zZ2i׼131 07uZ_]݀+9fVQ?.pU7wp߮*95s@#AI^ v@Yz aKlgl_>+ +_. 2~a{ [eRslgs}9>6@_.]?|RrJwl7u0s=g>f@`zO8|p6(/`q8#׻Oo`T0kA>Swmafz/l `Ζlkt?;ۿ!{2ww;wٰktr?6'W_'JaU Pyc.g~?Հ[""l,Yާeff_zu?0-/:X5T@s3|ВIZΜ& dQ9Iڗh`ؾn&WM_) ! i0e/TtP}.eՖFP?~azCL%ѫh[+v/gnl汄ۉ4 z{m'L3g% s\co7h9ȯڑ}´]+bjyП0q X8=p #!Is'2B~r#|I`I?&)_e\Rmmp~,8:^H>Ta &0h (Ͼ KψFDtF+S*lX$CX55 nvȣ%p MlEd%E8Mc18fY DZ(Diq?ISgIi%xZ k/᱘ʉP#-}z;B7|# \fqNw4bKsάz@WVʭZdcGEqص<Aܥ>*nf׎E /m`z߱ }0igG&\J;tvQPѥ=O+4?9]VlZ5p+DwO^3U oɇ'F|C*dhX"mA2AgjwJZGJڍ-.ߘP!HjD/#/OWi3d?ⵙb'-w737D0TsϣtO&p&mC-ξkN5x Vٕ$_ 56ukM Ay Xh39:sBeȡj2 sc5oKݒ<ȅeb q 5gkTPy$BL+HCjh&[gyR=&ԥ ayCBezQ3OX=%HjNpQ<rW%쾅-=d SŠny(l c2ᚵڣzlTECO;ZEVuZc:[g?:4yvMurPkU5?amVMܗA\E{X}]|TN89!9 +d\Y`9\[w1yT'#qM01M4.^|5s@2;f[e ˶S-t,kHd tbJjo[PZ]*B@(?>ݩBgwv*}-/2b||=ЄpH]xClI"./XSxdQZ/34sE~$cx fLV!玬RQ8gJS;E's /2Gю|Ʋ+e"]׸mN4yw]OZG҂Js4 ."x ;(:8ڿ05WȪt>eB&veUa[c)ѝATJP@ CP3"dp:dF?14(i!-ȳ+ڰj4K F--'=44bҷhq %_dsRl653 u:7Z_f|#nS싹fX]}lVj/ nBjSlEn!e+&x0O XQߍi++ )یRgDaV!|AL~O/4H?g;e83}JZ̬BIE@aIQ{.*7V[#߁4m8 8?2//*0"q/3ʭDh{QqpA"Х  _p{9yD%? (_k㖾*6JͱmT4'"u:ZjŅ#J71<0w&4a;3 A Ȟ}o?qL{ К;ja|HlX+'LcFb>jc{&>Xd$A<[4tiP TC4P*؆Q]q#;QJϷ0E{{7i)S0u1yK*0;SBRcxób|yM9l k5/%oǣ72RZPX!ܘ8AmLkD$Lh+QEJjY `鼋= ۦ_Eg( vڸuB~MWsВo#F}k"g84՜(d~ޮfNHLށay 9sv߈x\lUD=Z= &@Tf%F+ DbC[)OnzGKy߯J7U멜ա(syd ~ uY|˩.Loc#&ÀVqzBZ gǪEީbAR~8].K`h^M!v<(-p8KDŽ/4N7Hl._Eep4cݓr,:`LTM#599@ytn|ͣ! UM|jv?܌j}! E8Oc`g/OÄDJʇIeN.5wS^\ -!Znʞy@іvA1+' X&%}!#'7(x3\6S 0#9(;9v>ht[3r`q["\W7 gKLZS>|=nftDUoCG'voA{I&Y/=5vUQMDpH2jw$~`2Q'S>d ݌'`c,#q;{$)s4pA0yU.]{=Hw>j1w; (ԟ#C$UsSjVY)˅B4g~dZyg*[v0$һ]>h4p &'R ,<.Ww'DOvk*PASx`ALRi%[J_HeEu2`re䂔TzbӎeD<;V WB@ʯ:sѱZahC26favԅbRC.$v~|~=}`$j7 -=P~?g(pBi 6Pѹb'Zw/OCk3RVQJy}S?oq"I,8h6I]\;4d^iuPmTӌgW]w~WA 8-}u\9WC ~}ypWϵˎWuՔ?( ݹ9" *|h7^(W^VkɁE({"j $6eȩ~VH @bM(/L;E1z9|&IKRf VgbO9^uUf2UuD<ނD'a7\(O~ w Ƨ\m6 H ƉT»\~Nd`APB7ub~jK{ߖ%B'2OPM"M E]Kd)ęgF_Nnw NfgCbV*+uc_SDH.A6!n9igo*5B4{|![of`-="j8^Q>/hwV U~*Eݭ)T|Aު`.e.DK[B%9O]]i "2CIRUƒUkgќAVէ (dI;NĀ>/|Tz=Z pZ7]FR=VvZy`\7 Q) $D@C8 Aq2c|fmzz|frnA~ɛOs,]ezrĤzHB%Im< Y*!ݕS ݯIe_67LA'=8 7Z]A{Ɠh4=An\)KlI|gFvh]dHAai歽5ZKdQvevOrFv ͧmeO"4iЦ:>/KOXF=)[AG5ffp;}iZ~k93w,ne=z$)rZTGjI󍀑3pC@+b`1 [̠eWF,tRC?SD'ڰ kºw.9K.-g?);xn?P|2QrC3j]çhjtL}׋ŊdXN(4)|xHbeNЕr )O`R9<-}#+Wvv/}ƨku:LAjGqW>yHRAWMrQ }hC #H*7m@6nоk SqD<GkzXdČ7o58b^WS9W7mНbqÃe% $䅍j)Z*;guA}]Pتr"6ǯ9qzklZarS'Y'2rd[۳m*ׯ5~'}EHnԎMOHI ܇ŃŌ2rE-.?be͇= CofIUNaZLO@IlbslиCX{Tl0۲4`4Ȯ(9 E7ZZm&5gᢉ Kisϰ*޴?_Ѭ>vQF7U?= O.WV6OeKt 4zd8Yо.Y]] 2ۭ ePGIגf/ESߥ~F&@m[PKs|f4HgP [Uܾ)+!%!n0TTC%9pSc0ØRuw+Qml[@0spjkDѿ?:2&/6a+h7G2v{~#9; G>nʝ~:g|/t}<@tcT!XⒼ]ew3ZEͧ.R֏4RfusLA^R~շ!TZKC_gSK0fE$dD~r9pG!;5OP9贛s7v-M~Gr~wDzsMw++7-d )E[`˂#R:&X<'Av zEwaoz!bXex!$VAm<4t*y Y+vSǹ~wjP[cRho8/GV n7sxgQ_gp%oyԄ[Tc|'!N0CmGu6W֍"4`!.>|T_K>FiVHb|+lRJ<8ʙ2B| =/}tQT鬿 PAMȶ0Y)gU,AD.g 5Tt(BVo<8xUspho-z)x`|G4_-P $g2LJ)=&J-ؔ荹!בu9}3o蔨e; mK=?+ؠ%G6eӰ!Bў(-)XaI:CrL!Hrxx0'g?;Bp3tߔ{ҦCh|giY)uk p~hg{kq^7ar3{8>S5\_"ƞ4 C‰q.x85W{C١^g6GnF9O xiΫKQu(>x>v-$V[ClvB}čCvb=ov&AE7XW^;b~3h u[SP(͢I1PrhJrH'3>|>hb`C S_L EͧoWQ>"`aY][Խ(@{cL-\Z\xq,{ԫ*Shky΋ii 2]>C3Ҹ/qDPm"'oi5[w) zO1 KR @ěB-Ñr\쵻&iESֳI|jށHG9cOTb7'Kqq(ru'ǥ-:-X(U@ S"mn(;R=J t2XɯJzuNmפc:s<~L&WjS]| = Y_x?G~'jZM A'Ӫpv鞻)y.tGx;f/j;Tʼn-tpz{0 bIO?lw;Bx/2-+,rp;4k#1i獱[Zo1- u{^N\e9qGeΛf5Hr>5]Vvr[`xn|ﲒx+ >Dr3o }%xU{ t,Bkg)BT-4PNu>n8۹މ "QǢyjb!tƇT.ݚ&6]T0$1ycH<ڞBý(:r>MB:ŎQRL]xG|lTChv 3ۤ\7DI#M}F0.da3EF=R/C[ ZFә+$ry?!C/uH8U,$EP=# aR4Hoɘ["aܫN b=`Xc=FcSpx8zTY ]ڃ%D ))c$j 3 &ǁQnXd8Hq6q?1b..iwdJ (,:"=ݷFP=QU';Tߛ}:7BrdȹXuӗӇT*:V$h].Ü m3~c]¢kI139kmiNQ>QjOT07??(R$zHm'v!J堹m ۈ`sx=?XUڹf(KJ-%wʛә8ZBĐzߧ# ݙ x9k럄t1Zɧ=qXOQ<&_5z3Bt,ֿ7maʆNҚ>+ɧ 77x%,> \gPż^'QEflwkt-8tJ7`e'xo C6ZHx(e}O፷+6YK X" _MNu;0s$#6T55= ʧDN mM:eER*gNB/󇉟c2|oXo;osHaxt+h2j{*ib:7Tǵh~_ )]msvl)}*`Q{wtU"pH)Zȉ=.r2_JH"Ay;}8t-K}T}ֆ(ǂVt|-jAh_5;f3@S8jv 83ʒqE?ZTrW0M@Dח8(L"4`@QkdD0X1vJ=pZW{OwO$#¬ڠzEvRS}uK5_4hiuNRS'P^GBcjM"e{p({u0Mƭ(l(-@ zPcCkf^suƲPՅjI|wu ✕%+ }.!йY<RB  ²*-Cgyx PuC,_C_ȏBU>b3oAw(9v2-r&WX\GB咣@iftM ꎣ@Tow y~۞QoiwTam]Qt\5%wI%lژ끧, $DR{mEt<ʼnUz akEU!gB:šcE/i+H.(\M3 6E9}N|B2Ϝ\#.Ya NenF#t2?ߖ0ͧ ,uZ^_f^>}VJkWxw/;d흍%Did%3P[Hf~ذ:Q1E/2 +n!'ן9ۚiy<%2 E/>|q,+q21ݐC:ܥΓj 3iYZ48_^QhmaP68% ,4fuXIfx-4ix,LrQatym~0AOA_joP}`E C kAQϏ0oE*,v'rrMzch=4:ޒ~;;|ć{ZY,)~뎎OoA y Yebzwb0d =~w/F-oCfdKXlZ.n?2+ ։1|脬E z{h/o>U<*m4U+iyQ":GX|jFF1"Y1KT Nd"r>TNREr=-navHm{ɠ5sTr\=Kv- g[|#)\hj6hC rLHfuox{/x TLJ^_4bWmv+ " ŌGA)=UyR IQV,쫜Q &M+"Tvڶ2- čW ohĮ6Za& B%C)`J%&y7 @JoY֙` +.rIB̚š=b;99PsN=rRtɓoɏRjӡ"V/ yoB=!n0<_|iDѧr[:55{cUV(eFEzrϋCytvI/v7Wd (Nݹ?C )k-0$Q7|kǩȲdk@oX wL"|~w9;P HZw'o_T #ǀFV+{,}3Q ++dlX54ݯa 8e^/,#6Sic>oA-[U5O:9 D'Gh[.fc y:ErpxS*n;=IUD{gN2W v'x[C@""Zkd'*m~Z*n#А[vq7ȗFzW钁s#<_|\>@Uk5 %C^wN}^xÙ)7]dV %F):DSPʋ+2Ѹ;yre/95ADiug\:](FSlej7<%X9Z2 C0L+Oj`]Os| M 񔢂Mkp[ xD@a+3,| ZiVPi5HSFbkz^~ѓFo8{dSD rRIa Oz4WIbq[;d;Qtz0׉:1 r6D D~n7L, uJ8%!,I?@4Rk!J#syitT_A)=JNnqwYl-,bP3Q\se9$* ҞJ2zikaNաBw߆.&?nj ^mTfat{l3nyD5  k.s.@{)ԣBZ@`"QZn{`Нܭ%sln sl>EBcX*Z 5~kܤ  g6'i}3OoҎ7 .f:z Zݴ~EIg̀^+xCRUhSOnrI:ʁ_Y^G_cuS(Hw`]<%zQT@@ 5H QmsYTM?eAؓEM肭bΏ~*+9,1RdžR:LWï` *]U}jU*tX- R Bh%UM9 { :> =gǔ[uUqʸq"Ѳ""'hr 8|/PAdvf63k$O92-Ň:I^ 1ڿo Ǫ@l鍂O=gs} n_EaإyEӛ/ƉE8O)if%F5ErʼI[E6oqyPacҲ!0:`g| %ꧻ?db,dQYɈԛR4UȄ+l"h&ya($ߘ s&A*>]"w<oZ'<.=Kۙ{)͝a%YkR_G cۤGjՍy|Yi&SS |<{^P%SZAl~ѻ&cH* yʡ39 zW;Hβjlrr9#=sӓ  6G泶 浮uP Exp3MHe8,&Ntq63zYO? D1IF cvLo/].MHy_=(bB+Ɏ&tH8dЀZL؀.1]՟sCi,>˷$3ffr}_1>b hE}2VL'6}krT_U lcm05M5ٶmۘl۶>}o_Jn*.ƻu[? \=Ɨ5v$ؚ0afR]N}&.kB- ZbuaM+v$v0dC^n8UAtB&>;j\"]Q'>a09]hC%df78}թY /|p&ӝ;gK5((>~[⊆da CwN2*ahrzɅb\.6Z} jY[3?xw'̄Y -m?emDj >t0w [EF ]WA\&R?ƈ;\Jו#X%s4[3ӛNs)F5yy?}6I uŶqQm̙KN<4~!q &^=[{Fۼ^ y. T0)n9LѮ9 ofQ+"c։TUbPygu:dzτf2?6Gd5*-zR3lU# ͗Qԝ#GR4S 0葪 S ރ;^i-L^0}L$d T}yzX\_9.e~a.KX˒oR4ۺ^ګSkxeVs ޗgfS^Xqmu5;R^f%Cdi$bqU2%?T׼z#%$2Ձ[ 88?(|w<ړ7g{9HSbƄs5NW~*cۙ'>6&™RM8K~?NyE >TEmIO\U9g Î 9V3m1¼۔B.9L[%*Ǹߪ< P^U'JUן>*;ׯR-|4 R Qr/wvo2*#6EsEW-ᴴ %\E\U"!AB)QNW秪xX~#(Ά%=_|)E1JnA{ A>dF=27%wf䱛Nz=`[2SBO>Y2.Ѓ.FONk#2.H!Fm[<30b~?_ys'PeMZL~8"T!iD2+:GG}OLnE`R~=8۴Q`ū` t2GM 5|C[|[!X'QN Mc5xNPk>ASG=(lmz&LS4i$~sBo{Q!G<eAT}tVDHY[.1ݿ" $dzm'Sdz)\Gzbol U6sųip.߷$Sy>^vv¢|,SryRK3s-ŸOXKG*U=]eVJd k&G5#lA}5|fM0u@nr >r7WiW~)/dQJaR7ZO 1NeL>rFrE/n!|>U ĉM^/%n\bãFPhRţYxjUf1 q-qgtJ03vb~B`AJQ1uʟmYyKe9lsk}L-!~/9bߜѡѫNfvD|;88n%}iTr\/,(Z 'u _Z3(^?1"ᚾ K- Hr]oA’hÖ߆IGzeKd#T??+ Cc IPg&zB"U˝R~057k_5SS7`bF!Q\noОZ97v>R8LWB6f#gJ^x&ÓT;8J*-=v;g7v8kB A8c`wn 4Ξ2G&X3Q-O4t|vݶ'=܇`YS4d7JVc4.[oL'6-@|.R  w-$`G$t60e! % CU6o1]0:iAF%kOq?r}IPqa~s0s/^(赔i [T UbݨD XE n"t$W8cDΒ\ sQAӇhb 1z=v <@]!sh?ۿ ws#-aCyS 7rߤPXT1dGCBmw+~\#Oy sX#c#;}dk!Y/S?_8X"tǑNSws.N" 33`?/Jh *pZ<өO˧{F`ÈHUrqX7!_L1C 3?X*3TjZ|׻i-N@)R:'\W2rhTk6P<< ^Hf}AԯL&vG+QITN''zt`MeS x]XJ| Z$XNs}sE.7 i;u;Wrΰ 4 \:V( +6<;nm5o WڥИPVCqzoZ&J&wȇi[)|,D;B16'!z w=aOxmZ7@)ƵfYڞw'=9n*OQ"pCkDoJTDSjgO⏈&2lUɸtqJZu8 ㎑^"^G{shK5i}R$Vey)Gp- 眓\f8:Tj7٪Zkmi] ~ Hw;ՂNoʺ뒖Ԩ#sL8"u,1) 0%hj"ПMmkGCdm%u۾:dǭVn+}8o@FCgGҝid`$ D[]enpp4'5{oPmA@hj ,ъ{v_Wsx\urk@ *zy*i "kzίQݲdu£5=ݙYn8^,m: [/lfN@cqd ٔRuξl믎~HԉF:j`LG`lgrK,.k2rɐRt cSXD+݋ŕqz}[vQuȑMcX*4C| EUc̬3F]0X-m0KX8ٚ!6993͵UjU"b]R-k4HN? #\U? ΄9=x#lI,lY2; 5cLipv 8u>VޱƶaHr&^"L*Eo]M~Am@q-qmܔ|)JD,<C}h jd_׽Ъ.{؆:rOc,'٧nRc=;T-Cs&܃xM׽VA3uպa=M֍@2rRFQs _hњ7UWPTtGO+)\I|fr;bQvs_Rk^Û5*..*5j,[W>H V!^I;iu/ sʵ=ě'*h:ʘƀWsUj( C)dUkZBSzr]0l$"VV1~ {%~ʼs8| O~'&7+{C%MsK"Jfݫ|psAXe R~M!y@O(Qo:ԗ4 5fI=4P@ҊovI~vp,_|,n(nzXzDH{k^pf"`}- =6{¢}45-)^H%HJrq07oP[EZ.r6C %j+QMtۯ4D%+1WCYfǨDR4 &c"k r1TeDP8gvtN/(3ۛ׉m2lAn!OԢy~Ž0ṑ /.xJe|dmq0oqcjVL#$53=dLx73 G2k5=I>t tABu ɏcbQ ϱLr^t/-B0XMXPu6\ )3elD4k[Ԣ=$2y@X MH*\ǰǺ c^6ՙ7-~ ## sJA~:$ٝ"2JH%/+zϬw9afgЊ҇j깖aj+ R.|-JI'íc&"YlS̸drRL}TzZ# Hɞ. ?TW-8%mQek6vh&GsRv &Y_ov:ϛ$Ȏ&IW؊ϠZi5oL}:LGOm1G{٠Գ%6Lȥٙ`yaArgkP2V:'%Y cJ~zS;5fCakb- 5b]F OΘ.4Ë(|RG<@)3ʃ7'@4U3Po\Ԗ<=5U%C?-諣B㢨K% #k*q7N'{ wk(mdGjx@^Cf ar ]m&hixDM]j۠wE΢CF]\]ű[0V?U.I}Pī,ݣw$VmF *2`k#C Q0}]䲮~0 gۥMꉤ P>+c0#c%D&FG񎗞&5qfB.]F^[]2=Oqmw¨bH΅?ۃB̓uD9>?D0NYgǔ㾚d(WZMr{*,bo5'dR Em'`C](sǟF7)5kO^ux=uJp,>Q[dRT3oc03x") ;EIOm ,7G@*mLU9!G ՋDPqn?R7ܙÛq~Z5VN~p7!>e!4RP,)pLf Эόo0HF+/qU<b>Ѱ+iW6r`\W t6-w!q; ˵mǪ^B&@Gi_>$Z'i ; }& |BϾ;k5:lt~fr5gTx[:>XߚOEuR \*ueX "/j^uד c5OcS=bD=B8%PoLSrۈWGK~K@HRZUǨQ8 5W:$L=ѷ rf0} !}ӡo2Z{ ?I䫺%}&FRxV-vpMEpkLB?ujFw=6#N3G'ͻH١] eeR>'|H8j!Q'/҅0VvMlxU endstream endobj 1661 0 obj << /Length1 1905 /Length2 22897 /Length3 0 /Length 24112 /Filter /FlateDecode >> stream xڴuP=Lpw!`3&$]BNpw #{s>~~EZVgjUՙ@i0ȅ70-]L̬TTN@k0HqE!Rd ӛ` Phx:&U R$N֖V.880gțقݝm& s<3@fЂASl4ե2j*to`")S֐2jAo-o?u+IiiJy li~ST '_V..,,̖.`'KfiXY;NW'Ƹbϩ̀ g$iNV%]W[#\pp_{k d2{ t1qque{-purSC_.-/'35q3:{7f`ߌ@z?gf ˦$,'-6x &%[w@..ETroC*2ۿvF>I>z!u_2{=P$?9 :(o*ԡ ±{YmI2 eG*%6Ŭ)Mi>=݁|VnTZk|+)@sP>}"Air{ [<:aD;{(iayX]ZX4ƨgTķFkkDF -T&+ ϞR+($q_l`r$Mhnٿg"b+Ok!+9n6hgn.psg$H{y#ø(Mu) Z$q|鑙zp,;۱sà0r]9k y6eG*)_,Q5,>N2hS鷌KU{4#3VY>Dʬl}ٵgVMW )l{C s]C'qBY4”:K[2T`'.lo(6D2ʯEL̨u ,ŸŞS> |eF7N3 V:l(3'~̗~p/мmݍ͙kt؎oP&ť#2gQ;Vn̹JW%*VN/f/pАzʷV:J5tI9{77W} TY-5?jqbbAL-4>l Lӟ[T'PMOP9ϙ+26F&y3d쵡DUgY-p*hI.);z+'ݬsSdБ6^\pn+',Z VyU/aW#XB2Xpɷ+4$J+ڡ&/٤mS/J?>H 3 N Ųh-)5ؽ7ِ{w'l.NdM,_۩q{gJخ| Җyiџ{b}yQ1'NZx2+axvғ>?Qr`U .SCYڽ}JRK?e@6kF>"_ZKp3llDե'C$-`֪>Xa4:8%iaـT~֦U{lŖhm3 3MY)\t6-S̪Z`q2N }$ #Ϗ˯Ľ ~,(/H Iģ |dX0.7In&Q_@\ %89Dv\?Z4qyYWA؃+%K})yhv$1>59}Yap|kNtZ~*YUY^̶8]ƸDґ0cYS;Ӡ́fu'wSa 7}cr:N wY6zՉ؀,)Es%awklUX/BU5/#e^)ok_:3oVբ8|O6VPAf խ=8xEs6 'cAw(8&}7t[JiE:*>R?k"jϩ&0h926_i/96kEf_d?̠u,avy5lo4Ag,Ŏ{Y""T-̻6b㖠 D+ݣeozfZ^9iC8pBOƻOsyp"nÌ| (QONk=!vD=|CZ41dOAgm!8@ ֺfL": ſ?yOAn!TmYy_l_)'~1o}F= o8lmW)?yd{*ԏ l1=9};6w\fTKibqsTx@H)L9hZ!֥Cb^dW%_'5~(FX77{__8 .vԆMZQjY)Mv8`E6wrJ^Kcsa/LIߋ`+L>t8kߗ+o<0=xԦyW|&0BlLn>GqtAt**vzU.>!43fW*M/ЋӉ6R0¨3 #[OU0b:uk(V"Gb5b{bD{<.Uu'U% v ?fnZ~ɆpW*1Q%xG乊 kA:c=3km^K7cgu!zj / )d[V#g›wfhc~ [N5XhzIP|jC-bT@J95nhd2Gt KŕΈ3.WP…Xӄqhc܁a%"wMiۓ4#@ ٴXDB(;+A)O[L]8wg(SJV '>3_ꄜٕ|F(6}zRδVגO0%`ΫM{ ToIյ,"diw9oV H I[M+1Ds&((lۄHOo!Q gvR m1|ʋH+cE~Zy Sw #ҝDMx(|Խ8hyeLU W~ -*]c}> z"vXD?XwGM9YbG/;!dìDKDv>ޛtmŶMpE+}ic|XMTSzl3 nlWmK|SV)|Yd*)*(V]6-u|b5 :Kw΄YV@t·s ͙K+yܱʤ=ewZX(6z&^ݍ*Y[WtY q6X+} ̍riVeSY̩ c8Yf~!!ʽKıYp BcslI`PPtwKz\M`a,iZi9l k cnc|4ɀsQaVXFw N *Z °^?@92e.pP{ؠ[O}=1X+w1tFj#hG#uj>KN${pvl$4f㱗'fp4{%•G l#!۔o[{F 4zFcqD}}Ȼf0#Lqh auЎj =k ^hQ~Ƽ~vri- 6tw~۪BI/_[a< d08U~ݛГyձg-iMUꔪW {#eӢcR]u Ƌb z}VGALz;5Xe (yR<nЇ!aubWxOdMMnn-vƾ_ubI{`LyG NJ,fHc'êsfH=pe0T-8b16;AN(t'jM@`'6T |V"*7j} z;vKx.|y[wժFK-0j9^w~) -՚e+n[wj/쓳WeeU[)H / ~d9q Ӹ:M6ee˫itZC>+A(Хƍ Kͬwq]UZ?:j{[ZiDZyG rSuW%śg1*nnEx _ eB!9 *MPaH\&'Nu`hCsPՏ&uQXN՚S*{eCXVuFȈ" !bvqB̚L )Np+\2Dn_1)C?XjQ>~v}p*=U 3>F%6wBnَ1L+ >갫L~ZQhatm&~Ӿ]FPJMBreۗOU=)O,Rp{4Q@(_[@rst&L6i %pKgE!3APdW6G$5zSL+v"*PJh{P7_TJvBcyKcѕr:"d:g#m5J> -,MХv!6l߶Wʑf E۳oc> Sy_3zw׮Qʪ͗pC soYfq ؛!I Twgͤ^iyXKK}o{ i瞳X\k2{$_߄"kiM^v TF#h& s1Y57AtrjlĐ? ?X |$Zڇ!_*%Z*p_{BsDnQ8 HFcly%bc+<$o rzXm1WophcM%hΔtl ʹ*7g~$GRgI2L8?;K6K\BI*R`twfKS;,8>c$2CM7h8P\kcB1x9,:@ `]c8>@(^pP[p3\0x4[KW׸ݾ 8udK[ޞhhˬ[>_Ltzk,Bh4@2&hCQZѲò5͜KgDicY 3qOV}թ!$~N@>gA םO6ϼPJ6B|ˆZV*Zc 52ȁ\J6f \UnBR濱**/%׿2OV[#''jH^~'6ܮۨ a`x\I>'k7Cp Cj\~xz+zCYC32 tYR8'W U/l J;˽US) 80ufv9_RO$C!Yڳ?ԗeo{Zq+T.IE FP4a?GvL8FI pm:QK)ȣ/Y}~m,6 7w!7Q 9$PY,3w3 t5̘JB'*i 3ԛ#?TFoF,`I clk&ŋFۢh>s []&_}jzA4Fq~ c#&AԔ/#~լ]朸WlclF;DZX,L"us1efLYtZ/d{XaX@2<9{3g{}IXx1 C%DfcS@ ozaVwaSx@t 4^AX^Mm|>Q*%@̓""?QcJ4tXO)y PЏXP{7p[^^ϽLRs]賏N$  Oeپ\RZq NN IUa֮U1w"HY9{r߹ YNkHisbH rcT=y[FYtD:Ơ ,8]`+3tfl{H`,Zm&i{RE f֟֍飍K@{yGO] _ͣd3K^A[)Y2aZp 4=pEi\+dzĽeק M6k86X"ǟ*m|׏jv Tm؍d{MNY*k"~'Ho=+2BlxltrfMjM_xZ-HÎ`}]c$k%fI-(*Wm.k*\ |rY A%E}7gMb"j^%Mv؏Z2',U"||'.4 0~jiw3ңTdJ 7c{iwՅO# >7ԌZP 1@Xc"Naz7_#ZAK25.Oײ -vzX{swq2vbQa\F}]v"X1'^CEzw=kgwTKª~x7.N׏ pYOSݲQd#AkO/w\cgzkNe$IŅ1X`e<!>n\BRIT/13>'ɫ/]mDusr?]7[} m | 91j[*3f6Zˠ܈ߐ$}Wg~ыE멆2t+L|:sk! F{wFLN7 ZHhbtEiև/yz;k[3zKHw "|H(N:EU7hG`ڿ@Ŗz0);L%=ݱ rw:d(9 Sۭ)[Qo߀Hq _]2 稜@Se{ pc.9N}Ꞣe"f>/r9pI^XNsR-qj+Δ !sGqH6ݧV>Tm^J/v Bw%l#]N/OC^@ݴ׬zz7XmO5죻`@C6Q%@R4uFL8#X1Y±~#yXyxiLGAL:Jv  ^fpHF LF60|[~k}C+Z`mU3 gs\_8Q#y:e?$`Er;+گ $P{.eD 'TSJ ο?xǤD:)\%l[=P wVЩp(g//!lՑnv,/|DdSdAU|샱WtŻq|of8KN-M ݊g mYѯd83l1߯آvbeN -)3'AZ)lnNsOp L o$Ck:ne{V罦uX7lޕ ^)pQRKTvCH "pԥENp +u~Ò 4 j%cgEX\k A= rpk%>iFm)fW02q_־KVǴW]v(VQ,}׷s)D@\\3d;M5iʕΎ1E;7:њWh"*P` RW!a~~~mB$j|GkI,/Wsyhs[t ]G­^C;tm~lx^@֧\>+mܛB!J잚-l,slCR> ~ő[&Cq3z60O_0s|^´32o\W*EAzKSpD}]#G! U./lAE#ݧi^ ۚ! kl];ھN<ھKc n4FB Dk낎!-J*`d CF/xRlWܝ+SM`6.Ō>5gqkHhe4jPH^)4v4~MV2y9710ѢZ춿@Ʈ̞dnB׷jբa:RekCޠ_R>ӘdigeQUVI”ڷ<&W93VKWP00x(WV$7T[-yA؇GYQHBxiyv8ADDR`ʏ!SğeZ? fcL3ռykuj23򪐟\>ÜHvV d`Ĭ  bt׾ZǢuLɯW.׿,Z}SiKX50?lfLEאv8"2L5\~gU~n͹oR(^pr"cL C~T:(wydqC+@|4KCӺeӗKOTO3[UxIl"6Gu~v1FOHνE[ (_:nc|MӼ2g.ƑI@Z3ŝ 0Jcxʘ,x>1pʭ<ƦYOioÀnjH YE.4J{?5_rBC[4ƒv %C*=8[-Bct/;.9Sr2>-*BCmTh1/XVܷ?u1CK"qF^.,6jL RP ҏqtto`Jcee$fR0$5%CKH~3p<ڵUxGX~}^_jf$Knr&/& -h :?7ICn__"Ao0nV$,[և4txz>!\f ,ِ>er1Idpb]k?Mq;Z )nf[>ӏȟǾ$$U [d Id;P?*WMFb< 2OX И(/4A:-lz_8r>_4vݮcSgQJ|ʺ "C:#n;3^mc)<x:>ig6=X*ƄS!k0FbaOjeHԤ0}'ߛ>tiw7߭!,wgKҍh}(.Tj.=scP'cJXeSfu_%|MeVUSj69 j s7@Э :DHT 1=Zxն"+ٞ J!;ф [(6W"͘#*tݎ7KoZpbɩkw "$PwŬR؏C,'+-ކ0<=n{WA pno}|@=[JɌPrq^$,?_Cl _T3b(;^`eN+䉰[BC=k~MV'X#SF39,`_c8fot0 eBJaұ9 h&6!6O8GFYr9J1# ҄]0u ɚ6=RN[^2:U] 4Ta U,u4zA7 5E) BR ։u3O"rtndW(j_,Q߼hBL R0z~AyH,C):RuiZ̧*/aGoq"aHފ&3Lm!^.PLnh2v$6EƯLWN&E,*䁽iuU܁"W mٶpt!|# '=MME9XY}zkx~~y˿yX@ @Y>1 Fx5H.hns-}ÇI; }hc`ԫ\ ;(! m~ wD,DE-ǎ@/ ׃n)c@AP^xO<i_4֥ \O'zB^;v,$2,T E`H4ŀ)8!Wf1wĒcR"N3LQ?:ms!(N1abb(MxDΑT0¼y8ҋ(Rn: !p ûI7ᤆ> +x Ù>vg2nhYONW-M(q bneEQo뻡eHTmk\uu~#xL_HuKz3e2喴8[G883{6cWXBIO@O';t+Lj5MF6cCfs]9 "N ̎ey J?I>UE)珨ah] j %A["}kV}U2]>Lgb1;Z{ ceZQE3{帵<~Ds+`}~=K<#b駰 4rˆrЙAF֡x~[Ur‰cȐ=ވh£D06\?<{,6kK= ̟QUBJ^&+{6kOLחX1Q|R/F<5оb҃Uܲh#ֶ*Sb*F )ܾ,o_B kJg^А!2~ @Dϕ9BcXq-&J"/ :qAE\oǐI2>Ln5"ʰMql  ]i\V{9H@HߐXuz"kzd .FSo{R$iP^.+u0{sCROAynrb $T:8L0HH_k>aB(]v#j^woq{&i Qx"Ong`/Qg]ZÖJ>D}!VP81 ^^$2kLnd0$:u?x1p%cؗ3u$/ #Q|V^4J5+[j`gJ2hR*-"q>_{+!†cgH IQP6VX0VWNm(~£ư :J4k}j%4ɍ# Q~J-qGF$斆LqQU2:vvȤMWqߩ3N^!9NP8l~c,UTD$s^@%V*9oe?qs~"Φ%ܶvL_i.f3 Mg1w'78k/b[[\˝զh>,K'%1YF!,6(P*=QO6ȇXwtChiܭ9v=.&q~0/[-QV9𦍨: RS™)E[ /X~/+Y7zr$?\i7T ::Jdo$;EJSȉoLn⎗ܕѣ ḘlF+gfey\8 T&9.=#`&[k"L ?aHJ%ʛ< 9`\䇔R~bHagB$h)XV^LW}4kY݊Og2s'Me/y>Z;秪(;T\DSRψ 7Q/l"R HsU(SxUnGnv_d}{=47o>ZxXS+R,ḭj~ !Z?y^H<'r ƣbZ1/kIZu7w+ւh5eU9PDFKK,Oc<3q|*J1뤢f2~:}t'O#CB-"Ž!jdXަ>r/$\mk))z kP@[}2= ov;IC[-a>0$蕊_C!#!zyә{A*^)yyebԉ^< g:pTT6I$QwI঎УG2z~CʠhfWL RjjV81"ɅP4y@MAk;nKzV`'-^0 V֚9D ݓG/]ȳ@:m"q]0Ͷm۞\ɶkdk9um۶Nͤ]x${`dD[Et&{T|OX&nݬp vߎf^W+J-7̒ilZH{[H4n50}9r[~6ԺojP JO.X/I`*v{EaVH0&7IT!R&?'fEO<r,Bn`'a/_P;h l+}^CE@*JP?ԦVw^exT#~V3 'V}o3='FT`rNj0 =%n5~BYOH +{ xn(,!-fY2{]$U3WZQU},J첿cdw8zL? @Tņ N/d㾯['&k`2}W=TޕfYR)!ЮQ`_^w! L|kc0A iO(rԇ겖e(&o鮆!)͒ ulgGW-B Knz9r'9k%\-.< iTQxKz9DF-+ϝa.9ՂOܳ_]d^e7Bf ¢J[;+9 og*T 6bY8=8iK| ;^XL PW7-K 2K<Ȉh2Fcu"jUZ#{ ~WNeF/e}K=FF: ǖ t,<y_&g`hg,|wFx$uqFz'!fzCZ5A+r,ͮNG{\vPsu`mʳ l 3$kIIf(Ҁ˓7 <^yiCk7FD7m?9q=g.K'XTO9ip?Pq5mb~Lj5a2$hܯ==| IJQY#Bwk'4df!kuW ?҇*fqrY ت!nP~CǶ n!lRr W̦kcQ/)+|֢AteIMq\|X}?Ewqi?Ay*hvdfV) ۲iZ^M6Yj ˜1|q2K&wjI*h[-mnTbUMʺ*~98nyN_ S|tn.]BR#IP;sf|:T.,^r_q SOɎ%{hV8&dYEʓeE'b\sS'L\ ag9*fc qBjpsΡayLnTـ}g3;VEd[IiҬWYk&=wiq5txo=XL9"o ߝzœ 3~+uq6MqܮQ %U";OxC-e Aa@TDž;Yz%9A] Elf*=8uWWmݵ;b >ZH2ɯiaE/%xR2b)0+/tς)eD`+uVA9x> LR^9XMoOqu~SZ߸NnGQ ͔a)tpWsLSD(y3v$Urt|m/靺ca{M6]fa>P[TsG&P YS+g~]?ЌwJ:ic0/@';sAGYRA55yEMS]11c(Ez)rYpIsp,e8I׺ QU'llf+Q8<S#)r7m+b=>jq1TKћo9#T}\j Ue㉂ɋWZHV; ~G;Y3M;Cu|[B >ߣ<@@;&TKUKD!ÅUF7JX];|ix{XE}WJm%ry p" ^)e5k =mmFucpp.42)Pl7h"av(nl o|%;#KU =_pРeWFlȟuB(깧x/%@PijIx,4 ws6A&C&} e5t2c5j$8 UvU=;Mȱ YبVW{]c(QRrIRHrCR7`U];>IAGf$Suj \'_fǑ}N??jYl~wbZ>AD@*o}dMe!Sٵlrq3YmURLWx#5 f;&Yky#W.Z'G/) ٢0J@J#m.U$.LuʥmJ(幣&}{%vMd']NjMaePUkLdg67꿀3[GVm]ZC/B$܏0:pgY5u>! DN  C0JW demS^h" S׶T_q""sH^N GY+=[$Ϛk}h_4  =BKٝK麂=@$k=hV>KKXv6gy9q||ᓸ`x@쏐3E[pPe6);`nnp+jtKy"oO^/.-ĎlTTS7j\O7ӊK5G37*usCND%B&PԔxɔG )[`bdo;X#[}0=~>ЉDeQuXb 2[4jF>hD%BgsDzgNkY9rpJ$1{NSam\o7 ~*+XݞS 汢[NRށ<*`7]+.iKGu)n#)%I!/LHu(Wma.?6B5"9}i0p@QK orQ2E]&>9=?EY/4!TkT#_mB-i#o=z=qZ?̂ě$V$ltM7}:֩|uQT9ѦD)ƕ 3T W6xZEQ#6"{`xjƠBNZ<FIف ?]=:6'қ=o",sTSuCiC`fE "3/|*H~i{2*SG2~n?u左{Վ)U?&Q,;Y#ǖdMt讍P~*!_z ݓ1MR.VӟzF7LI TٍX6 a2zQlGd(qٯR15KKQ*QB5G1U)Rw¬><&ˀZp0Iwɸ2 PT7XHcDNo1|< s乃,$FF\*:kt!D3M;(3Kי!Hi_(MSwcĘ0\ u%9ӱ,c ͦ>ULaw `r/ 82]ER|;|հ-y4x$%'ۧh. V>{Jp+XRBzkh*"4ɺ k(syAGצn2fZ'䟥K -R7ȚZrQ&gѯ6Jqbr=,"1SŸLS`Þc % &30K#:wpe8ldI;9ҦwQI>bD g}mU{&Pi&n]U)B>jujlA[a:3 X ǹ8B^[v#͔k 1p'%jx~V{csERaX:E2-AK4ǹ@8S]p>94 4:0~\ Xu"~[Ur::)\ I`πy,!WN]*qw3s%$(f ;q1krEVy$~n)Yo԰~\MYc(热. EAI7UD:RD] YKJ\FRPH0oK;Rc*%ܦҥ{Au`@5R)h6\l\(K h e̞jͼpTN-Wv?e07CN_t FhΎ[!XxS=bʥ0~fƫVG {2z~nx8Osv endstream endobj 1663 0 obj << /Length1 1790 /Length2 20129 /Length3 0 /Length 21283 /Filter /FlateDecode >> stream xڴeX\ͺ-'kCpwwwwơqwwwנ݂Cw.]/g{~gxFBNJ/l2J]xr* ;c{vz \ hb3v8],.LLI=h0]<*J gzc3H" rtto" cS PV*=hilkԀZuUqU*5GbUW".IVP5j_Հ[>|8 WVVgf;3 lF߭};)tqqadtwwgpuva9Y08ӟ3d:mjoA%_ . @h $ʏ4Aߜr8?rJJr;c+{釣3 4W@mr?eul}sŌ]7{ڦ {g+gḙlwfV` jr³}c|br<.&37Cf ;'f ɓҵ=ͭ2ono /ߘ:0__oke{;.N@_i#xfNˇ?6 ?٥A$@Fإf {[OQ!ԒpU0R'glgetfA.!W_9 b~13VՇV\ p?&6n#?j3:݀F>+.N􁸃 tQ{0yS_'?cU' Pn?\]<*@?ED@zV&V3'' Śg| h 2 No /"f8ВI5Յ#V(XMQK EiўmLYhOI\x,_A=([~)H&Hm&#>v,1;U^eZ!{s+- ^7T7#F|q2ͬQQ֘ Cw]RhltqGǍ#Hn0B2J Ie<{ր钮ehpz)`e 11˔r^CY̳%/?tfqHUڵ) />OoSH.ݧ(bX:KN'=ER aloަP?dq876K`amGᇠxbvF!eU`Fte ͤU8i w|1#vtװkzT  G9ߴI,g/weJ>]ַd5Y_ۤ*=8qUJz*r DtIcL5rvh! 3~KU}*s./1Gn%GژR:\vHS9< z5m˲:u3 z;~PJq!&'eBMpw_EWj?z/KD)<Qwg\pncky]ɔח(z~4c<滎.䊟HMɥq( /ȹ>^A2mH{B5 h"cdGR }I,R&ZU)2s7a![Ew!qU|iVx zYOGw&H)f(eLO(xcaFWxmTFzhJ?7^ݻN>I:MtK&?}ı#>-x+Ab'>J9On nGUI뷫1z5Q:]7uID{qvue$A{J>h#Q ʕ;yAM&d1 2V^HHt'%h}9ۃw(qο]ŭ/%!z z=TԈL{ʝ;؛> A[kzyV}N2vC^}2IiT>4XZ9rnQ+ :iwQgEZbʍVA*F Rtѵ 02(Fq␔9\s8?A_d ȿmli(~.-AH-(qqL<֩q\OaY |ck5 V~c|ߡ~ tiE H̉K{O>}z;wǨ 'S%7v5Ks$WG4֗*Iw22l1{l=j48C^R ̔e~PXT U"jޯ9Re{Ci(UxGù+cr$FZow!^zK),*, ':oNDvA ::]:r]3&_6Y"̑j*:Otb8.Nj |NFY C-(K@ " Xg571zv3;3`X'5A-Q!Uc8, 04syB>[2U4n%9uY̕h?#Jˡ)|]uR}-dJTc(R^CwtBu0V?ƺނs_k;{y*YP)v)\dUKHB;ib V^r2 %2IqģCٝxepaJ#]CSr9eIީ`>&AO3rC*nv։xA.D[+H @{I-Y[D{FO^3N?3E1+yD~t2n6v]#HbjsgH*v/SGui%-hm@n/*KwpM76䚏Q %M0z^aF!%sZ'*98$tk%Qv:MDr2s]1vU7gf&I!||~9~QEQ8fI#4}6o[VzQԲB B:M|i2R#Faw-WhsӪ\75#!Z@ @Zc7^NO"ԢW}3 =d#Y UP^kb1Ldۢw_J4ax}ɛzXtWɥ:YQOx$|D]Db- Pn 5s|Hjѫ1%; \:QH+xVrG06*oa$eJ3nv1n70.yX4mZAxဍe&N?9ݐD}cr@N*xֆFxߕmI.ri\rcvoD!}ps/1/nzv0!Mk+͹WW[7rNaOOuo# N ڞNF켩QL _И_5,2*U W] AJ\3 g_ON)'u!Z5BSJeS+Qw?/:ӱeg-!`EToS3;DYgt^IEbXc]}J7t8Y%α.XwPOxM$c{ oXc<<ʸ%]SZ S\E43^S?dxF*'L_ |[.g ɜD,h$S!k)%ot"TRu[`(3LvqW`|)TiXz"i߰s*ΣKvd26y|@tžOrc7uX<Cu@iᥠJ6>h\;g~+RAc@~ >B~K&?Ua^}$Y `*sMSZP\t^"P)Qc=)8cCU7ś@5l76DXmzMۋ>, 2[ǶΏ صk1> SAV0Xn-txQt1Dt _L,y*\Vrb% GLo.ߩ籘wʪ^ԞzhǙ+mEhNvksYaZ2RbheW'/;K4~WMxʅ{OD[gqŝ}*rX4b>̀{@ \cZ .mV;[*|Jp(mȷJA)-i[BtV4재<ޓ #yYj3VT[b̉|9 k]. Vv9K)7Bre9AW MZ3v>I'U:qY؃T\mW\>墀#i[^kˉ`>(nJG]\B{rQ",]No9gWi&0>YF4EƉb)5@ p3.;EG@=+u\7z! yyn;-ۥFݸi2ӭԑI+<싒t8K 45ϯPEQT =m}VWѹ;+FHb Aic_xl?Obj?)dU*`cL $jPrmq=1ǾW6FϋFN;eƃq;-Ij<G+6m@H8[E#?ˏ{w'w j.q d8g\Qz_ Mu:; zږNۉ(w;ž-`:gg™w1+n{~F^eܘ;'5Mxh ݞcA񈄳;gT یNHkQ[ʽ-NKq'<- } eKvQ 2SCF@QK}?rs`{艬wo k2:Hu1nb%@;`H3W1ijT +럌oA29kā\'T[Rt<^cHE$?}fsRWzűmXĞ%%f(؄y?=NW xjFFOϿG 3>)v{N;i8=w/FLAɧO={i r4Xd$d_b5>NWgF\ GC)o7=K7_U௻?7Y.qiq8zv-\"2 5?Rmz}lV1SQuӗetcOy"5MzoEJpU+&ӛt-&@ϙRl*xF"[1 J"bĆi_}0VI27N]-C(Xup]e&ejvF.H9|wE-m;w=܋[{E?{Wa5.h(E9Wur}Z1%-Ȧ{6mbOcHLS*mg Z[>_KƤRn9 lYʎzR`+[H캯mK8CsFZ-kR-)46$_Yպ =_TSum| `itL8Hiu| MY)Pu6wlqPO(Ռ3q+jM)՚oѨ~pبD@9}^8Fs\MA=uAy*Wi 3536{u^75/RJPyJ[z\{' 0~qݳQz:mG jǩ}j۬p`9L{5D~xȃWU&Ai Tęɟ jCB/%'ЊB.vY<}nM#ɚp8 9e%?L:=V#*Lxu J9̻?z ?E|& @LDަY:\[G;qd3'[눪sf |.5LqZ' ?.O#NXT='& i5_<566Htx:tT;(5`) ~1;.=mxFe )&@;@X8w'Vz`[`;HRӛcO|n51gy1*6&Yk8T:d=Ef{ `OC;*Et8(G|{T`|(*=2Hg|sAOf .q=xIOዚc-˺+a1%vb)zx]{>Nݽڑ%_u(6dWAqpӗvpn4Y4a&Zbk 6R䙝.99H 6Ś LP˽\x6<]v :q6S]߂bzK`J_]Ӻΰ_GBC#o(iZhńnf grV~9PIw.=K<"O]mOŚf+7Qoi.gK:w8HۄN}!1Lݛ}y3C{}ЗER{{ ">: ܛ"b? ֖[D.W"w^,zK&0wX5TmDQcL8b'FAqӿh|lRW8nCP.Gc]ij֚RӼ]tiEZܛ3JC}ͥ7 U<7 qJ,.:)z)|[d7z* c:U]p_])]ZFGWϡMYB1$)O:~ m B$aG e,Ɏpe)@=|;}<@@*jD3ib|2v9EXq;>*mS cB=X\(n[,zXyyۊ%>JW7a1:. Mn#rO/r)0+ґ^c$g*m"d镘I$|Ɥ5 =p~mS t}Kl x Ky5< k2"u8 a.;,ӣ`Hv>G{ Q,`[1XcuG>șEO1>a@RL§nx,O*-n끭 ~UgVWƧ%>̜M6%_w(T2:8zG!0)ԲiTKp=S.OɲjQsII#`n9L50@ˁ[ێve;uH٧'9('~ 8U Ц}kj[ORe;֜3 O7fԣ;2 tۙ<gE᥈kW`W:Mn$o3{z*PG2)̲+2BBlj^ʃ," Ȏ5+G{u8(U>e7eд֊t$7ϰgϗ6\+o۱(!FYsx7 |4qK*ίםugy!ͼ%4uek--'5C0xKEwHIV2<-b8coeZY\ˢV]EltUj%aviOJгGRwgb\ԻT~'եӖーmv%. |m@Dx6_+f ;y7}%4XF 50M (Ĩes!MfՓ9Ncv&, ̿SGqn\2X*ɭ#.cLb췶a Xa؍̷4)rs6=x|ޮ7 9] 'hI<6;Tj~%z4CE 0f;A=NS7 UX:WQuL/ yqkxX#W'7%3#iC+Bִ?u1#/} aK'34B/;{GEIM!A7 3pA!y qJ T7 `P-Q^v@U}'W2>SLWRPxWә?D<ؐluzM0-MtbWw`<8Vwk/DhEMݷ:QA%^ʹWN桕e}DObȸL (Z`PiSVzxrk0Rĺkd=7'^20ޚ[s51Ki'%wWP*duA(Y?`'7N$;&KoߛmDiX"(ci3NNiw)hx0#MnR8JD:(6A x*79YG$ bWQ>`e#A;s`z1oN})3o-z{GkJd;"\Z)3kPT? d ؔJ!S޹×+5q_W*np;U7~ٓު4!ݧ/ (|$\+f璓`}>|/b.E;# ρU2Çʾu9W7y .pw-KoLLk%@\}siw\ϪN^Gy<+9H4&Ɛ\}#b~t9qMwUQ,|MDhM 9Y ˚Vo4sR5y'7W2gN$•WR(ٴ48n^pз{TvmY~]_a>ѭhlPJ@9Qnŝa:tGj(#%rvg-½Rצ RaÒ. JzʡKqRwuTe8LoL^~Un}TWU$)4S!8p|A긛 (!qv@iʴ@<#8ikbߟh,^4'CefgxnTH(A\N1P .zw$z+(,-(.%k,`E҉ Hx&Т{_"xd#'bg>we{}XD}Z04<BQ?Hu99{k!= 溜Ԟ/+}Gzw4D cFykh7WmK+36?Uֹ s,X G{OJ|Hmtyu1RF&.OIG"z%e`k2\ cqlO6>Rb(C(JLʯe:إN.I_* O?Q (!t~heFk{௳ [ by?aEKهR㌉E Tt^b7-/&gb2!h0Bq1xN 9i)ժcsQLg(ܜG.f&%¥} 1{[-1,3v4/α<(`8b})'^{+v,'tpZG7 o>D;+RZn) ]my͙5ij DP=28J7$=l#c9QnzsD/~_) })҄3%QN1MwΊхK9܌Pۋ+Sв& ۽+m~'2Zƞ Vt^dzMcD1G~0n6rv"WґYȰIݝT&SZì ˴L~[(/~}Ҕ%G i9W3w.sE'J`C* 6ƆW_i Bէ+G s.L Gr%N-х`Z`ԯ<2۹0л~4QR$`׳~G||[^DTJ;E\4R׮vrF ZU FBczuWBi<՛yVx҉ K8P`5\Q;֩Q9Ț؊2Q=sHRT(eq&k)ȽZh@xbduO$cVxTɏSf_b HD9b@dy {6閣Mg*Jy~浨z| б-,bJɖwr煣Ok{&H쟗]]\tMaSt~)h>KA[K#G ;Tvomu+/2eDx0'J ]ӚuC\T3E=&H[em wk܂^_WYa!kޤKU +5b/`4Uz {pN̞<xC:2Y]Dgth-TFC,`t73 T*ªX@xNsqq#wwc#=.כ*Yȃ<H6G1?\cLԥ}}CF '_4^%$&E-ѯ=av/O ŗY=Cq+Ao?v#L$KiY_(VRF'j$rmGFT[q&j+ ߎJ(-Œk:9~1xU[񍡜ӓRQ* ٵ<>;+_b6RֆCN*w^1^ƾ=%$?,$ψ[mzm4Ju7K Dƭ)i}]d G\fe7G;$$9~|/wj)PuqLuv$6{&}AR"$zIHuE_fքðQ;@b Q! T" ~ r>i}zWdY[t@!?8~HB 6\k/zP.Hp93k?6κ VCb % y5(f_| 1AQ%G(?` Vd9P9צƂVf W7!#yOgSX,pHC\X"!w̜&/*žմ9(왳[ˆe>f{+P8lqa뫖?yO??.kwш(Jų&i$^1)Fjqr @Ex8e"駾]c^G A <դdhV̘[_`!\Sթ(yf-Lp3Gƕ x۟^ֆX4\s 0yo7OHE3 J_53]h.[#7F8{ *FJZN#Luk_ot(_sR?76x5$k)"M1j80 l{spRzK,BeλRMÁ뜣Z T^/ l%ok5ULW~lf@Lgt񫳱xH5uCn޸qGhJ4>ߺ:5PInbyKUF]mlҟb|h󭔢 sRlM&2wX2?>4h*6J~29ZxKzԯZ/q?ʁ@6g S|ĸ_F-N xynTjĆ1f_LH@&|vuvԥ4&CfA=e\ pd2[ʉ@]c6`L Nm+Ϟ;TɘH\?S=}a g 5&TV Qf3["JT?j);0mV^:EԴ{X( ;f㾿j 1zxڃ?_ qeʑփ>…};^u小.*NT~K'/O8pzˌ6hA7tqAL&5EKjBtqc3 hmw:GWS|k*QnrHlR&iYfݙS?d*ˊM>)/Esw.ixKgdݾxǮqw+ymy>$sMTlgCpUk_;* dj%cƳ(xzVrp8d^FARQ`2Bj!+p} VGQ,:^Z?ʘj_b/+zYݜCq?yz+ <[ۅ2B/ޑs0;0 Ӡ3 4~vW`]Z?*+[n|%%{֒Fuڻ;+XoeY|1-%λ50xCvԝ^fSk3z-k&Kk,۳pjRc5F^nsihd2~x `A3zKnԌ<8 %LH֚C|8lrP2+Fÿ"vAv<N,~ߟs>[(^YQ>od=*!,^N "f ޸J5, q4aj>J] e=`+,+2zbwtAx:B IfI%F{'T/j Jg1 Ov /Z۸zT;ߏw{]|fV5n̾r}=$Ps>2{Ok3viQeJJ#~IRvPp-S|dI!n`GnᘒC xTfYU,m*O2?afSr*?3Sx{uxr[``4d C?fJk큼KF )bBWr6DOL1쑧4~Fp #!= |@f> ѻYjA42c'\Cdzbn/ Gʖ=+!`KpƟ !KaQhZ?+(A#; [+EN%|\0Ȁj IzmD${. \M-(A>4Dƚ$@x0ǩfPo9n[ -=tVPӚQA6ldd/řquí-|ZIlcL6Km 2Ai L%[cSp_ޮN-0ku_gƮ%dCMuEi>P)1tU?c+;CJJ@'3GW7%DD+"Y= #[zB핹ֻןW/`;ްc\` $&k åfcK|wb%I {U𯯍 =Ux.%޻+Vk&VY_ g%Ҹ9B*3>NZЈmYg8 E]np owfq *aCA5lbM4yD{ԩȍO4#eh(B3R^(_ib^}O]5M;z1[ϡN' t&8pe#'=0b[޹LR_ݢ&x8IQKllW Pu߳͂% ʙ2{\1- ӈi4r~;_6]r=n/;yTD*I SxGNqk7 <L:DpHݏH@NgQ3JI7ۯkK˭eNڲ/ytN|lHGczɛUg8ք<-AOf"'*[o?F6)V&hLw:0%Vp> ?L[Ad$#]$92c(@ޢ^{XwH(\ڧ.3K=s@oO^Fc;j$c*>oKDCyqqce hg:h] @#|"(6I0MVMw#AS=jhƆ\ ^I: ݬ endstream endobj 1665 0 obj << /Length1 1790 /Length2 21416 /Length3 0 /Length 22479 /Filter /FlateDecode >> stream xڴzeT\˺m ;ww ܡ=ww];<==RӚ5zMNL/dlkqgf`)Zm8@V@ 9dnk# t8 FNLLp h 0tȁ*v f𯁢! 15Qڹ;9JO'ӟha41H31m]As d2ؚT@Ue1%e25{beg;;[EDYEU *$"$TUl7ȫyw.'"(gf O3?[{5qɎՕщTw/bmt2Ϣd͍@6?Aߩ|zǝw"[3_khctrv?AƔ78;8!G¶3ӱm=As+П_ llٱaprsO>!QY .R1c[k'jΓ;ѵMmM0olǨjcn w) @nFf?0oO;[; mnztN o5`ln.Wv)[{'0ڨ` 2cuz}ĝ ?V?R:Xf(n2V4w22ؿq)'lL@g;Y1st蹸H#K#/蝅?%UŅhdr1561s@w8w<%m rK(F[7b15?+]+ 86_h/ڌ ?wcf_bW0z?_g'Usb5Vvr[M]t?ڿD ۺyҳ3Ylffv5k?N2[^5 Hn )˟*$f8АXNj$M\/VVG'ߦH<u%rGILh4[A5 ]nɯH:;Om&5:z,2zDu-ҵp m oi#&+L3k5* eӅ~Pg9q]3V֨s}( 5q Ww)1I#4" ÅUD3p&{(FtqrK ph>0VBe@#V}*3NVbS:S7P.ke^"WiDޖggh% ڮfQ2A h̙iƯ~ZJK%EԿ͏TDWa !}Y/i1*B"["]w E/Z,[>NjhG^!{Z޹G/ 1i##歚be%ݺaAXGX~f-(/j4^|o^@tpz#eX.jg*gr &1#^k>vlyT~TQD gD [08-C_eipy5]ߥds3^Dvv[S]d.BgY -`1xa\'YLtĽ0"jEZX_ʯbPYH 1|>( V4#VCEVɣ{VJEO~řH\W <`Hc'&y2:(]|4{-/MkN \>#N+C"Uw-ޗ1Sg0~Ojg!DSHF 9x;%,5.:O eȞ1o4s=sTwh.;\85!yQ6e(ex54^0P!b6 @,X2n5M̲N 0ͦ5Ƭ9}as}nR|z$%~~^w|>*(Oz3 4P=Q1ob.dĘi/CBjR)i.5b &GlwYaܚEO _GI71~WL{J$t>653R_I}dzY=ND@~ϊEVdtSW$eݽ m2_[hS5K3O=j]~ih@-XS؎/]߻hC;~EfO<(L?/٩i٫kCP !>"|k6MJ)+DZr\J^%pPTe37JeGu;6;N.0q^}AZ>ڂ^{5Q6NUNE Ջ`f=cBL?ܱ36c/cJ )FXOc!ˆs==`&dSڹ{jD߂t~dS ztFb;8ŏC{G^,JL+uON3L [,,MB2[V&ۡ3ֆ8 KJ!$6+ ^}ِum2(Ws2!CьFdnh+[j]tA߯MELZObG$fP ExDgqSaTgi0*6?ut1Kka#γV+UaNq"iG԰2lL$Fa֖Fb+FP~AzNW% u":UC4fZXw^|5NQcͤR"IB_\`NG3mjd}-LV-Xsl9 j״Pn.@U8)xj2"h쾯#*"4חyɛ]nzRcwTdH!]>sФ!OVlZ!>;kD#(u߬bd^w)| dWjh'ӡ9QѝӇ:}lЉϝ$7tD!1{,;,U$K9_OM 8R"T#+O0_ṯ.?V]pj1hs3{"'Ey .gW ZzXP׹H1I+Z-T/ԔF zU v58}$_(XBb:']$ QlUz1#aM5{ JY|F-Mڿf\.5 W׵ ckQ֕k`#Nh1S)"{~v[0o<,p2չ앧~%} rnRC',YkGR%Fl=X=/ThvwJTlIq>DOeU˹< EjU ۨ梖4Y;W*Ȑe6,+/˻;&Tarp"]mc37&zI|az6!83,:fJ+7% #3e,*owdctt_ s ~+awY H89C-g拘_:i^$x#虭nQ @ƈjUh!Lwz FYuL|&Fb ϐc_^zf)js#(VUc#yFJ^ZI l}]' iIJꋶ\6!)p[ꄠKC4GZaU:^HI$g]zQژ#r *FW>LpIy(qqz:.n#/|B$wz;-z:z^"TG)fic5B? ۯ8Zm$3T*^U9sq7JVz [F8/v1g0;PpJRgdR}8nx,4;$ ڵ1\,U&^{ؽgp&_hL il3e`I_R׵0oJ5"-86b;L)Rp=;ڳ~/~=,HZ" ՜;"R4I.7!a9CS..*wo1 9@@K.Cݫ-N<`^r'j싀 ^ L[454f-XRs?6y2Z@qmqMN>cr\1ʜv[\[ܩ\8\?{8^?㮘#}6d:+DXYɞ6 [\OcXpCD8"|0g#GV>дc:Ce. |A-X(lɮDQ nVo~m +)Y HFh|UrXN*:|U4,/7@ekxT,_ܓ%}E*4rp&E,CɞZh",w+uǮ/~:W؜!^,.rʄ:KӞ-y%ԟHsbf5貪&ZfA؊<Y7}NKȋ{k #zN^V(B5tϱ?B5B $S'1d38wgzȇ\gtw9§}=r.im^_0@5J!ъ&&!fL+U\2bvA#@G!s!~lΒE0-[pw-P# I^h.y3oe/fQ져fJ˯OMzK75_}r*G@Jh:UG}~.&.2ilwSRo|P>r I~r8y55vP#u.+LF(#LփGCdh_7Έ] GYBTh~H/"wcܧ+M_xH_aSas|Vtw|{}.]/am*}h'ѕRm@չLS Q/Gz6|'Cy1 0 ҈ج>uvĺ]]*B tkRDroZs76L1T%M2Oŭ˫l-EъaXt; Vc:Dq+?K#Xdm$#`t'ȵ燩j$Ї0z1A?s$=`$?}>IvQC#JϹ>)]$(7ij?߇"{ώNeen͇7}l Sr,0[Dܓ ۭt=i[ϫjlF"-׵H礋ݢ+S X/)S -eYӼ앭I+6U:Mk1+hmD=UA?AO W9]9M'a:ɿ.2_#2ZHQL&%VUz4gv '+[ܦ4_U\$] G{G n"`įcF\+9g>p߽T08"J!'E`ip* &C̺z3],ڵ ew܁nXlnUͳ7h„+ޞKb]irr[l.G\Ε~)yZy~}ql+`רw>woWfܜHbR*p>HJA oXf6Y3v.nɄݰc|Ԫ`9)|U]5-p*4 XWyT{,7fiNj *'.4 xuʊJb[mrsz"},HNTꘘ-_'O9Ҹp i# +866Ű )co|AMeb|IkJ|>ՒA*i*BGkӉ:5h@g828_1;%xk?A`Q'IqhBbqw#o8E^-6D W^u:T'⓪\.R ?{VZuC;iuDl7 E=q#G7A _1Է?ȲXg~e!X9y<=An +=X֋:V:TλxXAiq?M+uϳvz=`qҊu4w7. XȥT?כxRqVc| 7~(:z;&ψ!j&&oNpbav ̸b)21Se[R$[+Gr /w~H$]aה!R[Ő3YT85&)fzPSF904'ǻ 'mߞLuQo0vlS-Tq4o;RDٌk̩f#\ؘt`jR1/71+2fX|n옜Er +Hn ["TyTS"{)E66_ԵڃzGZ{Oި/&'(j[$uǟۤD>KE)}J 8BÏxXlODgEEß~{9N&l킠4)){4qQ(|QoH||G6JP$AO_;阕8$me>wu)3mF̶[~2萟0rQ!o׳*ԥjnAyRAws cp=YFX0#+ZG& l ,9fI<ۅH5Û+9ϙv8% >tIK`~1mNپy@l;"p>7Q6yҖ>hu"}Q73|!\N0 O+c=kiiEΑ]e M  u4#ܦ_7吁 YWhɮ*qP0P0苙HD%o<%srӏ^ 8aE挡E_…{XHr Aj{N~0~=:+D6f].}y0ܥ OTE,Ꮚ2g\x0 hBKb&uUܮ>ċ)H>G|Z/l1y,k|_ oVrO-%iVM}+z}"L%}Б g=g-[f+-ŠG7Fbص4]qB3P<]t I3W` Ws<ÁcJ!f4ulXLUWm Zyts8`}@zvj)HV axE9F*E8f+"j2{OU6\Fԡ$@~)Ux~:iLdω8*B83=Tàq7`;$=&[aS]];)h;3P^ 7lvi"bȪD"| =C_K E _ܼr+"<MjfuŰ-y4NJ\z߂&~b!`"rrO'ޗ]Qd.ԼW'}% [ *eߧsu`U, O%E2 g)PHݗYK^ $o_CD|͜)D뤻INvO߸T>r})>%u[\1︈DWpIN0[{/~zJ וV!ɡeW.{%G__Ъ^W^!J. g[ ZCyG1n>CPe򒒽@$[㯑9`B9uHiozE$9CO/JPch$8EA4ȓmchTDH^gMe·}҃PBx 6߿ݺY/p)0t˻4TtOfK<[Ihsh+k6j\(*>BfP๪,ZhF1~1lQ/%";Z5M7XIe:j K>Kt'+1>4Y<v wfk(IK['jfP9keGvqJ&+ r)G'ǻ^Ff!MպqJSN!y]ۍh:Ws/Tc]KxsۉuV؂?%Txڨq<ՎFkDxChw|3kYF7zJj@=UrtXvD%v3Z|PP&Pٿ iMhjo q9f:u6C38]NIC&ʡ~36GNdݧ?p2z'0r+K'˜'1Yi obe ЂiOyjbB(p1oNf >A6V G󇡴fB1 JZ4a\?/j 1-3selWm'FڒC)R"+kY Ek\ڱg>y6Oă;F00Z5٦IXX#{_? 1F)2jS6T ~S 77: Du'N$V瓆IO9 Ѱ::nD8$B4g5-2ehgcӠg#2_EQJ $u9gB.6\R9f6$OW%& ^ÐW}gAj 8BBb Dz6 khSTٜ]Eyv kTLW9_TS-5FH[?XClH:BOlg!O<ZfΚ hi :pRy(܂6W(&\?߂2Žfy:?vғO ^<TnhvIә}lz?֤% qLCcAoBX{X`=\ 'Lo> ɁgnwtفY b Dc`혅5G~3g'`)ۃkeE)h~2 J鮥L5YHްܮY$ %'{MoOnZ;NNE wO[6|۔Oya%8Qp.*X_+_4<[ܫI;T޼ϐ|Z݂cLyu—jT7(6WdjNQQ;6"q5N< Fx$vЛs ;0,;nid~"W7:0.@hTΞ4\\j@^L 3z*TtzV%řbfwj־twQxѾx-<>HSDslCrAtЅcہPi1z? yrcd32nDn;o:jwx=f'~K)4bɘf2N@Aqp@PU3Bi|klCP[ij&"7`65 ]1P?yiyzNq>Wr>`6SdwKC)o^H @8hBnz;=t)^1}IbW0Z?1* ?m"`Oձ}?O KW"yߩ<5ĘKdH%԰%m$2W_[w&\vYT+K6#Sf["\ 3Z/"M VԾ.̓M¦⧂ۣДYe+.r ft|=uBFPCLVсiߖMÄrGĵsSHtNЧ GF\6&0y:'0Cz>37ԋ`x>}b" R=|=5d&nBL]dwP6-HO`"}CÚHց{3B,~+B[0}g!{+U~w4:ssUR킛є@֊+e5 >ꖓkkS旼GMǍOK;g8A/ڻlF{"6;اKJYv lgֆfmC櫼 1MBlKh.YI(9ȭe8mJXܯo$PoBpp{!O d'7^^*U{Ie9ab@Ng1t.!~%C>"QR/7U}fZqulvS޶q%=-їnf]8zg2sw.uP:eV `> q+_6ZaGjЪ)q&W}{ Nohm47S1B(=|-RI2WV54A ǓEg1[UV,YfD|,0̏;%dWrsҫ%w ސeɊ7’Yu[?~#J4K+v6OQΪ q*5"AMuQ8z*\ urI ;5K!&65J:I٘}fN mE# _^QwAi>^ p fWmcVie~8FrݱXzD▧>mQ(n9X{ƌ T4^^1kvv ׃յvwf=Va1PawLn쮖FP wRFYSh 1OȻA}dN/&{.Q2 Nغ.|?Q®d#޶` Q$xR\Ye~[.8+?ҽ$" EsaFmIȖב UqO}RY͖A}v9E7oűǒ(Č쒩^haǬXiyK@rlJ>h@d}N%DvzՍ,@sRvvy TDI=(ЮA&3mֹ:bx 0c,,~ eL1A֬'ljQZ8/}' i%d_&x_86]n>QlIx gSΡ @<7lC4C<$\;F r"ħ& NX[#FQhV9?a\# mL%-)&` mQ+s0cg o#:|I<ř؝j5d})h[.?hX3^(˪Sչ8nv |.j57F)0LfsA - p)J3z'&X%bȢaQ/V..ۧ*Q=>l"א+uƊo}P );C-l !A.ovoSVA5ty_9!tdS{馟 xnJ!VAçUu?!^uzHC@z7mdRɑR18p}a`% `S#Gm_[(6/;IoH񧑻*kOt'14m8}X2~;}v7|u"o ~%3WH;"AC%i]y; $ZO!x1@rڈk D c:!n(@?Ե`.ԗG^yҋ~2%\zʕ)HV{1ZUu2s5ޘ;Mzi_-ڕ44Q- މo,;J ?zLá)7YʍqpFa[S/;{fNOoM,%%&Fm%v*Q>8@kC`D(=R+6>P8:f/c6ul[[\پiYˮU˵tmln^q9hLj$.:a4茑;daRB}b^% "8H Zú?;PWV6SS28 .Q+Fi ZF“?="U ]@O-b;thrHi֒h%O i<޾1h4j!&nxf+4yt?lk2̅(j;cZ-7@aZigEP#[4I&``1U2SijL! {nE{l%7U-ڤ@`.^rީ O 'FSJkvpd:nCp%#ݮҕ-Y28$)g4DnE}LC nUҬX7Z>>p>׏(7mkZ,fOhÔ4D;_!3?iZD/4 #9~0S}t`/6\h PZl42&P1'H ltL>_,fTVv׍*|D=iT. CRMDy{$‘I%*HۿeOXp(' F}wA@a|JcO)ȕ?XXK+JfgFY'W.OKafTrzI]]Қ5 %0Vø62йU'iƅŏJr5I8NWIbkSKwxLru-8[;1%Q4evRf.ͪW:k>gKfzbgY;|P>>a.,WNJϻ1yp,7{?LF| Tgȍ0y7C6;W#G-mK\l8w5a`xbϧqĖ%:~OWw[p85B8Eӻ~ hxsIP=)m_zd`QͭƷX-Lq5N.=qI *o>G:ƇbƳ$ӆ~nD$hm C5(|87ǣcW?DH M[:O4x-m%Jfn |QB aГl.Z|ǐ Ut>)enc}Y:{+1[LMw+cеЦyRf[|JXt@ED3,̨ kfh;>ԳI_LЈ >}Q%ا*4=n5>sӎQS+&hs<ʝG)_f'~(X7e/@$Q{~K"誀ONK|1^ xfŊ-{ٟ# Ϙ- 2aIHK17s!'*;A `Z7A!5.:roR)OǕ導Kĸn;{3pWR: R_gC]mtpaTwCqhIjfWtU!ˋfeau&$'OXJk5JMHOt_L <^8 (NՂsO@"A}\3T,o lZ{|'[q7lS\I='cX`@4?^2~M"MJ߶ý# 4{M2>%ĥ5`Y yCa$׶ 5ݝҼ]UR|q}5"%0E4h 'frlEAj}~{dTx1(zvolMUmmIsw[>"A{$?-GӚ@H- u0ja/^!d׆ՈK["hI>-ǥGvZU[[q kXS1z l\apm V53=2eDSuPY&Z P9P1`<\Y׹6{Kc$I ޼aCq Tn]xZ+_^hE\@KajacM*{=Ɣ(8R/(ٴ6ʶ>'"kEDzחP\d@9 z8H{uUߗ6j Bs&+NFZ9pax-<F.X;߸`XȤ^:YCEX lq|3t>C2"3՛a4 ok0uLMH']4yv 9-NN̾KTA'b z'?s[e[ %lh-<vZZF^)$#fHF`Pqb]r^EnْCS^^\P:@D!dV<Ypyl: OЌ\D~WO1lP+$.Ύtk0_>={0Iw}WDVU\,U",0,3׉hYo mFy^VA^@ᰣeZ< 8sLnd14cՊ Zf `Ӷ_EK^!GHP>U@;u N`Sg=a42x0G E)5}M` "0~$}B( ?>>#J8EAC@Kyd OdР@{f1(iy~T>jM-aM64i~WrLT ](j'?-LCn1 A/ zԪ,G?x?n2σBC\*12f$F GpT\RvZ;hւ|c=DKJ,|+IptBgHj=..P> m[WyR RԤzG4{>ݿz"bW&Nu9TX& xuv7t\cZl6YU)KNP`)p2N(|w .nu-l,o6LBSH{tvLE@݇ .M+Hm+=~pb%7SU: Fe k,km+Ԕ Vo_5%{q\cҾSuP4Zb$3.>[ `F^'_!nUA 0He%,gVb;[L (+G3XDkW $gyBAri6+Ŵ_"odg 6-6ˀ) T{ `xWu=@x67sAt Lf%qP{*Lc1d96\tНwB:TnK<0fxodH M#F+yɢ춫u}f:<\Id?ߓ`qm;R'iلHTfC5KJ  }t;K\^JnA.G͢q])T=zR>2le"_v3aĩ1*^`OPFl27'歀Ķ D`sw>~%Vdm}W[w\R@v&w0!|H\ń_Q3;Yp4{z}bC]VH%$4R첅͝i. E E6Riug >3T Y?fh)LA$s%H *&襞[3iE8{[^jw^o!y2/JqU݈ʒ =&ͅǡ xz3l~;9lz4>27~TLw5C-S%F?e]|MΟ-Le!&se-=du )r#BԇGX`16xFkA pupo#ɫd9;#/DNP.ߗ _64_q1o9N]vڰk*Qi\W4pH}KSvvZIO AU"ݸ_{֭5AϺzD6o#W(+tnU}Ǵp4\F Hp{${9XGm犨L rEU%-DvnWPni Ӯ!=w#财zdiBSA/35pK#[hurKЊv@\?be VO8/ k3aj~ HrFWjXr endstream endobj 1667 0 obj << /Length1 1791 /Length2 20995 /Length3 0 /Length 22169 /Filter /FlateDecode >> stream xڴeX\ͺ-{pݝݽqqw w nݝ]w[{:}]5朔Jf&@ {#+ @N^؞Qhjk `cba@uŌA@>7h `aEHF3'@2Vth(9M]>@{ +{ G%ovFƿF0dMm]lf&y&hq-m5@]U\E JXzUUSd + IuUj@- ju>ˋ i+2tv[?z >B͝)ݙ,\]@LLfipwp|\q7d W2ڻI8hAG?}_ 4v'VNII`gleۛ~8A./Ќ_ kE>fgk+fl?6uwr+#`ne ۽5VWUc=;L ? xXۛ:}t>1@Ξmo/fVN@iYA 0d[Y4z;:8̍m]V rvzO!r̬LAB,d7w M%6*.5s@gG- W[[c; R~vV?<4{Qpp3L dj/bK?/loa X Cgߣ I]\4Gm%Ii7q{S3+{ 'CloM=Q ptG? a0[}ln$ `v.@7cV3?bX>w0{of TבX`Դ2]yc.ˇbY?_(G7#; bMuz[>4EX`9:)O`[PK&j9cW,w 0% ANO/9оX2m5rLYhOޏY\xzPR@y|mmcQގw2|.~+ u=OI X>;?Dc$ExebcU ̗zDzeEgʖroya(o࿒ٕq3 rR*>Q>d2 z$ocs-Z;-\[i} ?HrJjөN=m_ן > *jkA81r?%o]EzbdY8ð ~bE˄7oj-"I4y?D9P4 1Q1T061E!hrRЌQ]j5劁`AxPjλNMTQFp @>4uѓSǪQ_Q|AE\1MWrWs/Rr {[=w 7k϶M6\֨_"㛖a0J*6dDein+jȏ `IH}.s?VOj]x٫QH؂Kj+ɡɝlKelYew i=^UX"'qc{ ްQyK#HAXB$s%!DxoTϾKŝUh}l=mb)aYXkיa ZE0)<5MttI]\BryF«2Pw۝巈ٹXWk0eR2G& q9ڨ2ӞBzH\s(|*Ƽf`vƿ~OȰxnECvt,f7tx~8Q6epѣ)偩^{|)<s.g`_Lnd pBʜn9&ǒ̉R $,9P'sM(ci/!v`n1ވuYkOp3%qWlW e)¯%\eQ_:4 Y-Mn#`j =Պ;6PY-]KJ0Y;jr3owJӲd sQ`#Mns=.%?[D6(Ꝍ3\ܷۢ5A`a[؟|A !"& 敃y%1گ^/[e%zDJ7M&?T ҇9̹4-70QU=>,ռ#.xG ۊΝ1 ˛%pcD+jF&4>FSTsXSH6n(U,Gt6BMӏ!4='uYrGzT@-4k'&r]xԣ" ܎ qEkӅGH;HHo^w<tp%N Ha >u 4)ǫ$%L%C9G^y+MJ%/r`OLLC+A`dM^\[<9BxvW;dwAov?aY!>ySeKvEg7 kq}gn/gN9Wa6KJwe-F".? Ӎ&pFpHVf2kSX4h#=t4X :gg[s+٘ٯ$^֖;Νj l"w=-<]QBν.㡈wc-%KRTWZ W-i)miZ',ٛ`U 253Nb*T #F0/2,,wT4I,I Ͽ"@1DS~M/l2TX+|ܬ,>2G(А:2Mc{VlےEĨy x /W?J.3YNpYl]{$A-ꁦ(Tq/zP[ :DznřE`,|YQŐW*b$bRQY)n 2%ku<#Cӂ\ng O[JWnE6,&ׄƙO42ߥ]QE3 3zhnp~a{螼hA&GxCڀq;02277"xiE!{mXOr`QM׸i xoSE ]>_$ƹf98AW7ӝ>,P=7^GsòD3[ pm֜`6shqIaBRi4\z|BsSZ'b ]j`9"-rUcsሦ/coߌԋ{&qK1; H0-=1T8h >=}j粈C9鴯 QPfj3kaU=rO͇^35Juߒ0.XLjY3}mbD621"/nj&3ʹkvPË;c;WDmc|"̅tWxcՃx9,GUIπߩϏ.2Z4H|e4σdZw]tş/=#}ru>SuC]2AdcYz 5'KXس-E$E3Shҙdv^.GZ4!oC&Bڏt}g!ld򴿊ڂǭan#55g% Fq~S mOP[ /!ήm-mWfgm[2z9Ѫp&{M?wk5*`5j\/JwѝFY?_CjTeSCBlI7~29FAÆ U4@k[\gٵ<=zmM tt(9haxt_]3f[B칲ZM[3Mo Ǿcc/IC)Jh& +b3h fdi,7Mn% mXs@Q!Nz\ѩ_?8OŚDM0 Hl|Jb1sWnWȴ*" :)[mxɎ:8dafWm+&rݣ Cr hCej>`{VT&(61Vͻ)Ty~=#rmʉPtYzgHvWԯB#z:O$]韊~贵eX֌a^+Fwf[6Qxk*/TEtpe`SFzJ>Icwp2pf OB|TMKSw^wj/]̌5Yf>b􅺔\^'n֤6B GLtd<\F =A([}heit[ᤊ*'7) nI"4R31Uܚ y܈!4n {w_za#+XYxOf,gY!IWlo4Bg!vp._?&6)&. YԳ=#Y5u,63| 7cnhr6uXF^ocMAhjk@}=)^*?W_3|QsUng}gKS>tT}u~>mz&ҥri"\b,fhȕ2Tf[Ëwcf% EX^z.(h?B)[bp?"W[g`޳=ヶK !Dvb%&)4}qLyȜ/|َW Nxs$n}hWU|b tY}_bJ_;jv6+y}[jgo.FQE !n G#0'Ļ%Qx~^FB(M<ąA㉄ b&}1OK+x;"]_uY[2ſ-I2}T(?7~VKe1*6[~뺺̙ͩmCvc4b9N~^{=f+|'V!1旼`4@' KW TX^8X0:7!핸!}/L[Zt~%"[~|pBW3J; 90vيêWCA΁}rWrW#" 0*+ Utl-7%CQVFaM(~\S)`Rzj5+ǵ\":=?,P<9\Ox{\Xd/AO"G|]9HoKB‡ڹ[5a&/{Ckϯc6܈}TilچyY7R[" ֜$0*"!Q2ȱ!9fܯ H?pR 9@ojfj6A⠥ )qoT;zC jWImt4*<yӝVPlwP 8>ݶ]/A:*'TD q>q1>. '&Gvi1A>[,+r!ۑU4ZؿȢ+mWTimZ+Y№-a_6Eu4 !(6 |Qh 刲&bAޒ3}_e@g`9ˇ!3_)Z; 4T\v잀#\x$4s/'eQ!v"VkQ0^[H+Yo3v%sRVTly~4&Em)YZEB0A\Y!DH^<~Mz Y(fDZ]aEӝ]0λwݰ:&NhL6t%e[df ?Q\(4(I(حdPaoAρ&{m@Ԕ)gFFӦI84\\0u )c]C!y,w \yJ2M>uwɞ]֢z:Vx>><8O.1UH'h4c7Nm`#ܽ>hRT\~^xHḿ 0B$j) Yy9[e0BJz-%mXܕPr,Z4~ Jb*x'IǓиՊ}VP}!;sh~FJ#|ujXIO1IkAϧxn# C3;wv+:m-p'UghOI g63wJ 8όíK5!saBo,O 8[ #9n_{;Q 1hř|{?CsIRlWtn}q4\'zR&&%>ϔ._1hbex(4PØG-;J5,`u'ݏE<ǹ%K-1z)YB׬ﲲc8Ppf\kʗn![\]Lܘ6qpyQ#S!=w% ѦիS(|J⃸PfJ3DWz?plq%^Sw ̹̿oZ>{tw>hg2n1bO˛Vvܜ(B%<A,l+`$: |90>d;YD` I9 B:tVE չ7$˳-T\\DZ}-ɦ3I`0M^֑|Hw\{G.v?),vɽ35ɋJ~WW#G!E KǎD]Ne˺^_la49uzxeד1pjø, jbwʞb/$9T :/ P E}~RM'vH%t@ߏw )B}-Md@XڑDé;|vK+Cu~VËEum#:`XigǮJ-WD0c; Z:ȗ =TE'SEYv|tD~11}z)=DaC"=Bc ;~o4"-09m^ѻ;o@_@daߤy`4ax{zͭsq]B%_!1R=CO!]R0M|)X)^#m{[}Ñ")pvshƨ$ I;FV2!FM0y*82lR V݆\d*}.#ڡWUׅAqV{=Cxf߫?]xmd(a b Q)X59MbKl~*]ENgu^{W *]vN K'&>8bCt#L.Q8toޚT4.m~Le6^ S&= :ϗr}d[j] N gXNz6E_{%h%QH(+u}4$'x"Hl.J=s+F74sBHgIo?j֊ıIoK{\_lHPr8GT1}jTJ#e0;yP(Ռۯ֯~MɖI ݯ,+aciR,J :}43?_l|j'cgZIonxuS?T7s+&F6ZbhD㺵`Žbb\z d?QOFQGגdCuʊJceIñO^ ~U ,WPPb=61w۩cgq忭9fq#iJV z坤0rJWfaN.,{yXؔ`+*BDWOn!]8Sxe{7%qzנUUJqnxn.q9`VRmŽ>9LY^ťGw<9("p-jAKGuA]D"A0UoR;%?>&vxNC֚NEGN}җauZSB@ ~t*ҩI~\ؾOYx^k͓(\5rpɗwy Hjl_5*%*H+ DceAK3(9A[U#,ࣝ%㦶:LgTv TQ_ : MsY왘,)ע#5e1$EǤ"huBdZWXsAYqy$yXI9h~S"c-e<+6!0J[|; Hk7*C"f]u3s~#Z`v[@/=5j2|놳Bp0>R#n m]s3ݿurЍ&1Su {b?ʞ񬩼޳8ސ-JKNc٩Γ'WRG"PEL\#2$*sD^ti`jZF6:"ӭ&M%$[`FJ S+ݴf]@*fϴQpy'#w=L `Ҡ ,b鱶=b'*RB_Ӣ#+:!5bчՋ뢜()߰=lńW+0|wqEr%bq>6(@EeD>dَ [{N2ҳdigZ^͌%o 2&Y{Ws 2Fn_/ IV*RI;I9PA x6@V|T$Iu 뾝#̲EuLf/BG:k-ABQc>_?\'aI-G+ihSg`4&)l͗dqphY²B`.T>@6>0Q'SCm&Uݘb}bδhN޳!D`VOVzgG`6@tuQfI', A0*Z ڡi;J{q`2I&9b4IybfM ƈmHR  s/1W%ה \rȥPbIL&o,,D*%^eҕIմ5 ˽_gܱº-wnFE#0EtO8GXA2|N~'k,k8?v ]p]`YOit.Ge(;>`<9c<[%V0 Uð bB&U3 >7g8ך53uǃQ\y u&U I H}0穩ruR ^4n?fA2Nr G ~kWT{]aR$ ػ/.!8zLEfΡODDjᵃ1!Mۧ1qu HoҀHwLzTRFr*::2U6[Qt %3rM5Ұi`Otr%@򻗗I2}(UKc}P3ޛ#Pk.@o) G2lXCoft*G:s/1a!a͉9t '3y\ C^eKFad<EIjMe>\Y64B }״eʟ$j0jq$p9;"a^kN(l6aBv0+1#X[%<~.WNB`J0?t _|Uk7\m;:d)P)`(V\OL%+lec8GpC+)u}ym~{("AMǿVtMdئɅ̚V@<34{ (.'2Чѡi[[CF"׼1wI'?)@Z&ߊ,ę- ^q,9= vs\߹˗8[=Rũ<\:8{ 4lDZ'c\ex՝ݯΪ<^کv* *y`OшFP&$uxP {"bgp9C#JN?`얞)-(ɮ j_Wsx˂=r~VO+ts6EsD;iWWJF^@C7lRG}(DA_}7bA۷C̝=*11'嫣,$)3{OsYsz uyu nU2=!"2FƊ#R:)6p݋-j :^:3 s/t-NeaScd(R)E#P(O ]BGp{gk<6|) }~0Gh^VlDx\TóDbgr҃7ZqPߐu}ќhT4b8zfhe!5H7I/"/ê HMf0uP_a F1r\ jn9-?jsBecrDJn3)hzͺEWw80h#3rX BH|xB.6~rrثwԪ^+仇TgFi +uw6"tAWd68r*(}] ا]Մ]a6i~-~1YT6&H%0%ԄJɭ5~2ʌ6m9 `ꇊ3s#My9keH|D`񢝄T)uF~a!] cO(f$)\h-$)OLKwQ?xN(Xß Xmw]Y|,aH~͉*|rJų/I-G v"ka1UwWA8|~JGPj:TGu8X/kxq.!vIZOFžƞQL5f#L/*DCfYw3q쯡Oqo2bj>7FV2Q6* z'sW+OKMQoӐ2No{6!,_kNm:n(ꮉ`Q%er0E e E]Yc"H%??fn TZc_w ^T4{/9̚K Ddi/+hpfѥ-UmpZM)dr徰U)5h\UhX>HK@^Tԥ}+F݈Z!_`lW o&&Ј`d>i}'OA}Wt߳5 zLn( ! > 6dUAOOʦ-op# $B~j<"<U{*xl3>tsX[ Y&| Hr`Aظ0+E)Q ICsL2,TᤘӤqwy];ls 痮|-&yYð݊QtƃӇMBt du{ .(UlޫBI(k3Ԫ2Hl[)k{ Fv_*䆌Ї%+#1f;JK"6 VNw_Ȗbl{vJOtIl. ;7"7{ғ2oX,F3uCz%%&r=SmeN|+BE (F<m-b(&WDtBr`{\;B)*tBK5ߥxk‰K^AmAmk25nȄq ՗䜷4$’L!I8p$EEm{<燑71~qS@ckjJͽU%dXK $/&eG`\xbuDHE7jq:߾z# >]޿C6=~)ĖSx4Z縤FwHǵ *Ri=.@㌴}iLof{_.i,IlRz\/@4X] =GZ^zGRcxԈ0I3uToTBt% wx@_: ;pQLe)z-<8vU+ETgwz_+h!.*l5A gvW"nVBM]Pgo rU.n QwMwxw"ç̻nP,6]BFun'烥SYkꋧDЬ9D^:rGd*_P4u1 ]X %>ǔa6q:=\J2ҏLgMn K~^glKCխ^^33mR"S+\U%Pcq[ܮEM*\"29-1zszxa$[yƣu<|ngSt>-& Ҏv*""uE&]m־^3ϽRuHl/F(lmSFߒ&x,Y*8: (6cKIwP6;&% GkQsbpόd)n aù.$a N+7rRgv yogxbƸa dK0߳X쒲zdˑ'q"-1HK\1uT>JKv(YfɟWZM+g.q}3_*Hӝ3烆pI7z` De VkI:j$/l֧GyǴ} ͇T?]IЧD7hSl䇱]gC@.YȚv qTJ^mrj7Pst_bq\T";ljُ;<8D2cd < pHԘ'S^lo 1=W[i߀D)=96]-y/|yʫQMfƖm?Ĥ\P޻װη1ij"C j#< + b5/eqjĜ'N)}L[=W73=eqӴe-)8\zOG-*X>"kpė Av^FzY5ՇLITiyw g18`ۘ8 ]4ۚ8*Z"+h7]IN]x57ҵ|:V]s4^}H|͂Q$T"?Q,b#M4Ũ[ࢉ吃G plz A|>@;ַ8R_r,[SZ冼q["ZA_j@=}-.؋Xp7wB7U@͠PZjfL?VI4gI:Zijg/=+[(}DY(N Q B"8D}7^8u^MLnrȒR֣JW9~Ժ&⸖ ޿=%EmnLl@s1YLwLɒC7j(A\/XLEG:yqqbEk#+tP&ql%wN:秤wpND-.H|%Ş }*fUUD+z<ʄ/]:_ޣϞu6*rI1#![[5<8l!9ɳ,rOB ț!:][wK S 8yVyƳԥGwt0DiӨd?xXs[)ԛc6\;jL0jT`Õ)Uۑ;?ǃǩ\2틽uSXd+{_r8_Zyx@?ZRǻ9\Ű˨1)Go DI7ʺ)>Fo/.;Y.gAunZd ruJg}5|5e1%ؓgm[+F@G)u&Dt;S pv-bDCÉ ,1e&VL~?Nu9B }\}=̲)Fz!8_&{#]a|4!eA-Ҵmӽqq[d4p,B}U[ 6 Oƣ<6 IU6‘ziz3Ch<=K} ˠIN["7>|e◰ۛ e3wH(=xpSɜԤoM@#}^Պ11;tk' Ҋ=.hwnkG=lܜŋ% /zG+ Rvw;yݜp*Xf76IlvyP1oL)8\fIUb"h"ՙ;0+jZI݋*b:CdD!+;/@"'OWڂ[#[d:"xf5-MUG"[ݸzK&I'cx88uQs@W:lD-8SL>~\)fv /,q@W vQ_Yq1ʕ깥?(!&x *c8vf\C 3UE2٥*8XI̹+܈PW\FXb"̻<>7v%36uIDn~,ٌ ԨŋE +u+oSlUIMB2/\> stream xڴeT\۶5J] ww $x=X=ww*{{k4(>sՀTQ^(nrgf`)X蕁NV&&6x Q{ 裁#hP0r#)@4䀎n@f_@LA@jp+aHYڸ8X @i9 h fV&*@MELY B.dkkc?\DUT$U@u:@)@^;]NLUXUSQgō 8*3GG[FFS'G{S[;\l-O{/a@`9̀)Ys# ;Io5XJpb`!״;_ks#d2:8:9&:!._"6XyxͿmdr0wpt"`bn K˂D/gVWzey\Lfn6xH@Ƣ6`hލ̵%& c;2휀R'lc3:@;Ȍw巙,-en{88N@/:9FAKLlL?#@ARch(ow7Rg`mnJ%ocom`_>sqsWmr4Ͼ0 ޔLjxnw @[#id :8X9r2a> RW?3Wd `a3 ic_`d8SN^{߻`mqE .?(` Qb0JAF?O p?Ab0AA~ߧ n t8&o.fdcX[i^? %Olu`f`ml`$.& "̬&V~8 8LO;6fnf@?"6@0o@Ɩ`]t@f0? ,2d5?XlcYm`%@60;'s_%ٸx]wݿ\egљ:Y8xa@k?gq0w0C3)bp @Ҝ\1ml@py@v!տ=iL.y! 8X5̍/3AWm&loՀϣ""6lsF*u!vn׳|c/b]Fs6Fb_)ʱ>IB-M~I,kM,*De֔P1~e$e-M,&<Π.z_:;Om*96| q2vDy%ڥp u`a=fLEi`aiۮr]3ṫI, Ͱ´FKU1npG`CFI(et&~ 觉eH.3E4}tq8V ɧĎh5\R6f_ˆNH|B+!Z~56<;ȜF9P1fxa!7s:3]HԊ6*KDpANQ#6ŷ(=)͢#_ՂՉ 0P#0"zO,Qݜwf+$TDs۪֚ƭ|J%# 0^IUlZ/1%Y$6VB4 z,A~c p%Gu3G]QpHQIerOM!݈e2*sTGU) Һ-5ۚC'21bzesh6mu4 MfʞH)Ci֌C+pFdm{ǼDψ. 8E^2qB/yب9z 0 S»S]mŤ&x<(7 Qaz˕;3W]  $jgh{?inD(}.כ&挓߁(ho);?xH> w*޶{+q]‡v)\[T8aap:]P=7ϤJ*"Mۻ6F_nR/gKA; -paNFٿ=04c) @S@ņ6-~XV(W8Ipy!KM:j=xo,TJ1\ l}H1bZbћX9hlC i W2`zžAw,ea6*gw_o)d2Vwfy/M{u@$bRb>[ W҆HSi4vU*_LE[ mE6+Idx UI=8<#TĈm,# {Z3=DIgws6LFImZ3FhoБ~^ p~cK(/j[H*bf4?R?%08u鳝:g0H󲣽pŜCXYxLXGucO"be]-8n?*}} Ph}׶+tq\j;}ֲ! P- BNÈJnVkh[tT%JF-\)<zx#r|J{}j/uZۈS}E,G G/0Ƭ~J[Z1EGӨy$U3+ NJc3?aπCo:%{s<!勖O{b"xO9}.G:.S$s<L 08V_1gR ESN]?8Ɗᕋ>|< b>Pt,I;^;Mr#Ot4Ay%'Γ@CJ{a,h#VKMqeOy !w84-K|J`Ft" ^\C"XyqΑ:.6Պ^It~_p^4?m'1X$0\kǽNȽK$ ǜV0{y]"zتdԮǯcFtV^pή^gDDTcMfƒiH y'U4jWbttXe+$L/Pc`8iʑL OFH8pk)}aB34P,T5$RUIJ)`rO8B[j\b_/M@C Gy5ޖ]$lPMx42]Os7LU |^Oi5ҳ 2PܾaB3]-]Z|#8m깅*AϕF\h}f&W(EK-4 {Dzei_T~J? .Aq(sq['[3XzJQ:?ß9ڰ;A]63sA~ZMxKl;u| ,[i#dvOB*/8 -+SjRhBԊ6Jj.[Qyր1<'+? R= 0kǟT:yoBgFA& Op7FfaW6nZO479[? Q]Jr)RftsKm/PQϼ|=yw̉fW $v"|νWuKVZ9mri\[zU.wݑ%v;ܾTعgqU+va*nvFb~*×X(,*P79ԿEvQ.gAz.36S."tPƥR3z@A8!e>,Z>'2YȚocfϕ= 蕰'z=F6GF"NـKJUAH'X=J 5ff8E8eNjUbZs>x֟%wȦ+PpXK$Hз]Oy{}tׂP!^]|^:mN`gH;?W'XlXQeB<7I?vٺCIûP}{^ȡ]DO* iι&Y]*&vYp4eψs3~Uy8aQ_ivDćd]uͽS}X|Cz,G%FV` u W] 7'`MۥAwFPo Jx5H SiE,ةKY op]*M6?R#ZJZf{=jBfSA]DȚX B9gsY8dlDV9u5Bύ\S53q׌9r5>Kh"]LMI%>u.tESWKSw!T>c3Ǯ SZ61; 3Hc7n8C99opk.5K-i|I;TF˖׫^䍉yD,Q#➵[ϞA'!M~H!Y;l(}L쯷m[,cZHJ; ?tAH}M?bM(}r? &]qe|58HNB>#Ǘ s;1ҏ-1"QlqNJ%I#c phm_,Av˦0Z3 kӵG4y6[7hWvY&'|tkoSY(;Pbi pA׷#+\1b""}u&C޻8R(Q,k:!Zz_KP[ I`4f'jtD@7tº!%kr-I?c@V3YQfuAUaOG# =U+TԺ8JFFCj.)AFʍDqU8NT^?ekLNI-ץxPP[c`"GQtTU]b0\([蘢 n 4ɒ3vGb ̈>]ܟW{+:`,…u@Yy bN^OA]<ڋ/},/az #ă2yK&vi pG#Lp8c5u|f:FꞂR8S/I0Ì+ c4l3^*ܙUj Zk'1pZZjەw&藬ɽ>ZmGJ]Jxl{,ۨkH+ƛ6Vs_)G\~&-T+k$&=' r1{e#4-x}= T.2 Wf"V Z Jt'. oW4&7ݞw5vYV.넲ZG%eLkp ߵ6}n3_S2(% OJZ5lg^}=^1h-`+7LY~w]ݴSp=}BntPgɥַ+n^c jh[&NǼŝ{ё&4B T@U;kR#dx7@$£LJW22fC8Pp>2+=UTQ]q?G-EEM;-ʪ-K<R?f 8OJ,s^ אU)f%Qd6oK2or\EE^L:&'>ixF9I0HQ[N=(d5,L^gmH+_Kp#Co뽇sHx\Q\/:A)MK&-nWDM7C!)(znŭEF=,a.ߙvF_ G\y\GPN_Q3y>1Rhy|0g4 3)f$C {Ctn|jonR'3.@m86[W&'.Zǭ gTQwk7Ue8#!K4.L8{J1OiUAkmgz^,}+w-3=r7bs(01Ņ î+vkz8}d&|ʺbˣYG:'mZ|jb}QTxMpNrC I|c;1]o/;6toi9%;D i$ZSG-7/(#|#(o]㕭|T!>(՛ b:krs1PCFMw DCC-?7R֮~N|L|? c?(R(k8 YhXOWCJU,'í>TGsV&rU Qө~\PEObwX[tjSޗ*q;quj._7v^f<:-7U0USQS>nWʁsFFrf;j6?XYirQ87U/De ΁6ꙿ"+taGB B b̒Y&&ȱN#( %ق92uwJ}^AP* [3v$>Ҷmc,MaX"}(7h$H̬%\My jU~uYW>4%RYOkF;Nw\'73PBz \RqG:p3,b~O 2п_^LHv)BX*EGJv!GZ'ϗk9{(>+.VpGWL)z78ń&TL Roɚo͵jbytÃIvvFZˉLH[ <$:?m]x(rJL1+ͦɀV n!f[;RDq]m|L:ܪy$)@[^Ыk3R Â]R* G#N8[#l#TL<|ijikѮcjUCr#M&L'O~C67 >g>ndYk? R?֚uhoa<32L$@$uIȃϮ!@Ka֬w!M΍ *УР7 kQ ,ouS;z?oD>.rK5 7N {}Qs!=:-zQXP:40EU3%te8P1!M5ߧ\уب}Vzg|sĈ( EIS Rz/!;i6!OJoϸ!l\.aCQRX+҅PJ=$+ ;ykmv x ß@c\oQܩ9_ކNYXR&r#UotqR~~j?VѬ =i=1 '^1'):0(DiՔpl!n-mg,2G0YEv?ESxT"9ULݱh>[PlBnx_jǗ/|4jZb+[oaR>TCT6nZATa$2֑lcIm1XVͧA&fxjw\9K\`HB f>C!Ravr{e:2d~oHPO$]E#i)q`,I}BU뱧fg&z*o: {I_(U}mO9 K1U6Ǿi{:ڦ)Q/s(~CNi//5u=&]FSj/Iwl1x̘-b]WqbK%ב cx Y#];tIBZ;7c>cQw(j1R;XjާU\;X),N tv^{vu[#Q=zoVf̗Gzyׂ_;d>dL k[V˂Φ}=JT0LNR^"JmA4L% 1)0璡f9>34_2HJs0[ U^|K6{{mֱY8&y@%AAe6]G-Y@++SR=vt҈0?kxnCh)_AoO#5֒wEEgO%SHdɔQrV+GHvuΩ 3~b8[}ey+||>.?e*&M5Hck?=^sh ^B&P-8^O ~_;Oa$7bCe C̛gtd_hIdYf/@ѭo 5: :Cetpi")Y$Y8C\mK?Dz9jbxM|j)J'zρf*u R>?JmCr6̗=xg96. Ebⶹ*1ЯLSv^4=t' \ma,4 k;-en^2=YRB bz ֊r'$L)T6J4|ץ7L4 }/Ƽ0D(i/ۈHJrEc/ӯ(;<7@nPL =!߉hx|HVh5c+<-j2=YȊ+̭//5e #Qj{mSl JL0[U^"r&ݒ3H(rgfU-z'ۡ8A֦iS ƾйrn]VUZ:߇ݧ<PMGD#wPIFD=P,#"nr\<% [nPQNja"d?,7rTèI8YOkj*x&CAEjT %l Εaص.R؄᫷.Caقi[ާPatJݡwdMgr[Ӡun/.^Lq =t'x[3: tYz:b(WUnmKCT3D!F5 z) ;W.״BgS%=)6?u C'/4:%zf^=2W4uhg kٮ \Q{ܐ{^icJ6 __4mAx[SR8{>CX }=#ܕn-&jYc;wfG(%V4j2ȩ .yAh[څ8$<A cUbDDN\Zc /eQYjP;+=#eã 0`rջUJH^D+[ed?NTT)ǝ=s&!c~u== "*A\=񠊼m*v- !}I2"7֝ڊ`Ȝ-??|[s[*ط8mkt!Qc7'*tS&OZrb ~b] G_O|eб,ZuW%,U^$Ԕ7ZnʕUI`J\V֢K &zgV(9Y ( :5F2wotmxTn,-pXu,Kc ?"56`EDYmXO2-e lq`1`+sԑ~?)~#&gc~~uZ晭"*xNN9a'eH؛j vB#8_7JI>k"f>@yh$o.G̱NaXE*gzK]z_}'<{CoAw,yl? I23~M*-o_쨼Ab$xbvˮ MD|q z'hg'﵉n$Mоهxdn4| z_]yxOPW+43$f{.tk]`Me'X,/ }?$6DO4r#Z5eb MaUi1X%1 1Am9VN{%/$ + ]5Ǫ5X9dCZP֒E'5:YX=ߴm7HuODrdH鹕!Ti,aB_C4[f6^RwE5Qm1SXLdTzzj\aYRyw\nv)")Ƀ w$ Jǹ;Z ] ̣ e n>PXt;(-u|-p qa^yvgtj휟Ĵ|f҅Z 3- G O`o\] bߵ{d2' IpS 2F,*/]TR}5(S5[ϛ[UxictXUʞl~ /Jxpچ] M:fvK'GFR{qNT.`%=wp-n4o{L2ihU&)XZ(ʽ㕣sR ))BA>V@~PY>Q"6YHiAq|hyqbS -yaH 2OϨO iEicoMѠuיּhe?KT3[R;A" ҷie^DCS\}w~W0 ƌ8p9lH6ڊ~֚xv?:LjWM-4. 'z\i$B h_y/x:fA~f};Q +, ^1 8J8Z\LDvnTYE/WSNir}HKVV}Buji4Xb9\ђ`dl%B,|qdFmdlmk ޶x$XJPmѐ0Af: ˍX֤- OB89 T6D20 cxvA EUCC{:v!OQх@%ho5D@$dj%B]/)] ,٭zm%2U7RM-˞&:/lZQӎw 7x^@-5h g8jd۞ 4%R~cLv7lUU&]31gyw cmaN\Sd3>|%t2hM:b@}i3T*yALߍR<2a@@C iȗ o6:&Z'GHp'i'[<1B +&D&[}VΫe:lm6Kl4䬂Q4\햃(OŚrg:Y6>`W []&OW'"i6S$ZkP彵5PQ`'r̋N4ULg^Va8۝~eϻ*.E}Q/5;Qoc3[*p|tpD̛:ASM]Oݑ9!n |㕲ʖ Cড়W:[uߦk˒t.A$|Ԋ2q2A4?ΆE4=VR=[~lߚvW6ҬWDf8DϩhC-g\~4gEbZ/6?5|p XE j\s|kŝƗH`9yyC[g/ˡij~46ZuöNA($ \۱m۶m۶m۶m۶m7EJUŹu';dRѯg1,D1\z/. I0+7^EUU20>e!ɍPRCdAiVieS?7./: yq?M$j xBܭt^gJAeo{iiY1-_lNC!B鵞Xx=Pt8#;E#0 v<ŝo"}u4 DzT KzDċx8fjM()L~ʢ9n0tY@å]0sӱ]Bl!mQlf{f?QSl# DϤDz"F:6 KҔ@;AuJr4oYBvo ߾2C bsPSo NUo /ӂ8j%aqnD U//寁$C7y$g0qc9 CjDMuF$vbcv{ۢ5 OZOȼ KqSg38+/QDkP3~xQ$I2Bhne5^Yٓ)XM_mnL/rGce;cl&}ޣyBQA^?4pm=AOFGiDʮaPÐvrU]04k*5JM2Y P׳r -b{G&"5l@)Gg7$D*쑮 ck=?kCM&ؘ־ @ foVivߜy K#OT%ZSY"Dn#k, Dڢ9X>ˣ)u}V䏁q60mH?Wb&uPaz-n4<#Qf5ڀ.wӣy*@tUH& 5,ٕ,ro>80ܩIsbKR  %6Q0HۨK\a6+dOH*!Ŀڢ}WWk9q,3Q9E~_{%pVc|c_C= $ .d8hfO+b;,yQ 65"qTCԫG` P*jI5?Mz,i ʓKA `O u-8ǞKKxDN 4Pb^CUQi,yJ.0_{Q.Sh˶j>[@_Yoy&MdAot:c,MxK*rj[<}{uGkykIbW䠌޺p䉷x&upВRRMr@$O,W.]kB7v s¼ d&Κ^g7q,2cͿ|oXo1jpwуuB*78aKfzw]صg:pt׹gzN}ix+(Z{[:Pn\ҴTbi?%--jU5g.1;HֲxցZpQ+'rה$'YbBmU4A5p '=o"<י#XϔZl8F,@ݱvO f.7MMyR]Şv 9葔9d8(.xatce`Ǖ{(|n,Y{OS@/H~ G Wϙt1a+Oηpi?mkcɨ/j>X!cV,cg=>:SG>/*u:IOjMoM4@Vۂ^elaܚFVsxk?e8 ;ƭQ89ڤr9L0 ~SbtT^U_ w!*@]Nx0&{&XG" "y2vhRK@[Ho-fhoo%b@%y/Nc-KiN؃6j.fDlQH,kmBW)8 YK-8hr4=X,5j aa߄UAiaԀ~JA%9-'|m×lY 4AN#y3`Y~j&Ĝ3߈.(SVA,I= E+Ex*Z S̾; )*^\՗ j|ny}i*9&OhTׄHHobNoƟa|_IIM@.ҫ~ XRnzy3 #5E|k`1*NoéuiFy` q/Uz$e}}s2S=pmf\qAxWݦFӞfgR{Y$7Nf4xS5'& *N񼆀29lc]G ﲑqcHԪLNb:HwkC5>"ݒ"‰2S^#sv޽6T{SX*N ?ʱjC\2ē2,<^[Cɲv"8!Jqsn:OnBhKO|=^o/ћΗ nh\SsMz~P:Njcof+W D=buF=(}S|@Ukx|;'%Lgܚth 걋3KV a/}eWވTIv Eԃ۠ɭLː~iK(45w椹`(D*IUpe^__ _F#y[Lp]['Ul;^'*1qkj=6mtr]@lW18^a()K77*T.sDU$4nz/|&w޳zi:};5rfm&ٲ4Z*l+FfY-l~~Vw &xs )VNb;E#Xlud ̣Zkz #mTkm NseC\TE6uߪQavCUo"337bP')L%K.Neg'LY#+,K}Eu7T H "w|҈'"-0tve]iPfQdf@ gk[t8C$pC%&c51=U;Rr8&B5z'{H#D\Rjv8Kƨ;鰔IXsmоOa1'm83Mx4Dw%? ?>sif"]SkV̝Esٗe TId.?6t0fЅuA,)fɣ*̋8nT "yP/#ɖɥ"ѻNHar{_z(y\۸|uA;u ˥F|Wm[rOMA v SF?ҸVAHHg1UXC|)Z"I4ivYHmLkWV B ]U,91%gt “~ҩo΍bPB`U*K tՎϩBN (™8=4h`ݦ늝ʤ$uNru$WaH"i%>2b 0Y~qQ_&ҜN2-2Op}(cM~D m "K zus:iQUkKP(>@vÞa? J^pb)Dwi*4I%Hmh v_ZB\.ƛbs[%중掽[y2& i3I"PUq79I닪I84&n~/_#glrpDa#Fwfzx*usk0k<#op3Qt/h4Q1ŚE&"A0\ te>Zw0R7~{0Lj2vŒuܹ  {3Q/* V ݻ W0ܹhKm/.70&Ѳ!ZkD!sbf՝xN8#pTӏ۔TsEftx @R&kryTZ-k 9) ]mViXz݀X8~2&4)cY/*_;ڂj m>66@iJ[/q:ܧN (e*pꍢpOY[QTp8Z$j%?Ӎא߮? 2'Z P9FL/0uv00s+{॓E.ـ YFu_FCU}uѨX0M9V$W \f 6m:* tP$; kOh캌g߸Z?) Ys7" vq {_S״5J1L' +:wRڻ΃H,?{خNnЮG}PZzb*V6GX`xBڇz X`U3$^ qd0+IÊg:yAOamz,225 ܄dܯ}[XÅOv[;..!Y_JF,lwjԖ_gY%N; bX=m#z[X8CLf vqV{duʸ,>&XFgHCg: Al}A8iaJ۽CO],=iרPй7ھGF<$.ŖomՁYUYECI]%Bo(RXC4]K98adCehm so3q;n !E?Ĥk,w-A[ETbcmx`H3Ts83A9g֘V |CI.9U_iDo\4%^_dqb`]||:FYt!Kq\!-szaLJ6fN-U1%ᷩaP1-tp}d 55:Ti ͠0:1 2v5Oi%{5Bwў2ƍHH~z-I2MܒA6q31nI }ol'.Eى7E_H_-{DIGt[ L7?E9cy~ 8wleUo *Lz(Tz`.p"A#wRS-ܵeNT0@pJ34W˽ ցnkU fL|OOVH_Qi6rlN$Ẑ$ xsRI&nr|9WjuZ(c( y 00 |8L=Jsq`Q&aƤi~7M"x?KGWJߦIfK,s"'(N}Jqy TԔ]}0+ѥD/aSU6i\l z?vcQɵ3!mKN| &h Q f]$g C{_Dqkz{ЇVdN>4;/Wֈl0_DAYj! ,4IbqxtEln-_O<Ȟ`Bx*mT.B@ҚJFpsA@dRp@Vr\C\Eou-m]3h^3W.ƟuZk< TEhOuZz_ҒHJ+̒Ͱ +-u>Ŕ,@T[;yЏTMKy0 Ǒ ȑ7]ҷnl: qR ;^Z2p l𳽟l*Ѵ+ѡMjUfal;gyYktpx)-G2qf4 qqe_5$Y,+ 9ۀr,cDC퐶u^S:_I FaHDXX[`VxصmTI<Oq?΃Tswe_* -5'$})VB~ hȱ*f71¥1n2pL}h Xx*8Lsr &Q'/=wЪ&}pt<j=@h3;N=we\f'w" ?]R[7o8+0Lȑ p- x$: Ũ6Ԥ/e7ذ)I:vpg%۸[`h| Z+g,urt(q͞Jgo^40x,E0JO5a[Y`FPGRv[R'5Ͳ4}6ne/϶Fcg^%&[4-h&Q;aDE C[ūUfPEeU)^:#拵Ғf5돔vjz,rmYxb4ulU'-j$lx̨!|0{"@71?`+:Ae. 265k\E&{Nn# |l $maq|)zP zSK;Uld[ePQNRa f&PxfXkfN?ɚ$Tr*DpE9f#WtDނR,C' ["mAΰm93ZҜ߫~-M[̋FcgB){BHҺB\`gƗ_D\9_-+2ߢ%8rxc~p+KhckΤx'/OxL+)5_K'2}|\HwcY҂p7axj$&tywY6VFbu"S©_05z%2Hʉ.kecΞ_@on#Tuipfi ?$Q(i֛V {>U@LSZw_.][-rmpa:JC)4FkԵ=KV l1y/0|@pn͠&/^ MYe^-GD܄-M%LP 2:m؋)#X~ CgLh2cb4`6sԈqDGࢉg6$lB7E*cgֺX&p4 *_RK-%VW(yT9m>2pzel _YgM^5 ug&c;ic*Yeڂ7VwU`^ X+dGnD'5[SmXBhol}``-C@C~J;tir1gɁs[T`'&{pTR3cD*~[KҒfrHl cZ?'рB(a|?z[go Y݆a8-U9<)vduO)>(,$W.G-ge&^uY[XD0+ΊA%xt;2=J*؂F&M%LA6?CEH{dKob\vQ3}S![@IBR2@3(:&-Ͷ1M>"sƧ|^ Ses(?K-s٭J `'gׅo;9FG\BWZ0[R~G PLkwRJx8οRҴ Un-PGŜ1i%’i0mÛ*!㛼E &C51xgvgE͘)1bK$/.q '0컷!)a*8u"[Hiם4O-G_sw^AeR8otdI7ۑfGdܼ CZ|{^̩m_?֥;Q|ġŒ=,0 Yn,M7~tm@V+>yxopXM]DCCP.O!sNB@< umeQt5X(qz[{bΆ#AX8%10ܔ_N ;xTPµb)Yu&>[ݱ7l!捯ڞ&˜Yl|!h}ϊ3 叫M봹$5c7A81^cD1<''05FmѸhgo06ɰˢoXҨ-SǙ"'׉p-P k>M*tczfk5< A.r]tM >qv?o*_A{}OG{%nכa6>rL$ny.7ۮ?fsU4PZjRnDE f4£ݫ[QCuZ̨Fr!V**poىM6SvGAۻurKU%t< ^ >U{nəѢM<\ۤ({?pg3P`؄%*%&KHiC'/ڟ"Kɟ)m%g)gR5W#lvR0] K2T`6*{NZg"grrF-*E=B vg`XL\O bϷW13ʻvoRډ}_FF\gh"Z8:x'o)1oſF"~ #va؇3zO&:AR9iQ T_Ct6qV Q4]ij(r|zƷB{ϱ61k<@a\*@kN#(EjkS8Ä %Uԋ :qp Z֬\;1T!J@m7|X_v,ROÐWS&R6b6Y<0孓WnvƣP48(TZ(5?+q'3p1fƕ*UG^G$r*!"p y؅:R/kD8Sa¿ޏffev4&^<02AlaQK%cEڦ;ggH:gD5앑V^>U߄ %A3Ǎ-e8l=Â4۰z*=T)=VZ0 dMůH.:T.zU:yQ:n@UL sF NV]5!y?p.Yh*Mx%kt;q2;EBcHA {}uGHdI:A1y)@YHSbnddDR(/W.#iN}$g: vOwu3M c0mĚ:5qcAZ`ě|m0'J2r6"$J@ Q1/0A@R3aCc T>ݎq#!z'AuNɳnp B-SJcEףVtlA7 KJ gN䭸 .P2߷#1즹,!E>skPy.J>fWeϗ3ҊH\!<@ oD.0 TU Η(,wǝKrm7L*7nHOP,*-nY$iO齗:ՏA.O ߟYWs~NV:|ʨ1a: >0$GߏG?Yк>,5Y_ Nj8"qf45aiJAlĻyc9iE}pQ,N)'ߌ1j,s!KX.wFx7,_ O{q[ˣ3A04FUE<~uH<~:YKjg(-@[}xK%EL/s! UB?`孠?g>O# јԐ9YT(z֫5 v;oXc7'9|)$eJr,M_¨Ǣ ~Ժf@G $Km{]>p-#q;qWN717Eʴ=E\PWR 鋇^VEK}|JypT)AkKC8Ã|-lř 1jQWLy1߆6 '\;Sbf1lVlXЃFd0%b#Öd"T5";ٸ+wA> 4NQaB'C8} $ 88N<2ěΒE/;2){1 `n8]A6?8WEݍ[ll>$ ŲCm4K`jňq]dGM #۫G+A)]zC3( 2Cv9 A !uEf&ʗ":dC.<`\L$U+/נAcJC^<+)pp } KJ8#>Ktn((u;;=_Ew)e)5o> <ijdLy ll0>ŜOi3ovoqDIR3_=w 6pWO$v*nğ/w@=мk֭[VE`GszWkvae2"$Št4R ʡ~"ʗcCr|h?+DV'ՏYU 塢Ա UΏ`9SAC"Sp(W0[0™Ƈ\F48GA:b(7]w`͏=!VLIbZ (D-:S\&-WX(K}lIp¾7{8`& b/s=ΠB\7P\~_+)ݮbW2-|g&7hk!Vd|Mfh'p?Dbb K>Q7A= ԍk1;eQoo&jDggo:4|n@yx*~"N_96&:mT1*xAMkv tDS٤*A \KٸޫXs1_ $ UJM$Q(Z ӐSUo٪Q3ޯ'8ֿs|=͋dK_056oyj ҁ1,<G h.b.7vľ9{+I]jx{RN%Ip@oE~5kKx0I~\M'?7 P*΄xqVNPs2y4OXg^B`BHc endstream endobj 1671 0 obj << /Length1 2885 /Length2 25221 /Length3 0 /Length 26859 /Filter /FlateDecode >> stream xڴzeX5H#!fݝ!MwJ Hwwtt ݝJnϹ98ƬkȕTEL I3#  5c28XX@v**1G%N r5q:X@*1= g#5{ /vrf46rAvv ZHW6F_~E2dLnN֖#;S,@!-4`;1 6*)JLĪ.`hSUSb(I@ )uU_ j:_ j"jJ,̿`,6j2oiP3G_4nnnL.NL`Gs&{YX:OG ƸؙBl;M[@$m!Ax 4WNN ?X9+$5sٙ@]q 7 \Pe+] Yۿws&`;'K'g3f6_~홥_qKHgnv^735yS{fu;K!o @rM/ i=`fd4A~x9Ύ. ? D(,\SKgȠC _ei34TZ)5xLAf(̊`g@9g%bchd wKgdkiOh~Q;f$i2Ut63Yؙl[+B _W/;l499؁@vK8d~0+iH*Mljig`9:y!b̶)03ف!!{g׮rrE~Q#No`Y `XR+Y7b0FzoAS T~#H߈AV? Inj rAnNDfG$C, FƎF&?gXAaHL8 bL61/ @HIo ښ9YA" 3?af[/G^6H3.4  1B.igy@Hgl! DY = vfH5džvAf{Ȟ~Cvp02̰s3;oӿxH1 eG0'?'d[2D?Rw_ p}n~m97,,ո!b'HR? @HJ !m)VPጱKRLlIa}K܌ "}yj-[qBVi˶K2$&rO_1_*\w^DLb46ܸV4}E>W$\#e=Xh`3@b-uKz!:Cn+yHX[MW!*u&5G󲋋]zJ yuEN)ľQ=S[m. w^B>~e i=+aplzv<:XF|ng/3Ƴ` 2kwC;IL<[g6gUq.qw^@IxƜ#N̽1rX_ERt."R=x _9G@r5jF$'S" ˬ:lTUxrRo_WiPl6L3xv;g(}p²YmP_Ax/eխ@oօyȦC4|8~^|5 ?8VVs4/EtZ9˹0&L5W !a&褂2z7Q#ՇPׅv˜@(M %*IqGJ})VljKad#]GKfM^ ޕH 3Q@G{ڦ;Z !XfFؕPy^iE\;9;g_OTB,oa/:(Q\=AoqfM?=%٤2iEBt~RZ`T6/{.a5%sD::wf<0TGiVc=?vyQԵ/EmJgg|sr'3c:cg\򑖠&PR). M[S4,\;(Z O:$`FttHD55I-}ß"A4abc&=UXի'{a1?3V axsS@\x_Es.[\Ji>9RU`j\ gRD v*9 , x^kȨ*O~ m.[VSpyB2;D&B۰DnR|K6 SnU"<`,փDF?g$-IqG57A,tʤQ;#WSɤ TܴE*O-ۖ?6h*Έ\6,$Ai}+hqfo$yD/n;Oo?Gx}= _7Xsu>ntiڤ̸ZC8ijOM.kݎlVBm30{~r&lC 5MJD=\Fk)݈i?gYJP0vKvSgKI_c=/ȗp?L'qb)+`O˜ˋYzк lr<_GQ:8Nۮ2 f df*x#SiWa#d ex}#PCIY};晧!3,u~[4ƟmIETL^MD#3E ~Ŋ22]?{%P|Kc6_EäR;9 5_я8]=I9|^SA$Pwtᧆ񺉳^M脣7ynIĎ6AX}OA2_xkShצLcJ 2ߟC]7;:P89g{n>OQ@p܅➌ YJ-JZ|37a@HXyH{x .ij-]R<44Yq.xU"V^BLAZۡ2}1P #y[{VFsNwz#';aznS&g(r)yxVw5! e)s~]>x'iLmݙtƄǐz)nP(ݚ$Л, ȗ#\"2UY^\\e&-|=#X2B*X8`AG>N 4vo`DKeˍZ>V{wVQO /σ=(PDUpH AzG;nI'.b0y. Ӂhp{YM/fYVkJ:^8hYK^&^tJsNOUWE)bƒ'9Ct-ͨMj́ w4ZJ~5 *`ڄlrS]{ ϶Y1A9:'{BϨ xYّfsKYpl&Al&QӉPl{lJk SG"aߨ(V k+TXsEaƺPJ&tA ) Li_圃mgv5999Oڡ9S WNozxh `ؖRa@ՄdG!ZBдF51XN&!PUCbm =b@%ys>uS322o}չ#Iخq\|y-DjYwd&WdX1ť ak?WUym_JNs1JUBdh6d|=ok4a=nu$ICYz(EGNs*^ETW4֥Ԥ?})Mܞmz5M~t RVKSF;0=Dc{ -#*"ʢl٧y/m`)xeVxK[rlOxSMmcw2YV`z4v-5#2[%/]RG:3eӪ[ lR nfU!]{?l9R8,QT&%trڌ˸lJ)v8ɇQwٛNojnr)Ʒxy?jx0:/B@^ |Ksnk*P"s,a% {c0bIVZGẄ́OLše+Po&V 2MrE7BM9%2,PٞWg\>g=堦/JA3 k9.@="ހ!H ==kP ŖPuꬎ29R^pul,D()m')y[WLϦр 9& LmŘȘzmMy @jٰx`msM-'I}ڙ!Ԧݲ,4.v`k\Ŵ?R#gݜ K/+GMy"]C8pq3𑕘UÇno5] Ts 4k~d&/8j US;dI1Qڴxbdm $״vǬ]fTɒ _T1#i#P>!VZ6i{)uK"Gt-*r6ϖQVU/ 9V9UtZ87UO#zu}OP;9B ܨ%aϷ.%8nsMMڨk0yǾ\@}A=;j<#)6 e̒ըʲV$;|*EU4\@ro~12_IG3=⬴떹9/׷o6õ^ptzwh߿H~H4$F ;)֤8cvveF}m$UØ :3ώr{uƬLfNr{xqb?x`Nkj +!e̋닚NyFfuaGxSIVʲ:\ dR*9ۛꅲdS})仈ub@yASN9.vú9FZ0KV2٪18Χ́}(_v5ca7BEcfOuh#2|_cVl6\U*̺S7[K e\֍TfqfcK6~'MHw9AMnLQ9KjJ{Ovl#ZzKܗlA؃t֙.~Ft2I\e~i#^3:P5B52Y*> 1wbhKa6n7(?Ϗi0QZ#$.v- h_(6͝Z{i>V^|U0pNCEZ!9X9yr9׉ hſ$~F9w?Lc:QzhY +&2aUy-:ڴBSgu +y (RwN "F4Ĭ/6&)6.+ 8N(<w?}F s)?gg>fDޗoe_&ru=[~$cJIZirKR%OGUSA-KD"e{Ā7=pwBĹ { ™9yqwP% &*w{8hZц)]ɻ[]l}o[NNֿQi(`E曙|3q.z7U@6I[IYj9c]^qQ酩Z~uuV:U{y#w/vNRÅi:DGoZȨU|/ T]^ⴌsH) ![ £$k‡|/:ۋ̯ηtK]/iKj&D3ں!:cQ5>(P-N_6lZR;#u4P[Qww#LQ,qAޒT:*vX #;GO3Ly^[\@ϳpaJNbQzo)?JdOxK<ܶoZa/z2V> 0;mC>4r/G&hF_w,A3qs J4?|p~1* 93*rCIn(I%^N9LOOJbD+Z'F5W/ƊX&r(^%+U<2yd"JKIԤO ,ckI4N)E-E>(t)o}UG5C FEA7ٽObK?T0r[]MId1: COa46uy[931|:65mϦCp\YΌFL^ՕSn;s` u]V6 r הcqqRԯ{64n"څ^NՌVnJRAP&we˝Ѩ(6wQJow]/ʂ㛃uzᤚ}a UldU&cKnA 8 OdS?,@\jʊHppW]K UnXF܎/]F|$A.8v=3|6p5]GFʆǡwׂs5`ܒ7ڼ&(}Znǥ1c{?UgFOz0vJ@;䋆( dю MluBjfgCz)]al0?XR ͔OW9ŝ=1ŏlS=\50Ш=qt1ID nգc'zgA*|Y?u7۳Pܓ̵̪Gg6y_/'^­$xS[pTPM"9 -W&}@ѣT^Dȕ9=nz#=eRƷ,P'm"2o)(?(X)5JZ@h/35( ."IR?;~x*0FҊEMwifޥa o9خ]BJ;ZH+# 2N9ً/>>]GaΗ7w~'☘ݤ?VLgȗ&=$(pMk]-^ e2%Rtq/UQ S(sbt7.!db E~4|Pw4eUĠ}O\d4n :TzK.14 $^9&~pZWJR1kd }N!O U(UAwE>jvy j!H;QLKnl .X!Kd%IEjj *J\eS4x/ظr͌ŐC+z|q,9uDOsMz@u-ݬU<-J#̎z#&߭FN hNe;s$|vт(.و幅onJ̓ZJm(&{|FXqљLbQ;OL_W6g]v1g4śi:bEGo V/]٫g[HabM׸'^4z!v(D.MK0L{鈛 gsCX+3ٹ虁όmdc^b%jsњ5C!ފѹ*Z7WR_Ъs254ʭ >F)e5~fH5I_]qkm Ʊ&@vв-]g%}@*6 ă H\vt- /b?놸 ӱu%Omҹ :8cy=Wu$]S(ΛPCuoj\_yQi]Z~l#Dѵ&~YqǼMJJvmOȮ,x[Xk˸Xo(R} q[3b_G,f;%3ϔ!vA=VGo0j:lQd  bP^wBiiY{"X`#9-M~~%a3|!<9G{ !>K)c -D)&w#9]I, f}87zV:Olieg@@5ceY,RcH`Ќ#FzJVjm4X6<aIw?Rz EOXZK}+&kaRYԢŃIn Cz( C8-RM@fY:diF^F%ᜊo(A2\Xs*Enp>DRr9:)"1@uq`¾,i\{og'3To6,FTzew,n-.w WxdB^4/7> :bYشAZeVIX7>w`/sS d)\!߶h0>ض*{ n¢?` \DL\$%Z%HP&:wFw[7})zD>*D XdoC0>x+aX{S4v$n+%ֵo2V5|Hb6/v|y5Hvaː359A0%5@Vռ:| C˯ˆۘV?poй{=J1Y|-z( "E*_2ͣWB}-YHan2#OBH\Mæjx/_VKtBY*-X۲PE.aF`y`|N6z x2]suN q]5) Э  ] ˝yixE)q&.<1ռx>\Dw{eCfe1>9tGPPQj\T]5QÄqdݚ*U[^s%֑H~= 5~2/]Q>v9(VAzno#폗k20T27izGvXDfBLF+ I?+wz,kHބ/ы zm&82qZWc-ńKIKl%qV>KZ*_N]6mM' U]=dS 焒@(%P&'wh[% LuHHE2~„ :Ƚ"X}Ÿ k򫺹HX4YS:q\l!ߌPqg Uo5v:T޺SAq+sDuB.)q}X[-F5 /#~yG'7%+qQ:e"≃8ޜƇ㌼E鵝G۫z~Yׁ$"N|._HIlRHőDŃTcfZd|~6N{n&5z3tQR 3zrl[ޠ+ț\^7c>WiGz}z?Oxq;8AKNrq*TV UʹH l l[S+-/ooF >.>؜V'~o7G.^Y 1|^2*Jo~q6il:Ɗ}yQU+xZ#┋0zC@ޙHkSTx%d%{i#|I8[$ KоxFHWOl3e' lI, +*,2]4E O_)Tq#ILpf]v|5.恉^ڨF |ro&^E綅0TDVrA'o~Gг,tLY@(R=i$\ a WDGkBCԵlѫ0k%98~H7}mNp'Hl4ZQ9_' Vx4v'Lhfu|(X$#KFƘ&/hǍٻS5I? SN3OiXǽ;WTstBrxVMP ' QI9]NqԶ0gny[963~$Z[)ʢCd|o}jq~QtV!! AKo,+ ʫdG'ڀhYtFK٩%\B>\`EZLz "ې{kMr-a_?;4YO %"}$I)ӢMBh9vmXOz"|nLW9-m YRlA<&c8BE{N˕;[_xHDA~`(ʅgq=S5: Hpk* qrYҡPI6*^r5 >V\V ;{D 발:E&Y[`Ľ pS@yh@X钻=k 6WegCo] m=UaᾨbODBo)J8{*T0^W݈~FzUƢRS5k%К VK/ߐԋ<)3hAi)79ȍh2uWb_E[@zYda_ٱf~K\>cr]jIU—Í$}.uvE=9NV捴a"D_7&ͨ};L+@as͜0eN66>!=j$X5VmY6,'MױEG鯷ɾAw%'ˢ߅6nI`dWx֘sDO!IW*2ڶ+Qy?hi ؈_9jEX-KO&o|*Dr`]L&dˋ)HLw#E{U6^DTPXۚsNҒ;T Oj~ ~{)##gZEF[jZ?lO+fDCb;pO?dN6M-vKhiW[xTOO *؜uv?V*MPCaڪ6SBՏ>T;o6ƽۼdOg}-'R3Oh,¾1y:[vF?ymmSs L.SQQw7}1BC '6/B눛( "JDkC|Yے6c?֋SBڦh  Ncq_8Pېn@ֺvGbh{mfp.w3dͶ1E,}Uᔂ)REdD#rlĤU%P(4 }/0!>{I6J8\tbi>c\iYI1ϲ|md^ioD"i}$>4-賷Tw & ـt@laՅ}) L OKiRxq Im9P yq|ura:S^pwIG%nAK~ ZHʏG.{( J|^کZWpo!kJR9t7BOf8>J4F3-I2#jGXRic x^ᴉxTe'VznCrk&#~]ˬ A# :EYCtUDSU,A#QB% |h1T5(蕻fƂgR]8vWJ7kq€ BdDG)S9è+Mzipgo7Ud)gt,oZP问 SS{aCnT`b+UѮ\Е̠!L棼&(ɘeCXhҐ_?ѐ˂NXC bIB!9.|}DU9p0XrX/И<;xYPV`4 7ŗYsc\'&.3s}X,/*؎_үPC=c׶ 0)H3V_vgkɣ~ZߵZ[ *'5k%?vSx:Q(,FB ?%E7tGΨ]YzI3\ 8b3˛&v#Ow_-;ht l1u@U+`F6еdȀ"3{.R*TEcNjI1",QlמsZJ99VX7Q?C2U>'(ꄁS1USùob65CMR ios"2YDB.L<qw_N. /\[H1NԚYlνH .RT nܸ-,5פ=·2w8TعN"AAk7 S EcvO1Jkt 4SJN+)mY{s6O:{p%؉yK&R* N[ZpTOn4zdʯƫd]J|PNX&iOY0ja d!GrOvRX},H*i A4>r^l ;2MwIٷNVgeD"B|XV =dO ?r/ MVg~s4#=ObīDaJȯaL\V,ޘfn:6:pyz3ks*]e9bヤ.ߴJn%h:>Xsh=n9 KP,c$ -8x n#UceI<) ~Y\dӅn hs̃ &G"Ķ(bZcF6h{@9U1 tw[9z{*OyǪr@tS񼯽ㅝ~;T6Xj*ty${EaG|T(vsiaS.cGAs2l{5Tv}S@M+kwZzbSSeOE6ڬȍ‘Bac eI1 <.*7h\kĊK~%(O+'+_$({_P}nIx>4ŌQ ڬ)肳Vr˺@{kݔϯ0ܾH"mmyusmBc;^"dׅNE.gˬٿarP O=PlzK:&aTF%|JIe!(\TNIp`X8t0  eG>]`=hØMdupC Ji/ǷcW̩Q̌J-0QEyHd/GF^(ku<NjTΡ?Hx(zr9_!Q5DHɨM)=(FgVpiaM[Vl#qj-ֳ -\<akkYgrvH ~5 ־|۲C lD/Rz毿b[Aïw{hw4Ѕ ZFmp)++t^sL4*}uͱSU"͛Ju:$kg$MւJ3Zrvxb~m4$I|eYpXp%Dn\2[C-IeWD(d_gpA?,hZLM8RT{/w-CJH=_ߓ&hɐ1KX?ԥ6vJA/QӶ*ظӼ)Eޭ$2u,\ҫb?{3m= q<__ׯ1>pYNT;$weqPa zgLpuEZcr@5TFeX~]$v<[iLsC#R G!8`WYޘu'"]&"ۻu2B5ųE_FdiZ0.Ϲrk^a3R !E5/ mP5VzK&aX /9JY?wCXT3:-4% Qp >j[@-? w boRBIp ܳ%T?t6.@_xYpWV$ta[PsJl6-QT=Vg$6&5#,po+Ez0E'SXX[`븤Nr4cAQWi0*=aNjA:cHڐ^wY>6JK*SRW&梐]vV_XP 20. TDHDxOz3D7G< KZhCx.āfL>R& ` QpQq 9I0^mD.}X FO@(A,)!{;W.XJߏp:7;wq7R7nmpXc=fvAgPvSU`#{̳0v<4os JPp:ŝ9H\"bSg7o,2ieq5}@5@ES~ JpW^ nILM8zʔ<hʂ6 d-X8Ÿ9 >r @(kڝmث;>7fw}'7l;.-5cwBr ss}үN'|xq= % "{wd_uBH,, ;"mCs Zs2x IB#OsB 3:)%CC38cb4K0"-3Pj> #R[)*')(_;5 agGp 5_zsR\yx u蜼/xTpLjzC'C 5sTd3WB(+UR,9K:TAYIoKŢ%1 P Dyƪ- Dg7фHqO/AMdG~J8az "~ѱI`zw uUcT\oARz>R2hھ XW B"8D}7]&fr g zN\l῞~; 1?[N%58g/*\tΕ"hAg:{ԩԐ>Cg o.<cú_b ~ECWbV~{Kcz#})r3פc #Y'Bp.%IW#V.Ba* z ZZ⥚B2ĩlbl_ p5  diKs5n$cyy6߃JɏϰvKQĠ Hg՜a"0eHN΁h8_4MH[RCR2Ϲ&݄ O}nqVʸʩܳ_\hxJQyGđh:f$ˇa|]Mxŀ|4E_<AdA(<= t݂1lA[_{& &rv\NPU?Vf3s9VtpVFM> ֞F~Qg2貃ww /P~%~Io3sۙ MX;3Yy+tB@\fаpOP $Z5s3Z1׎jas[;aӪNm :连.4b:NmhH(l76LaFK4f .`~0 N:jzja9J*85s^@>.c:j^ar J9N {έfsQD{si$Š=FO/Ig{k߮ [st(gI4;j?gWTAP1TA"Xo^I<*ΐ;мc #1mLϳJ(A,Z;5O\ Da|sn8JjY?![ωIq;W[=}JUK"Tؔ)σWPrHIUص<^ZGN ˱ UOò\:<`O9Ό!L,#FSqr4,ۭ n|^XRwʮNNrla 'VGfC[bkbwR @ USZ"EFh72sq6neۮ .?M %O*yvg:&H3ʥqr.?>X%Tp*#" C6q*fic0oٮb"uZr0o]zW-׌Yq N@@Bt7N)yXB/ ;v[`:ohQ΁}t[-k6eNVZUPZȽI?3 Jz~jZR4'D o§{,AtGN>}3.LUQbQ?#X;q0 7&y9+ Ƚ'Q\,E Y)x7[ɺ)8br:&ӕ=A h&u7g13Y{3Z 0iP4CR^v$!6sQq>:z:EnX\'P#BqB15c@+81;9@@Z#H< &4n*' BQJ:+LU28(3;q:I xvICpJO^D(,8rŊ>}V@ty5׋xG;W=_^x; J+2VM wD6R{FT;*mջe:( a u=fSe+Rb -:zy^+ǧkIju U3dr۰M*dWKa+g/Uf-M􇴱ջRSzCёc U/S${q8GiI[䦣1߂oA.ܮϷC'U;BMZRn9d"Z $ 9C=1" vk;_OM5a{s04@ܢ^6h-L$H5^&{S/={E8jRo=;ft5FUJͮ'G bQm78):«LvscNˬ%8JSv"w@o8h[^E}hikUP{A2>(ek8<`T22UOdU!1\\.EtWL<Ɛ"f$ˏzoUG,;aGB볧8i8+Ry![P( ಓe,:^?>`SN)bl-j@'r@TVAC)vԆîp$TxOV8;5 !SLs$k$˛GzBkVT- KiI:Pbӵ Ef2*^8֔ue2b|ˡ,EwgTwlԋ-Q3i"ETĆ1#T|>wl6F/\/(ϴ P-:Ӣ$ir5|2JHX|K՝CJv}'hT$ͧnlN"r/`k0_]м17&҆}Mr"B~ζQzj(_dcu8ԭ]w6/)"%B+k_+=\9gZpg}Jr'Qc`ĵ "O{;N}NMg*z4`SU*sqи l-$vvgٔ,SD` rUj٣R[@*C!UL 4dhTa{nƍsxKU|Oܤxߊ,7jq%`ΌFT|I,]+NW?%m~lN ͭƮGAQfPg]{zW5cIYs)&f%Ӯp@l907ǛxigbPT *nRx%IPӨ_=ak> aE+87R2[11Y]lp]ieu!kC\lx8;769NI=ª)^a]1Plξ2J[]|Tۚ=| A[mfz ("̊ u_ `"q @u m|aqAwp/] kˡǛd[=_nowGVT?Jk +OOydJ8$)4!lވ$?{ ~\p},(Til0p뷣 EG -+ϵZM-.\1 L $W VJqb^ۉ ăpW-]o}=m"-8Эa# .h:*fP&f+%4Ma?nKqKP0B<q|i endstream endobj 1673 0 obj << /Length1 1844 /Length2 12705 /Length3 0 /Length 13884 /Filter /FlateDecode >> stream xڵuTۺ=LqSݡ $A)ZHqw"݊w G}|ts=k-&%( CY8X*30'ddegFrAA(,/NvvTZ t~qZ=*@#`P@Y\^@5 |"qtY@baSO$+@b-*U`@3{+ hkhh4Դ5߲tut8)M-m9f fjy "%.7 OF oj/VlPGA66wwwVkW(+ٚ/~Z6 ; %+EN  ]d!;^|IzC؋?5\euue /P3 /hI7A @OͿKB^W m- ` @˟5H*hj( E+O= ieA?;/C22`K) k?I^tB=\ہ!`k-(oȦ 9'ńo5 `@ ??f?|!+3{/ b@]t'BX,/YP60FᯍeZBK**2 %jojdoI;d CA`f_>,hZ{InVv.^ο=A?G؋|/iar_//k6MMWM^__a2` %l 9;y 'e-M  ]+3U1xllfF/8l6? '!''"/a6@ߐ%+_?eTsSaM3 |Ga2/ǿ>GdKJBfޠ*.ͫk6Ɇr\([}_1l:F~{uc;s"o*L>xد~6r[iIHf-ڍW|Ml!RccC^c|E:0`POȧjSpΗ=t?y@ OoJ܆-뷒|VQ^=tvFEZ%catʎ & }榝FfQ+=jO{.%Pgvue2(#4rs>wKCCdDHafha 73Ce[Y w(ϴ'֢玻Tz -AӮ⾭Y:]1n",,/sN@C8n n/ eqG$WUhNuV'\ ̥},(M"әFmxŶLɦjЂbw=j:hedekDdjo4oM1^ĻG/9GOG7r6 }}0 gW J$]0Q~j[>WBf@>m•H0EiRc=b4N8I(Zj:sKh"[bsWcRqaBe}:!auֵi{1\-dY%%lls>SڪfAc #kc=]v־;ʚ:nlwIn`ss=:b_0y([8%vSܲrU2/s}f~ڿ֫hL6`&_Աj"5ӞBʯ[A|! C]e ܚ윕ĂX2ۉP +Bv2!Etdܺ >eB=óNy&:l d@Ɖ7(X2·|hfkc89$aV)yd(R$6pYE_pŸukȚ[*|3I6K}=-;\3!r͢nOb7Dax!Js8K,<\ʛe@ݜFM.Kfϩ{||氥7sVia՜.SgzbrWO 5@jk@]MHo+( d+χU#h@_jȴ'Z}'U?vG_<;J™hӯ\zI:CzjYsueFX3O<7^ '|$tJ\*3rQcJw얒n<$=V]IG ͙6Q/ W̋XNnyU -&<5ZNp xllœr\ kK3sߏ aK<$ugxo&s~Lg3+۱[i"srS v&sWtѡ,Jj' %jiaE?ZIK P<BY(N陟c5ɂGa+.Kym蓓Ai6&%N:OD ~l)>ő j[M*'o2]d (2dz`4:^:(r_9Zf_W0|@}"Knp5^DzSI$_TR^V+ͩLakwcE%7=OE@Z F(O1_IܰVWʑibObIg^p/zd}nB»*#D5Rؑ]RC{KGw1=wZ~]ݝQkWў'B%C2:kAr, ?3#֔WA?񴮲ZOX.WeYCΞQOQgRO} lz r]ė0;LǜEy`nu5z]N k,qslj0θ4Yb`_噏ocy4dPv &d=!=,e Ѡٖ-;9+PKiiXFa-vf0w5L'4N 5>e' ٲQ=սj)j Ѵevn'Δ\$Wk%FN21h]n}on{ݡLSgۣ, 쌢7L̝,=&b63-j^Nnim pT8WJtC~BGU~FCkO#%4cm"$NG.FrZ_=qd>ƴRZNy_G),ة^j6Q+zmB @ v/]' i>$7zo ?5mӔCyBЎh*v$.k#~He?;0}oy-|-jI,& VȚςS!;{{Ѝx)![I q0Z\gdeG^[ͱIۊ${m>}CFt@U ŗ"7b.D(j.SX-xFi`tZޘn喕yyURs_cuȔ4}@aTqZ\|>f_ G_aC_H2`-zi):KeʒtŐ{"C-R0tC ՛$&/hY \k}.$Ė2"N# kt4IL$KKbh2Le1gI_^"y6 |NfDZ68^tqt|?mY1SMWtYCx/B_=QB\_o)oj?(865Wwr[kwP QL '1Re$w2)NĎV5A})J q`V|^w:\5_l1ܥ8B׹]1%qOCzLȏʄ_IࣆtW-@T+YU!D-| Ó裆WKPJ0쏎x%Pz>+$`T &%u gZ3*/?]!w/H<¼|/YO/wcfD mn1Luj7ℋyԉ4YӷENX(V5Ly*ӕ;: c|a&>?2HVJJF*ɺXdgc<&Lj*զpyjhY6m~<7]8_b̮Z '#} `).8NhXQb=YK:ix)Clj@2A˝>ȯcĞWD>3 q--ȥC٥awAxImM8fR2U7seI K cN4*0qwJWmc~d-Ilj*g.i \̎ $/ՉJ~gQ4{ -FABƧZ|zX "C4, D@W])rjsjB _~O0xR|~ #.=7 ,g}\1sm_d\6֎aZojq50]r7?hm6~ ǝI hLyD!:PQF4S|A^p9+˷Bkؽ ;n}9=cJ3O/2-ps$S}+C #IBMImV>aԛҼG|{yC\}Sk '::]#5JٴQqAVA@SsStIz3ĺp͝"2K,1iXO(Wlս,JLFkj'6Æ׫zO>2~p%{ܶWM0RExMQȃ(ݕ1!_i A[׷8ĸ#H.]Hl@ͳ9Y/~6G-Bo_tΊ7QˤVo,kverYP7\']CnY\vxiIf }wǖ7w- xZ6C*-fD=pZ0=l6-iѸ$M*L+<ԪJ0uo8W _}N@i Iګa8xy[cs1̨DilA]aCLЅ{W9p:DFUzX U?Odϔ֧6L ظj?|'DFPԟ|Ę*9Bom+^-/xI!L )cܴ"SFi[ 'ID!}ee n7#BB[Zp 3$R0 GW6bqHLHx|AIڕi-73í0O> Xn=Cr]Å$5GqjH/Yd ǔ϶xujQV J)lz_*_S ;ie -$u]H7@ৌA 2H{ond^>sNoGeyL}6wOz ::O|bz`P 5Ű[z[a~4OZh3Ɣb>~P.$sV("UYnǃA8SYr+M ^iOF\ۦTynZAAc4Wk`U%p`ۣ0:|ʯS䠓>@ӴdˀɫZ~!t%D֫iS+G1Iwվ޽4Pk/fx. 󿊍>Xo]=#=mUK^[XsO;󸄖UF̡7\ߓMܮYxX.T2yDD`8MUx<'9}CP'2l0 QGLm 4` [u9,\o ffTVؙwۖ6ZJFA%TGj)6͂$dڦTDtMFsH6WWNw?Ffm$U*g}{KdӞvc:|l',v(쁜"Ǝ aizbȜzԘim0-|–|<֔f}0%| ~^[bV؟"zsX%n1ָ4~.K]Hׯ:mb]Kx˃?+R3 ,]."[r3m*`R9˿\gsKrO=ܯ:;ȃ{)}W qqBbZ[h'kfW#~+l 늲Y ۑi)v*Lc!3ua0SYpk=k.kEYCIV0x{K(U&졨/5%)G$"_F2ߟ抟[?p+KV+w"522fNG\R\c&qO'|`;:dv(]U%EHq^tj\|r?ynpuMyެR˵((Z<:!49;@jÝDȆ$~_F).ƏGBӃˋ IAI\IݭUE'3E*KBuuzLGH_;&+-(:= t2j|8lp8l&FF1cUx;P0<AU\% Ã!j)%T} cr$~Ӆɼ߿; ^:GQ{'DSy _B!i쯾</K"jzW}M,3({~:m&8?:n.Q3PGگ|sA떭1U:d?Pp7<-8è.#HY h4.%?FO&pb { /ty~\ĘФ/1>~ZׂWMF1Y@5&dpP7HOk4izɂQtٚ&5VMCV:?z\5hjlFK`Jf@TVh!0:bBEd_7=HۮpեE.rr^QOOWB*{4PPBsCDt0WGjUtyjF%(y,\6! VFJ:gV  뵽4['OHxEhԺ?DGea mlOt =f`Y1ՊlF< -U.S?Y=c+ZlFŃVq=W笙jHP9R(r;xMnzI7١X/nc c! H| nVQ*I){CLVq=%5"(d;B%^ ԙV>*Y{IR0UV/Z48;)w͂=\#r}uG)Pnlv1dE^s|ʃ8o0M +bm̘@בu} ⇒o?Fm5*PuT;?-;lpQRYK Q-F Z2`M{u7ƗQ5|]䑸LiP.M*މ;^Qt1gtHb037ًMyդnRƀEL i}?9DcaN3{?:/1ܶIJ(7zp IkR9?klb&j:4=RJА^3ʼt1cL82ѾL"gqJ43_og9}w }J;zY]Nk RdwPB䆘KX+"n Pa6^"ra%@b•q"J7' u$J۰nlՖ=$qu|ҟ{A StXRtDa@E/hF:a>$Vp[t8;9tA%j| _$ ?uKj_[t=X.쳔I(O+^+ȷ^/IGAՋvF=&S<=&qTj1/l;MNk"gнh VӠUN#UĸNXzds) [FF|Ԉ=`݀O*UE>"Y,Ѵxqr8bY91l|е4[?ҙUqY vq64M9¿[IJܒ{` baXtn|g Pᵘun6jFcGoYw%-9~Cn]j8,f̗) IXy8X3@U8 "~!މn%Ui^K쾟;A/N\4ۯo<[$rtjIbLyq_e.*)bӕ88$r=KYZ>W%X"U[Z;\{(Ll$L갔A0U?jx9 rg68c≦ndwBfڐa_| T6ʨRY Ԫ2)Ų{${WKAii<⼡SB4ZIg*~q6JɅ$c$.`:ۅC&EC D.u+F&pyW{ʅ{w-Vhձ_6XZN #GVlt|xdynqA;JS9}yZ'@OGHq6̓Wq,A?캦,~vW@؍,=][cFf8x}xQ\5Z L|/K)ةU"5q3E@ѿF< b*+ Ewԉe94@8xlp{ Vhǧt0~Y[l7fSXeIe)7 c,F~>X}ߟH.{ twJ7Ӆj>'VZ?eKL#XTJ!OK#^3 xߦ36*UAmr^M^CiuJxVH[d5W1ݘd <;9{ZV[̲ {3_K5ߥ-י|41+)lJN'I-wk͜u rY>vXܸ1\ O`'h 9ΏOwNXxz>en)EUN<0^td)?sBژ;'Q6F\}Qd_xov G\DW8;odaY)ɼ=wt@\m}FιV"F~2sĉ(l>('m$RSWz+.6i2oo 3]8ak:if Z~pӛ~ ;@,]? JV$j#yszI]HSd7zy\Dmd$]4Uh~; J'?/>>o>MbZ\*'uq;Tam oМLI|ў̺QW¨s#L6a2󹦻>`?h}72UHzJ0%~50*U^=vkR|U/F[CGj-|y[=!\cQ3^CU4. %N#UH`xF8]y~1-$V\~ŧ&!Kkt'K5?SOO(3BG9_بT[ ՇajBN*]&+$ KQwlq)n}5 LBhVn"qǣE{%E]P=A ^xjܱǮiL9G=!cJb^m-:DͲ+T+UHM<+5kO:#ʅ_;ѿ4k0ۚtd;oQ)9 ŒE3QMFS^` wwᣙy>'2T>K۪nGLskc2bʇ؉o]cQ0-oƊFs|]AI1c֋}UTb :Cఙܯ)d7;;{~Źẘ>/2#+,|B¿qSR(CwQXoLץс6~AMuKޔe͛H>G2i~VYCEg94JS ( w}ݛa G5IAg)$.eA>1F ͢C5ge-El!^DVh 7j=D2 "K7)o&VK00ZYn ҽauJ,uAiE0۬2az)J5,X4Kt&ےp1ϢndNdzSy} endstream endobj 1675 0 obj << /Length1 1683 /Length2 6433 /Length3 0 /Length 7537 /Filter /FlateDecode >> stream xڵWuXmߦDiDAI%Fw 6K@R:AZEDZiV@DIo|<߱c>w8`(  I:(wR_ qC@a!!QrNN4E,\:zP,  Isp$ _@zA䷡` \tB Ἰ/䌽!ZYЂ@]QWA-@s"p;C#` 7LF ,*F&wU%]c07_|Ñ8Nw]c\b.\ldl ^<h b쿰q+uDxXiAAooo'O VvpQhW㉄:48@#1"5ԟ;J\Ώ_`8"=8c!ߵڀ;‘$`=1o qT<:;/(ܓYB}b'o($bt7zř!}:Jj`#c~m:(;HwE?%Umi@RHIB80;5>U', +oY"QHvD a<=M{pM$\9}΂~k pH@y7 <}c ^p=O$P,NU!]qH G<ה0uQXx_b’0 J-b >p(8 *t"\<\I)%^\!TpuFՂ%BIhsHW J[C&(-YfAv6"f@3$H'URIXdHe;V~EhNVSE(^x{:uel!wsZ5I*m#8Hߘ}}~-G-KAuJU-7֥bX|@{ !0AqqX)EZJվߵ jx.KV _}$d+py@ϓAʼnyDzFWlEٹ 뙣+]``-y0ar`ۀP㷠{ܕIH 3?T?uT Z h7>x{D􈾍˰ns9/)M|N|)$2jXWn˘BsT5~2Ddm?j6Zb;n8&Л667?,=q{v5##3iZx^4G*r ׶ۭ=pNo!t[^t/Dz#jwleVEEݥa~VQtƂkql,[_er5߯&H_7}LX=1+Cn#.lv*Q$4Sߗ 3wg`.:_d%T8|0g\MneuoU\_[u ܬ`Ԑs_ qwvz5XZWӓ…:8eHQlc4vwOC&-WUYݒ[p.'DjLSD)*j=_MnecV4yaZ'&N}9|\PQ'G3 M tq{G:%ݡbqNSVUU4U(BmoqUHݭ`IEhBWmol\m puxY| dc$QJE 5җ(c9R[@CO,,GhՅLn##/?Ucf:BIoĔdg*)?m%C>&si,f9P~nJZ`2[Gɴ~ˀZZ#`rcÜhP؝B+-7o6+TZ#2 "9+Q)MP=Wlؒi47 ޫʿ07iz||j 7!jĔu;ƓJ %?1Tc.+ޚj[qYgl%)x3paF+Bq$I ZIg /l~#9/*גF.-lk%HwyP#).Do:rWS)ߙV44 ې0y?(yw[Y9UH,[p%2b->WvE\BWju[Մ"/g..8yO V\F ^_+߂5ΑGM;1z[e' y]FP_o{lg0tzb_*]/,yW-˙*lzp,#ǖ} ]+H4knTd"6pVU ֱv+MMUL6Rgsx4?|rmfFs)Ro;iSiJ|hz TYNS!5S0%bɫq`6)hTRnmzwYd',O|䚫1Q|/*E!WAme^~ ? B4{*YquZלw_ h ,Oyϖ&*i~6W*GK:9%C_ I; TQ$t+E;^=}ԥQ X Yi`6 lȊM$Ghd}X1kK-ǍzII㕵}&XVx!$*x!hz;[m.(DA%Q4./6ʑs[ٗE=$sR ׭ 0qpL7GX١ί;σ*}ʿ Kv|rpUz};s"Mٵyoݏm+t+ƈ$B\\l5kJLR|M%uhJ `tiH#Ƚn=JdwRYڔ-^GzBwq\!2Hs7'F9Iݱ/5Z7Q1t#6ɘst ylё>ɾ#?q_^^KD=&Zڈuݿ<{ICыowN$]eeJ}&JsѳnQcP;& ?/frFh_2YGJjjB.q/ ؤb7X3qP&,ZܸiR ͫiXq / ?ȧ]!`o7YAYU~iIV;+F{vIIdyq\`2yGq~`x7ZfMӒ~Lɔvh_C@xS~6!+_Φk'"'gTA<:fꦩ \G(app|)GM{?=5e55gdXߪ@  e2 JqF ]xb0,)Spe[3TGe,95f%'GG!%]@7:梿l .oC(#$^mah8c誆qr?%ݢ!-!$otVii\O/YA{ ݙr_SNO;{ϓQĨ/;UݶЇdTtr6E3i3QtGGި|1-Ni$Rg_qPPm0Զ-f!٦h`$$ӯjz5 *^ISӬG)QZWAN"݈xLK LU5hkrO7.gV؊I ɼ/tܽ8q5:T]^ȓ=-+N4-zA%eGEfhIoP 5\ޟVwJ|_< z!m5!qo;orH>a˂>)r+jq}nփ'tNe|܌NX%,ֹ_.xPs/ND˱c Kʏ3,󆞔#PkjkU1D<#GS0ܻD3e{=33{NftYm2[۩lmoZkzUoe(p&Nv ` f-NC>W=m؀uG'K>wo:͜ҔMh<:,ˋ;ʫ*8#dV˟i ʅ>zrĿ6A<KU. ]>L_5}!~ȶ{/ ~˘Q(EyJB^l5L⊏+iSk ]W>sugGBe泰}\QYV~f٭7'7)½CtR`Qnv҆{7J]O[[!WzjF8^]5_aɛ+D$#|~zܛE $çRr snu O7#@EîGG@!L/$/u] cBB;MqY@IrBpԐlfF}f&;m_sUFNDuExtJՍGhyp6#~3q{:6["w)z%J aZw&=ibv}ŌLV곤EIڜ׸*?V#rYzި9:|*bK5EsLQ=Ƌ\ R`WpeY,^!(\֝#6Wq,+1hzž`U)A)h2tJ<ʱĠ(6!b4TOݓV*c7#l{)+WKԱQلۈ}cuDwq^@I<˨zC5cdŕ6#+WП D¨d> pJ$ujoEЖO*E/qD6J fтa&iIc,^RĘc򇪲;>7cË,HX%W,7@cTӛ{6rj2#lW0lƓN㙽Ki@Lx{lrGN'/sF~Q?:޾6nePrgeYry Kf۬%lWZ>_˴M*轷GOz-s/'\𽛥2 #oMF׬Y[~y 9?*ݜ)FQn2w4qm7nS2 ~rKo͎ s0<-@(+Q D޹mJ{[PH8 Z1g'JW`ص4XIS ul"Z>x٤FaK3 %O=+/Y Ţ\rc~$t=b x2ZfGCyzav 赐Si<҂ucē=lS3QuڭB*D"_i5S ѮWuOHy+F.ƯFwF Sh||)!ԾDYh)r:^mᯧ)ny]ze%;aE f p^D3UH5 a0PF~|YAx {, _}UҐ=鞱zAhNwh#o[RN2b^% 8\#kͧb?m}<+(c^}SGKz~ܰy`%e}֖VXᑗosB_Q! yk}LpAۡMwvir*_\A#޷|?2&h}yOƇwY@LjUCZxJΧJ? 3sG-LBjIŻPgBs xI>tKM#9a)5N&q˹mmXBx^.]~wϼ7{ endstream endobj 1677 0 obj << /Length1 2008 /Length2 14712 /Length3 0 /Length 15951 /Filter /FlateDecode >> stream xڵuTPܥCpnŊkq(ŋwZˣ}##2HBNUYl ;2 @>fyW Zt;H]A^W+{3׷Lg?5@r~sL W#@ ]\M.on-Elmi'3J%X @3[5`P`Qf=ތ:d- ]@V=[a 7GGiԒeHhJ@LY- ? 7L7?Қzp9Xm4oj @g(bvdqK l x:@5V Y'IoIov {k럚v\@Z;fo@W7loO9AI7g?r9_%owfh:x7}f`kW+v?]_6eqyi Mfs`Vuǁ?ĥ|l<v~.ېJ;KT i[\^;ֶ`OY@RfB ` O3+?d3[| vAqn ?:!̭\m U] _)5;yAH*`׷qg D?0OT:=|.2֞ sUkW3o*qK;o֟-e6o{K3[ /mJ(k3%`6vpp@/$i9q8]Rn~ 3ҟ%}+7JrXސU?譊ο4X@?7 VV7Z7^@7^7^?!9[!-nod^^o?g_?a Wg-H!@WgkOf{P GӇ`.5\$x?"!--Cl[*`YDta )@%iJJrFtCp^Sjvhc,ZA?P(s}H hJwrLb^QVuzα8ay &1 z_bP7׃ K4aЅ_Q974Mm1 ыXh:W5OZ6*#da6B nHixVɄЮwNի'QS\;;T8Nn6֒ doL+,1K1M-j;I˙dXt{608lO;&Fq^^:]Ǡ_B9}U Na^BZ%mX&}qf+_7Up7e*p;GZPPIZBo'ͼZ;S#EqEpJ`q7'WY2 ;84 ϠR崿OӴ%laG؉sN(PzvW]*l .;E^5Ioʢ$ػ^GTBMRYȜZQآp #d^ՌOu-|K 54y|p 11mnN[:0"T('mmQHMY*3毲糃o(ijfҾ<.[-Jat G@u.}5_&wuWڨ5A]/H)ٴ`(pg^m( p6螿*qђ4@; m=Zؕ\<8a@^&e.sVT'6Y )^fm?~P"bR#JaNn}nYMYYFDÊRu݊9np/M5.kU\E(y|\ yf6"vJ.s!Odz.8wqsȖ&6Մ|OyG*m ޏP\ I'-˕su[q>#n,| (߾R#=hxY qr-ό/V\ ͜9fNCyf1;'i/>Ekwv$Xiu1kgjkL1kcQ&ɔ8tG4H6.SljJ RJVqp0 uuò4i }3+J꓅H{Ű GlxqNO+>OayI'J7T6F|4Ge` meZa#u<†rds{GإH^3ʼPv>Lb]Ni=5!AIǤ`8:^V[Xw-P~RY7@Oȸ^R=W^0bVԭC#HN i@K.(d5hK)18޶½C@!̓grjk)6<-v=gdkJ)gلRC.ۯ",s"hYc[O+P 2_Bs>I 'MT5{ÓԴ̲B ֙{|͞鳦׻:s!W&%휭iiLD^zy R63CNWI5KgNEv\yr}.̚&kest-"Q6(9 *>I0ưF'MK/x~~6&pڃ (.u*:Af%tQ'+~hpn&W8[<ߩlHMW 7BZAc""axGWM׮8˸7:ɋlP9 35 qN9~g@zap8:d9l'q䦠P9IfXO@կG  ,]T"v9 .u1WFo}ґԎjCxߥPhhy<Xke?hqo9](N,$Hx8hqy\)c̿`xpp3hlvLԔ7BӻA]؇F/ԙiIlraѥOFWfBj 2[7+*$}r{FQ|Ї,U#p@,=IWymJ,V6tlK Amg>,HQd]{Qa1c5ulGñC΅%LpwC/q_ yH m ,SF%j@A4Ly0>+=7,9HBǘs 1T`~apLi9ֽ<8ؗFwjNhQG-Pnef ק'RI/pa?:Uݨ{9}*lv C$2 q Kܘ"Lu$X'S@h};p{Ny-x< F, Y{r˪ƻ6 >Jy:M{5R<]A @uH+򄉤ϕ IN@SڥPnl:VޜUrحs[I eǮ.#71t}]Ћʏ"! _N|z>U2ad/{<$E -B4l_r#Uat,䵴qF1@t{ncZd{OU 4خb c5̶3hx*L1^$$wѴH`SRhk}랴@m8`tA4So) Sp9aޙq7[!ptv2 !qᑢ=2'_z7Ǡ KyF/ @Xlm=ʟGtHYx i')"S墘MU2y8h?;cx$N3C-1ElѴHTQ [& hh}쯇?dX-.OOk%vU P}8^J%Z%i>|̖tb%@qQNmۇW廈P gC=qB޵Ҋ& Íf{s 4%vxiHNq(dyүaSubhԻgYM: ]@ EልGeDyׅ~T9xD9}cItC+8:_F.jgs7xBez8q *eoťk<)<̓vua'E6-qSz%v_Z&Nȵ-2@BеtDew ?J~Ib˰|^ȬE㣹qs`}U(|h,l#h^?Wv%}`y=[8o aЍ+m[_GF\oDTCX1uB)oph4A_n닋t}\u?PN^ py/2>a_bmB u_F 0iL ~R`հ]A{$XFYLc1859o '"4NEr;= #j%^i9쪘B߉eV1V5X# Oyjtk'.ňdu5qD5˘לl[>v :].t;7s둼~$b_;Hh2a h؜i6zJ~  "n|}gaqp.H^ٻfW=>cDyC6G~26܆/}-[0t=5Jy5m8"V{"a=h?T1J08"qmSP1QnC;V$.4p=L6vl97MR\ƓFCWȾŒdZjv(H 4t1p("pXګ[͘p:a7EYagn4e*Pd #HoO=h sm`W~MVj̾eC]R!ʇ`e?6`k-޸ET X,i2ĚgHj% J]]$zcVw"T"6='"{Yund?g82rՌ/:Mq%$D{k\۽=ڱxv}dD!lYD-^t 'g> +  dJ3c}wi݌~0:vZXhp iKl+ysR]hDj`rMh*5ߴ(Y$̲!@`Mvk\+Ꮏ: ߾OJZȣ{xVS7dT͜Zy ͡3"l֓A2dU|:B7b)L {BT>a7]t_(jf5Ӹ7B{9x6#Szr77r.X1tB]lpY3YA@7j#<'Ӷsw#NПrR{ wIC1(:!o:c m o~=5 {lEy@W*iȴ\)MD:LtZcI6v4NЧ}V..ZUJQGٺX8ϳo1_,RɵzemYeݦSAlvY;%(,7,B r,a0qzrjM *ȥW k[Tak:0fEI~ FiE ١o'PH֥𯾔rPZ_Ǝ%B3S=b6(LoM xNM Z&' oS0tA K߆:u 9b m\\ʛ46|l3=|ꊬ8ޜLj/fCǭzX*D':O5NvQs߽[(U )]%㴪ˋ pv}Yl+\: tLq6T3ŐQVy22Y>(ņ~V@#J1ڕ0^|liD60ㅭa5^&#VC+z 8?k=|sjF,hp Xz9B<dk}ٚCΖ<}n'ڥkf %,CB:GVpd}ӧN#엯b[b9V Ni4)Bʗ ]zyNW 5gSWKR ;Lo/SX_#Z~,ZCTv7@oׂЏɱz3b6@%j='㤂lQ6qi@fofvnzWqt`2`5lTvϋa^^E};\H^{y4";.䱠Gxi?(J<0P7 fqߤ9_:5=<`v >ѠLZ9T*Yӟdk-#T|L3kd\6|}/hW3+K\Cr 8dS ,HQ@铟RC5[G[37 u u2\gLE,t ޓ{P(+˴5J9>2dOhEFrmibKJSAǦ&ޓ0PDC~ eP$6g!mUsOGl6K7oI:86mPY[uzzIqy=|DOX`ޱ #҇qTF8M!r1)4{B"ҫ)nq`CMV22Susa&-*@m}M'(c+. 7^鹿`5!՘ZlP7$m#xxfxυj;fpMW'ZH% MAݠ7=$z9}lgëJ;=(n5G"hУP$o7kdT~SGQts'Č:iç3>[>Pl@*!b „! \qY?v 0vJOUP<Ȓ<}>Oj֝WmHY :z8< ̕Nôn, "2[p&qDkN+( "|d|H)$c] $wh')7ÓZjMF%8\CmE?{o'?.6 /Jm7F&aH eḃaq6d6k}83#0Y몲}7'N{g'$?(ːK8 KÞ Tahc%cwTsmwTVRB""QpS'FuJ?Y}mr97VM"-P˰)NEgx=Cq|lQhOxmB8&CȲS2|/`exW֕ oI1ʏT%:lUmS 2E\=D!@Q9-'QB|}旑 >ܵcE|VRr>;w_( Ha^CéUݑա\O_ʇ1:~i}'$}n<ɻK۷[`BHW#pǨY;[+t+ŇL-.S&-iY`Bʀ?2AB4 @nW0-f 0|KecsF6JhRddٛ"#" VJ~,X໳xCsQ7kv㫹{~&GnV#3"b#1,YV-*u6\V쑅-G")S!%'<;>S`g)'oll}{XjAU[鶘tIEaO8Gvp[qsCޭt-wBu$ySbT"M 㝱nhLbvIY}Sm#%T#)d:8*G1 Ex{1t?oPPChWgH$5\$;)-1XJb^h~2D 2aEQ.XMg|y ߲Ǹb{GD2uKO6sD?vsݶ/!QC_ƿөY&"#S칚-[6Y>g5~r9$d bzmemy?O8FK;bwAIT>*7"sؼܮ\Ce2V$0#+>%W9,9sB= yʯ>0W[z=gQy+0Gdr!PV7Yf=s!`P%5y3Qĸ_g16?#CFh7>[X5xGP/%q*]dQ!z`넁^qXȔl2H8h>#ې))LYvȊ9+6" /rvSlBi2؃zf!XereꠈbkOw֡RƠS@댊iM4 t8؍HH\1d1W5dI^喉[z a-$5whe*;R8ӻu~XDs2H'͋%sfgEΜ#m̘b] 2zIP *JCIN}u>~=RXDZJ<7:ioNT4h(ͱd.5ђf]]a3yqGN!`} @k}g*Gu  lrΊTgΛg5+"wi)7"䑧*e ʹQ#ܮ2Iz#MXcQ D=OO KR#i"=Hᡬ0SIe6=0W5?m9eXSScnGh92 K(wEu h"WD(H1@98.LfT.qXbxA؟n_h`գ/ (8epC4Þn> ߡ`3ɼNtgoxzQB'0h{>\dg<>v4G%h eÛI.qoOH|i'T.;'e쀫}wQu)g {Wqm\>Wd",6kIt nL|DH,`7i-tR u]:A q 8sbPbH+@}oTdzo0ܝ'!ӏjt%Q"݋ kIqD2]N|\mn؞2nt) Ů,#.߬MoP%&q6X'JQ,Z(O+5D/. ^0|cWX`PW ؘiQ[͞[}|Xp+2kI᠚6`ϸ7!/0,+B ;ΜHw6>jѪ6H!+<9N͸;J!R Hi5^M@m5Nve_Ta8$wUjs!r~ʯ!aW3֌|)vp3n#n&./QI ?d?)䚝Qy9CX F4V>l3Ex_??:I=nWnH_:s-᮰5*'M`!guTQ {$y+zjRs &]ofC9ÚZYf؋Xs [\]PKˠs$PVf^~%,zqOtFeOUa}B#*йB=ˠ;2)ʴÍ{ӻ x"*]/@GۥW"#"q-cKn|t 0̴1f:r"2(5̶JA8XƜ1S"FPFvhn;хkFۮy޻d!# ^Ty'IXU٭r7"_,[%h+BMΡLi:x XyYS9x)M?QGx>)=Զڋ:^1;8MfovNO),ؚtuuS3V Zhi8j M&Lj1Y*ɠɳmpԼ{O7"R>?0\d_y|*M]C` $f0Ve&4n[f3+B c:} A4/=gB5< y21ˊ/q}mCY%M~7md3{W|[<n&E`ZUTw4gaAN9e*JiCh7e"fN=9sa7+>r/"T6 endstream endobj 1679 0 obj << /Length1 2104 /Length2 28983 /Length3 0 /Length 30143 /Filter /FlateDecode >> stream xڴeTݒ6w4܂;m]ݝ]Kp̙̜5ojʮڵkW-Tdj"f&@I{;0#+ /@^AdlfklfeaTZ;ؘXXĜ`+{;qc0(Y Rv@7{8Y4e{g0hgae}swpO?ޢLYcS{7g+@I h&Lƶ{s:P&RUPVez `_kH1E%@MGu[ E77? ":`Gnojd@c ;231Y8,lOfdx{v)[95y+S3R9[!b /Kc|啕 c+;0l vq#{>+A @¿UNMEVgc;fl?jmjol vWD '{?{feLADQFRBMQߪcvc'^sc[g9 ;}#DV.)?e<eo?Ěz̀̊1H?AViMh2;L lj"K.6~;"v@#;'[WM&? 5:;YQ ɿƟ̢2^X 0vr2@dyk6[ߛ 3=;!eNѿ_`Y Y/b0KElf#Y/zc7v]/zcWoƮEo|j;Y/zc57v]/zco`nv[𖀹 [bV[ Nm`7n?]vjYc'&";#;V|M5*oog@w)Ҽ)_uJchķwTJ3Q0Jt 2Y児H.WSȱ*;Ž9)dsy$g~W$ӧgsj pC+Ċ$md`?-習Jd[LZ9KT6ǃ~Eȝ.WZvIxm%.Cu^[@SH|qq]Cee×4Hz4m^0׀y#`;:NT2$*5(_B]!u-"~CH*)iyr gp;pT%U%~~vW$c} fwM{GͶ9Y'gJ8}st80x+L M`a?_`,VI[zZm+5$?\RҾt㫥.M4wd-[ŧgha:S%H\}!7Iɘ{jIZ=!_LqLq:3޸=c5^vNl%y`cJeoTb׻*{"a-y28>gjex' N#*l)9~Rq`y@ꩅ}.xA(;,sHxrE_FjՂDAASBcXQk5ҽ PQn Kn%Tm"}A (&lB]{3O2d\Nez!3q. :e9y],9ws-1(?f$ ogtb1gdDLx!rلk e֒'{Ԓ` ^J /7O6m 2?T)彵(& RWr{"X^>D (fC vW-fAA ${ǵ^\WwMx.JNr3D; O7d11Q3I)h]E+r|J}(63Ͻ|%Hi'o4?_I |f.2-(wy߿( $}]=#%ۅl8{ʢPv$cAlRv-Wp1~HB ~ٲC4h.rY3,SPu!w=**t(^DquܝddMgP#XKY-XẂ9 wҽE+ Vfola ꫅\zmfsU{cV<!4/G,=U? XۺN r {lu wd2b1K+o EJ~9q] OmQIp93WuH˕5&(7;wPJ(k@z]Vu/2Sѻ=*K.lj/.<[։ڄR~2*MsߦvbeJ G>m;/ Ɉ31reLPuLF qݙ%.|v5wA)r ŬNYsmzf76ހZO“U ̀^>2 Zн1+D$\`kwR } |&`- ) 5P G7] 3㏉y&^ڴ#?{/=S{>B`mwZtsƐcPo al_f I=F̦#yݸҷ.BF6 bޓBC#TYczׯ6 Ufŵ%|b9]TJ0ςBovgșqVcg+ !< VR4Aȃz“aWOL ̻W&s!XP 8}%8dWǜ0HT:7Nhu%u!#9Ja` yTM+./el:@u$#Get9)kv*(xo2A%0* 8M=W66X6IHG|~!zj5^Hx V2J7Dg{Hg*.J mm8ڗ8ޝMVF9"b81v^x=!Om(Go<lW{Εdas-SAܚI)Br($4A >W8\Sk06bY7ZRԈ+JT'Q\) j ff:sUc@/FoQuܮìK =:ӬbF/0BuSV{ r,y*@W{Dž̓AȖrT۽I|;U\ KMS*hj9).0H|ۉzQsJgayÍ= L̆4?wԁS!P^e{Գ`^`d6ursMޯvS#p&X~qyk1Ց2Ꝓ4` 4t~o8|;P?MDD"M%.Uۺ0tcl&%r1CГk*@&[a9s*!$e2O]qF}l$JZX 8`\G7cqHm'T=*mLP4 34͙׺::JK0 BzOtRvk W2'U w1 rE*8?EJQ'3ذv.RÅoL 55ϻ{Wbǫ3͡?~N@i9T6?4HuY~H].!mɐGFML_/;IXE+%~FD|_rX0رBdc/C^2=,-m{a Ȳ^Y\~ZIgzV{kvNmF\L}a]j)ܷ2ℨ5j ɃGq]CwP,&?"igU=GY,5JNw"ǡ:9RYwwpA e)8KXxnz3aq{~Z9YZ8yJzW)hBAtJ?J4!ܮ_0_/lm5C3t0N'F(YJD¬mpszY;X*ƹ;jھ)2$nl•3f eQDޞ?/fW=rG[r }R6-N:cM*šiM5غc/(_Q !Dowɑk1`w5ݦHVRx# }qͲXQJF_m̬gǷ[f&;X֜LihGFxlM=eV(ò!1Ink 3{^fdYYTVdʩc"2_-#n]6%]BMOs͖){^TϋUɮBrNx#>&HKz _ ^~_ΞhU_\k܃c%Tx,D|QqzNMǗH5~`ע%T\o Dqb[6i\CIp}lUW~BAN;r i"aw 2bqutB_vJ~AH bNÁ`,nedL!,-؀~H\jIO@.ʾLn V2 FIEDHiÿ< w]6S|4)`\>6Rz^ Ć+ZRSb\ sp4v?ir\D]V Em0"gR1VXaR>[ƋM&2b@qEgг` G)l SBo#|_ - h~e@#pE8y?TŘ|.&c_L?U&? "9 @62oE?nK:sM6 l"Yw0FY3Kbsetf}O0[; vaVeAO)U@4Ev9=LU6;m#f 4dE40RV fQ=gIdOuţ䴹$3${C$niXZ|;/`Z~ZLlԊĠޡƭ6v/5ݥNELVoA@0x&C7MpiK~9{@C%R6IbQ}Zy3sIQ5Z8D@BNxYrIeԾR@P<Ĩ#Bpju4=S:-pRt QDH~n_][Hu5IG_,n9 P%u[f8I fk(TL^rSb?i i-Y͆)֜ T|/G(9D_gPGn'^tPOFa4N*>SI.Xm*Z9e}#]Zsfcyf0cKlSjL4WzS+(e[bc7N1|Z_=kⶰ8\oӊэ2$vrT&p$kTsE͘в@[z̰ R"L3˺Rz"oryn=+vЪ[f0Ӝ,}pzB!f: mACbb]y5y$\{0:\Z ^k@vO[)kzKF%:7vMc2 4[[|ap ͋vϭ$dQQĿYU405?M=B#3(OInxSBNLL[f!e/nAg>U!to݉-NVk!%#thxnCT۠ҋ5j5qejh{ >ȹç!HgAwG}OpN{ŖQ`x@Gu>}T1sʡ,iC$mz8~(Ӑ J~ׁѸMvfXFbK&M#/q|_kb׏3ZϸSLW]ABDtNAzX׼j >}}&aWM Av}6r?_U40gEʝ1гr!)OQO{|PFܦY }:@]Űˮe7 ΍a moFNU )fek8q"~wZݕ HĔUt^Vl@yvx,ͭW U7 =lZA-vS%Qd<4]n+ژQ" ~'b$\4u]/"Þf9\~H.׋6)W"іհvkUpl'0-#sQ2I8Y]uLżK4W|_SV;V7:!{#Qj_%#<~fdC6 .OWy=G6%Wb$-Ҩ)qޓrٜlVFͩm*LjtKLhb!]5,֐*CLZu#dyscc:-Yv5[@ DNR{4QZ0jQ\M+{h84^|Heqǁ*ĩsd"t!-D^+ړTV'Cm- ,B?eu<k (DAsQP|U7-G4dEJ"<6#4lyMԫc#|͑d!lV6M3m)#?۫-Q`F[=*+׵,Ņ^0SsAje(-o1n(YP.$Af V:oHEGmZ*9SÝ+e><˲>W'"_7Wx~({uo`Yߤ| r'HPH2g:3I|b"|H6uWO)]# l3 ~1lI4Ǐ5Ass@j4"kj"{g-2u.͢|qޮ T0)\q(_5v*aA{]Y3xDǬM':=2W ~̞hrV@ߍu7 1 әkܯnmnkAOnZ=ar T;NP&:TruEcKIY0HEԩTTR;ʸU>X̤3PU4Rl1UL|0f-a1z+7&6O'j_DX綊ygw)qb)3r.ˬF~TxhBY&{] nPnCaCҡ?PJ@*2'\XQzzѦތ͒n/˕>QW]V+jT=D gDO#voSW=q&~T#E@L7G@ޣ=IƄffȭh`tyLQ9 zm9`{}K**TWGɆ!Px:$6| ̏o ̽tJ>vMj Ȩ0O>NYw0ڎ9W iwʩ{BÈd4ytx Zn_XrrȆN?nST~سTLLϘ+GNFp ԘQLd!ߦ@4aUSЦ47A8K$w{y7if{ :dhcU{1? Qe M і0/IA4om@:^*mhE$Svc/C˜S ln磻pU]nJ L@Ùړy (oPYpC."X_N.veD?V۳j,w_3[3ٸF6"z``yApÏU0/Ye#wZ2 Fɨdg;.LL yĢoZq&: 8_EB%L(J ,c 카\ L:l.( IN2k\ d96H|&V!DO~@ ]I^a9DyWp |P%AЉ?ŭp ^ Z.B\o?Sꁖ;9sB/P#4 .,~$cqEp:dQ~\"z5WF18veV}g5}^h'8~]{<2u䊪]a L/)^&b)B.L^wma'E5q9^)Co b"['y |1we'b2o>TF B!+hH$"4Yz`"/{0$ST}EfY&ܵ+}櫸f7ʤ,RS8vh pezB3$zDXŒ K>/YPo%^Nd*{*ЖŽ/.*Of"J&̴'ڹnsRh,bFvp;}wY b>_"xN[LP(|HO?XmPjyRF"cl 3\E ?{A ?ȊR7RS= ^lcEr?a*d&fA_z'#ui}OtdF7$$ ;jZ2нgG&DqA{`r$@ #㚗)$l;pͺ<2v g:4"鱆qdoFŠ,źNVfoy5+,"`~$){x5G|/1!j[\q;O Ke FE%:T+=Y$}li5g* q"_B.:(ES#wWs.Z&vɲ?M[(gjWLD_QُEf7!54먶 w/>MrF~J+xfa D̴-g߄>,c:{N֯/kHC3X7_U[y hC)!{k'}-+4:DVE#WeEm nʰ;xUPR``fHd'plc5k)X 5vD,:vQgw݀*%!)xyYՕױЯW`JrEc˭a?Cφ. 2b S@ 7c,iZ!;W~H$?y 7c叮5L#}[AFS$Wiʽ`U`mQmx!zbUA`؆lam|h46R7T (5^n߮`EjpFن:bku\ZK[ˤ榇3,Nv(hLz@$'":.Oyɇ"'ҾMa6/שA1%&JP25JZ0>2J>50xݦ-"w'Z}ܦvNJ:G"@|GYi O}̱t5檄V6Kf(ՎցK$ZUJZmGta8[,]N>O%&6dJuzOHh`_Z֪QiL|zYE2)2E_cCgP1h #-)=J}7c@iDNĜ͙U|^L h͛H>}M/-j>( ԉhwV$mu+[q44JW~K$*:_&RP]nt++3h0 *YN._pGTK6D\fz_u"_>mN~ܽSgl8R^ aW>??|!,5=yOyx-QIXAwcu;Ӷ8BT :W|^hVs  cC='Ux'T_~]@ F0)\|(}hD' ؠ4麏x G gct$Ǩգ(+p@J&ޝ&9PJlA<$AFr#E FͩZD4fxt00iN}M-SvRΑ ׹XiChCCGw*%b*N>+c+ac[QZVuy7Pb;OkDQ#m۶m۶m۶m߱m۶m{J%ݫ>K])yCr[N"4cԦO 0{Z7mEZ^:(>4w9Cœ QCS|1jIzUg*,ր,J%ܡ4Lz1 0ttœ%;N\2n[G*O*Ν˳22_lP-VOG YaCXOE"W:L"|cfټ]KK5{ǃ r֍-?p ,@OjPC@ gx~E) L%j,yމc-j[.XϷ6 ȩfBsv`*rS0c)܇tK j 6S`(j,b/߈o5գ_"0EMķ{!Fɝ__(?tUFb:.^ͩP`ńXJkjkn Xz)H4" RFQ=EFݤ(=v"q_G }Ih|.׍%ARZ(*URkB CasA(@rDl ɁXdNZ gȿT} 6X6@ѣ7ZCDμ?& Bl,/pg&}A&5<;~Lxfp~~B;L(.}!k.[5[)Z^6G: 'x݊k\D㌯ ڨȚ+qf=l_˦#ӆN/<<+ TBhe{ C7h "nSAd~iԬM- d5 FI/FaHE!0 /Ҷ;'e\%:c$Gw'f>7!пGԸY]Β S5Khyr?q8y,Q ;/Pz۾x5u0$9KݖNUŲke0(v4r,ņ,z*V6@5S*E{L6[nVZS ☷'g+Ltrf}P3)K][YpFT& e_0)pDJ."W0PVR$VnC-Po1 * 5_OuߑNJ;];1n+d9.>P%"/Y9ztIb^X]N> O/ MgJvgoݕu6GOt!b݋D1dD%pl8/W2j 47tƪx v*Z_Rp*_ߞ@t>ng-&$ #D 9‡X:sl %y1׉T4WWG%T\VـCWXf|[B#"ZWКYPO LkN[Gm6quw0]:VMeZWaƛ~.⃰|zvzR*\O:~?B)"0bmc FAq #],eW/fR+M U4QŸdKHվir>\ڸ)^.V[f2LHIv(fD$\#C3`BG>S%T\zE ݆`7gUl7{NщVLUGsyْˊ:Q!ɫCB/^&Nh).:fdpF{-nֶHrtYu?JB,h#v>(GhljԴ'ts^HJgxY)=p߸9Ic ;vd%vSw^HVWީ4,6{j_a=w4guRFʫ"XҐGOo^dƩtQds8,!%=w |Mr1I|J:'s8Lm\B#Wh7DƍD{r]|e钛d}24dGʈj|("Kl+Px;"?'FOqaG'MƂLq3i9}rq x]wZF°;w,ym#[ WWCq+\u¼sU2}ӹ&zaB^D 8@-)x m^tTοlw&Fㄛ|i mj,tNzY'?~Z߱{-3pRAR+{p  t$d J JBloY;1`))bpz\f"6EP%9N]r qYK4c0_poQRB8ew%"ᴎ$*ĝibQJ@DS5/u"qۿi^_֤g)AL6PŢS(a qIP#!-4S-RVlCt &aۭɚrgօxSi.LӬXU}AJJйD~rsN|4ˬNR̸]!2P⟒<#7ltj)|k. EvRyxifpIU.翊9YаͳBo,o&(VLy޳d(ԌP1g4n'|[  s2 \ *H+pbϕ {f8u4Ygfѥŷi89WGJߜ 'zRYEn766p(4<2+ӥ>%-,0hn q_ݪ|Me,O$Yɾfcꔯ](c 6Zy<84՜Є'J1zF2dx<6X@*u}BIbЭ( &@cd4}yWFzI+ dC:԰-?`(ޅbk1FO#Q\!F#YH">ChV׿ԗdq@r `J[?7wmٓ0971gt^NDM[([cWo6 {EeQjEA76U]^"5& ]1dOcޜ}6BUNܺ.:;ۨO *5,{%Su4dMܩH ?QO4VEW@B\x+N\\*`'qK:P"/pZE!d"L29ې^TdQxs@Q%ͩ2vxcAGUaٖ[\|+]lsmRUnk[n|R[5()fTlEUSX ++|H>~UC7yԨA>iGm%I,. ` f D8ʸًQBkNe7:({Z~`7D(J5|kcY>lx UhҘ5ΊZf{_U/v\F˛za×)Te6j<?IfvvSY ~sDVfvQWj v"H:o\8AJ7iT,;'itYoIHm@yK> Ϝ7[dqd Db0VDЍ|<7T}Dzt 8d !lv&52̗ Zayx|CxMSOaw$2H.y(`xK(Cc gT^Fc?|4d 8H%F Rz4Y:62PKm:Ipb'Z/|dS88t\7%/dDY|yȘ% \km)7уH_f "t.HO pZNB50*fʩ'qc_`tD!}Ɔ s]Yn',׳'+i]TRzncԩkQzAa1lMn%NLy>]x> 9f"#\]st]DG T>x6=~`{F96~EB&gc3'D2ۈ JRTgPP/2; 8,aE&ݥI8(Q"k+*"+%V&NpRƛhhbIin'mr9B`Jhrt#?\thh۞2<4bۤ|33Ѵ6n¡& 2 Fi+*\e٥@yXc\܎Qbl!Ҽ@)u៣,JߥX@τd΃J(J9ЊĕKuR#ĥk˵h  rx6>4 pP"5zX=_m(ȩ_iTØ la:r͈V$Ӭ;I&xs_ )B>άew2pfC>D`bhq.e0(QBŒe#q$T3ݜK`Pl EptOc%L͸FPp`ɜp9] CbkdMʒ >Oq3Ի-ᅡ}ܵ_tE|0csŋ:Ԇa"ձ*X,vk1*F3^׽RC?D̒*aCá!Zm_ X j)/3ON ADnο;phX02vt^%RH[~$h( )1c: tN&Bf'RCI!~aIļp9ؿp~~  SOMr3ok6͛t-;H|9\H:<BxRn*#]S2z2=9+{|}l鴙Zob~4-}B<5yHx)=bf q^<2Nkw@ QGíیᖕ W7 , IK>R<  G۞ NǶFf !&49OL\UϾ!N vfXdqcx ,z7D~<,ڵ15K޴//5('BNG论3`/og CTzg [=Ȭ !Cб{~ #ehna9珀J*~{akQi@5h⼳UY8&~PMǴɫ* ›qu AДf/B\BO/&} ;|U3f8g'\n,*,u8~)Vo|H#oe/Ms@ '>8+wެ={fT]y̆T&kH=,JM/d1#Xw2xRbǂ5}F?ޝ4va(F+ %~U-oWJF5%[ 錐\Q6+F i '^g_ĩͅ$0]8j <&שx$Hҁ_r?Hde E5.@sUat{U_ԦG K0-&TֵN@gMM/OY}=~v~Ĝxb{^1`cbL|v}$i/4ZK  dau4 *G$gUk t>|@Gء E$c'(BڮF-@mۓߕf+Cx_8F]Ī&#y c鎈R" ,[+93,dL: j%[ ?cU&3+lЬꐑ&|C=5Ւ}G[/`h\NRxtR1ۦg뭹umv.NO|6Nv`ato1 䪪:}vY4-+Q=i\P8;mV'%+v<#\BhpqܵgO٤wY2}FN6FIs5o8ڀݱ G/+Rx^Y ITqJq㕎/uuB.c | tī3{(PNtBwFьzssGd 3HʊW?T9Vтc XW865بjD+ +^~"e䈔 ЏTKK8nW&^±[)L9@/˴s馭s\jDZtJAUWZ Sy~GXg0џ5,U #F~i)^뒈&9 7X̾K.,R_uV sZN[*~p VQew2 _Mq?>3:jaws3@n<df\z`׿EC"WCʓU:atDPB\^n~l˙ցJ1kcXxāz9^Tb>baVkwZEȄtlK%tl{MS/ ΢UB\_&i/oAkDrTH46yc\i7fP7w8"nlJ1Ͻ|`Iǘ)FW_y~!f_b>>R€/CQJmW5y[=NY֍@OK^>:惱3ƕTB*щ$Tj(0Tt܅n+lǨ @l%0hd8n xP|}0jMsLs]#)i 9e pwXYlײT`A bz p? sO;J~P2kѸ_`=$5yX|\\Ɯ[„s 30M/ 3JV&aF Bm\}b)pZˤaaEsUj`)==!BPԭS4!sV &,8pbIHO %;RI}S Mc_a]:', Q.ʏFӄ~+]˱0.̹H$/s k]k*ħjA-}N'T)tBgfjn{cUK9RL|qo#b hPL_( M۸,rPq,잧O[L9v>K]V".;هc6.0cLm`o-`9_WfZKb?hJ.:q+?-IV-R0uvC@c!ņ,y<}B& AwdոJ$!I9%Fb(55)峆H{J%gfN7\`XLK:ˁkn/.>$ YL?ӠGܞ3HqHrA3NF"Չ78ND5ԽP ƪaF-/k sQ{ ^i 8f(FնrrVZHuv6O?o5qh9,TvtVջ &!ܬv[:dŪcjpҩ#?u:؄ z(-Sh2$Wlf M[>\Ht7Ene?pASXc"[_7';yaTY4>"c!/{ WPWR.JB #˗ČLjH fM\N~We}L%yI߼hRYB5ߤ[r?\k伮s ZId6&$L@q"֔T?M|%0QxDpƝ+w6mn[LbUS枞wK-b)+)&3 |ZsിHtz+YN| 0r5TW;wV=G؍T]Yտ_Z 7~N G=+qJh ɲE d\edOg5]9#8:lޔJ .!R3bԃv +~=nYX=[ápIaz?S犖Na_L)8rq)Zݿdr&!r9>mpPҭ]]⤫ =%9jIK@~u",1ފ}c}gl"GQ vtpYvfP#q@*PjEDi=O;#M&? 4y ѱ}cQݪ'ҐOH;GAbT+]Wnٕ;Va ;MDSV"> stream xڵT{o9]u#Ep.Q,.(*D:"L%Ge;;91L?"c=G%TRP!C<L1H~ iRP\`8IDI!&C1@J-)] KWI8W(@0 f WFDD.a D @`dxTMJxO }„ΟBݿb4~t&>t. .rt{\ յm* |FJ \o)JjLERlr=>a"J5N$M@9$4T"/;!FB]\ߩh;/0JWSH?kЈP@P@ T$HJB"]o}qzgq D=LE}_)*:PLo͛ Bia,>N) <WN0PШI> -K Op5E$R9HTJNj$ĉ4Q=>Lvr9{t'M_Oy|j$IwAс_axTĸdٍ@ 'Z$nn K_5z):(UTCw\WҢϡDU/=_c"dJK/jbhpi;YGkY򀻻[gDXk05Ph%t$43`ǵ!L}gRx픅8{g; q9L~%E欲36ςKqm,:8u7;ӗ6qe'mu{t,!ie~ڢi]ؼyi7Jr)ϝeHYAfWus#f`~F`ϤVl4ڠ-(!&u=ᣵ0Go =[Ue/yfZ|ǸI_-J6X3^`T{]9yYUwڍKmpjvLZe4<}sץFGbv^EM;ay߫-'T2Yw;DͦFSVWf^T,.mj1dª08ѓlV3[>t'%~+wwE] rx[)ޭ:%@2(^2vqQRWvS*َ掟83e+,=/{MC zTGnLY9݈,nhg 2dž(1MI+g4y,JߞkؚU΍8 -}6uJoaMC4##Kb\[~=[&yx;+ fMr 3dE u`gʶ [`UWIcۈ UǷ<|jՙfKpGּW%mӪ7ެ.޲H"m7zFy[{K6p^\n7{l;CY.z}φ_bow3eI5wi,4ekk,7򲰻T=TMj״GC^ֱhc_~{*+1L욇Ϗ/SbF.E)~v .Ynv]w ҥFj\i lo]6]js u};u _< [_!$W].ow9OqxjIfjw;+^E^C٪E'\\/u|vB]S W_miGw2gj;?9nZ-S+殩Tvk<}I[?Vi_jX0~T+t ewönβCgb5(tQOα/9 ׳B/{!SÆb]Nlȹ[ÜĚ&۟uu{jf]h==S<ۤ!gԤz<| o}:w:q$#ڂ7s1# f69Ǭ*dNr̳VV7I,<װ~f1cUЕ{x>uAM@ä-}ڒ*^`Dys+._ᔝoզfN,78TqwZyȢ-U3Ym&K\P6WkJĆh?kܴٝY8eͣ/l8oPסp ߘT<7rTv8:ޝ:<#\a!6֌sBuW`)_ yjҢ=+_s'7}݈MR|=qe #+REe#f7U8;oUT Wg endstream endobj 1683 0 obj << /Length1 2116 /Length2 25522 /Length3 0 /Length 26708 /Filter /FlateDecode >> stream xڴeT\۶5S@ ww;][pG}ϻ_Vs1jU(( l2vv2Ltv&fzFFV8rrG3IHG?&#,PlPsr32tPm,lT."vf΀,tt"H[ٹ9YY mM9;ೝ-hnhm 35bJ %yUe*.vEDYEU *$"$TU|mf9<eTT4Ęp::YI_(>jhgWgsgg{n777z3'gz;G3z{[8WG5¸ؚ cݿ6p;G!Ĵ9 _i QPZ:m m? ]>@D\?iM]ce:^>nc.N^ӿ"?왅_2Y!9)q1e:-_ p88YLƏ5`|urs`sM-lMLŞA(%?"ef@g#3IW3`jh0~\༜ ]gG?1qL,?]/>u@>Nh g9eKZ*f6e_6?d?N@ gc/G ٚY?/꟣dѳs1lNNNT"ߏa `"F 󗕘`hll/v6&z[;VBF,A?C3qF\ GQ-abHj`A.0`0~01CiF116a0,?(}[!Ӈ?3}oD>2#`puk*;9aAߐ?;vj _3_X nalOYCgG wmƏ!xJ@++c8XX&r5םqFa4[^3 Ln)˛*$?-אXNj! 7}MȷM- ´~h^1yk(k+닏$&4F.X:+Wu&%:z"آ3W8\g7H07TlUM mr"I&?2sb-mdHǩ<;]q'ՏxtSpq d׬E1݂Bq2Vv|qٕ̕/Xbq"^s?-$BAr?R4io\LUx MWa"]bL,Qa3Ɇ[w{Gl}%|k_`@jЋUL[Ol4s!ƒd T"'\$^V릑Jՙe"lQuZޓOj{̏cxJMBV&`LEޟ,2p,yQŒ{:-$IMqݖPQ5z%nkҔ}|bo8%Hә*nD'%ф PJ.T #8>ޯsf36Z>]3?q@lsɥf4[**ЋźxӨ `*$u it)Pmv ?CV=IIa91]ױmקFd=͖i|vp80?}9MO'FJH𛹋DjΤ+Ơn18F;xV.mXHӞ޼Ϻ-yf_Swn5ֵ '"BmEvtO-Dv7.ZŬ<#bKߔlbA< dMH4,Pٽři3[mQ:u\کs&`و&Pk)d nF cZ=38sv%DR1A-2X>I|+ZZ呿k6j)oɋg$bF2[L9 MվEۅz끦¡_0MFھĜVu(,%=+[1)̂ Q7 Jߵyt ָ-Ȁ(h IolA}밢nY4yoZ) +O7'՘TJAC~4kul4a`!Z),C 8*&^?;N 9m2];;y(@T 9kKTЖqKBZoėF6s$ MEi)Tfz s:N=1BmP5'X3Ph2oE'*W}=Ha+xOWXTĒyn7UWT9ĥǞ q$9r?k\֐k+ꝍrŀD30/tixZ1 ӺKyq'wxkz.Jn]$vydծ?h3bCt9bQ{Nbw+.3 +si"yfߗARBi㠆z%Ҿ2/2Ze+_{RM@'8zr "/vnEYrTkV->DQȑ Gֱ9g;Ws%?%,$K;j0S#Zaʠ/7Z%诛<’ȗz:U 2'ɚٌQ5L 8Sv@-Zc)҂x}YZMCm|X44myr;ix|;m

t,7F?[IG_xO(EPP%jhj#=VJEm{*25%*X\^f1Q汑]-˵>sr?W|t sLj!hĠ }i"%L~M.&SRKhV(oFjy% `ǵ1^c=S̫`x aW8~a@=5.^@2ݔ+[lGt[pQ@wd#(uı6,%.ݫqnUK:Ɖuá#Rw qe콣aytkPBo/Sn.-Z"ҍח8yxZr 0wuĀ}xs@^VR9"˝О`5n.]i󲆟wWvNac칡Ji|mЁ+hZ(E\zx&s(T -j>/l*,8&dzt|*zp(6@ךt|6Fx@]Â)> <`+\x8=ՓL R_UKIi4 uZFIh'ff.ܥm۠|d?)78~QRooilHHtkCwIo[Շ˕i@GXNMR gzlW,Pϫf*[3#:aLV蜗hu|,8gяJp9)eL]zĖby=X yW0zC,ǔ+Dյj f;Q7}<}0 FTaQջzhe%~pE魘X\6ŽO+4Mh*Ւ%NDkɰ~N&s{T7WtY|25D Fס~] `k]\@z%S6`g]N1 Q`ѲJ(yC^3़RZs⬔XY¶nâ:vkT'ߓLpoI8YQ1VInt8PBAc̞gF7|/"΋U vMXJvPgﳩmDڤ4=pq$a1"ўs`ZI|bE-GZ5/Eոc\: C5ZPKоS6nw41)P:ԫ<v}ѝ]|zNSetQϘ>6beΠ|JnP cDDi'%(i\yϗc\I;_e7^JēNrԇߣ(`.7n:)zC-5_mNBp@gv ̴Ŋ#p.{ KO)u-0 Rƨ* iSˮ<"גLJ#X r": gnQY"6l[b ;rᑚlrD#`u0x ^;F9Y[66ݷA{@ N|dە'>DY+oWipsfEA$]pZay+FbyZE. . o.DjV w@ Hxޮ(h3D0WxFd Z^js9W!lPKdiuXqHk:u!x|PW_$j^sY;ƨKdhL4a R !/Ynʻxm 8>K=Y_'qΥPd3T+ <_1v-Th=I4PKjZt"nBxS} Rvo^M䛜t}\qqĭz*@ΫU0YAH\OTn(-fȲNHsR>d}#ζmjNkŠeHKwVA=tM(:f,!at7x枤=\V_47cvgqpSJ*jW`L]}]O'?On'%|+ILb½eȚ (h$緯}<ҟ rZYAyI5~$p{A4Ö^Hbe[@6—ۀH'ũ\nq>]j0q;S o~,n#yFҏޟd3OBmjxG=ۤ4Fj4Iq rH$HVUD@re_'$4 X5$E5Ө,{vEX\ڝ)HrHi *$[  IY65F +%b<Š r,6QS?ݽ_H2BT ԒEn (O =qYOrGw(YM}1Mi,}M3ef2F5Zb0٭Jna O'OH^B~qoS e*Z7b({/N\xK>oFU !;e0ԓ.DsCބ'q)joB-rn-I/kHhg袚5~װ!~M-n]I,ͼ6fD$,]Ma9͕,pbRsXǑ &PfĨy^uD`O2Vn$k4;1KZۤ%Vȼ!'KX-NF]B]:%ifOW~c4hΚLSqcEM1Gm{~i@MSrxQpv=GSu)>sȒTqhs!^}5v3 #;.\гmh DW:%xke&/ Srqj?wmx@ RV¥/=Wڍv |WAᜃ'{|G %6,ns pXЈS;`)PVs %m0x)V iAwiH#of8T(¾Z<|Y!CƣCϧ(b"`A?)0yP@.=]8uJ7!9\,H|[@{0&fo8ޓq| j>"_=8PNk|lM]p|+0ԟG<#o  [&MXqB/}l:^)G7b$,à Q'ᴄ4CRVUx 3ﵼHa՝\'cv$b}x5N1JySV ~qi\A^ѽ+o$ +#WJbV|c,z)`XdS6+@Ɖn>V;Ym4C4!8,_;7ehbvf hng-P%F.eowP1#X~ ?q4bv,3f}s"*-`}23{$`>=bXٝa%6h2&\,YK`w] ML喓wh` (WBݧ(-G2TS$PǟEЂlf޵~lbiF(;Ε÷~!Ņ LL 4 "cq%w;`/8i_8d{dc~w, oZc!\xOϫ㨟wʛ0Ԙ}?]@!@FRC; ym 9${i=1Jd!X]'ýFQ u"^D~oʡQM@-f wrd+`be0NܖrBJ=9GYWgY s'xQ, e&XE82#OWq|Y?M :K g 8I;sFSqق3er>c^K1>(>fG8[T+k<ך^ O9Wh^MfV-3asKc6YzIE^$.4zTD75=Ȓ>NE\(1_iJtd:2wt{x ޴>)MRX03XeBR(hzum(KHѷwxcD.Ƕ2(Lng<I/6b6? L`8X+2ٸ< l\ kUXN7bװ &,J uhbkFŤTc իEn; JH?AAɡ*8sդQqzٌռ(A(@h=RrDGDusHhP;WE`9C&/ibV\%2CF9-SNgmB~'t#t n~O6B6hA˂bSyszǵgO=Le>: M%[mŪ>˳O ?1-Ot8#rQ2ErBȆρ(E+ ߫-`l?C\S15-i{[րL(9_N)W{J|+~h-npr=KQxl$2;V4r <>\qMDezI7t-jݝ_hI AҒ4W~aW]ci!վ1gzK|KE*&TRX׳x{U֕%- `^ S}1oKnT 2[B̑fyz TlFpd292W#991a9&˗oC@Qh˖}y#OiMy1&H8۵yxQ"ʥ,w:ؑF J.2Xѳ;㔡p<3^yBui8ɬ38ΌҦ͋ܟ <̡.u6鋸L`nK+VHPAw#h/͕WOSC נ|6QG2 bh0y5q52z?I|aC_x?: >Bl#D{],}6Az.[vuTǐI 'aSV8x *=,b [Jls~L͸dD9B-fkx !'Wo!Ǟ?.;P *JN m?L[ԬiÿZ!B}WP.b-aws^즼N~C&!:lF{,S1yj50n2%܌tȍ,{_QWV^ ^9U>'TlZe3?ѓ[\tY2m,{C/:‘`J[gzJɱN }[Ø&nb~HG)2ȉ &E5gE2_r?7B7s`}6'Ad@'O\"{zk{CHvF9&< g!eV tu~#Y z73lAQeh,$<P/I Xx]"/Pr ~C/\"CЧ9yp:ϥ6D9Rn&.L\>:ːB!A"&Xª!v5˃tLR#0>xV-OT<0)9S-:ƅ(Oh.b?D*$PtD ]1Юj"z{'ΚOIx :n˚ fSf@⣾fA0m]"0~(mOoQP|{T/N+N07N)U!;6Gdr܅JvQ XPӣ@HAS6\-UbSh~t^ٸMw,dHRdC+`\.{aNpOki4KWF 3f\㊜X_x] 5z>E ]hdE6ኈO2鐢| r՗ 5CZZ<_a{:V R %0ujiY\i[ Tr.ݰ7boj%`cN;R_P?u jԒ9xHq6a6<3`#c0w T2Bܚw-tkRcr\>vHGl4/ XT*ϼ-_sW5XfdkƥSP-"sPZc؂9`X+-$  E-GyoH(U EY*h}H1Tc{' 8PVwa0ߣ=D.h|zw*tdb@c&  /Csq]};?Djw/LҐ@"MN^ka,[/ipj133]jټe%,3$RSK/1ۦ/ ` 1} O(BUDx/VvO 32cIWssv8Ҿ^ JsyoSN:M;oZ0Yn(2sBwW:䢼s4[8o|Q: IDTZshTW'ȫhKԤjMIy} -D:xoW"W;~]"U-_Dt\;2~A,vi;H$T."=&\F|E=ѰjJ_'{Q7w/ےW{^nSMF`PY\-;X(↔:*ED;@x,^qX)>`QF0o*#AȎ{wA?{x/ws Tj[tv}|dj&Ҫ%[~7J)|g w'}A0l,x` Y鞌ˉ ̀]~9J /uV8mx@|@#B&ߐ>#ɢr-R؛ zs}Pl# ;(d k;~C!pE8ߔ ,2tW&2ݘ1J<%p Ǣл,%VOt $4 gmaie=/$࿏ XT9n:$ޖxJ>6ŢJ2'  A`G^gPf*WgۭNQDM`LTf{_w59 <玄xO<|x\ a[vPƚ%[1C;IA/QnGY-1.v0lF]|n)mMN#5t;.Y$' 5E/W*P4.ϲ\j) E-,!fF4! $ 1x~sC|vn9zݽPlv= nЌJ2ĥqcRdXsMĶ;Y9fh! F3Vh"m+/L>ȮP\xf0go\S&Z2^+xOfJɲc#|kx̃oNŠeKAL,=yx*#3:yHEі&|+ %"l^WT NGdU[QYOR+iE+SS ýl{\?N4KQ$jN/+d"V&NԶvEi))eMPPX[iGًJ!lBCC}_k7$YTjk]XfTWFr:}#IV(9,A &72,n{ =!Tczw\Ĉ&sݿIt.}/c\+X*3+,(n&W0ren.o:Vܡ*[:FLKZ5}3\kOcT6Otm.j? ^8 n[: %ScO* _Gz]M bJx!s$&6ك1gZXoIQ˱rezcV&%IϽKzi|[RFBO&?_wLi ˗sW@xLBV8zUG۾uL{$XZ쾷td7v -p_1QhJ53skgWb6Yoz(Ta94aZɩj[ )#FH HtRԁ),  أp[/옻Tm@xX8wFš9kŃ|d#;9yӉO 5$f׈21G]g$O$)obzjqoŢfx*qr+1w+w|&dہC@'"vOYʫq-`Y Q&il].3N 0.8k6d ?QbeWJAӌ«?֨luͺإ=sPN9Һl5ƒ Po}i@:gpFjhn-D':\R}վFQo%=օ$>4$u,L,0iWA*rf1 *z?MEk v)#"(GZHgtt ggwuɬ &~cb~ݗg8h_߸.G>U6j^!tDЭ=tr}'be:selz`z˳C23?}Ǎt-v E)LPe})'><$ly‘kE, uby0,S6C;n9,zҎQI$ʯg>sө_ύ7gt_ևT2oX ,4E.3Jc"M vrtcIw#;عHrڞ䄠e ^O rKZªZόcP.PNRgD[t Z`Ӕ$4u[/QY}s1h2__C0iE M@Jp*ڕod`)Y e*D Co&UXwt)}9p:6;il۶bضWlضm5fc7N}y65{v/Ǵ %:wY`[CeJk(KJޛmB<]klkbxsLV'} IJ2wFS; IF0M?QkC_QRIbS3O P{xPK#y; v]ȃ1\#Rq\&6+ js V_U[|Z618'n; N%_fDhW! |rШː]SARǰW PfA0>TݘmV 1WckWHX}b\X.w#*o+]s y̢hQ9JLc #|dtzতRhyP}1B~𕀇~3̍}͈#ya/%n~d&4:  ψ, hvX6RB @JVax!(Si lHu&3-sIw8mGtAdtB}Oޅo'f+JzǍHM0Ih:ݩV=yU;)(_`w1JD̏)k5f_=NД{#3 L?؏żGV#6K˄+%-ke?U}aPٮF\gۋ옺l(8+xq=P ։ y2Y: z=ڬ)(uwRM=xS=;Ƀ_@*о {[bWgCMOC\kIANLy^3sս>˺pLbBUc> m`d8{_]{ʊĄ9SΘoFgdЪh:͎$p33 j8{^ie؜owaqPq;4r`#?Wqlπ-h 8i@8 wYGsi**=ԧ+`40;Y˹?lXh%WńuxbyOj(O2N:A] Y+iO~¥@оc*԰&*E t4*01d7\_lEt=܇UݭbtTAcWc6zo_<eעSf8۔ǣRQ|vtx9.;g7µ@@qI{M>+qk;$=m2;,}?tયH((NJA   ' '\QW->`4&SlCVv^>EUf0bbPH#h:9YaȋtL1?`˳\\g8`vV{h^+X?zh5n!©S7/Lj[uR^]I n0A3t1#AwGH{k|C߮B Y><7xj;?ڶDuAQ~M PtWܮ2#-sR%?˅Iq]߳]ȴ +ɐɴ+*wgSb?@kS?cu(|a׹8î]'5nS.dgs^TpxdSV$w<[lm}~kJ< E }TT~mV!D$u&H{ \M!~Mfb`:4?ƤE h~&mi"Uh4`px\#Ǟ|$2B|eOJEX7d"B Qg}$m5=DY[Naݎ/󚇝;x-S߿}i3Is(iXt# 3Fip1ª!pjtd*ᆕFQ]fRWheu;w1Hki-38v1#tv X.DVC⢦{~r͉v,r_R2hv 2nYɓbFI^vC/:Fu \L^EP'ҟu2 V9j*_[!6|,=!lw/= HEo.n 'Ľh8 1wRkB&|zQ[8{9/6\R$DŷϟaX6R Ş{.>K1k%q>HЊQQUhcCFr?Dur{wa7@hRWZF)T(!9G]vjZØDSʙ\et3 ~S+ |UPt!,>C24|z=kUy ѝ.l<Ԛ2X S"ǚ Z}EiE̚(pSRו׼zBYf;?3_Y*L>dBXbY5xbF两G)8𴌦L,Wu˰lBR%ae?'*x= Aɪ;4׳襍CƣP}]w4j6 5͂7FYxmspTM_w@X_)$y_y x \(Ĩc7Ⱓ%HcNΉҼRy__6x_T*]㳑RlL{$+8ĩ/!D~_[d=t?c1qc,UI.<`|3 JE?6 m-ȼ5 m:.r-%fdYW=^Y\ ^:PٹQ X4AhT;ub9(sCģkIWIMm-i8αҟx'(O40zXm#{[ÇsQgKYH25E_a؎c'-Tsz`S"4|{O u/Uؾajҳѿ[Gۦ3iIֺnvgAdd#}R-՞cQX'`W,L '챈ʰSaό2+B,2g֠ygbL57mUnR14ǤX ~KsÐ"76^`2qg7{Z0c]9+53 hO/o:'})7˶Ne5+q &EDuGUH°uO|N"9nAMv[P=U#Q6xtzƙ7vI-ux&[ȣ c&THV'] Bo kϏ'm,Aj7FW* be2+lln[MŬ{%sTCƜ1> V z wmKi'X=@:-Z 4w{:eU|LP jS -y-:o Ah=6pRX;OBK$^ź?Zsѕx,8~0AT|OH}iK_5dz% It)o3e R+ovGT+:]~A.wӐ!TmEt/'qb|'qC6+,;8"\?['$(<>I83ѱp&Bc d%I;sl̐hpfUCrg|Au^_;#X Jli](z%A+U(gxR9t,RI>?J&"tԈ ttԗ//m#ׄUK;؁ Ni{ΚceAbjJ!]2 |kC\7,\)mmؑ!HPdƱ}|>eo3dĢ̂ =@{*({gV \gk-8UjaPHfS+M"0ryZ-^mlV::< K*;ir҄"eG e퇛*hm]mr×Z&mL7d$YZr2E'  (lZ1/5s&ݢEKŦ)zn*wq%! UN̤,E6Ch8nJ$ӠTg/"k25N&.@/]SPxbk׊! )Kx{ y%R˨:X/zmx̚e6%ޑ,ftG;EJuKuc닯ǵ6W'tH &ܾԇV( (ڿ-$u%?Is#K7"2M`)"r==|frpTk7"i}/'UrHvM]S -.8{n|RMYa6!ϯ \9fǟsɆa$3ro  {*Nqykdiaiuu[ q4H- Bk@zIz%8jyk'MJ~A}rf*(+ZɊF؜鱦,7)Ds# Mup?QPD:N5q4UF55&{QP?/3m~ O G"#p|_n8i:\OBz$V.9)mI;Wv Xh2z*X\j!:D`0Oa "tÝ>*C:h[T$ Kj!P}1xR"n7}Zת}PDF^lj8n~,݇lW"CL0[^@$::m'%R670ik-)PA,Z18 +>16Z r`Y@ezzhWFtEZD}%⺱pFJ-.\X*K? B|$9 -- jH3$\fhI$7"xM`pϺ'Em X!hO6=aܑOѡ%&n0O:|ebs7qRX;VE;%pBlezßϝ&VW[?=pfRn(0dQpwt͇4Xk#Jy!AxC-s_BG$61O+nDXJTjXy5 0 G9-W}Yc}E2Bg|peϠsI%WcT.,©BJWV@=u!`^Xs ,`Q}x39Yga-.rh?FʫtBer1 nH"6 `_MW4='8W-=*ϘRc-J IBJJ VyG#(OmiQU.7Xԃ:euuYt@Ce+({QwJR_?rT , }̂K-' ak+ ֟d^$Ubwޣ nm|Ԙ}גWj-m䴃mQ .Z[s$ZD1ɋLTe5 J HRf(va0TA|#ͬjy&/lFB _ &XQrjT(m.\xҖ Xv ;2 2 !լT*6Z=԰?pi8CZ  M?6:i՛ACpDaS+mwpbfxfG4'u w5*AS AmC,997 DzQ# maE]VKDc,GFqi(}1-ǼmFKOkύ}+F0PJeQ8SօQX5o j]Ab7Ny"Qk;\AdAc*u4 p3\EI;&\ojMQ몵[eޱjIvHJDGۀOGfhL)x41z&65#_9?Zn\PǮ0AXD5̴}j5kJ)UzE?B%#rL[ X eM0,!;: 3Eg򤯕~G ꨈ% xM$ /k}<^b>L@p^[O_ٓ^HLԽWELm*qpc%Kr*02PNJ$rb@nЬI1On/=}۹ZRs]T$8j{'$ڃ(_]W#6Ԗ*|/'CK=h{sgRK+aQd> O? iNڦX=0 erx"ڊl3TFZo6cLwC30fVɥ#VT(l 'j8طAyy~CCZT+njr~= )#^4z?MXpXIVt?i8ZzNU9Z!kK %:σV"|md\ ΑyAκ#ŌPӴ$)WyePsJ4clpoK6mnmhk1j@j=32AE_=X-2De!9J?J+@x{2 5֦%g辙q~=pڝ ;7 [%y 1pqtVD= rp܀kQRI#. WP".(ȋM3!Yvv)rD\jWF1;0˰No[n#~檥\IC629  i/\zQؙjz2)*l[Ty$-Kc*af"8tLbF)vlHX m2RoܿN`D[fHJa 롍E$ϊLv0Cѳ%cK9GThfپ*4?b+r/+ǂ %ՍI@@#[}8QqM6;fÅXί]NgMm 7wʵ`TR9O6."e咿 +'G)O1 "ȶE'e9xbhq/C"߈@sg@[z7 R/Kfd~;,"( 7_8DxMWDXw#(>ZJq,GGB$рWBI2JzYjE^Q(w)50хJ`.T9so)iI.9_|^t殻#!m15F;#ޙ&c5A&!O5SN~~ j!;(ُ>zyOzg!]{$ε,̈F I=A(SHjrw%5 Ӑ``qF+ lhR;x pW'1e[&45 )[rzDR m2>_DyG.E=Ȓ$9J xҙ%zAZ`DtHRObP[0|7edT!nϭ1~ܽqa4pAs p9 Vv{=q tE*]`_[c93KE]ia~ `Y,::%&i0[g[DJ⑑f7~a'+^OFNn;3WQdގTtCL6cY0Q8/doy:u XɓY LOUwl4}+xھ-fJ oүTN q|&(bݴk'g _R鞜u`jKju?^6 endstream endobj 1685 0 obj << /Length1 3079 /Length2 31298 /Length3 0 /Length 33011 /Filter /FlateDecode >> stream xڴeX]5Lt7.an]ҍ4H %ݍt-}~;<ǙcykNh)յX$,@N`VvA;& degFr6N@0H!vvZ qZ̼* 0P`Nn`3 rq1BR]mjp+[4st-*U'O0Y-Nm>@GKFS )?\u^%Te ݗ9-_j!^T!_} Ud% e8~j_ AR-]~70X΂llVn`V'W+VgmNvOW=09֠)esW)!I;b!j'5w2h9!` ` A@WuK:AV;ttwK/ ={_{fۦ" +͢ :) 9ӆ?6T]ZhoTHe$PSNŸoP=Krη)֎8g4We_=?Ns4c{c̏Ap$L)"8wv`2?tV>H>fȍ%Z<[YYQLv[~]| ,۠oVJݢF&9Tk!bHGl)^v=z5.˘Z#$;cK##+k9/saqN!io(W*:btM;iOrM@b`R,gO%Htf-hkܪ\H% JT}GZ@v Um&K 9}s7G+, 2kQ$ Ї[1(J).SOvɾd&><[=TPM8ylp9<T>exP" B׏os&x +4@\kq{[-uI_g~%29St|qcWtOcXѪOQiRR/zhtHe= ۂcbQen|wI3[ jPP+KCS9:޵voP ~*нb`RE{9"XIJIM^c3Jx[#]oxx Zwqca#=1)fmgRg^zscS$$n%ƇϨ.~bRFj+>cyB'('?iQ儙XGäܳũWYįYjۑ4FC>b3ôg|DVagXȦ֔|H۹WrvÍ$K;WaZVS>l82XۧOpTҺ]`۠)V{G@@pQ>\*vD|4m aV>pjI9*8e\Yge9k_0ָg,1ee~'W夠} {,Еsyە:!̼0q7yuT$brqod?B?-OLm[w ěDؓQD\DszIp\̃\a_旈hإ(U'BdCz&#^jm JwlIFy'xz?L1H`.BrtwfP2Pgbh#NtޖK'iκ:zP$IID -d%]hH?%""}GڳrK)Ԭ(U.7.}_Sɖe ,,eU\4 XFSb⋚bٱmt;Z4U?`Izv\s'(Q [U#ޢ#TNm@9;4Tߑ"}+|9RʦV-Nm0aj~lG. :]n(k yTt} mCDj0!bX)3< ds@8<`:摄 ۀZL>Uc#Lj Q]| B}ַicyjWJIh[F"T^dB52alHn9X5a_?oEWNۧ4Bjq˭rE.05a"aVimP5h\CDxYxKKfY FqQ16GciE왬Y!zt\~=s 5{C3+5JJ˛{> h v?oJ"8B]7tg4#?E#a%ޗ*"[x™6IUŻj#ʁ}fB1Zc-Uaj~]>(5UoUR6ݳ \n;1+F6r~O,L'&+]_OLJɻRN.Wʟ h[K`0 [;b*>LJ<5v0ydfP\e 'm g |Vsq I_<'- c\|ٜGEi-A ):w7e ] ;KWseHA~`㌌UXռyTpHĀkZlAIMub>$i^{UH]"ƮqO!pg|][*WǨV^ M&;arW|BS˙pT5~P37uNUPC%w8k%RƴHF.j2Q*χqO:5@W*>وZaE8,2O6]xA[8l+7yQ{=_3;b|5RJ-rI 5b:& :xE$J_RQ\!T ?x7'AemO@G ѓ & hk7 l }Tu:w)b&bnzӇj@8s2}Y%8E{9 @tl4o2&-8máA<&ć+聂l!5t.uu%(4gp{^= |)TIm8vo2~F5!G^Tg$?$a1Say豎pKZkS ,=3F4V=mH.M~˹#ßw斅g0*czg] P+b~y ͞.1T$  wvnn)UQ~IɌ^{mO~4rGW:tfOXMt/by>TѰ>VY'IRx%/Cr+MC o= v NMgSqmu/@+E1o"FDFwc |xI{=+}&̩֤3@(IUtxSLDɯu$Tߏo̺-(y)t:,U½* @~ŇuyۓA%xdT|*)MMDtlx0varzOç8Rw/-pI)f ^РM<ywp 6h:.Yk n,єKБMLu43J(05A:ԟv};H2Hc%\"; *np'?YM`0sm'=–ݝ{mI"_Z0$ qgkr}rTB#9v$G:lBUsklJ}W}9sz~jtnquME;1tK(F BQ\8`w fJy r*cgb& .C$ֺI¡F(sKh*)C:x1NwUZfjp.fUn/nhL[(=E)3n"kdfS8R>u v"(7f~wv"1WMڞ/Da|JzfH!2+6I`(?ʉ} G1`1ߺm'[%~hq Ku=Էw3 /V0z] grÊy zcaUю|QM()#*Ɯ DLͲDqCi=4fN3yZ֘iL.w9۞;@?4N"iw62rtͫ_IIzJu:Nƞ]@# 1^ q. _o&^m)p9F̟@n[(8uH~Jx+2ih [#(~~2a[3ZnD@iOzp|IzJ-ݶj*W)~ W(4+kXx.czl={^p-w8T]KѐLbѵ_̤`ٻc7~w|2]'k똭b_mT2; s&R Cyv=pHW^ys~fz?GZdyHKfDs䢂/{»k1=,N+[tu02LOv\˜\`V! a ]l,A0o5:NY !HasAewӦ>鿂6.&¤!㡼zy@'Y-kH6OKDͭBI Nz!Du/ t$PERqxZYX6H* T6\"pyFTh?ˆ}kݝ!yu?'[kbn!¶}ODwI%^jsތ} ,- [s_h<](}G8 } wqwI_8uhljRh7mn]8;y_B.Kgi_)u8"O A>ś"Ɗ#]!=(#{ (Sb'P!Fͺ>\/t"9‰xgpwwaR( Xox8|tAKɛG@f^gՖ|$8.#ˍL~`U|@GcDy:KNdz_Yl[P2PO>fÕX-]]-3ٕ cOZ-jS+1$b aU7EcRo!a˛"mBu-.`lv:/cܰ>YV>8f8x^ 5zbRkv]}.I(t٥HSuOT]kte"֢R/aY[5AsҫU7$/tTcDrIa.<%_0MnBN>aItU-L~f|]Ѡrq3=0率?-!E2bRs+2! 3CиMo|JbF%h}`*CɷWZg Of{n@h@Q[@XEHP5.瘔is )rbcYQ_)YDݩ,60tSO wnoI2+3R?Bo_3T5Rhvgp5]_GDZ)J=lvU{I_GȻ7n3nn*Ukb32PKG[Ҩ|2o><>cZMQދtĆ=b^FLl+iJ+X5|Tr$O5 %kv aAcjԛ F:"HR7P{s*ͼПQ{o[{yB K3\]:&)n"htyqQOYƝ]츗m*BptdqdhiN .4h}em,c V|;m*]OBw õpA|M) { :pIL;kUJ/H[ur#83XJuvk-@B#>kmefb3dh>&y]R6-KfQ$F'E.l(YoD+fU=K. f8k:Oto.PիYf<$OiOlԻ>j!q%v~a1^A u:'Pn*Bxafț0Ƈ]W^nf])3h w>[b_=wP9Di+޳}!qH*hй 80;9!"bWc/uSt >dr]W\sKV&nѺ^i (y͛y6掶 dm@=+Snʯ(^;f>pb+f}ɾC9 %?a)'/{c&w̐n-,WD2D., kv̎;>uxoabNBޱxA? :~̏3MM.g* eTHjzYyXNjӌ)ɧLzY}P_3ƘݷMcYeU+!Š&a)ҤklgI^6 WYdjswW(sՈn38t|@O| VJs^6䠡wٌ1|<xSUBlI.hQR,>4홛i%b0)G2*:^}8/Jξ{|7;XJvPMRKPՒ ß rOP<ڦMSU^) ӭ5ac#~91TꨊH{y eo''$@&Bxj3ḣӳR$Y FZ5pKՉ %nH-=|o^MyILC>cMv R Sa z)M{&W J(쟬Иŷb닏퓏*vt DHq]oM~I]T-[}#t*5uT'ؐT@>!#H W"-ΜNՔOl,0Y&7fraGՅYJå *Z1?sYZpUcrxA.GT) O޾@ҕi $ooYCN>77=?nI٫[1EƬz|JwK[Bpl#J]): PWf?׏DW}K%I-J=ݵ<u9kg9s}1ȅq'`K|'X-d$ٱSވul\p!ّdpFO^I#iTK$ۜ>GnOc*~ Xca'qd-/5Zy@Qu;4v嘇5 -VF0 qֵ^ ȱ i5!*J)K7Ii<+$@Q19 TyRV؉L-|k9F|3A|p^'A1\[ufA;x|7iLFD2vgZH{E%='!"x{a˭H5OMad.(pϬI+#t m.aN6H_4%ct{6쀄JKCd)aL&<Ժ8%&凸4bwJng7:u]P+TyU*yxy ?c3v4^ߪ2Z9,D⡞rмoV:Gl˃Qͷ_hێ201 } %9RgˁWj]fѩ2kpskdp^:bv ̰JsCOĞ,Чs1{IuXaex@=fm_|&ln>XO$](jWra|:CFǺ; #@!T?ϟYxW:<3ȏr6"}F]CG׳{#=?}o9srxE?>ՒI#iM0XcEQ)MQaC4r}3{diDȮ@%d["R uHb sY;/H΢ ={O9yro2W*BpŅpӢ*,ރ -*V{lq i\qd"}]V'> æpIh X}٣qD{sM$C{[/!/){b[}ƾVҔCQ'&cSd;6%&Ȑp3y&3 [T$bKwYJꑡ Y'B~'lL"|o%bj7qu[?XZ2?R"UʫbL&itRcVX P@e!^iܳa ? !c,s݆dU,бo`wư{pM=ҭ]JQ~G`E[m\:ӥ=(j S$G}/ ƴMhYGaa&,.xS!7=<0hW- Ɨϻ?s=^ ßm.ikXC=5}wadD m ;C,4^/2Z BlsB]J& p)eoЁ"g`&(s瞖(_,!=` M;2URBv VX&ʥ)`1܋X9@3Wp9@nVtү*t!(^ G1o#hkXΏp_TD&_ylYh|~y{QiKlOY9GeC Q/&98϶;LI?T%RĸQOb?0dY܉]$ŊhXiZ^M < "s ѝ弍V|Z@Zg L+sƙ.sH'OfV;hM?;P}j-GȱvJ'S:է`߯X2+dŗ+8x6Mq=U; )`Zᆻ`7aU4N8[*&FLetAΈ醠FHHF0[m;*@rz%?O&o}qzmLqO+#zK7E]oN$JkUֺw^ 8eV/%7LQesu[ e*%ɂYʂjUR/Y,c,cz&#gvt\_;%Rp]*qNC8dg,G6?,nn6] ղЧUHStȁV ϝ)CҥN:_mʯѕZ~siGo[v\Z(꾑Ev0M La)z7toq΃MS[FmNUKx;*No'SFvyB[l3i(GSUky7f̉ϣ6ɮ nL既bLCθ_[l#EvfߩbʽKM ku]{.9k/UNDma]# 蹶Nu|ܰ*:ψ8#x鶮q1ۦA:P=4NCDzϔuIQ!xUFf RHYg%up$N{olFS:s3jWu7ei2`S: NUYx=''SRH,F`6*ŠPRu<*YL#W5EˡOCWN^5/tRMgQ`{4.qt+F:')V;V~}}L "^< f QiKzB݀ VoՒ GUQEIYZmi" '\ZH/c/ j0"ƨ{Y^yB[RCDJ.m6y56h TigE6sʉc&9I,*8zer?B;nZdF^ou&ͮ'ʥ'LCR6 u!MeD)ho=lhc̺zˌܲ?w?Xz[FBygQ$cdt+~AM'w"7`o3/Mx#nxi{j|g/\!mlG-a 26V٤Klx`cgQT>+,k4&hʩ}i>VB*J~w+ts$t&o1Vg񉳁/kWFD1xS{8yP)a8̧f(^jx,X˸UXYu PܔbdU#)OʗYiUB˪nZp@WHʕZмId}u5+~_oӈVʻ&C|_{{Ȁ?&Zb~n&@p59|GcټlF1Mc稼SW~BeXg5O׀d-OoF&چ4N\+blzJ@GQ![?4Ewb1+L 2Tx5XaEo; ;ژpmٴonE:=?RYWO,X*KU8vfq S0}oy)]N%v\'0;r/ YW"oxL }E_V)D[%m]4T7w>^G6+O0̍kAdR(n˕J}^!꓈Sg0T76GΞ]D?Bi(=^LS\)&&~[CJ˓'dVCA&іkh=̜*D޺:m^li#,OWy-f쫖"_K$4!/D9ܸMu$Lo1PAOWŪ?>i O3ǼJ-Gta dQ,՞p\N`)eHЫR(ȏ=t#$o645au*%#JѢiJz\Eͻlr<6>c)0i*sȕ$my!ErJ8j@F¯%Br8m4=}{C+ȽNCN6d['Xo ?q^[ |QQWM}C՚^ R s2G&b*$Y_Hú띁+Ũ82|!<`WF E2Gp&/غ1Ζ>:M.{I.hEHe -b ,b=,H[1J. &Ec[/ZQD CF&8˓$~.CP@աP>bhk}h89zN r´'YDRf$"O=vr۰JES-8w.Su(R̕ zf+.y;Xfَ @'r6Uºf<2tPؓxL%;' |G`pK[AUgfOR[Rȓ1/b)wHhn,"`>.xY+!< Fnf` |jaU/|%t _Ii^*1DC4.qQR|A1+hw]qNȧ0],m[}W딧.&XZ%V^#7PXG8 *\,أ\^F.مkWb99wL|ivW٩ ,"ZkČ<ŷ+QqwɆibHJ38}bD3E@g3x?bИ@/Oߧ'b+Q_xwz'h,.4V-AvkYRVh>G0ߚNA@` P_ b>Ϧa80uMƷjj<}j* 4@FtfX&7!kyYTiw',?4ydE{I1]з*L>B\N꒒'H+Qgz_7;yԘA@QwTun :(%LvWFdut_۱Pt-;Ahjra_%?l\#!mscXa u'aN1zGV8`}XA:h= GmO\~kQuZ/\gc*yc;u

Gy)-Ӱ< /=l]"AndAcw^J` :^JYUziϙ'$YFf_i$͓{jtFυ'^+9!قx/Ă}w^~D :g[mfk4(\͇jWBwN}oRHmzcR%ˤ`BL(@%ٶFmaG9Q0`P}PMG k(G kFz_dYȕc~ )\Tws v8X+iNf3ꗠ9žƒ`s(4΢oǘty)'n]_s^z}Àx9ά?P Wg:4|: Ǹ!=w@(r =nK. LxA/ 0^ajTs,x?QllRy(FDk{ 8P\8FĖ҄T;kv*F9Hl_ɀ5țO*jjZ, _mU#85mM{^/`w=hBHqF >v_V'@wJlugͤk %C?`uE2i_k-Ke'8a!0)bY?A[Fcm*u>G{RzPSCKiw.Fמ8n$3hʓjkrۅ11V[~ 9[CJe vWNg7¦;MA~5Sd Adi8AxTtfkt/탂".j5#y=@aEw!Ч>R> H /~1Tx6<1>"ڮ `4?_#Db7Nz,~JOT@'윚[8~ַ{UXL)Āgkc"E<8W"?X2mr&ȃ|Oo5S0Z칠мkkt&YVۋi6ceQ/2W}i:]|:eā~7l ute&mD6nC 䴽ٱ^qa["yL d"8AXN;~Nog:=в jd+/Sȗ nGMqFOJLMl3=YG*rZ SПNB{w0[:SQZPyغ KߴCm-O45S u>{P KCtnAk{{I|ʈqxSbo;7|˄T}Th!Ai@ 3J͵/5J[( ЩEq* G g|Ϋ,ԗճ&3Uc{c=百Oϣ 4z3^`Kk] Ax' NA*</Cx9/v妞%$8 "V$۲7?kZHhjٯ*J \,D J'5zqE1$0YxLxJ^k7 lmoy<%2/@_o]M㦽\fGX9 IGG5FǓCIx[1ҰScH`nGglGuYX&1d:Ta*Fi\YٽGӳsn;XQ qe@sЛS>D?JK5/sjCV'_w!3){R Y8ʻ9!qu(o{U˙>Bqkmol1420q V GYmUy$ВZr}2ne#Tźkk<FpeqSDrǫ!g5z~[;tkxf<]%ک`9dYJߩHYg>ȕ&m&fMC|=(J <>sRSf5:;'{woyX߃<71!lB,Zˠ;L_KJ6y2p\,PӚOF(}gW(BhbS4^D:>8Ki߃O&b0PqQr:5Z[(;㣕Gjۮ="]t=sbȖ@Yc_V/x闚GaKkQrmLٺ;6`iWbH;/t٢o5a)lWj>3ɿ'}3 J>0f- Nb#K#\Ky`8*@LT8޻z$2'./gB/q= Ly&cY=-kϗwo^9/4>ڠlT${ыzOŔaAuq~ZÁȂ{  _3VZ6~Id{Qs N@O%ʎ >cXQOum Bˤ `r0+XUm 8kT#OR]OxFow[{5DO)<3`N^(8$b7]g},C.Ω -3m4s}Sx\2SoΖb܎U=y_49{S$U׹%b*`U_jҠ+1"w؍ekSQZǥ%j;feL= g&.wWl987F3 & `dA1݃[o. Nt=8'/c6Ű͛?U/@E`_awO*Lh}j}?h{lI00ezrW=p-'n[wQזz;Vz淡y5q ^F͈|fQ9N 3GE)H3qyw8w;Ҵ+1Dem35zPL2qc085D. Tzm}#R1X ̞z $ckY\J^r~~-nR0Sوթ)$(6綒onHbma^§Mg?W[0q;Hz '.5"ZkU t= KLkZ6Ta}kmWRB$r(>A3"1UاE#g4e8ukI;}]_a&Tڔ ¼p]/QG>^je$͒=~eO/s |0ݾɉ+]wyb (6 t.v1E@{oµ q=w l]krf:>G :d3pGE<$APЧTsq33D?փ:gKI+ n ]eLP$苿^fqX`&ն9uɏ?(s Spa#%@:s]3:j)lʑ:.X&&K@mt&R:Gshx|N٨G(~O\?e. le %Kq40߼;.lZKc!@?٤YM_$҈U/%͜B!.w]%]N"O,KH~ ?d'E0Ej[z锥 - ipJ%rzXJ !'*ލ+ŸkD9Ӧڼfڻx79uԲ?]))'ؓ {Cfkݨ(Q;X":AV%'T́2.r% v ,c5[ jw2Oڑl٬ɹTƟˈh[!0h;F/+Ӻ1|-X1B $-m2 blF'n/? !M>M@7]"oM0TL[ЂNJNRmEAE nGӝ_݊khM1N/g2 `##νceW*px =/7<}q9SI_$!~@}FrĘM-zNP+` mByYbJZ/GI')%X}«LitGH"WCQ ,xÆL;y7i&G+U kEL: (ӌz|؜W1>]Md|PA{ *VydL6*A?9_<s%Lb?3Q*D԰ TH[p<2k㸚ϰr3f.+Ў+l+;^1Zx/rbrKɩxDY-CK6iuh ?/͔VzH. 8*4:^I:jf-F㺼D:'8,r- rtab WU֔Qfnr: ԏ Tb8j 2>ncv1bgE/m nf¦ȮԃrQs1 m։40|$T4B|BXm]b=|^&d,'nrD'}>&ʴhNaF (15B.4ʢcy8S4 jds6 6Gn~D0oQ+?[FT[𽗋l3h;ẐBȊ3Nl/ͳRkn@H7l#P"g}QtaT"wK&çh{zK=D`8ߡFF J4g=Qy+c@<2P(3 'vw 5!%͓C|a9‘KzJM 熣c?:B% |h1u9QNhMRI[Ώ7o˿r+|Uyojd-ֶΙIl'2r/,P$wKC#}%تhp"ojIoҼG.,W*ag&K5GJ]ڈx#$h \}|[i/Z5U\OmP#ېX9L猜yzEty rטP,7oV)UD8EHj=/X}TퟂQ}o(Ocox1hAPW-惿.CD6N _J73g -Z7| Sԇ#o:X:0:$qبZ.eJSe!1 94o`zMOx2.?D}px?ٙij 5=)WD5ѐkC$`SI:SuIv)^Nŕ=ZW.zZQc˻]%9Ž6ZnɖЃ 廿nެ$ߞw1tu4)mQ+i OwC' OHYOGc]s95)wKq5NoHDXELi,͜u P @ێW(:ylo QܩؘG]-p%vg_LiUj 'PA%7Diy_;s^S`) s<㎥CbVu!&`7ɇB;$0|Wݜƨ&O&<%kfDu_l(2M]Xy!mdpn73b%._tQ#l"LAw%lxbXP^群6 Ealnes9 cadyLý~{lK-r frT'P낆",8Ďb{&EFr(z nXXI$V :f!vD:]ntEjtOo~JI̕[ }mu3-=;qw}c@!$s l+;:X 3JxPݪB%Pafhf]d8&0ďH0It팳R~X;[p4z3c0g.xa(A7a-l=C` -=eELdlykن> x FviɹR@of^P]" .+{IlrqV}Q}SqC :?$X#]ݜzV.i; RcB%4s XjqǠXkjqG OTXCґ z,3z ?G`B By*Y1t aq(Vbx qR޾nl)^c],"s/lJ̀g^t4hhst@!G ]aS l3Vr f[} MqW!=*˩UGWy #347EL  Zt@/jb3yNzipUz!J-J.%" U パ FO9[A@ yPX&?w3_Zi}(N#]+Gi2ڔ$Lt,AfJyJeUS*T9U,lo{7/1*6F*^)*^"[@~=GvI}O^*9\ k9˻vkeõtay+yyɄKXTi0GgStE6Ëέ'i)-@2 D6 OphwGݨ[0PTqUhG%ĭWZq~ksbzG)H<<_:D H6n㡉io]Cu`f]bn"^Kͳy~.̗UOWE)tsXI@O^014h*62[Q Z1[ h)iNՓFH'@-5qwwa60@Z/]ZwB~!KYjŪj&fs<*7HEwn`< ]`(ϸ}/y ÄJ-YՊ; )YT\7ڨJ[.5X|}Ҷ-'PM/˕^ӈ㣟Ew2yRBt,EJ KG@BdR 3=:}SR#nJʎd:Ł?|D2J/2KhM8hVZCv4} wvϣZ@BwICπ]Т3X6GS>^;1 1G5Wme$ Y&-kN$7q[@|Zs͍^2{8mΠl{Qyo2DZ#TjНdYbtQ/"5{"n9lqFD25-v% (#gjɪKrC11[<(:)1ct#{E}HK?_ R^@e꿚 o '%JHShu.(9]ZM?d^Kƽׅ57iR*p OQ-lpɍK"^?G q2)(_7|MՃ'f׀Ҧ1?- *-Դ|⠃jg\9fznhWc""{,.9rCÓSaVuй:\ɇK0f[z~~ѐ"7rL,bLF|/6Ȼ>aMTHUF/E2րH+IE+0d?K:eE,#a9 [J ;$)wA}IXky[ <>&( غk8SN;r04+Œát̽>*/J3Nb˂K'/^-zŽP=:c?xg:IP|%gA3_Ŧ4:bFi|y$WrWaYcB- /3QC,?Vř>'$p[lJX"zO}j޵jOY>4$ʏ)9* rj5di˙..~j# <'{0z L ʏU 0 r{Cʝ(ga?]q`Ac'nF6G҄,wCWEF©JV;(/B>`g MUPMϻvsG~Ed0>w2vs,WA,gh֗qViӸK['d| a:$#e@zHs+lBЌ0Ƶ>-9#GAiv/BE~b{H>AN3cRywqkgq2d@+is"דE6%oJ`l˕(Iui4TE:D -3:kgDоLC6MU%jL@}TA= As6ɚd&/AHf̎*,]uUm6l Vz':%ʆR)Rľ}b<{ '|ڕAZz +c1sR@Gj C(ĨQ4yT9M;"A;9@Nz†pn}اz.nc<"Y%͋NC1a= rsI*;G?۬'sT>nRZ6)t^ fkxi%m㗡 G]Io=#pSm_ĘAMMe{:A'w/jCq@Ѵ+tnϟ+O #g6<+a{=WyV9FҒo)~[!Ioҿ;ɞ{ǽ8Kٕkp_K72T/MELyE_|dA7?֊z:kr/GY^VHhfqi%g Z&- .3VßC`YhcW~OUtQgvjR,à4q ]'ֺbSHgSiE|v%ޏ/>y/Ij)_=P&Y%{hAi5cG$nED{K*s[sV4"zSYA HJ0F %aw2[IxPz2A|dtϻBa 9~]}7\BZ<%uznʈvXWv%X0-`F<h},aEd)_!g  υ=P<}U]zmH^ʝ> stream xڴuX]Jl@R6 ؛PAii i}s==kf{u)*(330A .z B h ًC<N%@z01qQ$@' ;ƿ 71CV@h(++=LE2Ʀ6 W PBVj=hilkԀZuUqU* 4鿵KĄ@ :گ?Հ- j_?ʧ (e]kljeejdl +[3?v6_f lG_I rBM h mgev7ef?1!_PLAMAY1_;?03A  dkwL*h 9:Vf[㿄ea uK ۛ؂pAXze.٠Y;XڬОѰ BE T _?PIjdlgkWP@cV+O Dag` og٘kߍg6tǕ g{٠:;:3' T[Gg5 /[whmRI/fh_MC !amb ;T+h?Ces |.A&u\nAh1A(ǟVIZA8k-fB>>#Y i ~d42YElSio#EۻnYƞ0$镶 07a;b-`t.LtBnc? /N 9|н%-M#fNj"C._jB*jҰ $υ ݪ\g,\hͮ(~qv\b *Vu;ٻ9uP8 9Y ϙ  aJ>l IJvu\"!6` ΦI6b d8BN<#_)[FI1̓[Yd$Eܮ.q5u23+ޚІe =kH4!lQ7.lS/ZګgI7 wUethu&p>_x\y] cmE8B')x(*t-qTY@!"gT*DKլ )ď13d--E$of=I^ Y4ިYOrkżlsdG㧧gxWMA#MIU js -/1pCsZ|h^B-ߊl1ʴJ,op{yk--p:#3\s@R,߉oiMxP"D>77; @:UM7.}wqFkui@0 ^ q*Jfƒ,\VQW4! cPk1e:JI2('>r7d։Ud$+W/ZҚs0waaBKyGQy4|JTJ]lOy@ݹ?4tR`a_>nrh>bƊ ^?']SJ/OŸA@1{Run.~KU(M! @_ !jYٛ18n@m>Uݡæ3n2*_"|{ۏֹ5]GSvo%cDZ.ob<dlI)J$\&BcLkI Ý=i]x R%[ gh'E]iɌ3)_{+4?o.Yfp0yê,v=LMAGfoe/Ž{-U_lpӯ޳JHbWfeX`p\KEOox=푺k=2޾c/W͗ "H޿m^@=x\gNa,F<-$5A ӨPL3qiʼǩA>ՒL<*?U+['8#kپwM@-/S:ۗ0l6SEym5S[q5NI}ꥥNmJ(! eܱ֜kEGX4aj-}~>|ӫ,8콫U_GV -</-M+U3^![I{ÀGJխHf캾.BjSmSoM~ Ѐ/7$AOiu T*OUY7J=EAyžyNl xos$!偝[yoKX*v3tmusuB)V6sCfڎtUBvN%JSV!Xv8eoApR袄,'5:# g~䛸42qI]ES&gD% =3>-^;9l3b+^@:}[jSD+<6ngӏqz z.Df˪^jᒺxmQ,.:Z]^0lZ~*f]( !h,agpo NsJ0ƸV<$[UU2:v8 9#y } /5U™L`LӧN!}Bɲ0WUu.:l^ %\Tr#HMTsuNj3p]](Z2p`i{"Y>ItmM1fKon'-MPq-{y}L% zع~Y8?'6~^kOU(& R픋t:1)/\Jn Iãޠ#S!:QTuj$( l/Lj9t[Zݽ,J^ϥ P/"za/Ƽ3SWe͛xUϮM䖈H"_#mO{R).B[ ETxD {W]WYr\fQMb?~X xtүμl3|l_3Xcxaz9t-MY>%K]n nNKc3*lJB*ޝ?C}y v=gW[Efأ( sCiu7 *md ӝV57  JaCrd 6)&{E3GK(DZ?,[NkR[YLl*bי(I)p7q\rc"bCP>ԭ*T[g{m}Tٜ4vnnT)%T-?5*F3=gmZ/S>5 o/۵`zG[1u}^? /9*m !I*|ojG~Vԗ Xx+kHCɆߒi@ϓu'~+MH-P\I5)[DyXWQO5z*MÁdgۡPC7+ u@ig\\u% 3N2CKPdGL)l!uk]Dxq4%zIJP|J>өjùK ~c8{f% R<5IqOMr@8˦$8Kfq!vY&b L0#q%/P[L"Rߓ4}Yz yvbxӻ2|,gćDő0> MHG'Ţ').Y{ b;w%%7͋a|9g~I,޸Wm&qOf!SqabCu^͝;ēu8 {uV[үrfMI= buNvn|Ҝ]XJ *H ?e֎,1cYU )c%]d/Y끝o:p瑅p|&2.,_}rE~t02B&t=U9iC ,kW*ZSWrυ^ᶸ k ܵ fm\ҹ /2L|=8 ["c6`35ŢΛMmMF[Dey8~҅]k?gָbFu`O|*KS NXuIlМpGmc Ȝ)) neY_Pӄ J Ym{Bj 4 YzwTz jV*𭔮us(MM‹ &HU߰}nD<9 5]izfj#,[ߋl;!+u 9eUk=ܕzwQ][Vsܙ<=l&qOHOKBO"8dYSMHIGu5i(\BFy:/-\^Z- x|Fg2bb*VOy}`gNI.j&31\=9m_t_\4{fyM {Ͽ0cf]]>ΠbO}=]SzɉKsgc*n#o<>ze`fQmB<2/cv*Di_j`N_ECPy΁R ,2bXn|-V5\N͞QIQ;5]BЀ(oEXKA篺$fdN8:A6 ktN˔,[8fj/cM[mrSv^=C  ʶ ~eUHc횼w\D1k[0V! e=stXk9^qZt%GFӳ>S[ж0b jA؋0_-vg*lnF\< 2*TJE:دp{H$|av_qPK/dl1Z{W4O= Zj?PiꁠpJ{b2SWUOٝUӍveyTPdWéA1?uEp "-W\)Ge]V<7@~vQ>Cw҄\f;b?o]]0Vw˃B*"y"X_D)ɧ%ʼuC- ^ޓ1ֽk{̧i8uS kɅ6LV'1Ѐ]dfb DVX,D~D&'ZuZă/RL5BTmnIûG~,?IlZ[(0\RLlZӠaQ?WrSd~6G[N9a;rz>Vwί՟ЯP _ RsT| )(kd842robY|_au`+/c[0ף*H2%KD}P:خ7M̳~ch [6QG r@JDz{{C$fOtEk-3P^LpG닒V9q[sJ:5ҩzLANaa#l5 \#\Xk.ܤa7\ È5%} y$ڷe=Ghygo:J^F sS1JT>ԝ`~+\P'Rtϝޝ_(\Der`"9ۅOw#of>إ0WyD>LE֯KWS'nۯnH0(~ A'ê?OLNNpKs|F4@E!ga,FJSJ2LLr'RLL~V3} _ j\ {j$ ܸn79'CB Iȼx?Ql Xmo2G;vګ9z. N %锠!^ ^zHN @-E(_MkSr*kչO70lax@# ܝ* v*N~|l9Cq[!WEEԋ ..ۅ_eY5R:㢫@Eztnvİc$ɶz"9Ob?H=_z һ@iγYFPؗ>Q m_T"]uBm17yK%u'1'_5`Pj&X3mMvAtBg LK;'m+),CqvtnJo?2;^5o60тTx&H<\V6Id#3t2khe=Q;-nEviFpf;ܴFxA˪wX\&p:yNz蒦! KkDx'ZwL5qM 6s3a~*O7l-.kr?n :,'\U,M6$v 67 djI!.߼#AycMʚáG&脂Dx\I]ތb{biF4IlCߵU<f^"^A~iكWDqD1*ر8gݚzZ!n9H$TSK#:ǍYm4&gL%r'֟~W]@Sy2Y;fᡶY,ZCkATշo0*V ޜonXϪ X SA5xyoT>3U\JĪ-1dkwɏ̠dwF '+z]. 35H5mGptd=ܜvqVEfrtf#.c9 +_ C} 9I Bl[Zdk\}\Rg/4D#&Yc6+'<R-1 f|"4}A{*+11@u?Wo:&av,|* װO[twc& /о7QI ?s?yu‡Jf#_#h ɨV cǼs; Iۭ%֙혥߉)^ ȬFzOY>=xƨlwZfzNl|!Iڈ dSz0풀Pc=Qyv-k +Nofoz֭NҤ޾}c6Ӿjw[6u`r´ekU.تGE" N`k澬ZknFyßB"ܜDxmr \#c/Q Vΐb,s8pCP"^]olCErMD;#"a I&'/yǬ3דFg?9m1i: %3mchZe*7e 3'.w+I~.+k_w)kgOMg,gKi/4o%Fu kTXڐԢf}P[Ɍ(:B2rT"ֳ_:bM.C))Quq,?[+?iг :ˆ/q*Qp+Vd}#3.dIbNJ6mx!T.[^ ^Nj ;F^9)*;YAf ]?[ }ߦo ?80>jޣ- ;YN"p;] ט1Nq`\6'ySvM ? NIai%wN 먡RYgqf$Y+-\FX:rTTҹ uy32ڕ^ݡmvѭ`nED)o$l`7TpX)T ~/Kk‰wqcH_ڹ .e ur l,>lً0_& }nf.Q-ai)saV_zu; 삛^Fb˥xۥ1jB7_iO /eLV{Ѩ`zU@OG7+Z۩ Li$wOvhn.u.1yCS^XolYiKu.kMWD@؎Q|;<&<fJ@g~ L>]~դ.5X}x|@ Bfjb݋UзWNVejD?LαUEp_5Wjߌ6RK0pFVV\-䪵DrXsk;^J\7N?ѐ6dP˴&wMu7}Bކ:9 0yu^&+yuƆb!E s%\ yL85$yè^idSU^nq&<C5/4.q =`k` c7O}]U%ut2ӽ[3Sq`|?[N6Cl9ķ9S^QC+Yc @*[,`rez;>mMX3MrfyO"쉟JƤ[| 1Q̋~ak%>+>Zr|lDFb>(;6$jrM (nlP`*wVrJ{Q\%=:iJbqSm cçJt$ML?N4ȯmq ѫ PJ\ϐ[si&!x{#`t^WF/pXk{4<Ԉg>=&J5k=kt톻ltQZaKu hbd]Hkd8[FG)lؘsU&[-L?LК%f?Y#C՝偈CoZ;ܮBu[{^n(Ere`:14f)2|Lr?|օ YA;cl;Gzd;AЮr(ЄYNKH0Jh6gڦB'u#7Cqpw$װ~}4 H?7d+^%rXU):"(Q"rfBtX|}BddG>.A-^w5 k dBeN̪5/wHY[Adu(e /w3'2{]ΣY;"NJZ{,IHv _Uova,SbqQM,vEXFzfw01Uik2-Gij0L)LBX.%]7{Y_,S#36NT!9«ж=SFES0Cn jp7dIb8( |ڣ&~}/Oi(CV%MWGܢGMC#iq-|J8Lze&ZƑ֗; 30Z}=J08qp;M0b޽M mM|2Q)-:n۩lGkmAhyRM FE7jx0ơDKjLsݻJ ^Ӈ-=dZM30zUa /ZF8,..ق}JQoԷ\Ftfm]n+_vɑ<Ǥ4ʧ7laW($sI s`nĞ ~6 E+lsYl7 eHÏ3/R,41ba/iN ";s0j؃{{`ǰiE:_V#DOĵzEʥX'U^ߪO3;k\Io-ܴd+WO`#mlQ\<蜄^Щ\-MlsW\~3i MXuzQ7oBӉt/O{}!ޫ J?#zYnD"Z1Pzɴ׫H&o>jM1.lhsu?q_#j$b9!d̔RR7+TI:ӯ7h'xId=#Qg?H9wl]/LQ_;DI:ۋ쬶_+ b LDzoP{3 ( Ù:3t@cFR@$pf!E @J,0Frs'`^2uT }RHɷ6^5bRwmF9㟉x`>n9# u<@w蕺 ww5&,$K! wu6d.Ⱦ|gXީR@C8mM͖EfΛ܏\Gj~=ps;MBR3‘T'<@kRy Xy~Џٚd0 0:$als[]߆$L{*?)GaӔ:8ۚ}<ρW.*/U_hRLG Y,|{+)yX2oƅ%,F)kDs_;#f\ZbRFQPo|OUD)!j\^xO08,%SWH)LIA/07E8Y[D=z%O6e&Ѹ6 S;M^N./v :pbiͿW~CЪj曉-`4&piMK SmX5XfwE6Zh3mmjtK-(\KL4 2ə5 󢌝p)zSAv4+!9n_[E|xgykIQ;ע١jJK[c{ /QV˓ Z ѧĐd361ٰX-V/&oM `maqLc$L.J,17gk]ߦޝgeu1a|7(رx@5Pa0q}C!`=L~t;Bbo&⚢zyth_hLJ%c} 8t2en0%Ḑf5&wj!X0־nIAncl}*T{j-M];'QFYN-NVt&YI9FozL (aX=V>-ֲN~s0a"EhI5M,Ց59p+daAn!!%9<+U-]$9p)4a8tQO4Ռ G %-;Qr`^\ D 1J$@F]9V7Ir*U+Dȭ4cWlLj+Tq{w,iӑ0>ycM}#Pͺ"PB>[C̗ՀCw,)[N8EI;.,+E|0jHf1 Lwn"I"ѧ{ hxAm}uKFf(|? `<ܞ}|U0Zj"{:Ac&궐QC:zqo֫Ϛ, GJ GUEضΜ0@2,y6:p;T+浞^1y=jqFk+ pu`7*;R;xrpz6 H s(g F&- 䀘 KF EYdlUn H̡^UHE pUPAt]ꀐn3E֍ˀoE*RsgU/vnFGF1K|1J{{~m'9-|0фv mhBYmsnz9,#7BOO#ø}崋DW_Y`FV`4]X3ŏ:q"T@aW3posk>^n5f~y㈆)=Pj]* \jËmD@!,y‘ԢS Ϡvw!_5q3฼![D@$e[ߤ \~~+숽{ ΰ",:7Q1kas~ g'6iFG2h~z"qDTZ]jX3UXMr(s`*GrrŷDGqEV@|`n&$qid!:Ͽ)Jh%w :Wz7CWBvz|"[W6{e-g/-eSo7̋_Pl:5e쉈YІ/M+ž7b$ω1 `U#G KSg,ʱ(,jjL;5[O&>qA9a +vŧF5I_wfV(zܳ&BX _m^|34'xnu">zk."yےQ7n9I's腬,f'Wԓ C<-]aSvF_ZE ].+'.?c}٩R*t5w~%fx> ϋT8+2?'fC4w a#hY/S$2˞4Y5C#̶9mۿRUٮZ~ 񿶢G{ .m5],Ȧ<(.DyXP>@o'u8JLB, 3މѧ>Wv1qP+(hB.`g*jՉ#hɑj^@1Zv`>4X =~R~Kad ) [4{'0T8J6ݩ9]&Y<]1 z ޞ&&*,?)W, g>W24CRU6Uf8{3Ep_wNvuxSW+;s0&Ʊ}HҡU..xY73lVA8YWiya 6I1 ve:rhf />R=l =MY8Z QlW$&i2`85Dy:I UO/?oTCaZix)ctE}CC /搠qCNE7q*yd>މyrKj>[Qj_35ruVE+Qr1Db׆/0>U[L$l,2PBLכPݭM1 uW6ϢDLT"U2ro cP-ʒ'lMY<ɘjz.ĞAsWeފ qԕq%S57&@Ux:BzR1o-m آ$g'ۨRz\/@4X^<>Ji"ݷvW:?Y#b芕#B2Q/ %KanFuo:4A5(jVH{;0·_XF/wIC\F |g+8t98UV< pI^Apܻo(TÈ\mŀf]JyՓ[Lc뙭 ):΄*p ((.'J{Oԃ13صp!jا]i`CWTr~ 9).}LfO+sanvICp W&+ ѱ#vYYrl0ӆUهb<9{-A94W3^GպϐpAs:2op_ B&ʰGׂ}5k-ns T8o4+V21 ު~P9!vLJ(F+%lhcfVjE_L!WCHUqn4MN5]e!r(4  *qdmQuf 8 U2ݮؖyrbPWlpjQBQvmw~]ݗ'Q<:L:eȠ+ UzC*UC0#NF9|Qu_ XPU?`N.<.򳑀-8z9-+~[(b1Ǖ;w9쨆BwR\ef3Sv 7E[|Ӯ|K` R6` KaְVc!n\b䫞 ]h[sj'eK C/V4T-P*5Lq.&rIȣQgl1Hd ڦE`hE!h߁iCQoM HgO Ҿnl2įG礎6CHɏ< X!<#cY^;c i6yA9m!#I)4a iw{[{>٫zs쑃B 〚(GHk x=k ;\U:@䟤cUdlZx8s}-{͊boi2)H{(kd|/zM-iߞi7B ԡB 5Iȫ*=$. bp栂Y% 0͏9c!Ծ>!;.005"A3THY'%:iĪR@_#Yr(}cvwܣL;P ]Im˖cYآpP^=ayC<0}.̹FfGULXGJ >P-lVk%Wp G M}Bg"Mu|PB+K~{E`> ܒ ( @O u{Ilglqp8~Mj=aDlL~zU"Fi% .SKAC<6̈́B4mԿ?ް)*3|~eA*c:^3@Ɛ)͗-)[ҙ߱ZmS x94E[Z`WU S˜}~^|ZVbڗcK,ul 6uːB\&xHXJjpțE*g =*7]='&pQN6o|{,Y!ƭؓ6m2&t#®K.Sk'CPSiFdcq&W,a- HRQNͩ€3WjY$29c7{*LVa^XQA#νceV|^a,8?.^$X6lO'9v&)I E)^0ncm`UԴWp;䙆#CF]uL򅟞{\-Wڻ;L߸hAoy/YS(?GWvA'M)L e` /KV#UCY(2|o4<|7&+ZYUVl"i-_l;^Ua .%+vblW>P1OvZRZt < ꦤB/u n\-ROr&L*& @uK^J WҸnHe+#Sp Oĩ#geq.<[mֽ-=Իe7_yS[& ٯ`Vf!:%9+qy9?Cu^!y?>yA4h6yeYJz>VD\ )):x#ZٯPv){_0c'K^BZL&|yz>d-ti66-gu75&ш\ MvqU@ڑ{/LDND?dk#G PjdUGUu8lBSy316k?yK$\yɽ%- P5 =c|IBA&?֓`x JQG.~1p hZkkV{h*Vr~it]KH'y}ըaxk)BO;oّTp&|AtQ+):&ZSFGG.Sߩhtf$l[PP/#tM`'Bpպ'dSgt~#( 딹oYsq4i;8ՓF,(韓'&8%\c'%`ezPuD/gQi ޒ*` 0J@.n#7~r#8`\1'~ W7yݖg˛k_-8>>e%)្x29>9*՝&!*b kk瓳:a>`^eC5֢.+T*K }|)$BLDKoV&RL*Msi 0湙O [ b1K^]ULrj름(DBupbf K /Io $rɟ[(r%/s"WEud>;5vz#2#;X6~lݠDž U<|,)MrwH]lHN oî^FS޷*/zuiNpKYl0$OI? E\Zg]X-I7҉gm ;kZWq*bm@(aBRO%#bMEi:h^iƴd =ck#JZn cCbw'n$ɀJq=-TWjS2Xhғ=_ E_vGՓB=r;tta\-`,YIP'8aKv1̐Vˤ 4VIfՄe@Y8l($ι׌ 0"?K:i AqU3΃KHH!^oUgq;07BtR@İk*cM$7'eڹivD{$)r)b9y(½s, uC|1\i1MnնR. TM1)RkA!{ Ao@KhV0;AzN"sN_ⅎ7|9<w!ȯ]K|r=V2NiBt-.ɋLߍw*CҩlE,|IkVK.[r \[x?wdΗ?ŊcDԡ/-HK'= r|_~ZdG߲~{܊2d.\ƞZ%`ZfDY|]jzv1 r.d\.Q 0Ezan J*hLW#Ƙ{ۀ 0j@Lg DdT# DAmoO4@ǐ3w:jcb;<S5V<<@ *ǽWFN7Mi1_u.q֧>)i(ǻ G3;@(WO +Ǧ o?>0THLب:NQJ*TK4=ץQ%ta->iR6+1d%`>{/Lʎav6R92ݬʧ_ T7/+9Ct,:w>*#x' p7d0|>L}x Tm,ThrsNh>StiJLNQX',C I|'<BgcfduljCvd =wD>Nl}`*D dkTY߂a8g5ʀFhNJ8_ v=͖BHDOMިmPh޳ԟLb9y9 %32G%vohb.r- r Tp-}4<6KDX-F?yXBG_V"Z?ƫz[,x]'h.n Qw`fҔ{+(0$ω~׮t"M,Xyu\'ɱw~ *c8vf\C %N?&e3~J}I<}Iǘd֛B;G {QVCިg$ ͚.z`gɠasq80DB$+<sE\L_*o)ݯ=W L lN:jX~/DDݨj_9Ȓ3X/;]Vx/ ~m+B rxF۸(f5bЂƂNċ̸ϾԀo {acޱY5ѣ0#yvB |[9(gmbN.敦Š-]V[f6;cMW?0K w1\q|k-"r>>%eL ov5\߫_wVH.~ӌN Nv5}; \`6~,<<n8JK `3i1ڗWMFQ9MO-~iZ>E6\KˑK*rd[uyz"o=9: {HIK ^XEP7o/;ѢQ}392AC !,_$ruz0A #PfAo-?% Mw1Tн;S۲7't4 :Zc@Fxe)Κ%]첼6݀dMEEΪ_aj&[|;gMw$crp tĮ;=c#c[eIMFy\1 W`pV:]/_ȭ ?dF{},[3_gl;I ~azuInqjs ⵐdJd쨷L9tXu r^Y^(G A䅗u{4%n#ܒ"ٞxIj0̵Z*R7gb=j斬P,񻎅:`UPCVYzGe{&hD^oҐp̒Ѐ#Go&yZ}~,ZPv-͘=)lD~n0dM)_*˩jD7ʴ͘ WꤶsriX ZX%:%CT{}M5pш5tEtIRӜۣDF}-1h޽| >hsS(yӘ&bvpy qYآeeH\=79 3 Jd$h=C/~\W;y)@sOX4B r ft$<[QjrU h]SJfRTR'C :\p`{bM||dxz=AItkmi`F1A'azM|͝8 {Ǻ=cť ("GloƎKlPrmh2b5 m-tsgfեg :׾M{g.G[i_<{tUlD?z5*7o\<=үʦv䃆צK רg~Y6`Ͳ5P-^AO:Jk)O;G#-eERִ&0mrM >eP|욕bsn>HD ;aU.P:["Cyin ,2Z;0'k׆:HS\ܓgw]3d9j`>[TR WieųV~-QWQsY9= a;OjF?k`60%ay0F-=˨j{Nϗ|ca*@Rq%\(n#]%?nNJcN0ODwFE e(ch O#p!yGc)7|MOke#! Ư}{a!#{zJ35=tCu+q8X:O O,)nuQ WfFMLMTvԲDrc Kn&V$}ld 7v(Yhn;/hrר]&8Zȩ0< *T{_yزlDbAǫN[3/GS]tqo(3PZ裘ZqBdO_8I#ZVfDZ>3, F(ç'Ý^f8;јf:*aY;du%!2yCgOuG#qm7wP?(rl-y&U% עuԣ=*]Jd:0軆 7rJK F_ NߧV1kMJ5+W6q| @>;.|T PuR#Ux1d2wjKa0g ']ĕ4]NW>YV'[1^)O˲ #I{ -hp[ye#絿m`( qKCݞjX@Jǃ~Ig/Ѿt<ܮ)E|ƅ=SU+3\uJ9x ) jm$ ȄB 4h? e= $Ͼ~_W%adQcǂz嚾ңv;!l B |Jo<w2yh`1Bn43rsX 7@N]ƃBol# bKMhPѺ15B 9!k$Կ$[ր`<S \beڮnn#=SZ5#"ڱVV""եtIq'ߋK? OpQ&,9ǖozH֍kӎVcjZ<ݡS~BpJuJwFK⃖o:=v|lmX>麦Σe, &ÁnN5_AW/E(.Q c^ ӫϤ&"6o[W|$ bjdQy>nEhG,Q\xE8m ;3ٍ.)Eʀ}dAƌ]OpFvV&e]yA,ǧX .ξw"4?mbW PNMmdaw:L#Bf`^ՏE4I!<#?~/J8h ʥ F8>랬pg|:,lehxYR[Wd56't$&/nKJ5*xZpe*3Ң6~n-N2<@",!jfly?<˕OB#`! Pq%Y2ge(EF`*rQUaܲe$Nat)a 8EjxpL@ؑ<{UHe~%`2$\ťwbCi7? oFpYۻ] j$=Qsi;YD8 ̳t@`^(dieև\ %tmC"?UX#-E؊`ڴDZoyzov|wC LJ3v sx dF6oJݽ'q,jWn繛D@H%Cg1Rp6^Ie+*} ~9FE'?{뿼A -j˖2IN}b`itkL 1FۈqQKI E卵<a4و7rYlRh )zZ͔=WfѸ)6*E<_g?IVLVU@X?ng63ِ(R+vyܹ[#Ҏy<%MsWuݔW1svވmTLQ3E0iАYc-u=QOjgɎT-)|vGGaovB\AndnR՝r+ WOHiU|и5/WR+C$zlƇPb ظC݃im.6e8NvXR0q ",8LO?G[7ո ӪwA,> p>dD#$^W5ne;N.)o)!zV#SҮQj&Xs/tm nM-ϓrh R]EdhZdoWuCPBGX2 jp-X|4ug+[akGÎ{ h⻝"  ސRy75 36mW6߁DDS\1@&P&êb ύN9V@^l0B)y^ihdrV^Mp*4P,?YWn|^?m׵o=!ZQruK26C5 endstream endobj 1689 0 obj << /Length1 2259 /Length2 15826 /Length3 0 /Length 17180 /Filter /FlateDecode >> stream xڴeT˶5ҸkݽѦqw w n5HpwwK}w~s鬵VU==*2%U&Q3;ؙ '/occeq6YؙYY9Vv` cg ?h`geC|oJ3@lad윜L@H"nghea'ӟHŘƦ6vnN6Vc@Y`&ځ&@Kc9J>(+1Vus.j j#ࣺڟj@ FڛO7?jjJl,`/noS{s5w+͍ٞəт?5K+' U[9- '9+S I_J۷R9ɝMbep#_rJJr[c+3l 6}3t6vvq%{  'ٽLc38y6lS;ӿ"V NzfK&/ #%$6x`&yꀙݝOTBmyxַ!ھvBS> :99zXۀ^O\YV.@1~!-:X@ԒOf变>^vscd 8;}O02u~󷭂Wt_7&kҽQ3;0`4G`Qs~vrmU63yYhPUs5IhdljW%W&Q`bdffFώczsm*Mm@''7_*[-[~R$s$ l`;:{ ;m̀M lwq9"(77E_"7HYXTF<#7d7|CNVVV."u ddo);Ǜ|2wa|ۚLkjzkѿXb [& ez;6y+|[%IdVoNgRg{Û퍕?ӽjvXc{wtηWObo?[Iѩ7ro<ysؕfO\aUgG;C?Lv~$1q-Oǹ}_W_._>@SY;S`_R*>ra-8VBr(q>;\y9ƭp>FF8?zi-@?e3" kՁÐu^ sm7T?_#ѾE561AV1W67M'u$!]vY44fE|4s=#ʲ;*91P"CڳUtMUbruʘ|4tĹ8G i5q?/23<>MTz-K4MVFhjbWpv"xTz>8VbܹXY9=OWx? IV2?i%I(}jI.N//Yc:6Tu&s*\rOg.֯!JW߇<]9tB{b |& n=A6F{R6qޖ@|ڂ[-VAl=+e@{9z_nڽb9">`Nvi^'=<'D9HZlrd=>y+f} 5Q9]p8_f-]c5&tY?UtUtRy̳tQ )8y됎g:W^m̋ߍKz uo=nt`}!)J]ؗĮTLL)IT=j|EbgB4JVP@CN%E%?% !$wC_9A*?¤R)@D3O$?}&+ gQg,Lʩh?W*Z0貗:`xDMwkR?.40"M[fT +i30,}>#Ċ:<"Phdva,QF4` Z([5(l}鶝=A**y%_޹V*@.~xc=E ?9GB^rq!oPhu`q聑)d%+fXjڱ>)<}{YU҄ $Of+?#'%W">&/O'֬9+O(-˭l1!nl^&=)3_7xP3MݻR =?ZyniOaU]\dNiS[mAZ>?0n Y 6*kYg6pb0![џ$=ܾ_Ģ ̥k~yopشך=dyyUyiZ3BGL&6Cv1e)&G/K5>ųu.8T*IwTr$1lӾ1=?M~=BhP t=$>rrH~̀!`쾗4䖡|e-r(%RS%3 ̬[=mȇ2~ڿ>̞O;ZY|`L٦'I^;MZށćʽ(m;S[&MqfDnT.ƈ&C/*qW]Jom1D: c*/  p)"hSigҴTdji|!-䔟YSE6z+e?Fweʫ.~G+{Zq5$wQKL/7Wz}Dv!L6o'hk|־Usxc`^4CA5l }J`G2ċϳ׏*) v/KAK! "HDO9RJt[c$Eߥ~KP9Wkb||B,YWZ DcN9? BhW.e +Z%V1CaY Kbo[Hg@@uMKmU@08k2x!c)Z:^ d'_IV(⤼JTA=^K #}(itrjr&\'k,ĎCRGn' iwץeۑk< \*ٷ{6RX\+r3HKF@_֯)҆,XF%; އT]GyMauxB65u3hKJ]}/- J *})uj8w N}^Cݱ6›hHNȝ[r;ziK~/O)L# ]#k+oYNW?X(?T T;oψ 6>i{>zŒZx=VlOB'zxQF0v T 2?*N]uR>($iuk^1琀5 nZ2'FOISGq3=-e}+}®2r^0?er;rPXT:rp!$6:m]~cY\[!RNyRu$Wxl2zl2 8ac쟀^g rdWBGje2Iˋ®)MAh m.y7̒ Xx8fW&7]U&Quei^;8G2ֈ7"櫄 Z6b  DfqzEk nR C4sg{zU%< nQ l j-d\!d'Tek&˶QO/DXEʉ4",p elyB{qJxLN r7e^hd-Ÿej0.ggZXгpW rA%/N/Jei"5XHdut/-! dz sIF`3bL5}4GeK֛:\ѵzd%{ĩ"24mB8\{ϼ}em >-bc`i F[K,Kݝi)>m >8}@ _X#]<ݱ$pGNƦ8N6K3 aɑ{.)EG9i{(ed3Cer]aɝDj[ [ɪA$>\TySopzt1<~7dLC23쌇"}c=nyڼX PEH.YvC}|GaU1J,}5,_+Ʀ%tKA L1  IJ$/,Ywc*edz'Ge-U}nq[v5^9PVa8!"E=H RJ EpM/WGSDgb`Q^1EJwE>.\G}-z#rK3LO(D棗Ef5)CDsV˰Q~K8H " ,Fk.~l)T{um_ԑkR>/Z\{ Q@ I!PԽ٘2`"*ȿ~'ԿΡ0]S \gf$V:q~hhRK0 "cf^S{f- V%UQJ}-"njG!7/I [*nƏ~U S;'aaM !\T%u]B4Mn,U#ύ[Tbփr)*jeݳcf~s)ړ*aWz=V8| v%:U wGׂ#_j; liEvUht*~ 7:҉^1;ڈf7X}2Qm g'OkjH-se7~Sl@]d̂Q 1!-bqRۂ$hɡs9o}ktdzc,^[c0i}Ig0Ɵ>JBsAr/HTALֳ [)MQ%2NwA'꾷5v/t]XK/R ~կKM&Vecw1?@BԾ9aUVhD܆5 &4࿓pK.<(yYQZ5ɎSV9++/u~e7tzF,Z!q!oNX@d˱: eb9%-pNqcw x!#ûXdTGvݷuG&Z i 4FϾ U>TNʋɥjT[ś~Bi^mgBBuyreiݘ•EPg8Pi0V.B’EHR2㹤5s NOQ6ŁkgWx!#X:&r9ceޔ$'*P@ ޏ{a2$mxo֐{Y)6V"+km`l(ۇI<\}iRݏz%xIP_Ə0WᣃyyQ+i >DVx 6ˡ){(E0/ ,Lhe"-Hsꑠ΂9T8vޕ.8+ГR9"=w̜/{q6AY cJ1)%KFn] S%_7v&tv$tesLՍZHӣߙ~r\8)"JtGCŬ}He6ϟ~a %7b6wO>n}B=||\nmC-1p͎L%WMEmȇ bVd!B!WH*\Oܝ nA;=[:LmxG}F^<{K؏1|`:,G#؞}/rDKسCɦݖ[#a;wK+I=%~>]inb: 2d%lp( U g$I:өe*8C9XiD^ j.+HmPѥ]"#Ϋg2 h8'x1xх4 ~ݻ>?MG l -$ >B.~&<ȗ:#YC15^(\wGZ(Gl<^m]ƫ,۷2LqrE1u8-`Ot7{WOV;9O߽;쯮vHz@!nsrdb@ʿkU{(Wf nC/K`ۏfſ1gVhW` j}ؙOBac ܗn^=l~{#K& @Yd^u, /| >ʄ<*V|XkuFdCzjP=>lD VM*ae /bCrx~~*y0_9 9^ Q ˡf]M:bHՉ{]R7/ ԭZ\-j}v_d\-bFcТ iXpBv[g6:zn[k'\gd4棤ԓOe⑆mz#}(I_|7VBFšxjKZ?%5ZQ;4Xx{_Vp:mJ5ljzoh90$32y,-2 ѻk"[wѰFw$y$*0=^TFKmjCNV)]nf>V_hbꞑYZY.uLT q3- ,5áe<4=+ i.+_N/Z7~Ѓ;Sv8YSœ=9RZoaq Qmxܦ(0lB/1-\5ƻ,!4CeOMc˄l{KWTn$YLݛoE kGgN(9EJV"SU%oE J* vڞ-kL?Y>%R2N$;N{jy ah׾˘vm,SWuK s6妟qr'R)%܂ߍDŽk0MN`bJΠ@ه:Mb%ZsI^;Cd1:2 ( zw6A0gOsT(kҾ~*,sf׳IM1_stSAE6Ĕ9f=wԲ?駂|QnKxu^R J|Lp?@ĮB:TH౓( ~~Kd;J$jUrk'6\Z_:O\B: fy81*Pfvf.Y˞bz0mmtj>ϗ&r,Y /hyE'f&+7h1J2Jz(Ŋ|{Tzdƕyh Ekl<GDԋz*QH5:d!) xQ`%!zoyGQ MZz66/a^{ϤLLⅸgE͵jQ?@GVΝl8\_tq$Ȳ} #Ym%#@Jڗku=I<=6?,䑓@n*9a6 Zd_4'xlyE zJfJ~K[ii%q Kg?̐D]b5>ZH8mf<ʲN,7iygʾ[02nPznO**'-N[Qݾ6 N& ?4Rm顃t +ҳF$t mb0J.*B&'ZilI9ũrP }rKԆ^~5`u_-*md]kc v\ aV9_M6TBq&}}ܝ$V֯H7ˋcЙ! |Pzlh>{RaMDcw_BJ7E8vVDsw? GYzV~p~Y· l=*~E^:0mqt/LR%{\r-as 5;Sܺ~%2Nw+q+maE'gmi"V*4幐pq&AEȯhut3ًQm2 Q̦٩6ATABkq|6~ dc2Kl#o"gIOXqÎzeQ_vs^[](#7B ,ns).| !4~M%V^n60+  R٥OE´T u>ځp#qBsYK5O[w V< lb!ߛ\S]dZa IgeW~6⊠kZ+2]M. Qv?dwo]+0 KG0u7D2QpB[MLGK]->3db99I#GG{QmRzDx{z&DF;!9!_쉓L]=k.!G;(@1-Lk -]dGQ_gc_pN=(b-9-.@w1 ̙`.o 1OdVBYY`G X)eHyIyjMXpzFap!+g>`-?3H!!|@F,d.TZaBh6tieavܟ?8OHܡc>՚e3K@,(rj1rMy2cWVqjw>昝:/tzF6Utj xfMFDգc=H5&SGW4o8 lu *uG ͨAۖ K"\y&8zPLGEu@S#=(鏩klsE69nR>aX $ ZVI N>-'>Fb)2PUDާ0yً|5=NP?ldI}[sn Őf5kr`ʹSt2V|7Y"cq8ՔWe0tF8ԄQ[>tH4/ћ Ӵlk4.e{a72f)5bJ#x6?#Zlp.6Z m CC9Ag1ԇ:{"lWEЕ *D+!CV %t[lX5\V+?$AqC?v4kPpf`s'#~/ <,|8uq9uy9iN'3Gu+ӯ9+VZN%ۏA^E] }50-k_8a6 YI%qbGĩϤ$fzMj 4lF "⍗7!$~k`=HS0ЩFհР(kwp4_s>8LoSrJF062fr8S(թȑՋ c4fRR(-&CVVTeFw/3xcw]é#C!+tkɍY:Q SEU+ZֱCfi xB}?p]\SC]҃=Eiltpag羟QlӐɅMuRK2Qtwe;z0-=Lq\z e~oM=h2#3{#C xhNk~]FR_F @b{0r\>#V4 G+^$cV9[`Wu)I`[o2̅d #ԃAi޻Ѳ)xlS}u<_ APB&RA1=GOT \WJǑ駃F&~FW׼_a"2 oOiPR${.&*9JT)Cdfâ*6zoOc ,n18篽,A:q xX*]"f׍m2ĂħDR R|{+k\'Jj,)&}bXJbʇ-9}^Lge ̕h|B+{u LD늨锾@_VN6 _$(ݕHOUF$q!bN5sS)0noU 딘98)!CM2&Jݠ2v;|tϿO㏯7%+dKӴB+ٰS՚UTVm CS3~WqQ?VGKvQdiBT]o(-xә  󩫔+P@@X8I/Hgx$bĶk xL|Y B|[,SCs7֔ht(bPH^#UkgU`P8Qrl~@00^ҜR`S/@sMظv ѓW}ؼ䙳>}R-# wG%{b]>x?^pseD4;79*y9W;m顩CJKf=Tatj~Yi cO,Է1n\d"mK&yD0'Sh,e7ezxш/d&A z_ Lֿ,t4ݬQd>r^6`0$i}w!KFU©Vڌ;! VSg܋J$6IܬI=KfIG1d*7`l>ɡWܪR]aEhIoʙ$?<`cHRDLGȃ.:*P== 招?yYwME2udV}v؉6 KSvYuZN]?2vY0j[0MAAc 7TΉ/V_\ނl&'~k/ L d&B OZ`u<&w^9tV]LՈ7 _bߏWM-S}XIMц:YUjU%W:[:)%5@!Ek殭>٘jXIXoԏw eCVF{V^к+Fq|9E]|_:ٸt݉M7Ƥ3Lԭ-Ouav{_Q ,Zܑ#U8 P˨?l}}e#-D',j|a ERjFP.H,vWw凎e 1c8x?[6]B{|z #l"Ŋ\a=uIug4kRhGŒNw?E4X#9?5&eAUJr !\v,"b@&shE7Q_ WwtB ,;Q͡^RME"Tøy0$RGA57 { thir{ԥ+ W»Fh:lٟ?P+X!8HrVlp%_X~4s׀U* j& eՂA[|AarJP4_܄T۵iQ3R*b⺧Q뚌*tS:aVy"ʾ0K0[<Ɍq$IBWv!-١*(MR=`#Ք% &=L&dHn(lHBfkrKs>%'|z&>Naqo冔!N.;(u;BsOLjײ'7]>1ٌT‹Hquz]-jUAvPE U_cu^]x0 endstream endobj 1691 0 obj << /Length1 1313 /Length2 1250 /Length3 0 /Length 2094 /Filter /FlateDecode >> stream xڍT TSgR+E9Ze# )a ;b-%y児Ơ bq qWD *ҊH)" ( vΙ99'{wn sc#Apg0h4&D1(|"R#  Й̦i4\q%xqC  kXB#1pd "#_R PЎuMj4 \)vsBqJ! - XU8p RT`*2D % _aA`FA#0@F D(f \&1-"`7" cBp2Q)CÀY`T%*'T jh6`B/\&C0BE1*w-uttn" erj8*Ԉ(4Q>X4&u1 $T|1:3Y^'@DQBPt*8Ru;(t: b|`'͈hN_ƃ4R~t@3|ޟ֐ Tn15<(gߢђ;==xsd)Ӊ \Ƀ~,O(Q?&Hdޥ7cBpR (& ܍!M)O3⪥Rv?,CZ(?DF6j٧^&wۇh<" E dD&F;Bl&5aTZ N7P! ' Wz •HVEPư J%$O7A:.X8<:Yӕ>s/Or{7&|SGӞ2 E޺teD1O[:TftVVFF31xR^ǣnQs^KJ^N9 <.#}9yE9MVuϳs;^`=uaږ;LؓIzh^c1iSh [BQRu@caL˾!̎X,T48ݜx;bڬ7fԞr\p7:;Ɩӽd(tL#;%phu4Jgn>s-k/oV;*ѽĹ&A/Y}WoBkpBMyAK-]βR4oqƄC0V^9 hւ.rrzx+!}AN׊=G2ROۡ_&~׭.:2039W fxo ˘ġ%3$V%?0*lreg+68/ Jmr7ߘ]gn??";E+siӒD}?ƛq|T5;/~F6RDs"w}e Po}y@b҂oݨZlSn9)$Y1/z\;Wdd M|Khη?9n٘tK:V(n}pbJ;0c9<6:%́=pо ?LZv ޹.ޯa6S\^Q& endstream endobj 1585 0 obj << /Type /ObjStm /N 100 /First 1002 /Length 5792 /Filter /FlateDecode >> stream xZ}$ΑnmmQZBXIJ6Rí p/m-lԅe!- FSobۀ_D+4K3CuP1e4bB,J*0 8"Jthl5B[B{hahCA; pBPW} L,L(G}H) ? )A +-ǒP PT cE: ^:ԆX҅ EYE\ڠi\FfΗ%Y K4@0>R1 # ! Rz |I+%8U:$VC5eY߄`ʇXqUEX*^X<ڇGbH< KAF9 ,8bܢQ3WƒEyFE9)TTyt+R ,v!bTiHA|b @,5"8T Q@ "4G$\e Yg)ڜ"8Vm<~!y%xa!N߽ ΨرMqXC8*\ WDHjȺ("2D]>G.?]w*pwj;TLtѤ; rC B*P$퀛 - ;0?9t4ףQ5)>Ov 2)^|j(Ldxx3:-9 <#Y4sl7ꚺV!ЏӣukT] DކΫ$?]'\#Jl:peжxӪ ~uE֏n>MO.6?٤7#q|_N& Q :~]<;хK՗Nz~ZJ5w}ٝװ;{ 7wdH_ũ?\~t |foѸՌb zwFL  #fP#3l2<>48 "u'L0L+ِv$FxYw0Fj<&~z!B=dt8x온~_X:e*CJwe5ތn-.@n@)u#rL l J\cԺ0q;vG_qٽ&&ae=@^+AF'~ –6S%aVFߏ!9Uj0!4 +k;*4,, iqdh|k-`:Z= ⤩A7  zWs񯢚XѴk,YB 4glzWR@Bέ*%r\n k/\Z[+I" !״Sةi[PS!PbWTb Qq'-!pleL_X_kzܶE؉ !|gH wo}' [tG_̖Vt՛)kښ w;[Qq+191 9bQArIڈA*5̈| !I ) ڲ3~}]!(P`pQdI‚&yG! ` <qd<∟b̐!ZDP5cC :òjM] 8ڴ-sO)G]vJ8N@ƍ1D0Q@)D` a` CO 90Dh1 N4; HH[B+-J(-MЂt2-~醡7x@%J#@qaR58I*\o0Y*L R5`$  ƻf~Br*oߺ ;'?y3f8{(<R0Å.(Dw#U=%Xh!o/ҕЗȲH8(0UlUkYUVmRKWlY{-O *^D@ww0 #YpSqńLjIIxV e21Wavv}RTqŦӈAeKAgiA$Pdr5BzEB-"1b"FצO} /C(/C`,[H.(w h20$K)BnS tp@ '{A*ZR'fޑBQH|iARs>-6=r(C%KPQNU8&rj1ӫ21k]vBbevʄ >^Ԛw;gEqP5L[lD872t햐i#..f9cœwm_v2-mQ<]Mזѽ~(ҋ?`Xw0EE[rk Ox\ &co?R4#dF`L1b?y _i&)p-'ܲ kpmJqC-LEXB\NMYR} [W~mFI=yztS4hziw>5%- |l6|^Z)eVIv1X:R)3erΜRfQܯn3SgmNV$K/Gϼlgs怜/I2gsgREƛKh报j}b ,}(:oloZex#!3 B#2{mK=r7᣿,_C냭OA?"XLJ -!-}ɫwY>Qp8XZf8H"K #L3?aD-c##1fH07zvY3k"L9t J-î;'[3Ń.b'$v,~ISNS)-g-GhOM;Z`g8e6Z,sE;,tZ^L70>5/q!wv-{:(Ci:b.ae-9>:>xҰJZOiT6gE/ ,vO$+n-~' +v9'9t|Vܽ"%ҭ;[O_լo"-J5;[Z^t{k3}˼FNɽ9Kޭn}X8>N2Z_xl&^!a)&bM;'YKxk9W6x)/qmx0^, Z^vW&.*Ԯ1+%+o?9x:RXgeo>ۭY9wo&/NŸ{EGqD̯[O޼:>ˣWopX/~ Jk07TC kW'oۘFPRf^۩qX^=}Y%Rj/~0O'zr1eX7_~UY,&}Wp|[|xӓ_xj*Ej/֍ƙ׍S~:8sjT + '~> stream xڭZYo8~_1y`0W;>cDZ<$=;U""EJ^{-Ȫ(` eBZcIrI(ǔVx͔i$Lp!pfBX΄Rj"gB{ɌULL2P 2!%^ 8n G] >LZ;mG[J PFy n4>\1px =i5: 0-s(r+@J_(W MW .g0ӕA+/`nu(ѸTQk,*XAG 8Mqǰޙx 34æ0f$p1 3S > \lŎ@B eD?$J܀wV)'-u`Rͬ1x,uFd y 4:8>9+rc(d(\gz(芳:g8e^bL+b",&W]IHPvybѐʼn&g]Z,DPD)F'Fd.a5پǪ%lOO z¶wNwN^ >iV-"t gi}>>}Ť7C4n_ļ=}rӋ^c̛y̌>ݬsrgn?99\?vZ8Ip`K`6,=|#l./O߯!l4F$ؤOG˝Vd/t>~#n;;w_j6B"b#?fnoZ?c bQ26qz!!q32nN.gP>cOͤ'/w` ]Q:E6mv#fgpr3]n߯+w4`#~apl^r35qvYQ[4 ٧ +lwJnǣw] +JBfӏG] +k :꦳Y ~fbt>wpp{O˓!gNSia4nVllWXsE)9|H}xcG`]x/'63ɦ{ίo |,O c!1^|޿9VM}yvJ_Mr(xibm4]DkX^5{w_ؿEeC ׄ_,UF\CS܅_*txL`=!Ok$kX<6fÙuF-6K$s#ΈA!^{F__dSz"=lo'>m-ہ)ӝ2+kw]vnkvO8U՞IƦH,Tt`TtQvZM/ m V@G<|w19[;f qkSmvҽpi5Zt "B^HxIYCVoeH9eGdqԵP]%3UbUG/3ex/;ڮ/;("AAvaa"5)ka]D/ n;XyA]D0䜶:]yƬW _h'fZ~;W՝cݸM rF$ȸn8fM'Gmv73dA7{C8s0V-i?toH{Fa7{:ۙtso6gt،m6j nQo*iMau|󼚡gnV))) wQnqFQY|nkʶV)@t޸nk~OgJxo2G78;Wi2Rmug:As̰{i;i)RYCJ8P{!{3e}}6__^揫-־2rzX[6]H9Ĵ:gbIbQׯ퓻w|}SMTa%zCb(%n -$}9|PJf_pR1(0X`_"ur| >QDW$.#6Tp&$.@W[ ygWFE!3"q> y( S~$ګ"ѕ%qD{(VH4 tZsZԈfa-u ZU#/"Z!J54"ڐ܌!7auyKq^8Ĺv|Wa q. ĹEq^I Ε+ݳyS cn,= +cjC21 qK CDp[ #N %>oaړ4zZOMѶ7eFR]Z dCjU#2I6uv>4<Ɦ8#\bF endstream endobj 1711 0 obj << /Type /ObjStm /N 100 /First 1048 /Length 4785 /Filter /FlateDecode >> stream xڍ\Mo[;Wha4`0z؊-leSdI<<Y*Rٚ,6[ؘ*K1i&+&{"r)IlY <Ź-.zH8]X\E黴SKvu ֠7K..!F}疐3d_B QOQD}*X*JSYm%)%%d`Tt n&oh’ӧdSZrYlXlU,%X}KIYJ #ڥME2F~B A?l@pI{.LTV1D+왤k̝Mxm::T';oȺVM?('p5b,{)ޫt>J+12ÂK@F Aχl*azQz"PhEF1[2UPd䴇"sߩ/2Z*:pAk:qdhժz:j3Sh%xm + Uz,dإTXN#v~GIVVq] &@e=C[cGAG]Efk.+ __?},Um^ڋ%&7go ޔ7Ue;H/ݟ=o/ۅr1'}zqq[^[p=-/^y=-]iwo=~_oXm?7KpLJf5QIz=o" %oʍ~5|{}=t.{~xy};ehGin%pu_jVF x+h;iSydU-7d,@ JƷׯ ӶSv|=a~F?Ee3[l V,ן!HfH饖Ԝps)f)s yEŒbqJ5m&8XJGR% Ŧb뗘T XO!HY.A@ EpZ0$R+p`2POm CpP`*CpN` Cpdc-CpbcݜX،RSbzЙQ?+≒!ě!_t[uF+]ݻ)+= ΐpbF:4' ˁtRgKLuT$G.L$I t3NaN!z?[]n$)1+%e6 VJ* JI-ebHt-YoZRSb-Z@!)ֵBJ *IN?Ib t&ci%J_P:˕LjHjfU"MAjJ,7bdZi+21 @CFoNXY[cOUmW@}$O cHgJQ% 2 l@Aep[XJ.y]S*5|-.abl+2V"yk+~氦 5%l@X>1!0I~Vt4-bd'0.5D !&0f !0SHsbe3JM9ql@8Ƥ8fL탼!^Sl5-)1-*)<|۲T $^i%5t_aGe;c6lXTj~rl@|ʞ!E#J$lZ$9JĴV+ !׆iH@| k£)9Hx^nA!Ap":$ !HkZR+ l b"s .\6p/X $&$7?%fs )Gg|B*VR ~8%uUHB*ئ 3iB挦eka~%T`PrC+0>$ad+TrBb% 9# "ݗ3n<@/$萺d>$a^䉢A$iq+BrKJ͸@j; -d=B96-[. B aFĖ H-f}"1+i. >\PWތRs^H sܢ0Z(-a,[픘fk܂i0U41Ƹ >EÆMhWٌRs^` !_ Us s2EC)^aNhꔘQjb[`Y--s E%EfMh[p,Z2Wƀ[,[{ <$R3΢cn[p,:ܾMxnfZ82yw̢c^ 訕;Be+EϜg3Y)m=s EϦ;.^EٌR+ϼ;f3Y+HFY $3#!yOi3MI^ 7uCvȱWdc{^hj @ +~e<9;g.99扽&<Ԭ]jJo8R)be#o_ov-j:%e_L JY+TJ*lJ+T*z$z^B(CR>G"rHDKDzx-OŦz _ږ%[ږ-tdk[JkՔ]}Q)<Xvٓ\cW{6dW?gCvHk6eV!%Եe!Oj&5E[Jlɶ+Rz;uC^RBm)PP@3bK[EX`+ܷ^NCg!@NjKk-~7@q/3$lWO i! endstream endobj 1870 0 obj << /Author(Einhard Leichtfu\337)/Title(Translating the Ding dictionary to FreeDict TEI)/Subject(Bachelor's Thesis)/Creator(LaTeX with hyperref)/Producer(pdfTeX-1.40.19)/Keywords() /CreationDate (D:20201014083818+02'00') /ModDate (D:20201014083818+02'00') /Trapped /False /PTEX.Fullbanner (This is pdfTeX, Version 3.14159265-2.6-1.40.19 (TeX Live 2019/dev/Debian) kpathsea version 6.3.1/dev) >> endobj 1812 0 obj << /Type /ObjStm /N 58 /First 571 /Length 2191 /Filter /FlateDecode >> stream xڭYn$}W@+^bdı8y2A;^B3rO{j[ /U9EGk`\HADH&HL' cw We0$󳓹Ģ$OC*d|%'`N@"#J)HBr*sGBEbB! +Vr0A6s'=f4Iqb.Tr, \3hQX@"'ĐeeJ6Db)nQIJIen&9,rBQ7%A&aQ7I1*1 (;=˙rQūon{_O/oxvƅ(uw՗{gݱBPGxL䍬"~'ԑ̑W7_iW ХxAN܉lu$q8u$D%e\yITazUgQԸ<\3\~:h}V'sTV';㬫%d` wAs\zPo[ڳ %lnιZ&Jst2lm$ Z@j(N+pKiuCjU@ ؤ.C"Th |3QCSxp9Ǜpq<+f12p3YQqat.Lu "ώWzمabx0ilab} ?q( Ktq:+Yö$DSP-!kG]Ճױ/xp?lo7>X 5)UޭG')ˋR *d+ U̡NBwM \9}a iF_Y{aOCTPCY=^o&=ek$aȤQ&B i]v{ryy ='-j\0IYl_ f0 G .p s- v,.5:8~%b pr94#ڷst~j#j#i1X_l_N70fq )fʲ\~_t?_?<}z')SNƫ~L!jK^y敚WmR.x:<~uS[PɃ 0dY%:\gPVAYeTnaI4e ڠ n礼<vP߉J ()TQa_|RU㬀P)RB*5**~h37(* U\u1*2sTTndEǤ2J(IQ1Q^3nj()Hu&EE[}LnRBH L m_1JPVBY e9+U8)N+'+RRLd*' HiR9KtD jcrx.c_Z/UFj 3V@%CjK.jCA?D<5[ݨ{nj;;%Vc%IMbͤvJj"!btx$a@HI' TPR %P\y/Q%PR% U?~vWK_C_H=ƗzVW~?:O?~77;?}Q^DJo_x{2rU~/˔=}%݇ۛo^?Xn||{K=C񏥾[CJ endstream endobj 1871 0 obj << /Type /XRef /Index [0 1872] /Size 1872 /W [1 3 1] /Root 1869 0 R /Info 1870 0 R /ID [ ] /Length 4303 /Filter /FlateDecode >> stream x%Y%]{nwuOOO̝;_RB\@*Bp +A BF&hp9[]U~S^Q76`szDo<}x,/P={X{;vםbNbwuLѝ⺷uBbtbb7u~?]]]+]]]]kŖ]]։]]Ή][΋]Şם3rTN%6%vVwn312uU)p@,== G= vӍNC3WэG8}|o ҍqAlL,n-%q+bZfE|}b}㑄4b{uC;b#b8C{bnrHE Gbv{"U,;t WbKx]c N|Ҩ1~fώ>3?Ʒ 5,&J"ft7q8IߏOf>B0V`6Kl=j0`5(k6x5FTf;xtgx؍,4[#L7hѐaX3/ RsnC xcD5nύQ۰16 Hs( B'=jx5l&6ާsxg+ ͚q.49b"1B۬ aXzN!awЃo@,O88~:B/QK|0 1V^y˞~7!8RX[a9 VX `liX`#l!XzGƷ  {`/ aph탡{wQ8\p N8 <܃éw.%W\p n6ֻRoOC?H#qGSx! wCې w֩ZJD; -q`SPkҧOt!~>΃j,`02IӒ%MK4-iZҴ|iYҲeI˒ a%W˫vڽN_n|Aݙz[]@>-}Z"$>!MOj[|iJK*-UZTiR%HK2eI˒%-Kڛ@*-Uڻ@>pV礎/$>sAKRRiaQqNfBHOO0Rw!1WPLV@LWAL@L;811 føi0Û uqfsG`{}ig6Ս> ^51i8gSp 랅 p.e''~nM6܁p}xci|3?O Sx i#` ׾j%}J_ɡC%J*-JƖ %X*X쁽@*%UJa_}ʣ@/%UJT)Tr$HyUJofLK WRvDžJ*9T2#)H[Df4 +yUKHS$MI%P%%KN߃}Q`,g_C&`6LvƱt̅)a,ERXaUZXalMV;:̼#q;a `7X(pj?XNK`8u,Iě: g,p.% W܄[pN0n܅{F<3x S9^KxoU֛uL9X#ޔCY 9r2de?9r ҘKc.)[EΈ9#ru{{Fmyee.06hN9-rZ̹s#ɥ;_k_r $'H1\풱8 GԯxSka|7QSOq('DHyT rf~Y R.LgR#qrɕ+'WN\U`Xn,7k9rɕ+'WN\9@.4m;UΫW97< T_:H؍ JuD(ձn2?(abǺHKǦM:CK'GKG̎wm^PGWuRڷ6q8qPǡKc]%ӱcIǒ%Yp-ĽphJq!tTEYWwNG-:ZthѢEG-:ZthѢEG G V)}↸ytZthѢEG{Aq4 t",X )8df$́0`>,X `9 :X` lMwvvv CCpQ8'<㩿Sq!*g p.eW\pnmw܇DO)<FHœqk/7#jՌ05#jF)PS@=7qٯ=z^|P&CM 5j2dP&CMz ТEMڗQ3fD͒5jy!Ӆgf>@ ..GRB!-!--bGjmǑZƬ|\9Q+(Ĉ)t7?lԶ [ʐ d:@fژGyG(ʌjcИ)4f Bc֗)edz\MRTC6Q=3L3kc$ꘙeb)fY( 8ꄙa:) fjr`Z{CaTvN Mpl{QB͍`ah;F/vJC$è.Xm(Eil):>n[,A\gC#`/ux 8 4iз{K#u>|ou!h]L#_KiiwEJ/GjMѺ퟈z)Ff;Ѻ~Ѻעu'i>{i~1ZӬ_=iw>ibh=JDqh=Iy.ZO?}fl*Zo8/OEe7*M6 ~X yb)<36cGV?Hއx`^7T\)W+leCRhyUv*W\xZb]E֊zc+UHXqbbEmū]YQUOR(_TӨ<Ө<+*WT\QrE+W,X\bq+W,X\bq,wʾxu.%QZ]P]pWI ۠Xٲ?TӤ2o}V_I|%W_I|%W_I|%L${/{Z 0,0 s`.L< `!,Ű2X+`%հ:X`#lͰ6Ӱv.n{a`?p8&ՙ×8N endstream endobj startxref 784733 %%EOF tools-0.6.0/importers/ding2tei/src/000077500000000000000000000000001423006221500171705ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/App/000077500000000000000000000000001423006221500177105ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/App/Ding2TEI.hs000066400000000000000000000276651423006221500215710ustar00rootroot00000000000000{- - App/Ding2TEI.hs - translate the Ding to TEI (AST) - - Copyright 2020-2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Translate the `Ding' to `TEI'. -} module App.Ding2TEI (ding2tei) where import Control.Monad (liftM) import Control.Monad.Trans.State (State, state, evalState) import Data.Foldable (toList) import Data.List.NonEmpty (NonEmpty((:|))) -- When laziness is not required, Data.Map.Strict is to be preferred over -- the default Data.Map.Lazy. -- - See: import qualified Data.Map.Strict as Map import Data.Map.Strict (Map) import Data.NatLang.Dictionary (Dictionary(Dictionary), Body(Body)) import Data.NatLang.Usage (Usage( Usage )) import qualified Data.NatLang.Usage as Usage import Language.Ding.Syntax (Ding) import qualified Language.Ding.Syntax as Ding import Language.TEI.Syntax (TEI) import qualified Language.TEI.Syntax as TEI import qualified Language.TEI.Syntax.Reference as TRef -- | Map from headwords to the last number given to that headword (if any). type IdMap = Map String Int type IdState = State IdMap -- | Modify value and also give out the new value. -- Similar to `updateLookupWithKey' from `Data.Map.Strict'. modifyLookup :: Ord k => (Maybe a -> a) -> k -> Map k a -> (a, Map k a) -- Note that (,) is an instance of Functor, where the first argument remains -- constant upon `fmap'. (This is why the following works; v' is passed -- through.) See also the documentation of `alterF'. modifyLookup f = Map.alterF $ \ v -> let v' = f v in (v', Just v') -- Notes: -- * Computation is wrapped in the IdState monad. -- * This is to allow for the distribution of unique identifiers, which -- should be of the form `HW.n'. where `HW' is the headword and `n' is -- a number unique for that headword--naturally chosen as the entry's -- rank in the left-to-right order of headwords of the same name. -- * The IdState monad in particular is a MonadFix, which does allow to -- not only distribute identifiers using a state, but also to access -- identifiers of "later" to be constructed entries. -- * In short, MonadFix allows for bidirectional flow of information, -- albeit the state is only transferred left-to-right. -- * We use this to provide entries that stem from the same line with -- references to one another--also to later ones. -- * Alternatively, one could have separated this in two steps -- (labeling, translating), requiring an intermediate datatype (or -- some Maybe values) and a whole new set of (trivial) AST traversing -- functions. -- * See also: doc/thesis.pdf -- * There may be several orthographically identical headwords in a line. -- * Concatenating reference lists all around (often the shorter to the longer -- list) is not very efficient. To keep the order of the references, -- something like this is required though. -- * Alternative: Use a queue or a difference list. -- * Note that the lists are usually quite small. -- | Convert a Ding AST to TEI AST. -- Errors when the result contains no entries. (The FreeDict XML schema -- requires at least one entry.) -- Note that the existance of a line in the Ding does not imply the -- existance of an entry in the corresponding TEI. -- - The Ding is permitted to contain empty lines - " :: " . ding2tei :: Ding -> TEI ding2tei (Dictionary header srcLang tgtLang body) = let (Body dingLines) = body teiEntries = evalState (convLines dingLines) Map.empty in if null teiEntries then error "No real entry found." else Dictionary header srcLang tgtLang (Body teiEntries) -- | Translate a list of lines, annotating the resulting TEI entries with -- unique identifiers. convLines :: [Ding.Line] -> IdState [TEI.Entry] convLines = liftM concat . mapM convLine convLine :: Ding.Line -> IdState [TEI.Entry] convLine (Ding.Line entries) = liftM snd $ convEntries entries [] -- | Translate a list of entries. -- Takes as argument--next to the list of entries to translate--the list -- of TEI entries' identifiers that result from earlier (more left) Ding -- entries in the same line. -- Wrapped om the IdState monad, returns both the resulting list of TEI -- entries and the identifiers of these. convEntries :: [Ding.Entry] -> [TRef.Ident] -> IdState ([TRef.Ident], [TEI.Entry]) -- Notes: -- * We want to provide `convEntry' with references (identifiers) from both -- left and right of it. -- * Typically, monadic computations would only allow to pass such -- information from left to right. -- * Monads being instances of MonadFix, however, effectively are stripped -- of this restriction. -- * MonadFix has mfix, which can--analogously to fix for -- cyclic let-expressions--be used to have "information flow -- backwards"; particularly there may also be apparent cyclic -- statements. -- * We obtain references from the left, which we pass both to -- convEntry and the recursive convEntries call. -- * convEntry additionally obtains the references from the right, -- as produced by the recursive convEntries call. -- * The recursive convEntries call additionally gets the reference -- resulting from the convEntry call--as it also stems from left of -- the recursively to be processed entries. -- * Note that the order of reference concatenation somewhat matters; -- we'd like to keep them in order. -- * We could have only passed around TEI entries, these contain the -- identifiers. convEntries [] _ = return ([], []) convEntries (de:des) refs = do rec (refs1, tes1) <- convEntry de (refs ++ refs2) (refs2, tes2) <- convEntries des (refs ++ refs1) return (refs1 ++ refs2, tes1 ++ tes2) -- Naming convention: -- * gRefs: references to other group's units within the same line -- * uRefs: references to other units in the same group -- | Translate a single Ding entry to a list of TEI entries. (Ding entries -- with several keywords (units in the source group) are split up into -- equally many TEI entries, linking to one another as synonyms.) -- -- Takes as additional argument the identifiers to all TEI entries that -- result from the same line, albeit different Ding entries. These are -- used to create "related"-references. convEntry :: Ding.Entry -> [TRef.Ident] -> IdState ([TRef.Ident], [TEI.Entry]) convEntry (Ding.Entry (Ding.Group keyUnits) valGroup) gRefs = convUnits keyUnits [] where -- Note: -- * Functions are local (in a where block) here, to not need to pass around -- the translations and `gRefs', which are only needed in the -- leaf-function, convUnit. translations = groupToTranslations valGroup -- | Create a list of TEI entries from a list of Ding units belonging to the -- same group (all sharing a common translation group). -- -- Resulting TEi entries are linked to one another as synonyms. convUnits :: [Ding.Unit] -> [TRef.Ident] -> IdState ([TRef.Ident], [TEI.Entry]) -- Note: Analogous to convEntries. convUnits [] _ = return ([], []) convUnits (u:us) uRefs = do rec (uRef', e ) <- convUnit u (uRefs ++ uRefs') (uRefs', es) <- convUnits us (uRefs ++ uRef' : []) return (uRef' : uRefs', e : es) -- | Create a single TEI entry from a Ding unit and the corresponding -- `translations', annotating it with references to synonymous and related -- entries (the local argument referring to synonymous entries, stemming -- from the same Ding entry/group). convUnit :: Ding.Unit -> [TRef.Ident] -> IdState (TRef.Ident, TEI.Entry) convUnit u uRefs = do let headword = Ding.unitHeadword u n <- nextId headword let ident = TRef.Ident headword n return (ident, makeTEIEntry u ident uRefs gRefs translations) -- | To a string (headword), get the next id (counter) and update the state -- accodingly. nextId :: String -> IdState Int -- Note: -- * Non-existing entries in the map are considered of value 0. -- * The first provided number is 1. -- * Dictionary people seem to like 1-based counting. nextId str = state $ \ cmap -> modifyLookup (maybe 1 (+1)) str cmap -- | Transform a `Group' to a list of corresponding TEI translation elements. groupToTranslations :: Ding.Group -> [TEI.Translation] groupToTranslations (Ding.Group us) = map unitToTranslation us where unitToTranslation :: Ding.Unit -> TEI.Translation unitToTranslation u = TEI.Translation { TEI.translationOrth = Ding.unitHeadword u , TEI.translationGrammar = Ding.unitGrammar u -- Note: -- * Parenthesis-enclosed prefixes are translated to -- `usg[@type="colloc"]'. , TEI.translationUsages = Ding.unitUsages u ++ map prefixAnnotToUsage (Ding.unitPrefixes u) , TEI.translationAbbrevs = Ding.unitAbbrevs u , TEI.translationInflected = Ding.unitInflected u , TEI.translationNotes = Ding.unitSuffixes u } -- | Create a TEI entry. makeTEIEntry :: Ding.Unit -> TRef.Ident -> [TRef.Ident] -> [TRef.Ident] -> [TEI.Translation] -> TEI.Entry makeTEIEntry u ident uRefs gRefs translations = TEI.Entry { TEI.entryIdent = ident , TEI.entryForm = TEI.Form { TEI.formOrth = Ding.unitHeadword u , TEI.formAbbrevs = Ding.unitAbbrevs u , TEI.formInflected = Ding.unitInflected u } , TEI.entryGrammar = Ding.unitGrammar u , TEI.entrySenses = [ TEI.Sense { TEI.senseGrammar = [] , TEI.senseUsages = Ding.unitUsages u ++ map prefixAnnotToUsage (Ding.unitPrefixes u) , TEI.senseTranslations = translations , TEI.senseExamples = Ding.unitExamples u , TEI.senseReferences = toList (makeRefGroup TRef.Synonymy $ map makeLinkedReference uRefs ) ++ toList (makeRefGroup TRef.Related $ map makeLinkedReference gRefs ++ map makeTildeReference (Ding.unitReferences u) ) , TEI.senseNotes = Ding.unitSuffixes u } ] } -- | Create a reference group. makeRefGroup :: TRef.RefType -> [TRef.Reference] -> Maybe TRef.ReferenceGroup makeRefGroup _ [] = Nothing makeRefGroup refType (ref:refs) = Just $ TRef.ReferenceGroup refType (ref :| refs) -- | Create a reference with `\@target'. makeLinkedReference :: TRef.Ident -> TRef.Reference makeLinkedReference ident@(TRef.Ident hw _) = TRef.Reference (Just ident) hw -- | Create a reference without `\@target', as is required for \~tilde -- references. (In fact, one could infer such a target, whenever there is -- exactly one TEI entry with the headword referred to.) makeTildeReference :: String -> TRef.Reference makeTildeReference = TRef.Reference Nothing -- | In TEI, optional prefixes can be encoded as collocates. -- -- Note: -- * Depending on the content (e.g., "etw."), might be more -- appropriate, but such content analysis is not done. prefixAnnotToUsage :: String -> Usage prefixAnnotToUsage pref = Usage Usage.Colloc pref -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Config.hs000066400000000000000000000132571423006221500207410ustar00rootroot00000000000000{- - Config.hs - configuration of some output variables - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {- - See also - * Language/{Ding,TEI}/{Read,Show}/ - * Language/Ding/AlexScanner.x - on configuration and classification of usage and grammar annotatios. -} module Config ( Person(..) , editor , maintainer , contributors , publicationDate , status , modVersion , programName , programURL , programDownloadURL , projectName , projectURL , dingDownloadURL , gpl3url , gpl2url , agpl3url , makeVersion , Change(..) , changes ) where data Person = Person { personId :: String , personName :: String } -- Involved people: person_shumenda :: Person person_shumenda = Person "shumenda" "Sebastian Humenda" person_eleichtfuss :: Person person_eleichtfuss = Person "eleichtfuss" "Einhard Leichtfuß" editor :: Person editor = person_eleichtfuss maintainer :: Person maintainer = person_eleichtfuss -- | Contributors to this program, with the corresponding copyright -- periods contributors :: [(Person, String)] contributors = [ (person_eleichtfuss, "2020-2022") , (person_shumenda, "2021") ] -- | Date of last output-affecting change of this program. -- Must be given as YYYY-MM-DD. publicationDate :: String publicationDate = "2022-04-18" status :: String status = "stable" -- | Modification version. Should be incremented for any output-altering -- change of this program. To be reset, when a later version of the Ding -- is targeted. -- May also be set to -devel to indicate frequent changes without version -- change. modVersion :: String modVersion = "1" programName :: String programName = "ding2tei-haskell" programURL :: String programURL = "https://github.com/freedict/tools/tree/master/importers/ding2tei" projectName :: String projectName = "FreeDict" projectURL :: String projectURL = "https://freedict.org/" programDownloadURL :: String programDownloadURL = programURL dingDownloadURL :: String dingDownloadURL = "https://ftp.tu-chemnitz.de/pub/Local/urz/ding/" gpl3url :: String gpl3url = "https://www.gnu.org/licenses/gpl-3.0.html" gpl2url :: String gpl2url = "https://www.gnu.org/licenses/old-licenses/gpl-2.0.html" agpl3url :: String agpl3url = "https://www.gnu.org/licenses/agpl-3.0.html" -- | Combine the Ding version and a modification version. makeVersion :: String -> String -> String makeVersion dingVer modVer = dingVer ++ "-fd" ++ modVer -- Notes: -- * chUsersShort and chUsersFull must be non-empty lists. -- * The NonEmpty type is not used to ease manual modification. data Change = Change { chDingVersion :: String , chModVersion :: String , chPersons :: [Person] , chDate :: String -- must be in YYYY-MM-DD format , chItems :: [String] -- translated to 's } changes :: [Change] changes = [ Change "1.8.1" "0.1" [person_eleichtfuss] "2020-09-11" [ "Initial import from Ding (version 1.8.1)." , "Fixed many syntax errors and inconsistencies in the Ding dictionary." , "Most explicit Ding annotations are transferred to TEI." , unwords [ "Several entries per Ding line are given references to one another" , "(@type=\"see\")." ] , unwords [ "Entries with more than one keyword on the source language side" , "are split up into multiple entries." , "These are linked using references of @type=\"syn\"." ] ] , Change "1.8.1" "0.2" [person_eleichtfuss] "2020-10-14" [ unwords [ "Identify examples to some entries; add the former to the latter" , "at 'entry/sense/cit[@type=\"example\"]' and remove the" , "examples from the list of regular entries." ] , unwords [ "Add annotations that are only implicitly present in the Ding" , "dictionary, such as: {f}, but no {pl} -> {noun}." ] , unwords [ "Transfer some annotations within Ding entries (e.g., when" , "\"Apfel\" is a masculine noun, we may infer that its translation," , "\"apple\", is also a noun." ] , "Fixed some TEI syntax, as per comments from Sebastian Humenda." , "Recognize more types of annotations (notes)." ] , Change "1.8.1" "0.2.1" [person_eleichtfuss] "2020-10-28" [ "Mark units with annotated inflected forms as verbs." ] , Change "1.9" "1" [person_eleichtfuss] "2022-04-18" [ "Update to Ding version 1.9." , unwords [ "Unify xr elements by @type." , "(Previously, each xr element contained exactly one ref element.)" ] , unwords [ "Place @xml:lang (more) correctly:" , "at top-level text element (source language)" , "and at cit@type=\"trans\" (target language)." , "(Previously," , "the target language was given at the top-level text element, and" , "the respective language at each quote element.)" ] ] ] -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Data/000077500000000000000000000000001423006221500200415ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Data/NatLang/000077500000000000000000000000001423006221500213655ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Data/NatLang/Dictionary.hs000066400000000000000000000026571423006221500240400ustar00rootroot00000000000000{- - Data.NatLang.Dictionary.hs - general dictionary - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - General dictionary AST. -} module Data.NatLang.Dictionary ( Dictionary(..) , Body(..) ) where import Data.NatLang.Language (Language) -- | A polymorphic dictionary type, parametrised over the header type and the -- type of elements (entries/lines) contained therin. data Dictionary header element = Dictionary { dictHeader :: header , dictSrcLang :: Language , dictTgtLang :: Language , dictBody :: Body element } deriving Show -- | The body of a dictionary, composed of a list of elements (entries/lines). newtype Body element = Body [element] deriving Show -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Data/NatLang/Example.hs000066400000000000000000000024431423006221500233170ustar00rootroot00000000000000{- - Data/NatLang/Example.hs - examples - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Examples, as they are found in TEI, while also being added to the Ding AST - during preprocessing. -} module Data.NatLang.Example ( Example(..) ) where -- | An example in the source language with translation. -- Usually, there is one translation given, however any number is possible -- (including none). data Example = Example String -- ^ example in the source language [String] -- ^ set of translations deriving (Show, Eq, Ord) -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Data/NatLang/Grammar.hs000066400000000000000000000063111423006221500233100ustar00rootroot00000000000000{- - Data/NatLang/Grammar.hs - Grammar (linguistics) information - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Grammar information (linguistics), as present in the Ding dictionary, - albeit also representable in TEI. -} module Data.NatLang.Grammar ( GrammarInfo(..) , GramLexCategory(..) , PartOfSpeech(..) , VerbType(..) , PronounType(..) , Gender(..) , Number(..) , Case(..) , Collocate(..) ) where import Data.NatLang.Usage (Usage) -- | A grammar single annotation, where "{f,n}" counts as two of them. data GrammarInfo = GramLexCategory GramLexCategory | Collocate Collocate [Usage] deriving (Show, Eq, Ord) -- | Grammatical or lexical category (resp. values thereof). -- See https://en.wikipedia.org/wiki/Grammatical_category for a distinction. -- Might contain information not considered related to grammatical or lexical -- categories. I am not a linguist. data GramLexCategory = PartOfSpeech PartOfSpeech | Gender Gender | Number Number | Case Case -- ^ rare in the Ding deriving (Show, Eq, Ord) data PartOfSpeech = Noun | Verb [VerbType] | Adjective | Adverb | Preposition | Conjunction | Article | Pronoun [PronounType] | Numeral | Interjection | Particle deriving (Show, Eq, Ord) data VerbType = Transitive | Intransitive | Reflexive deriving (Show, Eq, Ord) -- | Some pronoun types. -- There are many more, but the Ding does not annotate them. data PronounType = Personal | Interrogative | Relative deriving (Show, Eq, Ord) data Gender = Feminine | Masculine | Neuter deriving (Show, Eq, Ord) -- | Grammatical number -- The argument to both constructors specifies whether the annotated word -- may only occur in the respective number, that is, whether it is a -- singulare/plurale tantum. data Number = Singular Bool | Plural Bool deriving (Show, Eq, Ord) -- | Grammatical case. -- Only those listed that appear in annotations in the Ding. data Case = Nominative | Genitive | Accusative | Dative deriving (Show, Eq, Ord) -- | Grammar collocate, such as "{+Dat.}" or "{+conj}". data Collocate = CollocCase [String] -- ^ interrogative pronouns; often none Case -- ^ case of collocating word | CollocPOS PartOfSpeech -- ^ POS of collocating word; rare | CollocNumber Number -- ^ number of collocating word; rare deriving (Show, Eq, Ord) -- vi: ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Data/NatLang/InflectedForms.hs000066400000000000000000000032411423006221500246250ustar00rootroot00000000000000{- - Data/NatLang/InflectedForms.hs - information on inflected forms - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Data types for inflected forms, as they appear in the Ding dictionary and - may be (partially) represented in TEI. -} module Data.NatLang.InflectedForms ( InflectedForms(..) , InflectedForm(..) ) where import Data.List.NonEmpty (NonEmpty) import Data.NatLang.Usage (Usage) -- | Two inflected forms of a word. This is meant to be used for the English -- language's simple past and past participle of a verb. data InflectedForms = InflectedForms (NonEmpty InflectedForm) -- ^ simple past (NonEmpty InflectedForm) -- ^ past participle deriving (Show, Eq, Ord) -- | A single inflected form, annotated with usages. -- Note that these usages cannot be specified in TEI, or at least it is -- unknown how. data InflectedForm = InflectedForm String [Usage] deriving (Show, Eq, Ord) -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Data/NatLang/Language.hs000066400000000000000000000031001423006221500234360ustar00rootroot00000000000000{- - Data/NatLang/Language.hs - specific languages - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Natural language data types. - Only English and German for now, the main languages of the Ding dictionary. -} module Data.NatLang.Language ( Language(..) , showCode ) where -- | There are more languages. They are not of particular importance for the -- German-English Ding dictionary though. data Language = German | English deriving (Eq, Ord) -- | Show the full language name, in English. instance Show Language where show German = "German" show English = "English" -- | Show short language code, as to be used in @xml:lang. -- This is the ISO 639-1 code, unless no such one is available, where the -- ISO 639-3 code is shown. showCode :: Language -> String showCode German = "de" showCode English = "en" -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Data/NatLang/Usage.hs000066400000000000000000000052221423006221500227660ustar00rootroot00000000000000{- - Data/NatLang/Usage.hs - usage information - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Usage information, as present in both the Ding dictionary and TEI. -} module Data.NatLang.Usage ( Usage(..) , UsageType(..) ) where -- Notes: -- * In the Ding, usages are represented within <[]>. -- * In TEI, usages are represented in tags. -- * See the TEI doc on . -- * https://www.tei-c.org/release/doc/tei-p5-doc/en/html/DI.html#DITPUS -- * See also the TEI Lex-0 documentation on . -- * https://dariah-eric.github.io/lexicalresources/pages/TEILex0/TEILex0.html#index.xml-body.1_div.7_div.2 -- * See also the Wikipedia on Varieties and Registers (contains a list). -- * https://en.wikipedia.org/wiki/Variety_(linguistics) -- * https://en.wikipedia.org/wiki/Register_(sociolinguistics) -- | Usage information, bearing a particular type. data Usage = Usage UsageType String deriving (Show, Eq, Ord) -- Maps directly to TEI recommended \@type values for . -- | Usage types. -- These map directly to the TEI Guidelines' recommended \@type value for -- . Some unused types are omitted. -- See: data UsageType = Regional | Time | Domain | Register | Style | Preference | Acceptability -- likely unused; consider to remove | Language -- @type="lang" - do not confound with @xml:lang ! | Colloc | Hint deriving (Eq, Ord) -- | Show the corresponding TEI recommended values for usage types. -- See . instance Show UsageType where show Regional = "geo" show Time = "time" show Domain = "dom" show Register = "reg" show Style = "style" show Preference = "plev" show Acceptability = "acc" show Language = "lang" show Colloc = "colloc" show Hint = "hint" -- vi: ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/000077500000000000000000000000001423006221500207135ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/Ding/000077500000000000000000000000001423006221500215745ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/Ding/AlexScanner.x000066400000000000000000000351241423006221500241750ustar00rootroot00000000000000-- Language/Ding/AlexScanner.x - lexer -- -- Copyright 2020,2022 Einhard Leichtfuß -- -- This file is part of ding2tei-haskell. -- -- ding2tei-haskell is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published -- by the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- -- ding2tei-haskell 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 Affero General Public License for more details. -- -- You should have received a copy of the GNU Affero General Public License -- along with ding2tei-haskell. If not, see . -- Notes: -- * This file is supposed to be processed by Alex, "the lexical analyser -- generator for Haskell". It contains both Alex code and regular Haskell -- code. The result of Alex invocation is a regular Haskell source file. -- * Alex User Guide: https://www.haskell.org/alex/#Documentation { -- Haskell module header and import statements. module Language.Ding.AlexScanner (scan, AlexPosn) where import Data.NatLang.Grammar import Language.Ding.Token } -- In contrast to the "basic" wrapper, "posn" allows for keeping track of -- positions. %wrapper "posn" ------------------------------------------------------------------------------- -- Definition of named character sets and regexes ------------------------------------------------------------------------------- -- Notes: -- * $x is a (named) character set -- * @y is a (named) regular expression -- * $x # $y is the set difference of $x and $y ($x - $y). -- * See also: Alex User Guide, chapter 4. $anyLeftBracket = [ \{ \( \[ \< ] $anyRightBracket = [ \} \) \] \> ] $anyBracket = [ $anyLeftBracket $anyRightBracket ] -- Any character that may (!) have special semantics (incl. <:>), excluding -- $slashSpecialChar. -- Note that <:> is only included to avoid matching <::> as part of @word. -- Some characters, in particular all single colons have to be merged with -- their surrounding (e.g., text atoms) later. $specialChar = [ $anyBracket : \| \; \, \/ \~ \+ ] -- Characters that are special in that they may occur enclosed in slashes. $slashSpecialChar = [ $specialChar \% \@ ] $textChar = $printable # [$specialChar $white] -- Anything printable, not containing a special char, nor whitespace. @word = $textChar+ -- To allow infix whitespace, use the below (in place of @word): -- @text = [$textChar # $white] ($textChar* [$textChar # $white])? -- A left context that marks a slash as "left-free". This is used -- particularly to identify "opening" and "closing" slashes, where the -- former is "left-free", but not "right-free", as for example in -- /abbrev./ -- Notes: -- * This must not contain '.' ! (consider /abbrev./) -- * Most interpunctuation is usually followed by a space. -- * $white includes '\n' (which is desired). $slashLeftFree = [ $white $anyLeftBracket ] -- A right context that marks a slash as "right-free" (see just above). -- This should in theory contain all interpunctuation (that does not require -- preceding whitespace). -- Note: -- * Expand the interpunctuation character set as needed. $slashRightFree = [ $white $anyRightBracket \; \, : \. \? ! ] -- A non-empty sequence of slash-separated words (@slashWordList), which forms -- a special case within slashes. -- Notes: -- * The final optional dot is redundant, as of now. -- * This should not be used as generic word macro, as possibly desired for -- "<>". (General mord macros should not allow a terminating '.'.) -- * $textChar should possibly be replaced by something more sensible. @slashWord = [$textChar # \/]+ (\.|\?)? @slashWordList = @slashWord ("/" @slashWord)+ -- There is only one known smiley, as of now. -- Smileys are treated as abbreviations. @smiley = ":-)" -- Abbreviations that cannot be easily parsed by normal means. @specialAbbrev = @smiley | "/.ed" $word_end = ~$textChar ------------------------------------------------------------------------------- -- Mapping from regexes to Haskell functions (actions) ------------------------------------------------------------------------------- -- Notes: -- * The functions are expected to take a matched string and produce a -- corresponding token. -- * Because the "posn" wrapper is used (instead of "basic"), most such -- functions have to be wrapped in `regularToken'. -- * Alex takes longest matching sequences (maximal munch). -- * If several rules match the same longest sequence, the first one of them -- applies. -- * The lexer should always succeed. Hence there is no special error -- mechanism needed. -- * This might change if the allowed whitespace characters are reduced. -- * Also, if triple+ '/' should be caught at the lexer level. -- * At the parser level, they'd be caught as successive '//', '/'. tokens :- -- A header line. To be parsed as is by Language.Ding.Parser.Header. ^ \# .* $ { regularToken HeaderLine } --------------------------------------------------------------------------- -- Separators (excl. slashes and single angle brackets) \n { regularToken $ const NL } :: { regularToken $ const LangSep } \| { regularToken $ const Vert } \; { regularToken $ const Semi } \, { regularToken $ const Comma } \~ { regularToken $ const Tilde } \+ { regularToken $ const Plus } \<\> { regularToken $ const Wordswitch } \{ { regularToken $ const OBrace } \} { regularToken $ const CBrace } \[ { regularToken $ const OBracket } \] { regularToken $ const CBracket } \( { regularToken $ const OParen } \) { regularToken $ const CParen } -- This is only caught explicitly, since it may not be caught as part of -- @word (which could be changed, by complicating the regex--<::> would -- still need to be excluded). : { regularToken Text } --------------------------------------------------------------------------- -- Slashes -- See doc/syntax.slashes -- Divide slashes into categories. -- Slashes have many roles (in particular one similar to brackets), but -- unfortunately there are no different opening and closing '/'-characters. -- Hence, infer such information from the context. -- * (The description there is not strictly equivalent, but should be in all -- practical cases.) $slashLeftFree ^ "/" / $slashRightFree { regularToken $ const WeakSlash } $slashLeftFree ^ "/" { regularToken $ const OSlash } "/" / "..." { regularToken $ const StrongSlash } "/" / $slashRightFree { regularToken $ const CSlash } "/" { regularToken $ const StrongSlash } -- Treat single special characters between free slashes differently. -- This would typically be done in the parser, however it is difficult to -- identify such expressions using a CFG. -- Consider "+ / % / ~". Since "word+ / word+ / word+" is generally -- permitted, this could not easily unambiguously parsed. (Note that the -- "correct" parsing in this case is unclear. $slashLeftFree ^ "/ " $specialChar " /" / $slashRightFree { regularToken $ SlashSpecial . pure . (!!2) } $slashLeftFree ^ "/ " @specialAbbrev " /" / $slashRightFree { regularToken $ Abbrev . dropLast 2 . drop 2 } -- Treat stuff like "/ AC/DC /" also as a special case. This is because, -- in general, strong slashes may occur in between weak slashes. For this -- reason, this rule may actually incorrectly catch such cases. However, -- the ambiguity needs to be taken care of somehow and this is the best -- way known to me. -- The dropLast part would be more efficient if the "monad" or -- "monadUserState" wrapper was used (one gets the length of the input). $slashLeftFree ^ "/ " @slashWordList " /" / $slashRightFree { regularToken $ Abbrev . dropLast 2 . drop 2 } -- An awkward special case that cannot (easily) be handled otherwise: $white ^ \"\/\.ed\" / $white { regularToken $ Abbrev . dropLast 1 . drop 1 } -- " -- Plural abbreviations. -- Note: No more than one word between the slashes is permitted. -- Otherwise, lexing + parsing would become more difficult. $slashLeftFree ^ "/" @slashWord "/s" / $slashRightFree { regularToken $ AbbrevPlural . dropLast 2 . drop 1 } -- Double slashes are used (onserved once) to bind two adjacent alternative -- expressions (bound by a strong slash). -- Semantic: "a/b//c/d" -> (a or b) or (c or d) "//" { regularToken $ const DoubleSlash } --------------------------------------------------------------------------- -- Angle brackets -- Angle brackets are special in that they also may signify less- resp. -- greater-than. Fortunately, they are, when brackets, always close to the -- enclosed object. Treat similar to left and right slashes. -- Important: This rule must be above the generic \< and \> rule(s). $slashLeftFree ^ [\< \>] / $slashRightFree { regularToken Text } \< { regularToken $ const OAngle } \> { regularToken $ const CAngle } --------------------------------------------------------------------------- -- Keywords -- Note: -- * Multi-word keywords need to be given the right context $word_end. -- * This ensures that the word ends there. -- * Ex.: Let "a b" be a multi-word keyword, and "a bc " at the start of -- the character stream. This should be matched as two text tokens, "a" -- and "bc", not as keyword "a b" and text "c". "to" { regularToken $ const KW_to } -- Grammar keywords "f" { gramKW $ Gender Feminine } "m" { gramKW $ Gender Masculine } "n" { gramKW $ Gender Neuter } "sing" { gramKW $ Number $ Singular False } "pl" { gramKW $ Number $ Plural False } "no pl" / $word_end { gramKW $ Number $ Singular True } "no sing" / $word_end { gramKW $ Number $ Plural True } -- "v" { gramKW $ PartOfSpeech $ Verb [] } "vt" { gramKW $ PartOfSpeech $ Verb [Transitive] } "vi" { gramKW $ PartOfSpeech $ Verb [Intransitive] } "vr" { gramKW $ PartOfSpeech $ Verb [Reflexive] } "adj" { gramKW $ PartOfSpeech Adjective } "adv" { gramKW $ PartOfSpeech Adverb } "prp" { gramKW $ PartOfSpeech Preposition } "conj" { gramKW $ PartOfSpeech Conjunction } "art" { gramKW $ PartOfSpeech Article } "pron" { gramKW $ PartOfSpeech $ Pronoun [] } "ppron" { gramKW $ PartOfSpeech $ Pronoun [Personal] } "pron interrog" / $word_end { gramKW $ PartOfSpeech $ Pronoun [Interrogative] } "pron relativ" / $word_end { gramKW $ PartOfSpeech $ Pronoun [Relative] } "num" { gramKW $ PartOfSpeech Numeral } "interj" { gramKW $ PartOfSpeech Interjection } "Partikel" { gramKW $ PartOfSpeech Particle } "particle" { gramKW $ PartOfSpeech Particle } -- "Gen." { gramKW $ Case Genitive } "Akk." { gramKW $ Case Accusative } "Dat." { gramKW $ Case Dative } "Nom." { gramKW $ Case Nominative } -- Interrogative pronouns (not grammar keywords in the strict sense) "wo?" { regularToken IntPronKW } "wohin?" { regularToken IntPronKW } "wann?" { regularToken IntPronKW } "bis wann?" / $word_end { regularToken IntPronKW } -- Regular word / text token -- Note: -- * This must come after all the specific keywords, or else this rule -- would be prefered over them (the single word keywords, to be precise). @word { regularToken Text } -- Whitespace -- Note: -- * Newlines do not count as whitespace, they serve as line separators, -- where a line is a well defined Ding entity. [$white # \n]+ { const Whitespace } { ------------------------------------------------------------------------------- -- Auxiliary Haskell code. ------------------------------------------------------------------------------- -- | A simple token, as identified by Alex. data SimpleToken = RegularToken Position Atom | Whitespace String -- | An action helper for regular tokens, to take care of the AlexPosn. regularToken :: (String -> Atom) -> AlexPosn -> String -> SimpleToken regularToken f p s = RegularToken (toPosition p) (f s) gramKW :: GramLexCategory -> AlexPosn -> String -> SimpleToken gramKW gram p _ = RegularToken (toPosition p) (GramKW gram) toPosition :: AlexPosn -> Position toPosition (AlexPn _abs line col) = Position line col -- | Remove the whitespace from the token stream towards the respective -- following regular token, as annotation. mergeWS :: [SimpleToken] -> [Token] -- Notes -- * There can never be two whitespace tokens in succession. -- * One could use `foldr' here, but this is not very straightforward - the -- folding function (in some cases) has to inspect the first element of its -- list argument, which therefore is inspected twice (in such cases). mergeWS ( RegularToken pos atom : toks) = Token "" pos atom : mergeWS toks mergeWS (Whitespace ws : RegularToken pos atom : toks) = Token ws pos atom : mergeWS toks mergeWS (Whitespace _ : Whitespace _ : _) = error "Language.Ding.AlexScanner: two successive whitespace tokens" -- Ignore terminating whitespace. mergeWS (Whitespace _ : []) = [] mergeWS [] = [] -- | Drop the given number of last elements. dropLast :: Int -> [a] -> [a] dropLast n xs = take (length xs - n) xs -- | Convert a string into a stream of tokens. scan :: String -> [Token] scan = mergeWS . alexScanTokens } -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich.hs000066400000000000000000000036001423006221500233370ustar00rootroot00000000000000{- - Language/Ding/Enrich.hs - enrich the Ding AST - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Enrich the Ding AST with various information. - This partially starts a translation towards TEI. -} module Language.Ding.Enrich ( enrichUndirected , enrichDirected ) where import Language.Ding.Enrich.Example (inferExamples) import Language.Ding.Enrich.Grammar (enrichGrammar) import Language.Ding.Syntax (Ding) import Data.NatLang.Dictionary (Dictionary(..), Body(..)) -- | Enrich the Ding AST in a way that does not depend on the order of -- languages. -- That is, `enrichUndirected . inverse = inverse . enrichUndirected'. enrichUndirected :: Ding -> Ding enrichUndirected (Dictionary header srcLang tgtLang (Body ls)) = Dictionary header srcLang tgtLang $ Body $ map enrichGrammar ls -- | Enrich the Ding AST considering it a directed dictionary, with a source -- and target language. -- This function affects elements in the source and target language -- differently. enrichDirected :: Ding -> Ding enrichDirected (Dictionary header srcLang tgtLang (Body ls)) = Dictionary header srcLang tgtLang $ Body $ map inferExamples ls -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/000077500000000000000000000000001423006221500230045ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/Example.hs000066400000000000000000000126731423006221500247440ustar00rootroot00000000000000{- - Language/Ding/Enrich/Example.hs - example identification and linking - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Identify examples in a line, remove them from it and annotate to the units - they belong to. - - Examples are identified on the unit-level. That is, a group may contain - both examples and non-examples. If it contains only non-examples, the - whole entry (!) is taken off the line. - As a side effect, an entry that did not contain any unit on the source side - from the beginning, is also removed. - - Examples are only recognized when occuring right of the unit they exemplify. - - Note that the enrichment done by `inferExamples' only happens on the source - (left) side of the dictionary, therefore the dictionary becomes directional. - This is because example identification may have different consequences - on both sides, in particular entries may be lost. - - If both directed outputs (deu-eng, eng-deu) are desired, this module's - enrichment should happen separately (and therefore twice). - - Examples are built from a unit in a group on the left side and the set - of corresponding translations, which are in the surrounding entry. - - Note that this enrichment should not be done on the TEI AST -- as might seem - more natural -- because - a) this causes unnecessary calculations on later identified examples, - in particular, - b) there would be IDs generated for examples, potentially causing - unnecesarily high number suffixes in other (retained) entries' IDs - (likely rare and not really important). -} module Language.Ding.Enrich.Example (inferExamples) where import Control.Monad (when, liftM) import Control.Monad.Trans.State (State, state, modify, get, evalState) import Language.Ding.Enrich.Example.Augment (augmentEntriesWith) import Language.Ding.Enrich.Example.Identify (isPotentialExample) import Language.Ding.Syntax (Line(..), Entry(..), Group(..), Unit(..)) -- Notes: -- * Only non-examples need to be used to search for examples. -- * Examples are right of their exemplified units, so go from left to right. -- * A unit is a known non-example iff it is not an example to all preceding -- (non-example) units. -- * Therefore, stepwise (left to right) consider units for being examples. -- * Units in the same group may be considered in any order, they should -- not be examples of one another. -- * In any step test all previously identified non-examples for the new -- unit exemplifying them. -- * Two things need to happen. -- * a) remove units (and possibly entries) when examples. -- * b) add examples to the units they exemplify. -- * This is achieved by accumulating all identified non-examples in a -- state monad. -- * The state's preceding non-example list is used for any new potential -- example. -- * Any newly identified non-example is pushed to the state. -- * The entries in the state are reversed, since the state is used as a -- stack. -- * This is not a problem, their order does not matter when -- identifying examples. -- * The list needs to be reversed in the end though. -- Note: State [Group] (or even State [Unit]) would suffice, at the expense of -- readability / complexity of the code. Might be more efficient though. -- | A stack state to accumulate the entries. type EntryStackState = State [Entry] -- | Push an element to a state's stack. push :: s -> State [s] () push x = modify (x:) -- | In a line, link any examples to the units (on the source side of the -- dictionary) they are examples to and remove any found examples from -- the line. inferExamples :: Line -> Line -- The entries are accumulated in reverse in the state monad, hence the need -- for a reversal. inferExamples (Line entries) = Line $ reverse $ evalState (handleEntries entries >> get) [] -- | Perform the enrichment on a list of entries. -- The result is pushed to the state's stack. handleEntries :: [Entry] -> EntryStackState () handleEntries = mapM_ handleEntry handleEntry :: Entry -> EntryStackState () handleEntry (Entry g gTrans) = do g'@(Group us') <- handleGroup g gTrans when (not $ null us') $ push $ Entry g' gTrans handleGroup :: Group -> Group -> EntryStackState Group handleGroup (Group us) (Group uTrans) = liftM Group $ handleUnits us uTrans handleUnits :: [Unit] -> [Unit] -> EntryStackState [Unit] handleUnits [] _ = return [] handleUnits (u:us) uTrans = do mu' <- handleUnit u uTrans us' <- handleUnits us uTrans return $ maybe id (:) mu' us' handleUnit :: Unit -> [Unit] -> EntryStackState (Maybe Unit) handleUnit u uTrans = if isPotentialExample (unitHeadword u) then state $ augmentEntriesWith u uTrans else return $ Just u -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/Example/000077500000000000000000000000001423006221500243775ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/Example/Augment.hs000066400000000000000000000100731423006221500263340ustar00rootroot00000000000000{- - Language/Ding/Enrich/Example/Augment.hs - augment unit with examples - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Inspect a single potential example unit considering a list of known non- - examples. - If the former is identified as an example for any of the latter, augment the - latter with the former and mark the former as example, i.e. to be forgotten. -} module Language.Ding.Enrich.Example.Augment (augmentEntriesWith) where import Control.Monad (liftM) import Control.Monad.Trans.Writer (Writer, writer, runWriter) import Data.NatLang.Example (Example(..)) import Language.Ding.Enrich.Example.Identify (isExampleOf) import Language.Ding.Syntax (Entry(..), Group(..), Unit(..)) -- | Add the potential example `pex', together with its translations, to -- any unit on the left side of any entry in `es'. -- If any change happened, forget about the potential example (first element -- of result pair is `Nothing'), otherwise `Just' keep it. augmentEntriesWith :: Unit -> [Unit] -> [Entry] -> (Maybe Unit, [Entry]) augmentEntriesWith pex pexTrans es = case runWriter (updateEntries pex pexTrans es) of (es', Changed True) -> (Nothing, es') (_, Changed False) -> (Just pex, es) newtype Changed = Changed Bool -- | The (Bool, ||) semigroup. instance Semigroup Changed where (Changed b1) <> (Changed b2) = Changed $ b1 || b2 -- | The (Bool, ||, False) monoid. instance Monoid Changed where mempty = Changed False -- | A writer monad used to remember whether any change was done. -- Uses the natural Bool monoid with (||). type ChangeWriter = Writer Changed -- | Return a value in the `ChangeWriter' monad and indicate a change. returnChanged :: a -> ChangeWriter a returnChanged a = writer (a, Changed True) -- | Return a value in the `ChangeWriter' monad and indicate that nothing -- changed. -- Equivalent to `return'. returnUnchanged :: a -> ChangeWriter a returnUnchanged a = writer (a, Changed False) -- | Augment entries with the provided example, wherever it matches. -- If any change happens, this is recorded in the `ChangeWriter' result. updateEntries :: Unit -> [Unit] -> [Entry] -> ChangeWriter [Entry] updateEntries pex pexTrans = mapM $ updateEntry pex pexTrans updateEntry :: Unit -> [Unit] -> Entry -> ChangeWriter Entry updateEntry pex pexTrans (Entry g h) = do g' <- updateGroup pex pexTrans g return $ Entry g' h updateGroup :: Unit -> [Unit] -> Group -> ChangeWriter Group updateGroup pex pexTrans (Group us) = liftM Group $ updateUnits pex pexTrans us updateUnits :: Unit -> [Unit] -> [Unit] -> ChangeWriter [Unit] updateUnits pex pexTrans = mapM $ updateUnit pex pexTrans -- Note that examples are appended, using (++), to keep the order of the -- examples. This inefficient for large lists. The lists are expected to be -- very small though, often ending up with only a single element, if any. -- - Runtime with example-identification actually seems to have (slightly) -- dropped (likely due to smaller number of entries generated). -- - Nevertheless, the (++) is hence ok. updateUnit :: Unit -> [Unit] -> Unit -> ChangeWriter Unit updateUnit pex pexTrans key = do if (unitHeadword pex) `isExampleOf` (unitHeadword key) then returnChanged $ key { unitExamples = unitExamples key ++ ex : [] } else returnUnchanged key where ex = Example (unitPlain pex) (map unitPlain pexTrans) -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/Example/Identify.hs000066400000000000000000000055051423006221500265130ustar00rootroot00000000000000{- - Language/Ding/Enrich/Example/Identify.hs - example identification - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Identify examples. Both on their own (`isPotentialExample') and - in relation to other unit strings (`isExampleOf'). -} module Language.Ding.Enrich.Example.Identify ( isPotentialExample , isExampleOf ) where import Data.Char (isPunctuation) import Data.List (isInfixOf) -- | Decide whether a unit string should be considered when searching for -- examples to a non-example. isPotentialExample :: String -> Bool isPotentialExample = (||) <$> isPhrase <*> containsSpecial -- | Identify non-trivial composed espressions. isPhrase :: String -> Bool isPhrase = (>=3) . length . words -- | Identify unit strings that contain interpunctuation (including quote -- signs) containsSpecial :: String -> Bool -- TODO?: This also catches '/' and '-'. -- * The latter is likely worse (ex.: "A-Dur") containsSpecial = any isPunctuation -- | Decide whether a potential example should be considered example of a -- supplied unit string. -- Any potential example should priorly afore have been identified as such -- by means of `isPotentialExample'. isExampleOf :: String -> String -> Bool -- TODO: -- * Better example identification, suggestions: -- * consider umlauts, e.g. "Ball" ~ "zwei große Bälle" -- * consider change in capitalisation, e.g. "Ball" ~ "ein großer Fußball" -- * [consider whitespace, e.g. "lang" ~/~ "... langweilig ..."] -- * Require the example to be longer than the exemplified unit. -- * Define a minimum number of words and/or characters in difference. -- * Prevent phrases where for example only the gender changes to match. -- * In particular important, if umlauts receive special treatment. -- * Require a maximum size (number of chars/words) for the non-example. isExampleOf = flip isInfixOf -- Simple way to perform more flexible matching (not very efficient): --isExampleOf _ "" = False --isExampleOf pex hw = prefixMatch hw ex || hasExample (tail pex) hw --prefixMatch :: String -> String -> Bool --prefixMatch = ??? -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/Grammar.hs000066400000000000000000000073711423006221500247360ustar00rootroot00000000000000{- - Language/Ding/Enrich/Grammar.hs - Enrich grammar annotation - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Enrich a line's grammar annotations and infer such from the presence of - inflected forms. - - This includes inferral from and transferral of grammar annotations, see - `Language.Ding.Enrich.Grammar.Infer' and - `Language.Ding.Enrich.Grammar.Transfer', respectively, - and inferral of grammar information (POS: verb) from the presence of - inflected forms, see `Language.Ding.Enrich.Grammar.InflectedForms`. - - Note: This module works on lines. It could equally well work on entries, - as `Language.Ding.Enrich.Grammar.Transfer' does. This is purely done to - be in line with other enrichment modules. -} module Language.Ding.Enrich.Grammar (enrichGrammar) where import Data.NatLang.Grammar (GrammarInfo) import Language.Ding.Syntax import Language.Ding.Enrich.Grammar.InflectedForms (enrichFromInflectedForms) import Language.Ding.Enrich.Grammar.Infer ( expand , joinVerbAnnots , joinPronounAnnots ) import Language.Ding.Enrich.Grammar.Transfer (transfer) -- Notes: -- * The order of enrichment steps is deliberately chosen. -- * First, identify verbs from the presence of inflected forms; the -- inflected forms constituting the input of this step do not change -- during the whole grammar enrichment process. -- * Do inferral (expand) next and hence before the remainder, to allow -- e.g. `POS Noun' as derived from `SingulareTantum' to be transfered. -- * Do join*Annots last, to account for any new duplicates. -- * All functions operate on GramLexCategory, the rest of GrammarInfo -- (Collocate) is of no importance. -- * It might be better to separate GramLexCategory and Collocate into -- two distinct lists in the ASTs (TODO?). -- * enrichFromInflectedForms only needs to apply to the english side. -- * It is, however, applied to both sides -- for reasons of simplicity. -- * The side's languages are unknown here. -- ? TODO?: Remove duplicates from the initial list of annotations on a unit? -- ? TODO?: Testing: Quickcheck. -- | Enrich a line's grammar annotations. enrichGrammar :: Line -> Line enrichGrammar (Line es) = Line $ map enrichEntry es enrichEntry :: Entry -> Entry enrichEntry = modEntryGrammar (joinVerbAnnots . joinPronounAnnots) . transfer . modEntryUnit ( modUnitGrammar expand . enrichFromInflectedForms ) -- | Modify the units in an entry. modEntryUnit :: (Unit -> Unit) -> Entry -> Entry modEntryUnit f (Entry g h) = Entry (modGroupUnit f g) (modGroupUnit f h) modGroupUnit :: (Unit -> Unit) -> Group -> Group modGroupUnit f (Group us) = Group $ map f us -- | Modify the grammar annotations of all units in an entry. modEntryGrammar :: ([GrammarInfo] -> [GrammarInfo]) -> Entry -> Entry modEntryGrammar f e = modEntryUnit (modUnitGrammar f) e modUnitGrammar :: ([GrammarInfo] -> [GrammarInfo]) -> Unit -> Unit modUnitGrammar f u = u { unitGrammar = f $ unitGrammar u } -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/Grammar/000077500000000000000000000000001423006221500243725ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/Grammar/Infer.hs000066400000000000000000000123031423006221500257700ustar00rootroot00000000000000{- - Language/Ding/Enrich/Grammar/Infer.hs - Enrich grammar annotation within - a unit. - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - From a set of grammar annotations on a unit, infer others to apply to the - same unit. - - Note that this works on grammar elements which are common to the Ding and - TEI, however this module is deliberately placed under `Language.Ding', - because the enrichment depends on inference rules which are particular to - the Ding. E.g. `{f}' implies that the corresponding unit is a noun, in the - Ding. There may however, in general, very well be other parts of speach - bearing a gender. -} module Language.Ding.Enrich.Grammar.Infer ( expand , joinVerbAnnots , joinPronounAnnots ) where import Data.List (union) import Data.NatLang.Grammar -- | Partition a list according to a function that yields `Maybe' values. -- Values that are `Nothing' under the function are put in the second -- partition, for the others, their `Just' value under that function is put -- into the first partition. -- Such a function also exists in the `utility-ht' package. partitionMaybe :: (a -> Maybe b) -> [a] -> ([b], [a]) partitionMaybe _ [] = ([], []) partitionMaybe f (x:xs) = case f x of Just y -> (y:ys', xs') Nothing -> (ys', x:xs') where (ys', xs') = partitionMaybe f xs getVTypes :: GrammarInfo -> Maybe [VerbType] getVTypes (GramLexCategory (PartOfSpeech (Verb vTypes))) = Just vTypes getVTypes _ = Nothing getPTypes :: GrammarInfo -> Maybe [PronounType] getPTypes (GramLexCategory (PartOfSpeech (Pronoun pTypes))) = Just pTypes getPTypes _ = Nothing -- (TODO): Consider to do a `nub' somewhen. -- | Merge several verb annotations in a list, if any, to a single one. The -- resulting annotation replaces the first of the old annotations joinVerbAnnots :: [GrammarInfo] -> [GrammarInfo] -- Note: The location of any first verb annotation is kept. joinVerbAnnots (GramLexCategory (PartOfSpeech (Verb vTypes)) : as) = (GramLexCategory $ PartOfSpeech $ Verb vTypes') : as' where (vTypess', as') = partitionMaybe getVTypes as vTypes' = foldr union [] (vTypes : vTypess') joinVerbAnnots (a:as) = a : joinVerbAnnots as joinVerbAnnots [] = [] -- | Merge several pronoun annotations in a list, if any, to a single one. The -- resulting annotation replaces the first of the old annotations joinPronounAnnots :: [GrammarInfo] -> [GrammarInfo] -- Note: The location of any first pronoun annotation is kept. joinPronounAnnots (GramLexCategory (PartOfSpeech (Pronoun pTypes)) : as) = (GramLexCategory $ PartOfSpeech $ Pronoun pTypes') : as' where (pTypess', as') = partitionMaybe getPTypes as pTypes' = foldr union [] (pTypes : pTypess') joinPronounAnnots (a:as) = a : joinPronounAnnots as joinPronounAnnots [] = [] isPlural :: GrammarInfo -> Bool isPlural (GramLexCategory (Number (Plural _))) = True isPlural _ = False -- | From a list of (all) grammar annotations on some entity, infer others, -- which are not explicitly specified in the Ding source. -- -- For most annotations, this does not infer anything. -- -- This function should not be applied after any grammar annotation is -- removed (e.g., when relocating an annotation to a higher level). -- -- If the argument does not contain duplicates (recommended), neither does -- the result. expand :: [GrammarInfo] -> [GrammarInfo] expand as = let as' = concatMap infer as -- Notes: -- * union first removes duplicates in as', then deletes any element from -- it that also is in as (see implementation of union). -- This is exactly what we want. -- * For longer lists, this would be quite inefficient, but in particular -- as' is expected to have few unique elements. in as `union` as' where infer :: GrammarInfo -> [GrammarInfo] infer (GramLexCategory gram) = map GramLexCategory $ inferGLC gram infer _ = [] -- Infer from single annotations. -- Note that {noun} implies {sing} iff {pl} is not annotated. inferGLC :: GramLexCategory -> [GramLexCategory] inferGLC (Gender _) | any isPlural as = [PartOfSpeech Noun] | otherwise = [PartOfSpeech Noun, Number $ Singular False] inferGLC (Number (Singular True)) = [PartOfSpeech Noun] inferGLC (Number (Plural True)) = [PartOfSpeech Noun] inferGLC _ = [] -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/Grammar/InflectedForms.hs000066400000000000000000000042621423006221500276360ustar00rootroot00000000000000{- - Language/Ding/Enrich/Grammar/FromInflectedForms.hs - Recognize that inflec- - ted forms are only - annotated to verbs. - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - From a set of grammar annotations on a unit, infer others to apply to the - same unit. - - Note that this works on grammar elements which are common to the Ding and - TEI, however this module is deliberately placed under `Language.Ding', - because the enrichment depends on inference rules which are particular to - the Ding. E.g. `{f}' implies that the corresponding unit is a noun, in the - Ding. There may however, in general, very well be other parts of speach - bearing a gender. -} module Language.Ding.Enrich.Grammar.InflectedForms ( enrichFromInflectedForms ) where import Data.NatLang.Grammar ( GrammarInfo(GramLexCategory) , GramLexCategory(PartOfSpeech) , PartOfSpeech(Verb) ) import Language.Ding.Syntax (Unit(unitInflected, unitGrammar)) -- | Annotate any unit that is annotated with inflected forms as a verb. -- Such new verb annotations are appended to the existing grammar -- annotations. enrichFromInflectedForms :: Unit -> Unit enrichFromInflectedForms u = case unitInflected u of (Just _) -> u { unitGrammar = unitGrammar u ++ verbAnnot : [] } Nothing -> u where verbAnnot = GramLexCategory $ PartOfSpeech $ Verb [] -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Enrich/Grammar/Transfer.hs000066400000000000000000000110131423006221500265060ustar00rootroot00000000000000{- - Language/Ding/Enrich/Grammar/Transfer.hs - Transfer grammar annotation - within an entry. - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Transfer grammar annotation between units in an entry. - Units of the same and of the respective other language are treated the same. - As of now, only part of speech annotation is considered transferable, - further excluding verb types (transitivity, reflexivity). -} module Language.Ding.Enrich.Grammar.Transfer (transfer) where import Data.Maybe (mapMaybe) import Data.List (union) import Language.Ding.Syntax (Entry(..), Group(..), Unit, unitGrammar) import Data.NatLang.Grammar -- Note -- * The things in here could also be done in an attribute grammar (~Happy). -- | Transfer grammar annotations within an entry. transfer :: Entry -> Entry transfer (Entry g h) = Entry g' h' where -- Notice how the below only works because of lazyness (and Haskell's -- automated fixed point calculation). -- (The below two function evaluations depend on results of one another.) (as1, g') = handleGroup as2 g (as2, h') = handleGroup as1 h handleGroup :: [GrammarInfo] -> Group -> ([GrammarInfo], Group) handleGroup as (Group us) = fmap Group $ handleUnits as us handleUnits :: [GrammarInfo] -> [Unit] -> ([GrammarInfo], [Unit]) handleUnits _ [] = ([], []) -- Note: -- * (++) may cause many duplicates here. Since the lists are short, it -- is however ok (possibly better) to remove the duplicates once in the end -- (at the leaves/units -> repeatedly). handleUnits as (u:us) = (as1 ++ as2, u':us') where -- Again, this works because of Haskell's lazyness. (as1, u') = handleUnit (as ++ as2) u (as2, us') = handleUnits (as ++ as1) us handleUnit :: [GrammarInfo] -> Unit -> ([GrammarInfo], Unit) handleUnit as u = (transferableGrammar uas, u { unitGrammar = uas `union` as }) where uas = unitGrammar u -- | From grammar information, extract any that is considered transferable to -- units in the same entry (i.e., the same group and the translation group). -- -- Duplicates may arise. These are not removed, because the result of this -- application is to be merged with `union' anyways (union removes duplicates -- off its second argument). transferableGrammar :: [GrammarInfo] -> [GrammarInfo] transferableGrammar = mapMaybe reduceGram -- Note: Collocations such as {+ Gen} are dropped. In many cases, co-occuring -- units share such collocations (without explicit annotation on each), -- but these cases are hard to identify. reduceGram :: GrammarInfo -> Maybe GrammarInfo reduceGram (GramLexCategory gram) = fmap GramLexCategory $ reduceGLC gram reduceGram _ = Nothing -- | Extract grammar information that shall be transfered to other units -- in the same entry (i.e., the same group and the translation group). -- -- Note: Handling in-language synonyms and translations the same way is not -- natural, but works for a German-English dictionary with the present -- grammar annotations. The grammatical case might form an exception, -- but it is rarely annotated anyways (possibly even no occurence). reduceGLC :: GramLexCategory -> Maybe GramLexCategory reduceGLC (PartOfSpeech pos) = fmap PartOfSpeech $ reducePOS pos reduceGLC _ = Nothing -- | Retain all information except verb types (reflexivity & transitivity) -- -- Notes: -- * Proposition-annotations ({prp}) are often made seemingly incorrect. -- * It is assumed preposition means adposition. -- * See https://en.wikipedia.org/wiki/Preposition_and_postposition -- * Otherwise, a preposition might be translated to a postposition. reducePOS :: PartOfSpeech -> Maybe PartOfSpeech reducePOS (Verb _) = Just $ Verb [] reducePOS pos = Just pos -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Inverse.hs000066400000000000000000000027141423006221500235470ustar00rootroot00000000000000{- - Language/Ding/Inverse.hs - Invert the language order - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Invert a Ding dictionary's direction, by mirroring all contained entries. -} module Language.Ding.Inverse (inverse) where import Data.NatLang.Dictionary import Language.Ding.Syntax -- | Give the inverse of a dictionary (invert the order of languages). -- This function is bijective and its own inverse. inverse :: Ding -> Ding inverse (Dictionary header srcLang tgtLang (Body ls)) = Dictionary header tgtLang srcLang (Body $ map lineInverse ls) lineInverse :: Line -> Line lineInverse (Line entries) = Line $ map entryInverse entries entryInverse :: Entry -> Entry entryInverse (Entry g h) = Entry h g -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Parser.hs000066400000000000000000000114721423006221500233710ustar00rootroot00000000000000{- - Language/Ding/Parser.hs - parser - - Copyright 2020-2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Parse the Ding dictionary from a list of tokens as identified by - `Language.Ding.AlexScanner'. The header is parsed manually, the body by - a `Happy' generated module. -} module Language.Ding.Parser (parse) where import Prelude hiding (log) import Control.Monad (guard) import Control.Monad.Writer (runWriter, Writer, mapWriterT) import Data.Functor.Identity (Identity(Identity)) import Data.Maybe (catMaybes) import Data.NatLang.Dictionary (Dictionary(Dictionary), Body(Body)) import Data.NatLang.Language (Language(German, English)) import Language.Ding.Parser.Header (parseHeader) import Language.Ding.Parser.Line (parseLine, FailWriter) import Language.Ding.Syntax (Ding, Line) import Language.Ding.Token (Token(..), Atom(..)) -- Note: -- * The parse log is completely separated from the returned value. -- * Consequence: Writing both in parallel (to stderr and, e.g., a file) is -- difficult. -- * This should not affect performance too much, however. The parse log is -- usually short and we cannot begin writing the data before most of the -- parsing is done, anyways. -- | Construct a Ding AST from a list of tokens, plus a parse log composed of -- parse errors and notes. -- Iff one of the errors prevents constructing a valid Ding AST, `Nothing' is -- returned as such. parse :: [Token] -> (Maybe Ding, [Either String String]) parse ts = ( do header <- either (const Nothing) Just eHeader guard $ not $ null bodyLines return $ Dictionary header German English (Body bodyLines) , concat [ either (pure . Left) (const []) eHeader , bodyLog -- Note: Appending a single value is not optimal. Prepending instead -- would yield an unusual order. , if null bodyLines -- The FreeDict XML schema requires at least one entry and no such can -- be generated from nothing. then pure $ Left "Input contains no valid dictionary entries." else [] ] ) where (headerLines, bodyToks) = separateHeaderLines ts eHeader = parseHeader headerLines (bodyLines, bodyLog) = runWriter $ parseBody bodyToks -- | Parse a list of `Line's. parseBody :: [Token] -> ParseWriter [Line] parseBody = fmap catMaybes . mapM (mergeErrorToLog . parseLine) . tokLines -- | A writer logging both informative messages (`Left') and error messages -- (`Right'). type ParseWriter = Writer [Either String String] -- | Convert a `FailWriter' to a `ParseWriter'. -- If an error occurred in the `FailWriter', the `ParseWriter' returns -- `Nothing' as value. mergeErrorToLog :: FailWriter a -> ParseWriter (Maybe a) mergeErrorToLog = mapWriterT aux where aux :: Either String (a, [String]) -> Identity (Maybe a, [Either String String]) aux (Left e) = Identity (Nothing, pure $ Left e) aux (Right (x, log)) = Identity (Just x, map Right log) -- | Separate the initial lines belonging to the header from the body lines. -- Header lines are converted to strings. separateHeaderLines :: [Token] -> ([String], [Token]) separateHeaderLines (Token _ _ (HeaderLine hl) : Token _ _ NL : tls) = let (hls, tls') = separateHeaderLines tls in (hl : hls, tls') separateHeaderLines tls = ([], tls) -- | Break a list of tokens into a list of lines, the latter being also a list -- of tokens. The separating newline tokens are retained. The list of -- tokens is expected to be newline-terminated. -- Similar to `Data.OldList.lines', but on tokens, with separator -- `Language.Ding.Token.NL'. tokLines :: [Token] -> [[Token]] tokLines [] = [] tokLines ts = let (l, ts') = breakLine ts in l : tokLines ts' -- | Separate a single line, including newline token from the stream. -- Requires a newline token to be present. breakLine :: [Token] -> ([Token], [Token]) breakLine (nl@(Token _ _ NL) : ts) = ([nl], ts) breakLine (t : ts) = let (l, ts') = breakLine ts in (t : l, ts') breakLine [] = -- TODO: Encapsulate error in the logging monad. error "Input is not newline-terminated." -- vi: ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Parser/000077500000000000000000000000001423006221500230305ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/Ding/Parser/Header.hs000066400000000000000000000047121423006221500245600ustar00rootroot00000000000000{- - Language/Ding/Parser/Header.hs - parse the Ding header - - Copyright 2020,2021 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Parse the header of the Ding dictionary. -} module Language.Ding.Parser.Header (parseHeader) where import Data.List (stripPrefix) import Language.Ding.Syntax (Header(..)) -- | Parse the Ding header from a list of lines. -- The expected syntax is very strict; it does for example not fit the -- header of the es-de Ding dictionary. parseHeader :: [String] -> Either String Header parseHeader (versionL : copyrightL : yearsL : licenseL : urlL : []) = maybe (Left "Failed parsing header.") Right $ do versionInfo <- stripPrefix "# Version :: " versionL (version, date) <- case words versionInfo of [a, b] -> Just (a, b) _ -> Nothing copyrightHolder <- stripPrefix "# Copyright (c) :: " copyrightL >>= stripSuffixChar ',' years <- stripPrefix "# " yearsL license <- stripPrefix "# License :: " licenseL url <- stripPrefix "# URL :: " urlL return $ Header { headerVersion = version , headerVersionDate = date , headerCopyrightHolder = copyrightHolder , headerCopyrightPeriod = years , headerLicense = license , headerURL = url } parseHeader _ = Left "Unexpected number of header lines." -- | Attempt to strip off a particular terminating char. -- Return `Just prefix', where `prefix' is the remaing prefix in case of -- success and `Nothing' otherwise. stripSuffixChar :: Char -> String -> Maybe String stripSuffixChar _ [] = Nothing stripSuffixChar k [c] = if k == c then Just [] else Nothing stripSuffixChar k (c:cs) = fmap (c:) $ stripSuffixChar k cs -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Parser/Line.y000066400000000000000000000602001423006221500241070ustar00rootroot00000000000000{- - Language/Ding/Parser/Line.y - parser for a single Ding line - - Copyright 2020-2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} -- Notes: -- * This file is supposed to be processed by Happy. It contains both Happy -- specific syntax and regular Haskell code. The former is in some ways -- similar to the latter. The result of processing with Happy is a regular -- Haskell source file. -- * The Happy syntax should be very similar to the syntax of Yacc/Bison. -- * There is an important relation between the lexer (produced with the help -- of Alex) and the parser. -- * Happy Documentation: https://www.haskell.org/happy/ { -- Haskell module header and import statements. {-# LANGUAGE FlexibleInstances #-} {-| - Parse single Ding lines from a list of tokens. Only accepts newline - terminated lines. -} module Language.Ding.Parser.Line (parseLine, FailWriter) where import Prelude hiding (fail) import Control.Monad.Fail (MonadFail(fail)) import Control.Monad.Writer (Writer, tell, writer, WriterT(WriterT), mapWriterT) import Data.List.NonEmpty (NonEmpty(..), (<|)) import qualified Data.List.NonEmpty as NEList import Safe.Exact (zipWithExactMay) import Data.NatLang.Grammar ( GrammarInfo(..) , GramLexCategory(..) , Case(..) , Collocate(..) ) import Data.NatLang.Usage (Usage) import Data.NatLang.InflectedForms (InflectedForms(..), InflectedForm(..)) import Language.Ding.Partial.Unit (PartialUnit) import qualified Language.Ding.Partial.Unit as PU import Language.Ding.Partial.PseudoUnit (PartialPseudoUnit) import qualified Language.Ding.Partial.PseudoUnit as PSU import Language.Ding.Read.Usage (readUsage) import Language.Ding.Syntax import Language.Ding.Token (Token(..), Atom(..), Position(..), tokenToString, tokenToLine) } %name parseLine -- name of the resulting function %tokentype { Token } %monad { FailWriter } %error { parseError } -- name of the error function - must be defined later ------------------------------------------------------------------------------- -- Link Happy tokens (left) to Haskell patterns (right). -- Notes: -- * If `$$' is specified on the right, the Happy token will evaluate to the -- token's part matched by `$$'. Otherwise, the Happy token evaluates to -- the whole token. -- * It is common for simple tokens to use as name the string representing the -- token, however not at all necessary. %token '\n' { Token _ _ NL } '::' { Token _ _ LangSep } '|' { Token _ _ Vert } ';' { Token _ _ Semi } ',' { Token _ _ Comma } '~' { Token _ _ Tilde } '+' { Token _ _ Plus } '<>' { Token _ _ Wordswitch } '/' { Token _ _ StrongSlash } '' { Token _ _ WeakSlash } '//' { Token _ _ DoubleSlash } '{' { Token _ _ OBrace } '}' { Token _ _ CBrace } '[' { Token _ _ OBracket } ']' { Token _ _ CBracket } '(' { Token _ _ OParen } ')' { Token _ _ CParen } '<' { Token _ _ OAngle } '>' { Token _ _ CAngle } '' { Token _ _ CSlash } 'to' { Token _ _ KW_to } tok_slashSpecial { Token _ _ (SlashSpecial _) } tok_abbrev { Token _ _ (Abbrev _) } tok_abbrevPlural { Token _ _ (AbbrevPlural _) } -- Note: -- * The grammar annotations are separated because gramCase needs to be -- separately available (and Happy tokens may not overlap). tok_gramPOS { Token _ _ (GramKW (PartOfSpeech _)) } tok_gramGender { Token _ _ (GramKW (Gender _)) } tok_gramNumber { Token _ _ (GramKW (Number _)) } tok_gramCase { Token _ _ (GramKW (Case _)) } tok_interrogPron { Token _ _ (IntPronKW _) } tok_text { Token _ _ (Text _) } -- Just ensure that 'to' has lowest precedence. -- * Giving it right associativity causes 'to to' to cause a shift. %right 'to' %right ',' '~' '+' '<>' '/' '' '//' tok_gramPOS tok_gramGender tok_gramNumber tok_gramCase tok_interrogPron tok_text '(' %% ------------------------------------------------------------------------------- -- Grammar specification ------------------------------------------------------------------------------- -- See doc/ding-grammar-naming for a brief syntax overview and naming -- conventions. -- Notes: -- * Only a single line is parsed. -- * Left recursion is recommended by the Happy developers, due to space -- requirements. -- * This causes lists to be reversed, which requires re-reversal (or -- special alternative list structures), which makes the grammar often -- less readable. -- * Since all lists are expected to be small, space should not be an issue. -- * Hence, ignore this. -- - https://www.haskell.org/happy/doc/html/sec-sequences.html -- - String-concatenation that uses left-recursion has the same problem. -- - (<>), when applied to tokens does essentially perform string -- concatentation. -- - mconcat can be used instead. -- * The parsing happens in the Writer monad to allow for logging. -- * There does however not happen any logging as of now. -- * The syntax would be the same without a monad; only the type of the -- parseLine function would be simpler. -- Note: -- * The newline token serves as EOL token, such that there is never an error -- on end of tokens. -- * (One could also have given this parser a non-newline terminated stream -- of tokens.) -- * End-of-tokens errors do not report the position of the error, which -- usually is not a problem, since it is just the end of input. -- * However, since we only parse single lines, where each contained -- token is annotated with a line number, this would be a problem. line :: { Line } line : groups '::' groups '\n' {% makeLine (tokenToLine $2) $1 $3 } groups :: { [Group] } groups : group '|' groups { $1 : $3 } | group { $1 : [] } -- Note: -- * In some cases, there is exactly one unit in a group, which does not obey -- to the rules of a proper unit - the headword is enclosed in <()>. -- * It does however (in practice) obey the rules of a PartialPseudoUnit. -- * Use these, to ignore the unit. (TODO: improve) -- * See also: todo/parsing.paren-units group :: { Group } group : units { Group $ reverse $1 } | {- epsilon -} { Group [] } | pseudoUnit { Group [] } -- Notes: -- * Produces the list of units in reverse order. -- * This is to ease the application of `addToUnit'. -- * Only accepts a non-empty list of units. (because of the <;> seps) units :: { [Unit] } units : units ';' unit { $3 : $1 } | units ';' pseudoUnit { map (PSU.addToUnit $3) $1 } | unit { $1 : [] } ------------------------------------------------------------------------------- -- Unit unit :: { Unit } unit : parenExps partialUnit { PU.toUnit $1 $2 } -- TODO: Do not ignore <>. pseudoUnit :: { PartialPseudoUnit } pseudoUnit : pseudoUnit gramAnnot { $1 `PSU.plusGramAnnot` $2 } | pseudoUnit usageAnnot { $1 `PSU.plusUsageAnnot` $2 } | pseudoUnit angleExp { $1 } | parenExp parenExps { PSU.fromSuffixes ($1 : $2) } -- Notes: -- * Any infix annotations are silently dropped (see plusToken). Prefix -- annotations are considered invalid and not accepted by the grammar. -- * Parenthesis expressions may occur as prefixes though, they are -- recognized in the `unit' rule. -- * Initial abbreviations are equated to regular text, iff they are "single", -- that is contain no <,> or <;> separated list of abbeviations. -- * If prefixed by 'to', ignore the 'to' and mark as verb. -- * This only happens if 'to' is immediately followed by something -- recognized as regular text (unitText). -- * In particular things like 'to (have a) lisp' or -- 'to (give one's) consent (to)' are not specially treated. -- (They are quite infrequent though.) -- * Also, allowing () would complicate the parsing. -- * PU.fromVerbToken annotates the unit as a verb. This may be superfluous -- if there is an explicit annotation (rare), but such a duplicate would -- be resolved during enrichment. -- * angleExp ("<...>") is ignored. (TODO?) -- * Semantics unclear. partialUnit :: { PartialUnit } partialUnit : partialUnit gramAnnot { $1 `PU.plusGramAnnot` $2 } | partialUnit usageAnnot { $1 `PU.plusUsageAnnot` $2 } | partialUnit abbrevAnnot { $1 `PU.plusAbbrevAnnot` $2 } | partialUnit inflAnnot { $1 `PU.plusInflAnnot` $2 } | partialUnit parenExp { $1 `PU.plusSuffix` $2 } | partialUnit angleExp { $1 {- ignore $2 -} } | partialUnit reference { $1 `PU.plusRef` $2 } | partialUnit unitText { $1 `PU.plusToken` $2 } | partialUnit 'to' { $1 `PU.plusToken` $2 } | partialUnit unitInterpct { $1 `PU.plusToken` $2 } | unitText { PU.fromToken $1 } -- 'to' has lowest precedence (and is right associative), so the -- later rules will be preferred. | 'to' { PU.fromToken $1 } | 'to' unitInterpct { PU.fromToken ($1 <> $2) } | 'to' unitText { PU.fromVerbToken $2 } -- Some units start with an abbreviation (usually without further -- unitText). In such, treat the abbreviation as plain text. | singleAbbrevAnnot { PU.fromToken $1 } -- Notes: -- * As of now, keywords (except for 'to') have no special meaning in -- top-level text. Hence, treat them as simple text. -- * Cannot use gtok_anyKW, since it includes 'to'. unitText :: { Token } unitText : tok_text { $1 } | gtok_gram { $1 } | tok_interrogPron { $1 } -- | Interpunctuation in units. -- -- Note: "<>" is ignored. unitInterpct :: { Token } unitInterpct : ',' { $1 } | '+' { $1 } | '//' { $1 } | '/' { $1 } | '' { $1 } | '<>' { mempty } ------------------------------------------------------------------------------- -- Grammar annotation gramAnnot :: { NonEmpty GrammarInfo } gramAnnot : '{' gramAnnot1s '}' { $2 } -- Note: <;> and <,> are used with different meanings in this context -- (usually AND and OR, respectively), but to keep things simple, they -- are considered equivalent as of now. gramAnnot1s :: { NonEmpty GrammarInfo } gramAnnot1s : gramAnnot1 ';' gramAnnot1s { $1 <> $3 } | gramAnnot1 { $1 } gramAnnot1 :: { NonEmpty GrammarInfo } gramAnnot1 : gramAnnot2s { $1 } | collocAnnot1 usageAnnots { Collocate $1 $2 :| [] } -- The two rules for CollocCase are necessary to avoid a shift/reduce conflict. -- The first rule only allows a non-empty list of interrogation pronouns. collocAnnot1 :: { Collocate } collocAnnot1 : interrogProns '+' tok_gramCase { CollocCase $1 (tokenToCase $3) } | '+' tok_gramCase { CollocCase [] (tokenToCase $2) } | '+' tok_gramPOS { CollocPOS (tokenToPOS $2) } | '+' tok_gramNumber { CollocNumber (tokenToNumber $2) } -- TODO: Consider to add '/' as alternative separator. gramAnnot2s :: { NonEmpty GrammarInfo } gramAnnot2s : gramAnnot2 ',' gramAnnot2s { $1 <| $3 } | gramAnnot2 { $1 :| [] } gramAnnot2 :: { GrammarInfo } gramAnnot2 : gramLexCat { GramLexCategory $1 } gramLexCat :: { GramLexCategory } gramLexCat : gtok_gram { tokenToGramLexCat $1 } interrogProns :: { [String] } interrogProns : tok_interrogPron ',' interrogProns { tokenToString $1 : $3 } | tok_interrogPron { tokenToString $1 : [] } ------------------------------------------------------------------------------- -- Inflected forms -- Notes: -- * Inflected forms are distinguished from grammar annotations by the -- absence of grammar keywords. -- * NonEmpty lists are used here. (:|) constructs a NonEmpty list from a -- single head element and a regular list. (<|) joins a single element with -- a NonEmpty list, just like (:) for normal lists. -- * Considering the implementation of NonEmpty, using (<|) is not very -- efficient (decons (:|), cons (:), cons (:|) - instead of just -- cons (:)). inflAnnot :: { InflectedForms } inflAnnot : '{' inflAnnot1s ';' inflAnnot1s '}' { InflectedForms $2 $4 } inflAnnot1s :: { NonEmpty InflectedForm } inflAnnot1s : inflAnnot1 ',' inflAnnot1s { $1 <| $3 } | inflAnnot1 { $1 :| [] } inflAnnot1 :: { InflectedForm } inflAnnot1 : inflAnnot2 usageAnnots { InflectedForm (tokenToString $1) $2 } -- Notes: -- * Most inflected forms consist of a single word. Not all -- (e.g., "creeped out"). -- * Cannot use gtok_anyKW, as it includes gtok_gramKW. inflAnnot2 :: { Token } inflAnnot2 : tok_text inflAnnot2 { $1 <> $2 } | tok_text { $1 } | tok_interrogPron { $1 } | 'to' { $1 } ------------------------------------------------------------------------------- -- Abbreviation annotations -- Note: This rule exists purely to provide a prefix for unit. singleAbbrevAnnot :: { Token } singleAbbrevAnnot : '' { $2 } | tok_abbrev { $1 } | tok_abbrevPlural { $1 } | tok_slashSpecial { $1 } abbrevAnnot :: { NonEmpty String } abbrevAnnot : '' { $2 } | tok_abbrev { tokenToString $1 :| [] } | tok_abbrevPlural { (tokenToString $1 ++ "s") :| [] } | tok_slashSpecial { tokenToString $1 :| [] } -- Note: <;> is more frequent. abbrevAnnot1s :: { NonEmpty String } abbrevAnnot1s : abbrevAnnot1 ';' abbrevAnnot1s { tokenToString $1 <| $3 } | abbrevAnnot1 ',' abbrevAnnot1s { tokenToString $1 <| $3 } | abbrevAnnot1 { tokenToString $1 :| [] } -- Consider content a literal (in particular do not drop any inner annotation). abbrevAnnot1 :: { Token } abbrevAnnot1 : abbrevAnnot2 abbrevAnnot1 { $1 <> $2 } | abbrevAnnot2 { $1 } -- Note: -- * <()>, <[]> are rare here. -- . Ex.: "/Hg(CNO)2/" -- . Ex.: "section 15, subsection 3 /s.15[3]/" -- * Nesting likely does not occur at all, but is considered valid. abbrevAnnot2 :: { Token } abbrevAnnot2 : tok_text { $1 } | gtok_anyKW { $1 } | '+' { $1 } | '/' { $1 } | '(' abbrevAnnot2 ')' { $1 <> $2 <> $3 } | '[' abbrevAnnot2 ']' { $1 <> $2 <> $3 } ------------------------------------------------------------------------------- -- Expressions enclosed in parentheses -- Note: -- * The below rules give (String, Token) for a parenthesis expression. -- The string is the content, the token joins the content with the -- parentheses (and retains any spacing). -- * The token is required to allow for infix parenthesis expressions to be -- merged back with its context. parenExps :: { [(String, Token)] } parenExps : parenExp parenExps { $1 : $2 } | {- epsilon -} { [] } parenExp :: { (String, Token) } parenExp : '(' parenExp1 ')' { (tokenToString $2, $1 <> $2 <> $3) } parenExp1 :: { Token } parenExp1 : parenExp2 parenExp1 { $1 <> $2 } parenExp1 : parenExp2 { $1 } -- Notes: -- * Several inner annotations are ignored (on purpose), such as references or -- <{}>-annotations. -- * Nonetheless, for example <{}>-annotations should not be simplified -- (allowed to contain more or less anything) - the content should still -- be a valid annotation. I.e., use the grammar rules for those -- annotations. -- * Nested <()>-annotations are included literally. -- * They are rare though. (Hence the nested (<>) application is ok.) -- * One might consider to include abbreviations literally. (TODO?) -- * This would require an abbrevAnnot'-rule, producing a token instead of -- a list of Strings. -- * Alternatively, one might reconstruct an equivalent representation, -- based on that list. parenExp2 :: { Token } parenExp2 : tok_text { $1 } | gtok_anyKW { $1 } | reference { mempty } | gramAnnot { mempty } | inflAnnot { mempty } | usageAnnot { mempty } | abbrevAnnot { mempty } | '(' parenExp1 ')' { $1 <> $2 <> $3 } | ';' { $1 } | ',' { $1 } | '+' { $1 } | '<>' { mempty } | '/' { $1 } | '' { $1 } | '//' { $1 } ------------------------------------------------------------------------------- -- Expressions enclosed in angle brackets -- Note: They are dropped as of now, since their semantics are ambiguous. angleExp :: { NonEmpty String } angleExp : '<' angleExp1s '>' { $2 } -- Note: -- * Separation by <;>, <,> is rare (two/one occurrences). angleExp1s :: { NonEmpty String } angleExp1s : angleExp1 ';' angleExp1s { tokenToString $1 <| $3 } | angleExp1 ',' angleExp1s { tokenToString $1 <| $3 } | angleExp1 { tokenToString $1 :| [] } angleExp1 :: { Token } angleExp1 : angleExp2 angleExp1 { $1 <> $2 } | angleExp2 { $1 } -- Notes: -- * There are nested angle-expressions. It is unclear how to handle them. -- Drop for now. (TODO?) -- * They are rare (two occurences). -- * Only one occurence of nested <()>. Dropped for now. (TODO?) -- * Only 4 occurrences of nested abbreviations. Dropped for now. (TODO?) angleExp2 :: { Token } angleExp2 : tok_text { $1 } | gtok_anyKW { $1 } | angleExp { mempty } | '(' angleExp1 ')' { mempty } | abbrevAnnot { mempty } ------------------------------------------------------------------------------- -- References ("~ref") -- Note: -- * Due to the simple current word-matching in the lexer, this might catch -- unexpected references, such as "~word?". (TODO) reference :: { String } reference : '~' reference1 { tokenToString $2 } reference1 :: { Token } reference1 : tok_text { $1 } | gtok_anyKW { $1 } ------------------------------------------------------------------------------- -- Usage annotations -- Zero or more <[]>-enclosed usage annotations. -- Unified to a single list (e.g., "[a,b] [c]" -> [a,b,c]). usageAnnots :: { [Usage] } usageAnnots : usageAnnot usageAnnots { NEList.toList $1 ++ $2 } | {- epsilon -} { [] } -- A single usage annotation (e.g., "[a,b]"). usageAnnot :: { NonEmpty Usage } usageAnnot : '[' usageAnnot1s ']' { $2 } -- Note: <,> and are treated as synonyms. They possibly shouldn't. usageAnnot1s :: { NonEmpty Usage } usageAnnot1s : usageAnnot1 ',' usageAnnot1s { $1 <| $3 } | usageAnnot1 '/' usageAnnot1s { $1 <| $3 } | usageAnnot1 { $1 :| [] } usageAnnot1 :: { Usage } usageAnnot1 : usageAnnot2s { readUsage $ tokenToString $1 } usageAnnot2s :: { Token } usageAnnot2s : usageAnnot2 usageAnnot2s { $1 <> $2 } | usageAnnot2 { $1 } usageAnnot2 :: { Token } usageAnnot2 : tok_text { $1 } | gtok_anyKW { $1 } | ';' { $1 } | '+' { $1 } ------------------------------------------------------------------------------- -- Grouped simple tokens gtok_anyKW :: { Token } gtok_anyKW : gtok_gram { $1 } | tok_interrogPron { $1 } | 'to' { $1 } gtok_gram :: { Token } gtok_gram : tok_gramPOS { $1 } | tok_gramGender { $1 } | tok_gramNumber { $1 } | tok_gramCase { $1 } { ------------------------------------------------------------------------------- -- Auxiliary Haskell code -- Logging: -- * The FailWriter below allows for logging of parse notes and errors -- (`fail'). -- - Errors indicate a failure and, so, no value is returned. -- * `fail' should always be preferred over `error', unless the respective -- condition indicates a programming error. -- * The filename should not and cannot be given here; it is prepended at a -- higher level. -- | A writer that allows logging informative messages, but also to fail with -- an error message. -- The informative log is retained in case of a failure. type FailWriter = WriterT [String] (Either String) instance MonadFail (Either String) where fail = Left -- Note: The token list can only be empty if the token list given to -- `parseLine' was not terminated by a newline token. Which should not -- be possible (an error would occur on a higher level). -- - The first error is thus deliberately handled by `error' and not `fail'. parseError :: [Token] -> FailWriter a parseError [] = error "Parse error at end of line (missing newline?)." parseError ((Token _ (Position line col) _):_) = fail $ "Parse error at line " ++ show line ++ ", column " ++ show col ++ "." -- | Create a line from the left and right side's groups. Fail, if the groups -- are not equal in number of entries. makeLine :: Int -> [Group] -> [Group] -> FailWriter Line makeLine line gs hs = case zipWithExactMay Entry gs hs of Just es -> return $ Line es Nothing -> fail $ "Line " ++ show line ++ ": " ++ "Number of groups does not match on the two sides." tokenToGramLexCat :: Token -> GramLexCategory tokenToGramLexCat (Token _ _ (GramKW gram)) = gram tokenToGramLexCat _ = error "Not a grammar keyword." tokenToGramAnnot :: Token -> GrammarInfo tokenToGramAnnot (Token _ _ (GramKW gram)) = GramLexCategory gram tokenToGramAnnot _ = error "Not a grammar annotation." tokenToCase :: Token -> Case tokenToCase (Token _ _ (GramKW (Case cas))) = cas tokenToCase _ = error "Not a case keyword." tokenToPOS (Token _ _ (GramKW (PartOfSpeech pos))) = pos tokenToPOS _ = error "Not a part of speech keyword." tokenToNumber (Token _ _ (GramKW (Number number))) = number tokenToNumber _ = error "Not a grammatical number keyword." } -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Partial/000077500000000000000000000000001423006221500231705ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/Ding/Partial/PseudoUnit.hs000066400000000000000000000057541423006221500256360ustar00rootroot00000000000000{- - Language/Ding/Partial/PseudoUnit.hs - partial pseudo units - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Handling of syntax elements that syntactically look like units however - only contain annotations, which are to be applied to preceding units. -} module Language.Ding.Partial.PseudoUnit ( PartialPseudoUnit , fromSuffixes , addToUnit , plusGramAnnot , plusUsageAnnot ) where import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NEList import Data.NatLang.Grammar (GrammarInfo) import Data.NatLang.Usage (Usage) import Language.Ding.Syntax (Unit(..)) import Language.Ding.Token (Token, tokenToString) -- | Partial pseudo unit that may be expanded with annotations and finally -- adjuncted to a unit. data PartialPseudoUnit = PartialPseudoUnit { plainToks :: [Token] , suffixes :: [String] , gramAnnots :: [NonEmpty GrammarInfo] , usageAnnots :: [NonEmpty Usage] } empty :: PartialPseudoUnit empty = PartialPseudoUnit [] [] [] [] -- | Create a `PartialPseudoUnit' from a list of parenthesis expressions. fromSuffixes :: [(String, Token)] -> PartialPseudoUnit fromSuffixes sufs = empty { suffixes = map fst sufs , plainToks = map snd sufs } plusGramAnnot :: PartialPseudoUnit -> NonEmpty GrammarInfo -> PartialPseudoUnit plusGramAnnot psu as = psu { gramAnnots = as : gramAnnots psu } plusUsageAnnot :: PartialPseudoUnit -> NonEmpty Usage -> PartialPseudoUnit plusUsageAnnot psu as = psu { usageAnnots = as : usageAnnots psu } -- | Add all annotations contained in a partial pseudo unit to a regular unit. addToUnit :: PartialPseudoUnit -> Unit -> Unit -- Note: -- * Due to the use of (++), this is not very efficient (for larger lists). -- * This could be improved by applying to a PartialUnit instead, which has -- stored its elements in inverse order. -- * This would however likely render the HappyParser.y code less -- readable. addToUnit psu u = u { unitPlain = unitPlain u ++ tokenToString (mconcat $ reverse $ plainToks psu) , unitSuffixes = unitSuffixes u ++ suffixes psu , unitGrammar = unitGrammar u ++ concat (reverse $ map NEList.toList $ gramAnnots psu) , unitUsages = unitUsages u ++ concat (reverse $ map NEList.toList $ usageAnnots psu) } -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Partial/Unit.hs000066400000000000000000000136641423006221500244550ustar00rootroot00000000000000{- - Language/Ding/Partial/Unit.hs - partial units - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Intermediate representation and handling of partially parsed units. -} module Language.Ding.Partial.Unit ( PartialUnit , fromToken , fromVerbToken , toUnit , plusToken , plusGramAnnot , plusUsageAnnot , plusAbbrevAnnot , plusInflAnnot , plusSuffix , plusRef ) where import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NEList (toList) import Data.NatLang.Grammar ( GrammarInfo(GramLexCategory) , GramLexCategory(PartOfSpeech) , PartOfSpeech(Verb) ) import Data.NatLang.InflectedForms (InflectedForms) import Data.NatLang.Usage (Usage) import Language.Ding.Syntax (Unit(..)) import Language.Ding.Token (Token, tokenToString) -- Notes -- * A PartialUnit contains information on a prefix of a unit. -- * It can be "modified" by appending annotations and regular unit text. -- * See below on "Modification". -- * "Modification": Inefficiency -- * A PartialUnit is immutable, hence any operation that might look like -- an update actually creates a new PartialUnit. -- * Cost is proportional to the number of fields in PartialUnit. -- * The problem could potentially be avoided by changing PartialUnit to -- essentially be a list of a sum type of all potential elements. This -- list would then later be processed by means equivalent to `plus*'. -- * All `plus*' functions are meant to be used in infix-form. -- | Partial unit that may be expanded with annotations and regular tokens, -- and finally converted into a `Language.Ding.Syntax.Unit'. -- All fields' contents are stored in reverse order. data PartialUnit = PartialUnit { headwordToks :: [Token] -- ^ tokens to later form the headword , plainToks :: [Token] -- ^ tokens to form a potential example , gramAnnots :: [NonEmpty GrammarInfo] , usageAnnots :: [NonEmpty Usage] , abbrevAnnots :: [NonEmpty String] , mInfl :: Maybe InflectedForms , suffixes :: [String] , references :: [String] } empty :: PartialUnit empty = PartialUnit [] [] [] [] [] Nothing [] [] -- | Convert a token to a partial unit without any annotation. fromToken :: Token -> PartialUnit fromToken = (empty `plusToken`) -- | Like fromToken, but mark as verb. Meant to be used for units prefixed by -- "to". fromVerbToken :: Token -> PartialUnit fromVerbToken t = fromToken t `plusGramAnnot` pure (GramLexCategory $ PartOfSpeech $ Verb []) -- | Add a token. -- All previous annotations except parenthesis expression are thrown away. -- (Due to the presence of the processed token, they are infix -- annotations.) -- Preceding parenthesis expressions are treated equal to preceding text -- tokens, and therefore effectively retained in verbatim. plusToken :: PartialUnit -> Token -> PartialUnit -- Use plainToks also for the headwordToks, because they contain literal -- ()-annots. plusToken pu t = empty { headwordToks = toks' , plainToks = toks' } where toks' = t : plainToks pu plusGramAnnot :: PartialUnit -> NonEmpty GrammarInfo -> PartialUnit plusGramAnnot pu as = pu { gramAnnots = as : gramAnnots pu } plusUsageAnnot :: PartialUnit -> NonEmpty Usage -> PartialUnit plusUsageAnnot pu as = pu { usageAnnots = as : usageAnnots pu } -- Note: -- * One might consider keeping one of the abbreviations in the example -- version (i.e., as Token) to allow for abbreviations to be retained -- verbatim when infixes of an example unit. -- * Further analysis required (TODO). plusAbbrevAnnot :: PartialUnit -> NonEmpty String -> PartialUnit plusAbbrevAnnot pu as = pu { abbrevAnnots = as : abbrevAnnots pu } -- | Add an inflection annotation. -- If there are added several ones in succession, the last one is prefered. plusInflAnnot :: PartialUnit -> InflectedForms -> PartialUnit plusInflAnnot pu infl = pu { mInfl = Just infl } -- | Takes both the string representing the suffix, without potential -- enclosing parentheses, and a token that includes potential parentheses. plusSuffix :: PartialUnit -> (String, Token) -> PartialUnit plusSuffix pu (s, t) = pu { suffixes = s : suffixes pu , plainToks = t : plainToks pu } plusRef :: PartialUnit -> String -> PartialUnit plusRef pu ref = pu { references = ref : references pu } -- | From a list of prefixes and a partial unit, construct a unit. -- Takes a list of (`String', `Token') prefixes that stem from parenthesis -- expressions, where the type has the same significance as in `plusSuffix'. toUnit :: [(String, Token)] -> PartialUnit -> Unit toUnit prefs pu = Unit { unitHeadword = tokenToString $ mconcat $ reverse $ headwordToks pu , unitPlain = tokenToString $ mconcat $ prefToks ++ (reverse $ plainToks pu) , unitGrammar = concat $ reverse $ map NEList.toList $ gramAnnots pu , unitUsages = concat $ reverse $ map NEList.toList $ usageAnnots pu , unitPrefixes = prefStrings , unitSuffixes = reverse $ suffixes pu , unitAbbrevs = concat $ reverse $ map NEList.toList $ abbrevAnnots pu , unitInflected = mInfl pu , unitReferences = reverse $ references pu , unitExamples = [] -- populated during enrichment } where (prefStrings, prefToks) = unzip prefs -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Pretty.hs000066400000000000000000000130611423006221500234200ustar00rootroot00000000000000{- - Language/Ding/Pretty.hs - pretty printing of the Ding AST - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-# LANGUAGE FlexibleInstances #-} {-| - Pretty printing of the Ding AST. - - Note that the pretty printing does not generally reproduce the originally - parsed Ding syntax; the result may even not be accepted by the parser. - - The former is because - a) during parsing, some information is dropped, - b) enrichment may have changed the order of annotations. - Also, potentially identified examples are not displayed. - - The latter is only the case when grammar inferral (enrichment) was - performed. In this case, grammar information such as "{noun}" may be - inferred, which is not originally present in the Ding dictionary, albeit - useful information during debugging. -} module Language.Ding.Pretty (pretty) where import Prelude hiding ((<>)) import Data.List.NonEmpty (NonEmpty) import qualified Data.List.NonEmpty as NEList import Text.PrettyPrint import Data.NatLang.Dictionary (Dictionary(..), Body(..)) import Data.NatLang.Grammar import Data.NatLang.InflectedForms import Data.NatLang.Usage import Language.Ding.Show.Grammar (showGLC, showCase, showNumber, showPOS) import Language.Ding.Syntax hiding (Dictionary) ------------------------- -- Some helper functions -- | Modify a `Doc' iff it is non-empty. applyNE :: (Doc -> Doc) -> Doc -> Doc applyNE f x = if isEmpty x then empty else f x -- | Like `punctuate', but remove nonempty `Doc's first. punctuateNE :: Doc -> [Doc] -> [Doc] punctuateNE p = punctuate p . filter (not . isEmpty) slashes :: Doc -> Doc slashes x = char '/' <> x <> char '/' class Pretty a where pretty :: a -> Doc prettyList :: [a] -> Doc prettyList = error "Language.Ding.Pretty: This element does not occur in lists." prettyNEList :: NonEmpty a -> Doc prettyNEList = prettyList . NEList.toList ------------------------ -- The Pretty instances instance Pretty Ding where pretty (Dictionary header _ _ body) = pretty header $+$ pretty body instance Pretty (Body Line) where pretty (Body lines) = prettyList lines -- See also: Language.Ding.HeaderParser instance Pretty Header where pretty (Header version date copyrightHolder copyrightPeriod license url) = foldr ($+$) empty $ [ text "# Version" <+> text "::" <+> text version <+> text date , text "# Copyright (c)" <+> text "::" <+> text copyrightHolder <> char ',' , text "#" <+> text copyrightPeriod , text "# License" <+> text "::" <+> text license , text "# URL" <+> text "::" <+> text url ] instance Pretty Line where pretty (Line entries) = prettyList left <+> text "::" <+> prettyList right where unzipEntries :: [Entry] -> ([Group], [Group]) unzipEntries = unzip . map (\ (Entry l r ) -> (l, r)) (left, right) = unzipEntries entries prettyList = foldr ($+$) empty . map pretty instance Pretty Entry where pretty (Entry g h) = pretty g <+> text "::" <+> pretty h instance Pretty Group where pretty (Group us) = prettyList us prettyList = hcat . punctuate (text " | ") . map pretty instance Pretty Unit where pretty u = hsep $ [ hsep $ map (parens . text) $ unitPrefixes u , text $ unitHeadword u , prettyList $ unitGrammar u , prettyList $ unitUsages u , applyNE slashes $ hsep $ punctuate semi $ map text $ unitAbbrevs u , maybe empty pretty $ unitInflected u , hsep $ map (parens . text) $ unitSuffixes u , hsep $ map (text . ('~':)) $ unitReferences u ] prettyList = hsep . punctuate semi . map pretty instance Pretty GrammarInfo where pretty (GramLexCategory gram) = pretty gram pretty (Collocate colloc usgs) = pretty colloc <+> prettyList usgs prettyList = applyNE braces . hsep . punctuate semi . map pretty instance Pretty GramLexCategory where pretty = hcat . punctuate (char '/') . map text . showGLC instance Pretty Collocate where pretty (CollocCase iProns cas) = pref <+> char '+' <> pretty cas where pref = hsep $ punctuate comma $ map text iProns pretty (CollocPOS pos) = char '+' <> pretty pos pretty (CollocNumber number) = char '+' <> pretty number instance Pretty Case where pretty = text . showCase instance Pretty Number where pretty = text . showNumber instance Pretty PartOfSpeech where -- Note: showPOS is expected to always give exactly one string here. pretty = hcat . punctuate (char '/') . map text . showPOS instance Pretty Usage where pretty (Usage _ str) = text str prettyList = applyNE brackets . hsep . punctuate comma . map pretty instance Pretty InflectedForms where pretty (InflectedForms infl1 infl2) = braces $ prettyNEList infl1 <> semi <+> prettyNEList infl2 instance Pretty InflectedForm where pretty (InflectedForm form usgs) = text form <+> prettyList usgs prettyList = hsep . punctuate comma . map pretty -- vi: ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Read/000077500000000000000000000000001423006221500224475ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/Ding/Read/Usage.hs000066400000000000000000000224471423006221500240600ustar00rootroot00000000000000{- - Language/Ding/Read/Usage.hs - read usage (<[]>) annotations from strings - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Classify usage annoations into common TEI categories. - - Only a subset of all usage annotations are explicitly classified, all - others are assigned the default `hint' category. -} module Language.Ding.Read.Usage ( readUsage ) where import Data.NatLang.Usage -- Notes: -- * The lists below have been created with the help of -- util/results/bracketexps. -- * The meaning of the annotations (which type of annotation they are) has -- been determined by searching examples in the Ding and infering from there -- on (if not obvious). In some cases, Wikipedia or the Wiktionary were of -- help. -- * See also Data/NatLang/Usage.hs and the notes there. -- TODO: ? differentiate languages (English vs. German annotations) -- Note: Words tend to be listed in decreasing order of frequency in the Ding. -- TODO: -- - "übtr." -- - "eBr.", "eam." -- - "mainly Am." -- - "bes. Süddt." -- - "Am., auch Br." -- - "also Br." -- - "alt" -- - "pej." -- - "obs." -- - "Sprw." ~ "prov." -- - "tm" (Trademark) -- - "archaic", "dated" -- - "also fig" -- - "auch übtr." -- - "humor." -- - "Reitsport" ~ "riding" -- - generally: German vs. English form -- - "log" -- - "especially Am." -- - "academic" ~ "sci"? | register? -- - "literary" -- - ... -- | Convert a usage string to a `Usage', thereby classifying it. readUsage :: String -> Usage -- Regions where German is spoken (in a particular way). -- * Sometimes expressed as the corresponding dialect. readUsage s@"Ös." = Usage Regional s readUsage s@"Schw." = Usage Regional s readUsage s@"Dt." = Usage Regional s readUsage s@"Bayr." = Usage Regional s readUsage s@"Norddt." = Usage Regional s readUsage s@"Süddt." = Usage Regional s readUsage s@"Mitteldt." = Usage Regional s readUsage s@"BW" = Usage Regional s -- Baden-Württemberg ? readUsage s@"Westdt." = Usage Regional s readUsage s@"Ostdt." = Usage Regional s readUsage s@"Mittelwestdt." = Usage Regional s readUsage s@"Nordostdt." = Usage Regional s readUsage s@"Südtirol" = Usage Regional s readUsage s@"Nordwestdt." = Usage Regional s readUsage s@"Mittelostdt." = Usage Regional s readUsage s@"Südwestdt." = Usage Regional s readUsage s@"Lie." = Usage Regional s readUsage s@"Westös." = Usage Regional s readUsage s@"Lux." = Usage Regional s readUsage s@"Berlin" = Usage Regional s readUsage s@"Tirol" = Usage Regional s readUsage s@"Wien" = Usage Regional s readUsage s@"Ostös." = Usage Regional s readUsage s@"Oberdt." = Usage Regional s readUsage s@"Westfalen" = Usage Regional s readUsage s@"Sächs." = Usage Regional s readUsage s@"Pfalz" = Usage Regional s readUsage s@"Ostmitteldt." = Usage Regional s readUsage s@"Hessen" = Usage Regional s readUsage s@"Vbg." = Usage Regional s -- Vorarlbergisch readUsage s@"Südostös." = Usage Regional s readUsage s@"Schwäb." = Usage Regional s readUsage s@"Rheinl." = Usage Regional s readUsage s@"DDR" = Usage Regional s -- Regions where English is spoken (in a particular way). -- * Sometimes expressed as the corresponding dialect. readUsage s@"Br." = Usage Regional s readUsage s@"Am." = Usage Regional s readUsage s@"Austr." = Usage Regional s readUsage s@"Sc." = Usage Regional s readUsage s@"NZ" = Usage Regional s readUsage s@"Ir." = Usage Regional s readUsage s@"Can." = Usage Regional s readUsage s@"In." = Usage Regional s readUsage s@"South Africa" = Usage Regional s readUsage s@"Welch" = Usage Regional s readUsage s@"SE Asia" = Usage Regional s readUsage s@"Northern English" = Usage Regional s readUsage s@"Northern Irish" = Usage Regional s -- Languages being neither a dialect of German nor of English. readUsage s@"French" = Usage Language s readUsage s@"Ital." = Usage Language s readUsage s@"Lat." = Usage Language s -- Times readUsage s@"archaic" = Usage Time s readUsage s@"altertümelnd" = Usage Time s -- ~archaic readUsage s@"altertümlich" = Usage Time s -- ~archaic readUsage s@"dated" = Usage Time s -- Registers (TODO: more) -- * See https://en.wikipedia.org/wiki/Register_(sociolinguistics) -- for why some of these are considered registers. -- * TODO: Consider to map to ISO 12620. readUsage s@"techn." = Usage Register s -- not a domain? readUsage s@"ugs." = Usage Register s -- ~slang readUsage s@"coll." = Usage Register s -- ~slang readUsage s@"geh." = Usage Register s -- ? ~formal readUsage s@"slang" = Usage Register s readUsage s@"formal" = Usage Register s readUsage s@"humor." = Usage Register s -- ? readUsage s@"vulg." = Usage Register s readUsage s@"iron." = Usage Register s readUsage s@"fachspr." = Usage Register s -- ~ "sci." ? readUsage s@"euphem." = Usage Register s -- ? readUsage s@"dialect" = Usage Register s readUsage s@"literary" = Usage Register s -- ~ "poet." -- Styles (TODO: more) readUsage s@"fig." = Usage Style s -- Preferences (none; TODO) -- * TEI Guidelines: "preference level (‘chiefly’, ‘usually’, etc.)" -- * TEI Lex-0: -> frequency: ex.: rare, occas. --readUsage s@... = Usage Preference s -- Acceptabilities (none) -- * No further description in the TEI Guidelines. --readUsage s@... = Usage Acceptability s -- @type="textType", as in TEI Lex-0. Currently considered a hint. readUsage s@"jur." = Usage Hint s -- not a domain? readUsage s@"adm." = Usage Hint s -- not a domain? readUsage s@"poet." = Usage Hint s -- not a register?; ~ "literary" -- Domains: readUsage s@"ornith." = Usage Domain s -- the most frequent (!) readUsage s@"med." = Usage Domain s readUsage s@"chem." = Usage Domain s readUsage s@"geogr." = Usage Domain s readUsage s@"bot." = Usage Domain s readUsage s@"zool." = Usage Domain s readUsage s@"comp." = Usage Domain s readUsage s@"fin." = Usage Domain s readUsage s@"electr." = Usage Domain s readUsage s@"cook." = Usage Domain s readUsage s@"econ." = Usage Domain s readUsage s@"min." = Usage Domain s readUsage s@"constr." = Usage Domain s readUsage s@"auto" = Usage Domain s readUsage s@"mil." = Usage Domain s readUsage s@"pol." = Usage Domain s readUsage s@"anat." = Usage Domain s readUsage s@"mach." = Usage Domain s readUsage s@"sport" = Usage Domain s readUsage s@"soc." = Usage Domain s readUsage s@"mus." = Usage Domain s readUsage s@"textil." = Usage Domain s readUsage s@"geol." = Usage Domain s readUsage s@"math." = Usage Domain s readUsage s@"agr." = Usage Domain s readUsage s@"hist." = Usage Domain s readUsage s@"phys." = Usage Domain s readUsage s@"relig." = Usage Domain s readUsage s@"biol." = Usage Domain s readUsage s@"naut." = Usage Domain s readUsage s@"aviat." = Usage Domain s readUsage s@"ling." = Usage Domain s readUsage s@"envir." = Usage Domain s readUsage s@"telco." = Usage Domain s readUsage s@"lit." = Usage Domain s -- "literature", not "literally" readUsage s@"psych." = Usage Domain s readUsage s@"biochem." = Usage Domain s readUsage s@"art" = Usage Domain s readUsage s@"school" = Usage Domain s readUsage s@"meteo." = Usage Domain s readUsage s@"transp." = Usage Domain s readUsage s@"arch." = Usage Domain s -- "architecture" readUsage s@"astron." = Usage Domain s readUsage s@"print" = Usage Domain s readUsage s@"pharm." = Usage Domain s readUsage s@"stud." = Usage Domain s readUsage s@"phil." = Usage Domain s readUsage s@"statist." = Usage Domain s readUsage s@"photo." = Usage Domain s readUsage s@"astrol." = Usage Domain s readUsage s@"phot." = Usage Domain s readUsage s@"theat." = Usage Domain s readUsage s@"mech." = Usage Domain s readUsage s@"laser." = Usage Domain s readUsage s@"insur." = Usage Domain s -- does not really fit the common syntax: readUsage s@"Beleuchtungstechnik" = Usage Domain s readUsage s@"vetmed." = Usage Domain s readUsage s@"TV" = Usage Domain s readUsage s@"riding" = Usage Domain s readUsage s@"Reitsport" = Usage Domain s readUsage s@"Radsport" = Usage Domain s readUsage s@"cycling" = Usage Domain s readUsage s@"archeol." = Usage Domain s -- different from "arch." -- Default catchall readUsage s = Usage Hint s -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Show/000077500000000000000000000000001423006221500225145ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/Ding/Show/Grammar.hs000066400000000000000000000057141423006221500244450ustar00rootroot00000000000000{- - Language/Ding/Show/Grammar.hs - convert grammar annotations to strings - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Convert grammar annotations to strings, as they might occur in the Ding - dictionary. Note that they generally have a different representation in - TEI. - - The functions herin are both used by the pretty printer, but also by the - parser, to convert grammar keywords identified in the lexer back to their - string representation, when they do not have special meaning due to their - context--of which the lexer is unaware. -} module Language.Ding.Show.Grammar ( showGLC , showPOS , showNumber , showCase ) where import Data.NatLang.Grammar showGLC :: GramLexCategory -> [String] showGLC (PartOfSpeech pos) = showPOS pos showGLC (Gender gen) = pure $ showGender gen showGLC (Number num) = pure $ showNumber num showGLC (Case cas) = pure $ showCase cas showPOS :: PartOfSpeech -> [String] showPOS Noun = pure "noun" showPOS (Verb []) = pure "v" showPOS (Verb vTypes) = map showTypedVerb vTypes showPOS Adjective = pure "adj" showPOS Adverb = pure "adv" showPOS Preposition = pure "prp" showPOS Conjunction = pure "conj" showPOS Article = pure "art" showPOS (Pronoun []) = pure "pron" showPOS (Pronoun pTypes) = map showTypedPronoun pTypes showPOS Numeral = pure "num" showPOS Interjection = pure "interj" showPOS Particle = pure "particle" showTypedVerb :: VerbType -> String showTypedVerb Transitive = "vt" showTypedVerb Intransitive = "vi" showTypedVerb Reflexive = "vr" showTypedPronoun :: PronounType -> String showTypedPronoun Personal = "ppron" showTypedPronoun Interrogative = "pron interrog" showTypedPronoun Relative = "pron relativ" showGender :: Gender -> String showGender Feminine = "f" showGender Masculine = "m" showGender Neuter = "n" showNumber :: Number -> String showNumber (Singular False) = "sing" showNumber (Singular True) = "no pl" showNumber (Plural False) = "pl" showNumber (Plural True) = "no sing" showCase :: Case -> String showCase Nominative = "Nom." showCase Genitive = "Gen." showCase Accusative = "Akk." showCase Dative = "Dat." -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Syntax.hs000066400000000000000000000060741423006221500234250ustar00rootroot00000000000000{- - Language/Ding/Syntax.hs - general AST structures - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - General Ding AST types. - - This does not include elements of the AST that are common to both Ding and - TEI, which may instead by found in `Data.NatLang'. -} module Language.Ding.Syntax ( Ding , Header(..) , Line(..) , Entry(..) , Group(..) , Unit(..) ) where import Data.NatLang.Dictionary (Dictionary) import Data.NatLang.Example (Example) import Data.NatLang.Grammar (GrammarInfo) import Data.NatLang.InflectedForms (InflectedForms) import Data.NatLang.Usage (Usage) -- | A whole dictionary (set of German-English translation pairs), together -- with a header. type Ding = Dictionary Header Line -- | The header of the Ding. data Header = Header { headerVersion :: String , headerVersionDate :: String , headerCopyrightHolder :: String , headerCopyrightPeriod :: String , headerLicense :: String , headerURL :: String } deriving Show -- | A set of related entries. newtype Line = Line [Entry] deriving Show -- | A pair of corresponding `Group's. -- Upon parsing from the Ding, the order of languages is the same as there. -- The order may be flipped later though. data Entry = Entry Group Group deriving (Show, Eq, Ord) -- | A set of `Unit's matching the same translation. -- It may be empty, in which case a later created corresponding TEI entry may -- have zero translations (if the `Group' is on the target language's side). -- If an empty `Group' is on the source language's side, it is not at all -- represented in the resulting TEI. newtype Group = Group [Unit] deriving (Show, Eq, Ord) -- TODO: ? Represent units being abbreviations differently? -- - e.g.: add UnitType argument -- @type="abbrev" -- | A single (key-)word or phrase, in one language, with annotations. data Unit = Unit { unitHeadword :: String , unitPlain :: String -- ^ includes literal <()>-annotations , unitGrammar :: [GrammarInfo] , unitUsages :: [Usage] , unitPrefixes :: [String] , unitSuffixes :: [String] , unitAbbrevs :: [String] , unitInflected :: Maybe InflectedForms , unitReferences :: [String] -- ~tilde references , unitExamples :: [Example] -- initially none, added during enrichment } deriving (Show, Eq, Ord) -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/Ding/Token.hs000066400000000000000000000133731423006221500232170ustar00rootroot00000000000000{- - Language/Ding/Token.hs - token structures as produced by the lexer - - Copyright 2020-2021 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Token types, as produced by the Alex generated lexer and consumed by the - happy generated parser (and the header parser). -} module Language.Ding.Token ( Token(..) , Position(..) , Atom(..) , tokenToString , tokenToPosition , tokenToLine , tokenToColumn ) where import Data.NatLang.Grammar (GramLexCategory) import Language.Ding.Show.Grammar (showGLC) -- | Token, as produced by the lexer. Annotated with any directly preceding -- whitespace and the position in the input. data Token = Token String -- ^ Preceding whitespace. Position Atom | EmptyToken -- ^ Neutral element in the monoid. -- Note: -- * This instance's show function is not injective. Information on position -- and preceding whitespace is dropped. instance Show Token where show (Token _ _ atom) = show atom show EmptyToken = "EmptyToken" -- Note: One cannot simply use AlexPosn here, since this would introduce a -- dependency cycle (in the current setup). -- | Position of a token in the input, line and column. data Position = Position { positionToLine :: Int , positionToColumn :: Int } deriving Show -- TODO: Consider to add an explicit constructor for each possible separator. -- | The essential part of a `Token'. data Atom = NL | LangSep -- ^ "::" | Vert | Semi | Comma | Tilde | Plus | Wordswitch -- ^ "<>" | StrongSlash -- ^ see `doc/ding.slashes' | WeakSlash -- ^ see `doc/ding.slashes' | DoubleSlash | OBrace | CBrace | OBracket | CBracket | OParen | CParen | OAngle | CAngle | OSlash -- ^ see `doc/ding.slashes' | CSlash -- ^ see `doc/ding.slashes' | SlashSpecial String | Abbrev String | AbbrevPlural String | GramKW GramLexCategory | IntPronKW String | Text String | KW_to | HeaderLine String deriving Show -- | Convert a token back to the string it represents excluding potential -- delimiters and dropping the annotated preceding whitespace. Uses -- `atomToString'. tokenToString :: Token -> String tokenToString (Token _ _ atom) = atomToString atom tokenToString EmptyToken = "" -- | Convert an atom back to the string that it represents, excluding any -- delimiters (). -- This function is not injective, in particular the distinction of different -- kinds of slashes is lost. atomToString :: Atom -> String atomToString NL = "\n" atomToString LangSep = "::" atomToString Vert = "|" atomToString Semi = ";" atomToString Comma = "," atomToString Tilde = "~" atomToString Plus = "+" atomToString Wordswitch = "<>" atomToString StrongSlash = "/" atomToString WeakSlash = "/" atomToString DoubleSlash = "//" atomToString OBrace = "{" atomToString CBrace = "}" atomToString OBracket = "[" atomToString CBracket = "]" atomToString OParen = "(" atomToString CParen = ")" atomToString OAngle = "<" atomToString CAngle = ">" atomToString OSlash = "/" atomToString CSlash = "/" atomToString (SlashSpecial s) = s -- pretty: "/ " ++ s ++ " /" atomToString (Abbrev s) = s -- pretty: "/ " ++ s ++ " /" atomToString (AbbrevPlural s) = s -- pretty: "/" ++ s ++ "/s" -- showGLC always gives a list of length one in this context. atomToString (GramKW gram) = head $ showGLC gram atomToString (IntPronKW pron) = pron atomToString (Text t) = t atomToString KW_to = "to" atomToString (HeaderLine l) = l tokenToPosition :: Token -> Position tokenToPosition EmptyToken = error "Tried to get position of empty token." tokenToPosition (Token _ pos _) = pos tokenToLine :: Token -> Int tokenToLine = positionToLine . tokenToPosition tokenToColumn :: Token -> Int tokenToColumn = positionToColumn . tokenToPosition -- All tokens have a string representation, which, together with the preceding -- whitespace, identifies their value. Two tokens may hence be combined in a -- natural way, into a canonical `Text' token. instance Semigroup Token where -- Join two tokens by concatenating their string representations, with the -- correct whitespace in between. (Token ws1 pos1 atom1) <> (Token ws2 _ atom2) = Token ws1 pos1 (Text $ atomToString atom1 ++ ws2 ++ atomToString atom2) -- EmptyToken is supposed to be a neutral element. Note that this means that -- `EmptyToken <> tok' retains the whitespace from `tok'. See also -- todo/parsing.elimination. EmptyToken <> tok = tok tok <> EmptyToken = tok instance Monoid Token where -- The unit in the token monoid is the empty 'Text' with no preceding white- -- space. mempty = EmptyToken -- vi: ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/000077500000000000000000000000001423006221500213345ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/TEI/Show/000077500000000000000000000000001423006221500222545ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/TEI/Show/Grammar.hs000066400000000000000000000076431423006221500242100ustar00rootroot00000000000000{- - Language/TEI/Show/Grammar.hs - show-like functions for grammar elements - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Print grammar annotations according to TEI syntax. -} module Language.TEI.Show.Grammar ( showPrimaryPOS , showVerbType , showPronounType , showGender , showPrimaryNumber , showCase , shownSingulareTantum , shownPluraleTantum ) where import Data.NatLang.Grammar -- Notes: -- * The string representations are chosen according to -- Freedict/fd-dictionaries/shared/FreeDict_ontology.xml (short). -- * If not found, annotated as UNCERTAIN. -- * The existing dictionaries may serve as reference also. -- * swh-eng is the only dictionary to contain elements. -- * lat-deu is the only dictionary to contain elements. -- * Some could not be found anywhere and have been guessed/invented. -- * See also . -- * This module encompasses show* functions, which are similar to show, as -- defined in `Show' instances. -- * They are not defined in `Show' instances, because -- a) The datatypes are shared between the Ding and TEI ASTs and it is -- inconvenient to write two complete sets of Show instances by hand. -- b) Some of the functions are not injective, they hide important -- information, which is generally undesired for `show'. -- * These problems do not exist for Usage. (UsageType has no -- representation in Ding; all other information is encoded as plain -- string) showPrimaryPOS :: PartOfSpeech -> String showPrimaryPOS Noun = "n" showPrimaryPOS (Verb _) = "v" showPrimaryPOS Adjective = "adj" showPrimaryPOS Adverb = "adv" showPrimaryPOS Preposition = "prep" showPrimaryPOS Conjunction = "conj" showPrimaryPOS Article = "art" showPrimaryPOS (Pronoun _) = "pron" showPrimaryPOS Numeral = "num" showPrimaryPOS Interjection = "int" showPrimaryPOS Particle = "ptcl" -- UNCERTAIN (common gloss) showVerbType :: VerbType -> String showVerbType Transitive = "trans" showVerbType Intransitive = "intr" showVerbType Reflexive = "refl" -- UNCERTAIN (~swh-eng) showPronounType :: PronounType -> String showPronounType Personal = "pers" -- UNCERTAIN (~swh-eng) showPronounType Interrogative = "inter" -- UNCERTAIN (~swh-eng) showPronounType Relative = "rel" -- UNCERTAIN showGender :: Gender -> String showGender Feminine = "fem" showGender Masculine = "masc" showGender Neuter = "neut" showPrimaryNumber :: Number -> String showPrimaryNumber (Singular _) = "sg" -- UNCERTAIN (~ several) showPrimaryNumber (Plural _) = "pl" -- UNCERTAIN (~ several) showCase :: Case -> String showCase Nominative = "nom" -- UNCERTAIN showCase Genitive = "gen" -- UNCERTAIN (~lat-deu) showCase Accusative = "acc" -- UNCERTAIN (~lat-deu: akk) showCase Dative = "dat" -- UNCERTAIN (~lat-deu) shownSingulareTantum :: String shownSingulareTantum = "sg only" -- UNCERTAIN shownPluraleTantum :: String shownPluraleTantum = "pl only" -- UNCERTAIN -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/Syntax.hs000066400000000000000000000066651423006221500231730ustar00rootroot00000000000000{- - Language/TEI/Syntax.hs - general AST structures - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - The TEI AST. - - The types are quite closely related to TEI's XML elements (see - `Language.TEI.ToXML' on how they translate. - - This does only support a small subset of TEI (for dictionaries), as is - needed to represent the information that stems from the Ding dictionary. - - AST elements that are common to both Ding and TEI are to be found in - `Data.NatLang'. - - The TEI header is not explicitly represented, instead the Ding header type - is used. -} module Language.TEI.Syntax ( TEI , Entry(..) , Form(..) , Sense(..) , Translation(..) ) where import Data.NatLang.Dictionary (Dictionary) import Data.NatLang.Grammar (GrammarInfo) import Data.NatLang.Usage (Usage) import Data.NatLang.InflectedForms (InflectedForms) import qualified Language.Ding.Syntax as Ding (Header) import Language.TEI.Syntax.Reference (Ident, ReferenceGroup) import Data.NatLang.Example (Example) -- | A TEI dictionary, with a Ding header. type TEI = Dictionary Ding.Header Entry -- | A TEI entry, with a unique identifier, a form (containing the headword, -- i.a.), grammar annotation and a list of senses (containing translations, -- i.a.). -- Note that annotated grammar information should pertain to all senses. data Entry = Entry { entryIdent :: Ident , entryForm :: Form , entryGrammar :: [GrammarInfo] , entrySenses :: [Sense] } deriving Show -- | A form, containing a single headword and potentially some related forms. -- Pronunciation information, that also belongs to a `

' is added by a -- separate tool, after TEI XML generation. data Form = Form { formOrth :: String -- ^ headword , formAbbrevs :: [String] , formInflected :: Maybe InflectedForms } deriving (Show, Eq, Ord) -- | A sense, containing translations and annotations. data Sense = Sense { senseGrammar :: [GrammarInfo] , senseUsages :: [Usage] , senseTranslations :: [Translation] , senseExamples :: [Example] , senseReferences :: [ReferenceGroup] -- ^ References, grouped by type. -- No two reference groups with the same type should occur. , senseNotes :: [String] -- ^ from suffixing Ding-<()>-annotations } deriving (Show, Eq, Ord) -- | A translation to a headword, together with annotations. Corresponds -- to `'. data Translation = Translation { translationOrth :: String , translationGrammar :: [GrammarInfo] , translationUsages :: [Usage] , translationAbbrevs :: [String] , translationInflected :: Maybe InflectedForms , translationNotes :: [String] } deriving (Show, Eq, Ord) -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/Syntax/000077500000000000000000000000001423006221500226225ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/TEI/Syntax/Reference.hs000066400000000000000000000043161423006221500250600ustar00rootroot00000000000000{- - Language/TEI/Syntax/Reference.hs - references - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - TEI references, corresponding to the `xr' and `ref' XML tags. -} module Language.TEI.Syntax.Reference ( Ident(..) , ReferenceGroup(..) , Reference(..) , RefType(..) ) where import Data.List.NonEmpty (NonEmpty) -- | An identifier representing `HW.n' where `HW' is a headword and `n' -- a distinguishing number. data Ident = Ident String Int deriving (Show, Eq, Ord) -- | A list of references, sharing a type; corresponds to `xr' in TEI XML. data ReferenceGroup = ReferenceGroup RefType (NonEmpty Reference) deriving (Show, Eq, Ord) -- | A reference; corresponds to `ref' in TEI XML. data Reference = Reference (Maybe Ident) -- ^ target identifier String -- ^ target string representation deriving (Show, Eq, Ord) -- | Reference types, as describe in the FreeDict HowTo: -- data RefType = Synonymy | Etymology -- ^ not present in the Ding | Comparison -- ^ not present in the Ding | Illustration -- ^ not present in the Ding | Related deriving (Eq, Ord) -- | Show reference types as recommended by FreeDict. instance Show RefType where show Synonymy = "syn" show Etymology = "etym" show Comparison = "cf" show Illustration = "illus" show Related = "see" -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/ToXML.hs000066400000000000000000000055641423006221500226450ustar00rootroot00000000000000{- - Language/TEI/ToXML.hs - convert TEI to XML (AST) - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Convert the TEI AST to textual TEI XML. -} module Language.TEI.ToXML (prettyTEI) where import Text.XML.Light import Data.NatLang.Dictionary (Dictionary(..), Body(..)) import Language.TEI.Syntax (TEI) import Language.TEI.ToXML.Aux import Language.TEI.ToXML.Body import Language.TEI.ToXML.Header -- Notes: -- * haskell-xml does not catch invalid input ! -- * `Language.TEI.ToXML.ValidateChar.validateString' should be applied -- to the Ding input before attempting to convert to XML here. -- * Currently done in Main. -- | Pretty-print the TEI AST, as XML. prettyTEI :: TEI -> String -- Do not use `ppcTopElement' here, it adds a boring default header. prettyTEI tei = xmlDeclDoctype ++ (ppcElement prettyConfigPP $ teiToElement tei) ++ "\n" -- | String form of the XML declaration and doctype (haskell-xml does not -- support such properly). xmlDeclDoctype :: String -- TODO: Put the referenced files into place (e.g., by symlinking). xmlDeclDoctype = unlines [ "" , "" , "" , "" , "" , "" ] -- | Translate a TEI AST to the corresponding XML AST. teiToElement :: TEI -> Element teiToElement tei = unode "TEI" ( [ uattr "xmlns" "http://www.tei-c.org/ns/1.0", uattr "version" "5.0" ] , [ convHeader (dictHeader tei) srcLang tgtLang nHeadwords , convBody (dictBody tei) srcLang tgtLang ] ) where srcLang = dictSrcLang tei tgtLang = dictTgtLang tei -- Note: It seems more reasonable to calculate the number of headwords, which -- is to become part of the TEI header, in the ding2tei translation -- function. However, as of now, the TEI AST is unable to store that -- number. nHeadwords = length entries where (Body entries) = dictBody tei -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/ToXML/000077500000000000000000000000001423006221500222775ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/Language/TEI/ToXML/Aux.hs000066400000000000000000000041151423006221500233710ustar00rootroot00000000000000{- - Language/TEI/ToXML/Aux.hs - auxiliary functions for XML generation - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Auxiliary XML generation functions. - - The used `xml' library is very simple; this module tries to compensate - for this. -} module Language.TEI.ToXML.Aux ( uattr , xmlLangAttr , xmlIdAttr , text , mergeContent ) where import Text.XML.Light -- | Attribute with unqualified name, analogous to `unode'. uattr :: String -> String -> Attr uattr k v = Attr (unqual k) v -- | `@xml:lang' attribute xmlLangAttr :: String -> Attr xmlLangAttr lang = Attr (QName "lang" Nothing (Just "xml")) lang -- | `@xml:id' attribute xmlIdAttr :: String -> Attr xmlIdAttr val = Attr (QName "id" Nothing (Just "xml")) val -- | Text content. Upon pretty printing, special XML characters are escaped. text :: String -> Content text s = Text $ CData CDataText s Nothing -- | Combine adjacent text and elements into the correspondonding string, -- wrapped as a single `Content'. -- This is an ugly workaround to the awkward "pretty" formatting of -- `Text.XML.Light', where a list of `Content' (length >= 2) is always -- converted to a corresponding list of idented lines. mergeContent :: [Content] -> Content mergeContent cs = Text $ CData CDataRaw rawString Nothing where rawString = concatMap (ppcContent defaultConfigPP) cs -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/ToXML/Body.hs000066400000000000000000000166621423006221500235430ustar00rootroot00000000000000{- - Language/TEI/ToXML/Body.hs - convert TEI body to XML (AST) - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Convert the TEI body to XML. -} module Language.TEI.ToXML.Body (convBody) where import Data.Foldable (toList) import Data.List.NonEmpty (NonEmpty((:|))) import Data.Maybe (mapMaybe) import Text.XML.Light import Data.NatLang.Dictionary (Body(Body)) import Data.NatLang.Example (Example(..)) import Data.NatLang.InflectedForms (InflectedForms(..), InflectedForm(..)) import Data.NatLang.Language import Data.NatLang.Usage (Usage(..)) import Language.TEI.Syntax import Language.TEI.Syntax.Reference import Language.TEI.ToXML.Aux import Language.TEI.ToXML.Grammar import Language.TEI.ToXML.Ident -- Notes: -- * The auxiliary functions are set up in the `where' block of the main -- function so that they all have shared access to `srcLang' and `tgtLang'. -- * Otherwise, these variables would need to be passed around a lot, even -- to functions that do not use them directly but only pass them further -- down. -- * Some functions could be taken off the where block, but they are not, -- for consistency. -- * The functions `node _' and `unode _' are heavily overloaded. They may -- operate on e.g.: -- ([Attr], String), (Attr, Element), ([Attr], [Element]), Attr. -- * If a pair is provided, the left element is always the attribute (list) -- and the right element the content. -- * The @xml:lang attribute is placed -- - on the top-level element (source language), and -- - on cit@type="trans" elements (target language). -- - This includes translations of examples. -- * Reasoning: All but translations are in the source language, this is -- the minimal approach. -- * Note that there are some fixed abbreviations (e.g., for grammatical -- gender, or register), which violate this (usually English; TODO?). -- * lg1-lg2.tei suggests otherwise. -- * TEI Lex-0 suggests instead to add the attribute to and -- elements. -- | Convert the TEI Body to the corresponding TEI XML. convBody :: Body Entry -> Language -> Language -> Element convBody body srcLang tgtLang = convBody' body where ------------------------- -- Some useful constants xmlLangSrc = xmlLangAttr $ showCode srcLang xmlLangTgt = xmlLangAttr $ showCode tgtLang --------------------------- -- The converter functions convBody' :: Body Entry -> Element convBody' (Body entries) = unode "text" $ (,) xmlLangSrc $ unode "body" $ map convEntry $ entries convEntry :: Entry -> Element convEntry e = unode "entry" ( [xmlIdAttr $ identToXMLId $ entryIdent e] , (convForm $ entryForm e) : maybe id (:) (convGrammar $ entryGrammar e) (mapMaybe convSense $ entrySenses e) ) -- @type=lemma is a TEI Lex-0 recommendation. convForm :: Form -> Element convForm form = unode "form" $ unode "orth" (formOrth form) : map (convAbbrev "form") (formAbbrevs form) ++ maybe [] convInflectedForms (formInflected form) -- Encoded as suggested by Sebastian Humenda (on , -- 2020-08-29). -- To be nested inside the main element. -- Note: The tagName is "form" inside and "cit" inside . convAbbrev :: String -> String -> Element convAbbrev tagName = unode tagName . (,) (uattr "type" "abbrev") . unode "orth" -- tns[@value="past"]: no source - guessed / newly defined. -- tns[@value="pstp"] was found as an example in the TEI P5 doc (9.5.3.1). -- - TEI Lex-0 sug.: pap convInflectedForms :: InflectedForms -> [Element] convInflectedForms (InflectedForms (sp :| sps) (pp :| pps)) = map (convInflectedForm "past" (Just "indicative")) (sp : sps) ++ map (convInflectedForm "pstp" Nothing) (pp : pps) -- Syntax: -- a) E-Mail from Sebastian Humenda (on , -- 2020-05-03) -- * Nest inside the main form. -- b) TEI Lex-0 (3.3): suggests value as content, e.g. pres. -- c) TEI P5 -- * 9.3.1: grammar tags -- * doc: tns, mood example: -- d) FreeDict TEI (@shumenda): Do not use @value, instead content. convInflectedForm :: String -> Maybe String -> InflectedForm -> Element convInflectedForm tense mMood (InflectedForm orth usages) = unode "form" ( [uattr "type" "infl"] , unode "gramGrp" ( (unode "tns" $ tense) : maybe [] (pure . unode "mood") mMood ) : unode "orth" orth : map convUsage usages ) -- When there is no content, there needs not be no sense element. Hence -- `Maybe Element'. convSense :: Sense -> Maybe Element convSense sense = let content = maybe id (:) (convGrammar $ senseGrammar sense) $ (map convUsage $ senseUsages sense) ++ (map convTranslation $ senseTranslations sense) ++ (map convExample $ senseExamples sense) ++ (map convRefGroup $ senseReferences sense) ++ (map (unode "note") $ senseNotes sense) in if null content then Nothing else Just $ unode "sense" content -- Note: inflected forms are dropped for now - unsure how to encode. (TODO) convTranslation :: Translation -> Element convTranslation trans = unode "cit" ( [uattr "type" "trans", xmlLangTgt] , unode "quote" (translationOrth trans) : (maybe id (:) (convGrammar $ translationGrammar trans) $ (map convUsage $ translationUsages trans) ++ (map (convAbbrev "cit") $ translationAbbrevs trans) ++ (map (unode "note") $ translationNotes trans) ) ) convUsage :: Usage -> Element convUsage (Usage uType uStr) = unode "usg" ( uattr "type" (show uType) , uStr ) convExample :: Example -> Element convExample (Example srcEx tgtExs) = unode "cit" ( [uattr "type" "example"] , unode "quote" srcEx : map convExTrans tgtExs ) where convExTrans :: String -> Element convExTrans = unode "cit" . (,) [uattr "type" "trans", xmlLangTgt] . unode "quote" convRefGroup :: ReferenceGroup -> Element convRefGroup (ReferenceGroup refType refs) = unode "xr" ( [uattr "type" (show refType)] , map convReference $ toList refs ) where -- Notes: -- * Some references do not contain a link to another entry. -- * Such links need to be annotated with '#'. convReference :: Reference -> Element convReference (Reference mRef tgt) = unode "ref" ( maybe [] (pure . uattr "target" . ('#':) . identToXMLId) mRef , tgt ) -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/ToXML/Grammar.hs000066400000000000000000000071661423006221500242330ustar00rootroot00000000000000{- - Language/TEI/ToXML/Grammar.hs - convert grammar information to TEI XML (AST) - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Convert grammar annotations to TEI XML. -} module Language.TEI.ToXML.Grammar (convGrammar) where import Text.XML.Light import Data.NatLang.Grammar import Data.NatLang.Usage (Usage) import Language.TEI.Show.Grammar -- | Convert a list of grammar informations to a gramGrp element. -- If the list is empty, evaluates to `Nothing'. convGrammar :: [GrammarInfo] -> Maybe Element convGrammar [] = Nothing convGrammar gs = Just $ unode "gramGrp" $ concatMap convGrammarInfo gs -- Notes: -- * For collocates, follows the suggestion from TEI Lex-0 (however stick with -- ) [0]. -- * For POS collocates, a potential subtype is simply appended. -- * For Case collocates, the potential interrogative pronouns are prepended, -- in the same form they appear in the Ding. convGrammarInfo :: GrammarInfo -> [Element] convGrammarInfo (GramLexCategory gram) = convGramLexCat gram convGrammarInfo (Collocate colloc usgs) = pure $ convCollocate colloc usgs -- Note: It seems impossible to represent the present usage annotations -- (TODO). convCollocate :: Collocate -> [Usage] -> Element convCollocate (CollocCase iProns cas) _usgs = unode "colloc" $ "[" ++ prefix ++ "+ " ++ showCase cas ++ "]" where prefix = case iProns of [] -> "" (p:ps) -> p ++ concatMap (", " ++) ps convCollocate (CollocPOS pos) _usgs = unode "colloc" $ "[+ " ++ showPrimaryPOS pos ++ subType ++ "]" where subType = case pos of (Verb vTypes) -> concatMap ((' ':) . showVerbType) vTypes (Pronoun pTypes) -> concatMap ((' ':) . showPronounType) pTypes _ -> "" -- Note: Ignore potential singulare/plurale tantum attribute. -- - Does not occur in Ding version 1.9. convCollocate (CollocNumber number) _usgs = unode "colloc" $ "[+ " ++ showPrimaryNumber number ++ "]" convGramLexCat :: GramLexCategory -> [Element] convGramLexCat (PartOfSpeech pos) = convPOS pos convGramLexCat (Gender gen) = [unode "gen" (showGender gen)] convGramLexCat (Number num) = unode "number" (showPrimaryNumber num) : convNumberSubc num convGramLexCat (Case cas) = [unode "case" (showCase cas)] convPOS :: PartOfSpeech -> [Element] convPOS pos = unode "pos" (showPrimaryPOS pos) : case pos of (Verb vTypes) -> map (unode "subc" . showVerbType) vTypes (Pronoun pTypes) -> map (unode "subc" . showPronounType) pTypes _ -> [] convNumberSubc :: Number -> [Element] convNumberSubc (Singular True) = [unode "subc" shownSingulareTantum] convNumberSubc (Plural True) = [unode "subc" shownPluraleTantum] convNumberSubc _ = [] -- References: -- * [0] https://dariah-eric.github.io/lexicalresources/pages/TEILex0/TEILex0.html#collocates -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/ToXML/Header.hs000066400000000000000000000232111423006221500240220ustar00rootroot00000000000000{- - Language/TEI/ToXML/Header.hs - convert header to XML - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Convert the Ding header to TEI XML -} module Language.TEI.ToXML.Header (convHeader) where import Data.List (intercalate) import Text.XML.Light import qualified Config as Cfg import Data.NatLang.Language import Language.Ding.Syntax (Header(..)) import Language.TEI.ToXML.Aux -- Notes: -- * Whenever plain text co-occurs (on the same level) with other Content -- inside an element (e.g. "textzmore text"), one should -- use `mergeContent' on that list to avoid a pretty-printing like -- -- text -- z -- more text -- -- and instead cause everything to pe put in a single line. -- * For information on how the header should look like, see the FreeDict -- Wiki [0]. -- * See also: note on `unode' and `note' in ToXML/Body.hs. -- | Create a `ref' element with equal target and value. plainRefNode :: String -> Element plainRefNode tgt = unode "ref" (uattr "target" tgt, tgt) -- | Convert the Ding (!) header straight to TEI XML. -- A notable share of information is not extracted from the Ding header, -- but rather hardcoded, or configured in the `Config' module. This is -- because the Ding header requires a lot more information than the Ding -- header provides; further much of that information does only depend on this -- program. convHeader :: Header -> Language -> Language -> Int -> Element convHeader header srcLang tgtLang nHeadwords = teiHeader where ---------------------------------------------- -- Information extracted from the Ding header -- Note: -- * The headerLicense field is not used. Instead, the licensing -- information is hardcoded below, since the interaction with AGPL is -- non-trivial. dingVersion = headerVersion header dingDate = headerVersionDate header dingAuthor = headerCopyrightHolder header dingCopyrightPeriod = headerCopyrightPeriod header dingURL = headerURL header --------------------------------------- -- (Partly) non-structural information -- Contains information customary to this particular dictionary.. version = Cfg.makeVersion dingVersion Cfg.modVersion title = show srcLang ++ " - " ++ show tgtLang ++ " Ding/" ++ Cfg.projectName ++ " dictionary" availability :: Element availability = unode "availability" $ (,) [uattr "status" "free"] [ -- Copyright holders (Ding author & this program's contributors) unode "p" $ (++) "Copyright (C) " $ intercalate ", " $ map (\ (p, y) -> p ++ " " ++ y) $ (dingAuthor, dingCopyrightPeriod) : map (\ (p, y) -> (Cfg.personName p, y)) Cfg.contributors , -- License notice unode "p" $ mergeContent [ text $ unwords [ "This dictionary is free software: you can redistribute it" , "and/or modify it under the terms of both the " ] , Elem $ unode "ref" ( uattr "target" Cfg.gpl3url , "GNU General Public License, version 3 (GPLv3)" ) , text " and the " , Elem $ unode "ref" ( uattr "target" Cfg.agpl3url , "GNU Affero General Public License, version 3 (AGPLv3)" ) , text $ unwords [ ", where each of these licenses applies to different parts of" , "this (combined) work." ] ] , unode "p" $ unwords [ "This dictionary 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 General Public License" , "and the" , "GNU Affero General Public License" , "for more details." ] , unode "p" $ mergeContent [ text $ unwords [ "You should have received a copy of both the" , "GNU General Public License" , "and the" , "GNU Affero General Public License" , "along with this dictionary. If not, see " ] , Elem $ plainRefNode "https://www.gnu.org/licenses/" , text "." ] , unode "p" $ mergeContent [ text $ unwords [ "The \"source form\", as defined in both the GPLv3 and AGPLv3," , "of this dictionary is composed of the original" , "Ding dictionary (licensed " ] , Elem $ unode "ref" (uattr "target" Cfg.gpl2url, "GPLv2") , text $ "+) and the " ++ Cfg.programName ++ " program (licensed AGPLv3+)." ] ] -- TODO: Add more notes (e.g., to provide an ontology -- for grammar keywords -- et al.). notesStmt :: Element notesStmt = unode "notesStmt" $ [ unode "note" (uattr "type" "status", Cfg.status) ] sourceDesc :: Element sourceDesc = unode "sourceDesc" [ unode "p" $ mergeContent [ text "Home: " , Elem $ unode "ptr" $ uattr "target" dingURL ] , unode "p" $ mergeContent [ text "This dictionary was generated from the " , Elem $ unode "ref" (uattr "target" dingURL, "Ding") , text $ " dictionary, version " ++ dingVersion ++ " (" ++ dingDate ++ ") using the " , Elem $ unode "ref" (uattr "target" Cfg.programURL, Cfg.programName) , text " program." ] , unode "p" $ mergeContent [ text "The Ding dictionary can be obtained from " , Elem $ plainRefNode Cfg.dingDownloadURL , text "." ] , unode "p" $ mergeContent [ text $ "The " ++ Cfg.programName ++ " program can be obtained from " , Elem $ plainRefNode Cfg.programDownloadURL , text "." ] ] projectDesc :: Element projectDesc = unode "projectDesc" $ unode "p" $ mergeContent [ text $ unwords [ "This dictionary comes to you through nice people making it" , "available for free and for good." , "It is part of the", Cfg.projectName, "project, " ] , Elem $ plainRefNode Cfg.projectURL , text $ unwords [ "." , "This project aims to make many translating dictionaries available" , "for free." , "Your contributions are welcome!" ] ] --------------------------------- -- Purely structural information -- Code that only reflects some (TEI/FreeDict) rules & converntions. teiHeader :: Element teiHeader = unode "teiHeader" $ (,) [xmlLangAttr "en"] $ fileDesc : encodingDesc : maybe [] pure revisionDesc fileDesc :: Element fileDesc = unode "fileDesc" [ titleStmt , editionStmt , unode "extent" $ show nHeadwords ++ " headwords" , publicationStmt , notesStmt , sourceDesc ] -- TODO: Use @xml:id and @ref for , as in eng-pol. titleStmt :: Element titleStmt = unode "titleStmt" [ unode "title" title , unode "author" dingAuthor , unode "editor" $ Cfg.personName Cfg.editor , unode "respStmt" [ unode "resp" "Maintainer" , unode "name" $ Cfg.personName Cfg.maintainer ] ] editionStmt :: Element editionStmt = unode "editionStmt" $ unode "edition" $ version publicationStmt :: Element publicationStmt = unode "publicationStmt" [ unode "publisher" Cfg.projectName , availability , unode "date" (uattr "when" Cfg.publicationDate, Cfg.publicationDate) , unode "pubPlace" $ unode "ref" Cfg.projectURL ] encodingDesc :: Element encodingDesc = unode "encodingDesc" projectDesc -- For reference, see eng-pol.tei and swh-eng.tei. revisionDesc :: Maybe Element revisionDesc | null Cfg.changes = Nothing | otherwise = Just $ unode "revisionDesc" $ map convChange Cfg.changes where convChange :: Cfg.Change -> Element convChange ch = unode "change" ( [ uattr "n" chVersion , uattr "when" $ date , uattr "who" $ unwords $ map (('#':) . Cfg.personId) $ Cfg.chPersons ch ] , unode "list" ( [uattr "type" "bulleted"] , ( unode "head" $ mergeContent $ [ text $ "Version " ++ chVersion ++ ", " , Elem $ unode "date" date , text ". Changes by " ] ++ nameList ++ [ text "." ] ) : map (unode "item") (Cfg.chItems ch) ) ) where chVersion = Cfg.makeVersion (Cfg.chDingVersion ch) (Cfg.chModVersion ch) date = Cfg.chDate ch nameList :: [Content] nameList = intercalate [text " and "] $ map (pure . Elem . unode "name" . Cfg.personName) $ Cfg.chPersons ch -- References -- * [0] https://github.com/freedict/fd-dictionaries/wiki/FreeDict-HOWTO-%E2%80%93-Writing-Text-Encoding-Initiative-XML-Files -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/ToXML/Ident.hs000066400000000000000000000071241423006221500237020ustar00rootroot00000000000000{- - Language/TEI/ToXML/Ident.hs - encode xml:id's - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Convert identifiers to valid `@xml:id' values. -} module Language.TEI.ToXML.Ident ( identToXMLId ) where import Data.Char (ord) import Language.TEI.Syntax.Reference (Ident(Ident)) -- The syntax of XML id's is described in by the W3C's XML specification: -- * https://www.w3.org/TR/xmlschema-2/#ID | #NCName -- / https://www.w3.org/TR/2004/REC-xmlschema-2-20041028/#ID | #NCName -- * https://www.w3.org/TR/xml-names/#NT-NCName -- / https://www.w3.org/TR/2009/REC-xml-names-20091208/#NT-NCName -- > NCName ::= Name - (Char* ':' Char*) /* An XML Name, minus the ":" */ -- * https://www.w3.org/TR/xml/#NT-Name -- / https://www.w3.org/TR/2008/REC-xml-20081126/#NT-Name -- > Name ::= NameStartChar (NameChar)* -- * https://www.w3.org/TR/xml/#NT-NameStartChar | #NT-NameChar -- / http://www.w3.org/TR/2008/REC-xml-20081126/ -- * defines NameStartChar and NameChar -- * Deduce: NCNameStartChar and NCNameChar (- ':') -- | Translate an `Ident' to a legal @xml:id (NCName). -- The identifier's number is appended dot-separated to avoid ambiguity. identToXMLId :: Ident -> String identToXMLId (Ident s n) = encodeNCName s ++ '.' : show n -- Use '_' as escape char, since it is legal everywhere. encodeNCName :: String -> String encodeNCName "" = "_empty_" encodeNCName (c:cs) = encodeNCNameStartChar c ++ concatMap encodeNCNameChar cs encodeNCNameStartChar :: Char -> String encodeNCNameStartChar ' ' = "__" encodeNCNameStartChar c | isNCNameStartChar c && c /= '_' = pure c | otherwise = escapeChar c encodeNCNameChar :: Char -> String encodeNCNameChar ' ' = "__" encodeNCNameChar c | isNCNameChar c && c /= '_' = pure c | otherwise = escapeChar c escapeChar :: Char -> String escapeChar c = '_' : show (ord c) ++ "_" -- Treat `€' special to work around a libxml2 bug. -- - -- - Otherwise, `make validation' would fail. -- - TODO: Revert once bug fixed. isNCNameStartChar :: Char -> Bool isNCNameStartChar '€' = False isNCNameStartChar c = 'A' <= c && c <= 'Z' || c == '_' || 'a'<= c && c <= 'z' || '\xC0' <= c && c <= '\xD6' || '\xD8' <= c && c <= '\xF6' || '\xF8' <= c && c <= '\x2FF' || '\x370' <= c && c <= '\x37D' || '\x37F' <= c && c <= '\x1FFF' || '\x200C' <= c && c <= '\x200D' || '\x2070' <= c && c <= '\x218F' || '\x2C00' <= c && c <= '\x2FEF' || '\x3001' <= c && c <= '\xD7FF' || '\xF900' <= c && c <= '\xFDCF' || '\xFDF0' <= c && c <= '\xFFFD' || '\x10000' <= c && c <= '\xEFFFF' isNCNameChar :: Char -> Bool isNCNameChar c = isNCNameStartChar c || c == '-' || c == '.' || '0' <= c && c <= '9' || c == '\xB7' || '\x0300' <= c && c <= '\x036F' || '\x203F' <= c && c <= '\x2040' -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Language/TEI/ToXML/ValidateChar.hs000066400000000000000000000032061423006221500251630ustar00rootroot00000000000000{- - Language/TEI/ToXML/ValidateChar.hs - validate character data - - Copyright 2020 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - XML string validation as per the W3C XML specification - (). -} module Language.TEI.ToXML.ValidateChar ( validateString , validateChar ) where -- | Validate that a string is valid in XML, as defined at -- . -- Throws an error otherwise. validateString :: String -> String validateString = map validateChar -- | Validate that a character is valid in XML, as defined at -- . -- Throws an error otherwise. validateChar :: Char -> Char validateChar c = if c `elem` "\x9\xA\xD" || '\x20' <= c && c <= '\xD7FF' || '\xE000' <= c && c <= '\xFFFD' || '\x10000' <= c && c <= '\x10FFFF' then c else error $ "Invalid character in input: " ++ show c -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Main.hs000066400000000000000000000152461423006221500204200ustar00rootroot00000000000000{- - Main.hs - main program - - Copyright 2020-2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} module Main (main) where import Prelude hiding (log) import Control.Monad (when) import Data.Either (isRight) import Data.Maybe (fromMaybe, isNothing) import System.Environment (getArgs, getProgName) import System.Exit (exitSuccess, exitFailure) import System.IO (Handle, hPutStr, hPutStrLn, stdout, stderr) import App.Ding2TEI (ding2tei) import Language.Ding.AlexScanner (scan) import Language.Ding.Enrich (enrichUndirected, enrichDirected) import Language.Ding.Inverse (inverse) import Language.Ding.Parser (parse) import Language.TEI.ToXML (prettyTEI) import Language.TEI.ToXML.ValidateChar (validateString) -- Notes: -- * The input file should be newline terminated, even though in most cases -- it will not matter. -- * One might consider adding a final newline, if there is none. -- * Alex needs terminating whitespace, since the end of input is not -- treated as '\n' in right context (unlike the beginning of input). -- (The whitespace in right context is needed to differentiate different -- kinds of slashes.) -- * The input is checked for only containing valid XML characters -- (validateString). -- * This is not concerned with potentially to be escaped characters, such -- as '<', '"'. These are considered valid here. -- * There are only some very unusual characters that are not accepted in -- XML. -- * This is done here instead at the XML generation state to spot the -- error early. -- * The result would most likely be the same; the separators present in -- the Ding are all valid in XML. main :: IO () main = do (opts, mInFile, mOutFile) <- getArgs >>= parseArgs when (optPrintHelp opts) $ help stdout >> exitSuccess input <- readMFile mInFile -- TODO: validateString should not `error'. let (mDing, log) = parse $ scan $ validateString input (if optSkipErrors opts then printLog else printLogAbortOnError) (fromMaybe "" mInFile) log when (isNothing mDing) exitFailure -- If non-critical errors are to be skipped, they do not influence the return -- code. when (optParseOnly opts) exitSuccess ding <- maybe (undefined "Unable to parse input.") return mDing let ding' = enrichUndirected ding let ding'' = enrichDirected $ if optInverse opts then inverse ding else ding' let tei = ding2tei ding'' let outStr = prettyTEI tei writeMFile mOutFile outStr readMFile :: Maybe String -> IO String readMFile Nothing = getContents readMFile (Just fileName) = readFile fileName writeMFile :: Maybe String -> String -> IO () writeMFile Nothing = putStr writeMFile (Just fileName) = writeFile fileName -- | Write log up to and including first error message to stderr. -- Exit with nonzero return code in case of any error message. printLogAbortOnError :: String -> [Either String String] -> IO () printLogAbortOnError inFile log = do let (good, afterGood) = span isRight log printLog inFile good case afterGood of (e:_) -> printLog inFile (pure e) >> exitFailure [] -> return () -- | Write log to stderr. printLog :: String -> [Either String String] -> IO () printLog inFile log = hPutStr stderr $ unlines $ map (either (errorPrefix ++) (infoPrefix ++)) log where errorPrefix = "Error: " ++ commonPrefix infoPrefix = "Info: " ++ commonPrefix commonPrefix = inFile ++ ": " data Opts = Opts { optInverse :: Bool , optParseOnly :: Bool , optSkipErrors :: Bool , optPrintHelp :: Bool } defaultOpts :: Opts defaultOpts = Opts { optInverse = False , optParseOnly = False , optSkipErrors = False , optPrintHelp = False } parseArgs :: [String] -> IO (Opts, Maybe String, Maybe String) parseArgs = either (\e -> hPutStrLn stderr e >> help stderr >> exitFailure) return . parseArgs' parseArgs' :: [String] -> Either String (Opts, Maybe String, Maybe String) parseArgs' allArgs = do (opts, fileNames) <- aux defaultOpts allArgs case fileNames of [] -> return (opts, Nothing, Nothing) [inFile] -> return (opts, maybeFilename inFile, Nothing) [inFile, outFile] -> if optParseOnly opts then fail "Too many non-option arguments." else return (opts, maybeFilename inFile, maybeFilename outFile) _ -> Left "Too many non-option arguments." where aux :: Opts -> [String] -> Either String (Opts, [String]) aux opts [] = return (opts, []) aux opts ("--" : args) = return (opts, args) aux opts ("-i" : args) = aux (opts { optInverse = True }) args aux opts ("--inverse" : args) = aux (opts { optInverse = True }) args aux opts ("--parse-only" : args) = aux (opts { optParseOnly = True }) args aux opts ("--skip-errors" : args) = aux (opts { optSkipErrors = True }) args aux opts ("--validate" : args) = aux (opts { optParseOnly = True, optSkipErrors = True }) args aux opts ("-h" : args) = aux (opts { optPrintHelp = True }) args aux opts ("--help" : args) = aux (opts { optPrintHelp = True }) args aux _ (o@('-':_:_) : _ ) = Left $ "Invalid option `" ++ o ++ "'." aux opts (str : args) = (fmap . fmap) (str :) $ aux opts args maybeFilename :: String -> Maybe String maybeFilename "-" = Nothing maybeFilename str = Just str help :: Handle -> IO () help h = do progName <- getProgName hPutStr h $ unlines [ "Usage: " ++ progName ++ " [OPTION]... [INFILE [OUTFILE]]" , "" , "When INFILE or OUTFILE is missing or `-', use standard input or output," , "respectively." , "" , " -i, --inverse Invert dictionary direction." , " --parse-only Only parse the input, do not produce output." , " --skip-errors Continue on errors where possible." , " --validate Equivalent to `--parse-only --skip-errors'." , " -h, --help Print this help message and exit." ] -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/Test.hs000066400000000000000000000051341423006221500204460ustar00rootroot00000000000000{- - Test.hs - manual testing - - Copyright 2020,2022 Einhard Leichtfuß - - This file is part of ding2tei-haskell. - - ding2tei-haskell is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published - by the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ding2tei-haskell 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with ding2tei-haskell. If not, see . -} {-| - Manual testing. No function exported. To be used in `ghci' exclusively. - - Re-exports many data structures and functions. -} module Test () where import Control.Monad.Trans.Writer (runWriterT) import App.Ding2TEI import Data.NatLang.Dictionary import Data.NatLang.Grammar import Language.Ding.AlexScanner (scan) import Language.Ding.Parser (parse) import Language.Ding.Parser.Header (parseHeader) import qualified Language.Ding.Parser.Line as LP (parseLine) import Language.Ding.Pretty import Language.Ding.Syntax import Language.Ding.Token import Language.Ding.Enrich import Language.TEI.Syntax import Language.TEI.ToXML parseLine :: [Token] -> Line parseLine ts = case runWriterT $ LP.parseLine ts of Right (l, log) -> l Left e -> error e header :: String header = unlines [ "# Version :: 0.1 1970-01-01" , "# Copyright (c) :: Jane Doe," , "# 1970" , "# License :: GPL Version 2 or later; GNU General Public License" , "# URL :: https://example.org/" ] -- | Some example lines; all except the first from -- . examples :: [String] examples = [ unwords [ "Bäckerin {f}; Bäcker {m} | Bäckerei {f}; Backstube {f}" , ":: baker | bakery\n" ] , unwords [ "Wetter {n}; Witterung {f} | Witterungen {pl} | bei jeder Witterung;" , "bei jedem Wetter :: weather | weathers | in all weathers\n" ] -- Outdated syntax: --unlines -- [ "Wetter {n}; Witterung {f} :: weather" -- , " Witterungen {pl} :: weathers" -- , " bei jeder Witterung; bei jedem Wetter :: in all weathers" -- ] , "Whist {n} (Kartenspiel) :: whist\n" , unwords [ "(Schaden; Mangel) beheben; (Missstand) abstellen; abhelfen;" , "in Ordnung bringen :: to remedy \n" ] ] -- vi: ft=haskell ts=2 sw=2 et tools-0.6.0/importers/ding2tei/src/preprocess/000077500000000000000000000000001423006221500213555ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/preprocess/de-en/000077500000000000000000000000001423006221500223455ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/preprocess/de-en/README000066400000000000000000000040171423006221500232270ustar00rootroot00000000000000README ====== This directory contains preprocessor scripts, written in sed. They should be applied before running the main program. About ----- The scripts are used to fix some irregularities before handling processing to the main translating program. Generally, only irregularities that are assumed as mistakes are fixed. Targeted at the German-English Ding dictionary, version 1.9. Called with `sed -E' (see Shebang); i.e., extended regular expressions are used. Updates ------- When updating to a new Ding version, `update_help.bash' should be used. See there for more information. Conventions ----------- * Use the g (global) specifier, for anything that seems likely to possibly occur elsewhere, even if it does not. There might always be added another occurrence in a later version and `g' does not cost much. * Be as generic as possible and as specific as necessary. * Do not overgeneralize (e.g., "(n)" -> "{n}"). There may be legitimate uses of apparent misspellings / bad syntax. * Ex.: "to proffer sb. sth. /sth. to sb." -> "... sb. sth. / sth. to sb." * Bad: "sb. /sth." -> "sb./sth." (when generalized). * Use groups (<()>) and back-references (<\$i>). * Use <`> as separator to avoid the necessity to escape (/). * Use "\<" and "\>" where applicable. * On changes of this script, diff the result with the former result to check for erronous replacing. This is usually doable in time. * When changing to another Ding version, review the diff (!) and check for new irregularities (optional). * Ideally, when grouping (using parentheses), group in an understandable, rather than an efficient way. * Ex.: "s`\<(to take)/ (to carry out) (a measure)`\1 / \2 \3`g" * Groups 2 and 3 could be merged. Helpful tools ------------- * Regexes (grep/$EDITOR/less) to find common mistakes * e.g.: '\([^{]{0,10}\b(adj|adv|prp|n|m|f|...)\b' to find misusage of parantheses (instead of braces). * util/extract_braceexps.sed * Analyze the output to find any irregular brace expression. tools-0.6.0/importers/ding2tei/src/preprocess/de-en/abbreviations.sed000077500000000000000000000034611423006221500257010ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/abbreviations.sed - //-abbreviations # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Notes: # * Syntax breaking slashes are fixed in slashes.sed. ## "." -> /./ s`"(/\.ed)"`/ \1 /` ## [abbr .] -> /./ # Found by searching for "Abk" and "abbr". # - Note: All fixed in version 1.9; kept nonetheless. s`\[(Abk|abbr)\.: ([A-Za-z.-]+)\]`/\2/`g s`\((Abk|abbr)\.: ([A-Za-z.-]+)\)`/\2/`g s`, (Abk|abbr)\.: ([A-Za-z.]+)\)`) /\2/`g ## (.) -> /./ # - Note: This alters semantics (TODO?). # See also `slashes.sed'. s`\<(secondary hyperparathyroidism) /(SHPT)/ \((2-HPT)\)`\1 /\2/ /\3/` s`\<(tertiary hyperparathyroidism) /(THPT)/ \((3-HPT)\)`\1 /\2/ /\3/` ## Misc # <,> -> <.> s`\<(signed) /(sgd),/`\1 /\2./`g # Remove {}; guessing a little here. s`/in \{prp\} \./`/in prp./`g # Qustionably placed: 'A; B /a; b/' -> 'A /a/; B /b/' s`^(der Ältere)\; (Senior) /(d\. Ä\.); (Sen\.)/`\1 /\3/\; \2 /\4/` # [.] -> /[.]/ s`(^|:: *)(\[sic\])`\1/\2/`g # This one is repeated, so possibly not a typo. s`(supervised injection site) /SIS/Ms`\1s /SIS/s` s`/SIS/M\>`/SIS/`g # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/additions.sed000077500000000000000000000016211423006221500250230ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/additions.sed - add information # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # ## Missing entries s`^(Betracht .*) :: *\|`\1 :: consideration |` # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/all.bash000077500000000000000000000027041423006221500237620ustar00rootroot00000000000000#!/usr/bin/env bash # # preprocessed/de-en/all.bash - run all preprocessing scripts, in correct # order # # Copyright 2020,2022 Einhard Leichtfuß, 2021 the FreeDict project # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # The Ding source is highly irregular. The preprocessing steps help to keep # the parser clean by transforming irregularities to a more regular markup. # # See also: # * src/preprocess/de-en/README # * src/preprocess/de-en/update_help.bash dir="$(dirname "$(realpath "$0")")" cd "$dir" source ./order.conf.bash || exit 1 sedfiles_ordered_escaped=( "${sedfiles_ordered[@]@Q}" ) sedfiles_ordered_piped="${sedfiles_ordered_escaped[*]/#/| ./}" sedfiles_ordered_piped="${sedfiles_ordered_piped#| }" eval "$sedfiles_ordered_piped" # vi: ts=2 sw=2 noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/alter.sed000077500000000000000000000027641423006221500241650ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/alter.sed - syntax alterations # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # {relativ} is not an indpendent annotation. # Assimilate, with {pron interrog} in mind. # Note: It can be assumed that both below parts never occcur in (separate) # groups when belonging to one another (since then their relation would # not be obvious). s`\{(pron)\} *\{(relativ)\}`\{\1 \2\}`g # New in version 1.9 (rare); change to old syntax (see also above). s`\{(relativ)\.(pron)\}`{\2 \1}`g # Adapt grammar information in {+ X} to match the usual syntax for X. s`\{\+ *(Zahl|number)}`{+num}`g s`\{\+ *verb}`{+v}`g s`\{\+ *(Einzahl|singular)\}`{+sing}`g # Not actually alteration of syntax, but of content to fit syntax. s`\<(R/S ratio) >(1)\>`\1 > \2`g # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/drop.sed000077500000000000000000000023631423006221500240150ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # de-en/drop.sed - drop some information that cannot be properly parsed yet # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # s`\{(adj), usually not used before a noun\}`{\1}` s`\{(prp)\; \+ Fall des jeweiligen Bezugsworts\}`{\1}` s`\{(\+Gen\.)/(bei|von etw\.)\}`{\1}`g s`\{(prp\; \+Dat\.) / von jdm\./etw\.\}`{\1}` s` *\{Quantifikator\}``g # TEI does not specify an element for degree of comparison. s` *\{\+ *(Superlativ|superlative)\}``g # This should probably become [prp +] s` *\{prp *\+\}``g # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/grammar.sed000077500000000000000000000043371423006221500245020ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/grammar.sed - fix errors in grammar annotations # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # ### Semantic errors # Wrong pos annotation s`\<(makrohumiphag) \{m\} (\[zool\.\])`\1 \{adj\} \2` # - (de); translates to "macrohumiphagous" (en), which is an adjective # - source: https://doi.org/10.1016/0378-1127(95)03580-4 ### Syntactic errors ## () -> {} # Note: There are likely more of this kind. s`\<(Felge|Glanzleinwand) \(f\)`\1 {f}`g s`\<(Futterkattun) \(m\)`\1 {m}`g s`\<(Normalnull) \(n\)`\1 {n}`g s`\(adv\)`{adv}`g # See also: alter.sed s`\{pron\} \(relativ\)`{pron} {relativ}`g s`\(\+ *(Gen|Akk|Dat)\.?\)`\{+\1.\}`g s`\{prep\}`{prp}`g ## [] -> {} (and nomalization) s`\[kein Plural\]`\{no pl\}`g # Note: {no sing} never occurs, it is infered from {no pl} and {sing}. s`\[only plural\]`{no sing}`g s`\[no singular\]`{no sing}`g ## Superfluous <.>. s`\{(adv)\.\}`{\1}`g ## (.) {.} -> (. {.}) s`\((sich)\) \{(Dat\.)\} (die Sonnenenergie nutzbar machen)\>`(\1 {\2}) \3`g ## {.} . -> . {.} s`:: (\{adv\}) (idiosyncratically)$`:: \2 \1` s`:: (\{adj\}) (conflicting)\;`:: \2 \1\;` ## <+> outside {.} s`\{prp\} \+ (which/what)\; \1 \+ \{prp\}`{prp +} \1; \1 {+ prp}`g ## Consistent spacing and "dotting". # Note: This could also be delegated to the parser. Doing it here simplifies # matching keywords at the lexer stage, since '+' and '.' can be # considered part of the (unique) keyword. s`\{\+ *(Gen|Akk|Dat)\.?\}`\{+\1.\}`g s`\{\+ *(conj)\}`{+conj}`g # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/inflected_forms.sed000077500000000000000000000045131423006221500262130ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/inflected_forms.sed # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # ### Annotated inflected forms (on the English side) ## Incorrect syntax and semantics. # See . s`\{(strove), (striven) / (strived)\; (strived)\}`\{\1, \3\; \2, \4, \1 [coll.]\}` # See . s`\{(stank)/(stunk), (stunk)\}`{\1, \2; \3}`g ## -> <,> (normalize inner separator) s`\{(chided)/(chid)\; (chided)/(chidden)/(chid\})`\{\1, \2\; \3, \4, \5\}`g s`\{(got)\; (got)/(gotten \[Am\.\])\}`\{\1\; \2, \3\}`g s`\{(outshined)/(outshone)\; (outshined)/(outshone)\}`\{\1, \2\; \3, \4\}`g s`\{(leaped)/(leapt)\; (leaped)/(leapt)\}`{\1, \2\; \3, \4}`g s`\{(smelled) / (smelt \[Br\.\])\; (smelled) / (smelt \[Br\.\])\}`{\1, \2\; \3, \4}`g s`\{(learned) / (learnt \[Br\.\])\; (learned) / (learnt \[Br\.\])\}`{\1, \2\; \3, \4}`g s`\{(staved)/(stove)\; (staved)/(stove)\}`{\1, \2\; \3, \4}`g s`\{(swung) / (swang \[obs\.\]); (swung)\}`{\1, \2\; \3}`g s`\{(struck)\; (struck) / (stricken \[Am\.\])\}`{\1\; \2, \3}`g s`\{(girded)/(girt); (girded)/(girt)\}`{\1, \2\; \3, \4}`g ## Alternative form in parantheses instead of separated by comma s`\{(swung) \((swang \[obs\.\])\)\; (swung)\}`\{\1, \2\; \3\}`g # Do not allow multiple conjugation annotations. # In this particular case it is also unclear (from the syntax only), which # forms the [archaic]-annotation applies to. s`\{(work)\; (worked)\} \{(wrought)\; (wrought) (\[archaic\])\}`\{\1, \3 \5; \2, \4 \5\}` ## Double }}. s`(chid)\}\}`\1\}` # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/misc.sed000077500000000000000000000175001423006221500240030ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/misc.sed - fix some irregularities in the Ding source # # Copyright 2020-2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # ## Abbreviations missing terminal <.> # Exclude "so" ~ "so.", since it may very well not be an abbreviation. s`\<(etw|jm?d[mns]?|sth|sb)\>($|[^.])`\1.\2`g ## Misplaced {}-annotations s`(\(Kfz:) \{n\} (/AND/\))`\1 \2` ## <...> s`< (\[mus\.\]) >`\1`g # The only "<>"-annotation syntactically clearly on a unit level. # Since semantically wrong, fix as follows. # See https://en.wiktionary.org/wiki/delt // 2020-09-06 20:39:00 CEST s`\<(to deal) \{(dealt)\; (dealt)\} <(delt)>`\1 {\2; \3, \4 [archaic]}`g # There is one single occurence of an `s' after "<>". I see no reason for it. # Also, the first english form should be in plural, too. s`\<(Atomforscher \{pl\}\; Atomforscherinnen \{pl\}) :: (.*) \| (atomic scientist) <(nuclear scientist)>s$`\1 :: \2 \| \3s <\4s>` # Superfluous space. s`\<(Türeinfassung \{f\} \[constr\.\]) <(Futterrahmen) >`\1 <\2>` ## Syntax-breaking typos (excl. slashes) # Missing "<" s` (Bucuresti)>` <\1>`g # "->" -> "~" s`\((superlative of) -> (level)\)`(\1 ~\2)`g ## [.] -> / # Note: Information on predominance is lost. # TODO: reconsider s`\<(table) \[(tabular)\] (spar)`\1/\2 \3`g s`\<(progressive) \[(prograde)\]`\1/\2`g s`\<(spheroidal) (jointing) \[(parting)\]`\1 \2/\3`g s`\<(axial) \[(optic)\] (angle)`\1/\2 \3`g s`\<(tight) \[(close)\] (sand)`\1/\2 \3`g # TODO: This is now really ugly. s`\<(post) \[(nach|after)\]`\1/\2`g s`\<(scrivere) \[(schreiben|writing)\]`\1/\2`g ## [.] -> (.) # Optional words. Information is lost. # TODO: Consider to encode differently, e.g. [[]]. s`\<(eine Schar) \[(Personen)\]`\1 (\2)`g s`\<(a clutch of) \[(persons)\]`\1 (\2)`g # Unsure; possibly better translated to a slashed alternative. s`\<(Brazilian) \[(optical)\] (pebble)\>`\1 (\2) \3`g s`\<(to guzzle sth\.) \[(drink)\]`\1 (\2)`g s`\<(to stitch) \[(book)\]`\1 (\2)`g # Description. Should become a . s`\<(Sauerkohl \{m\}) \[(in einigen Regionen alternativer Begriff zu Sauerkraut)\]`\1 (\2)`g s`\<(listicle) \[(list \+ article)\]`\1 (\2)`g s`\<(eine Portion) \[(Mengenangabe)\]`\1 (\2)`g ## Empty () s`\<(pathologische Abteilung \[med\.\]) \(\)`\1` ## Superfluous <;> # - Note: Fixed in version 1.9; keep nonetheless. s`\; *$`` ## Smileys # Note: There is exactly one smiley. Make it fit the expected syntax. # (TODO/CONSIDER: alter expected syntax.) s`/(:-\))/`/ \1 /`g # This is not a smiley. Possibly an alteration of syntax though. s`\(@\)`/ @ /`g ## Mixed # Superfluous semicolon; enrichment. s`\<(note)\;\; (/N\.B\.\; NB/)`\1; nota bene [archaic] \2` ## Missing <;> s`(Beide Schriftstücke sind online verfügbar\.) (Diese Schriftstücke sind beide online verfügbar\.)`\1; \2` ## Misc syntax errors s`:: <> \|`:: |`g ## Further seemingly incorrect entries (TODO): # - Note: most from version 1.8.1. #ein gutes Blatt haben {n} -- {} applies to part #Freier, der den Autostrich abfährt {m} -- {} applies to whole / first part #solange sie die Stelle innehat {n} -- {} applies to ? #Bouquet garni {n} cook -- [cook.] ? #Bemühen, das Gleiche zu erreichen {m} -- {} applies to ? #einen Unterhaltspfleger bestellen {m} -- {} applies to part # - alternative: disallow {} when refering to part of sentence (remove) #eine Sache, die Probleme bereitet / Rätsel aufgibt {n} -- ? # - article ("eine") uncommon # - {n} refers to "Rätsel" ? #den Strom abschalten/ausschalten {pl} -- ? #äbtlich {m} -- ? #auktorial {n} #eine Auskunft / Auskünfte einholen {n} #bizarr {m} #deckungsgleich sein {f} #diese {pl} # - first pl annotation on a non noun #englandfreundlich {f} #derjenige, der die Sache herausgibt {m} -- whole #entomogamen {pl} #etw. jodoformieren {n} #leistungsgerecht {m} #gut/schlecht schlafen {f} # - use the somehow ? # - {f} misplaced #neapolitanisch {n} #spiegelbildgleich {m} # ... (TODO: search from here) #obszön; verrucht, schlüpfrig; aufreizend {adj} :: raunchy # - comma -> semicolon ? #to write <> off sb./sth. #auf das Angebot/die Zusage/den Vorschlag von jdm. eingehen #etw./jdn. einführen; einweisen {vt} (in etw.) # - does the prefix apply to both? #guaranteed free from / of sth # - should be a strong '/' #jdn deputieren, etw. zu tun # - parentheses ? # - generalize in parser ? #(+ Gen/von etw.) #(in Fragen + Gen) #as at + date #lest + subjunctive #(+ {adj}) #(+ comparative adjective) #as a matter of fact + do; actually + do #(+ singluar) #{+ conj} #(+ Substantiv im Plural) # ... (/+ /) #<"> # - literally, not a single quote #eines Baukörpers/einer Maschine #to take out/take down #andere/r/s # - et al. #frühere(r/s) # - et al. #to chide {chided, chid; chided, chidden, chid} sb. # - infix grammar annotations (for a simple verb; i.e. not in a phrase) #"Are you a good singer/player?', 'I do moderately well.' # - <"> or <'> # ? general: allow both? # . <"> seems more frequent. #(von Pilcher / Werktitel) ... :: ... (by Pilcher / work title) # - frequent (with differing authors) #Delegation {+Gen./bei} #:) #/A/cs/ #/Hg(CNO)2/ (Sprengstoff); # - parsing question #/sw, s/w/ .* /B&W, b&w, B/W/ #Ver­brau­cher­zen­t­ra­le {f} [tm] :: consumer advice centre [Br.] # - a bunch of \u00AD contained (soft hyphens) # - Could be used to infer hyphenation. # - [] #eine Abschrift beglaubigen {vt} [adm.] :: to certify; to attest; to exemplify [Am.] a copy # ? prefixes in ()? # ? During enrichment: Decuduce from {vt} or "to", that something is a collocate #(formal) #to get (oneself; sth.) to safety #scanning | scanne$ #/s. and s.c./ #leise; ruhig; still {adj} | leiser; ruhiger; stiller | am leisesten; am ruhigsten; am stillsten #name-dropping > > #das Grundgesetz -- article #Stahlpreisanstieg {m}; erhöhunh {f} :: increase in steel price; in the price of steel # Hard to identify collocations (the part after the parens) # pressure (on sb.) to adapt/adjust # to mumble (away) to oneself # Infer participle information # kollokieren; nebeneinanderstehen {vi} [ling.] | kollokierend; nebeneinanderstehend | kollokiert; nebeneinandergestanden :: to collocate | collocating | collocated # <-> - annotations (<-> likely always surrounded by whitespace) # PS (lat. Postskriptum - post [nach] + scrivere [schreiben]) :: ps (Lat. postscript - post [after] + scrivere [writing]) # Racist. TODO: Consider to annotate somehow. #'Zehn kleine Negerlein'; 'Und dann gab's keines mehr' (von Christie / Werktitel) [lit.] :: 'Ten Little Niggers'; 'And Then There Were None' (by Christie / work title) # Found when searching for units that contain a /./-expression: # Gesellschaft (des) bürgerlichen Rechts /GbR/; /GdbR/, BGB-Gesellschaft # emergency medical treatment room; emergency room [Am.] /e.r./; /ER/ # - questionable # Saint ...; /St/ [Br.]; /St./ [Am.] # - questionable # cash with order /CWO/; /c.w.o./; cash in advance /CIA/ # ante meridiem /a.m./; /am/ # later; later on; /L8R/ [comp.] # TODO: - '([A-Za-z]+ ){2}\{(m|f|n)\} *(\;|\|)' # - find badly located {}-annotations # - slashes (e.g. "er/sie hat/hatte bekommen") # - /+ *(Gen|Dat|Akk)/ # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/order.conf.bash000066400000000000000000000003101423006221500252350ustar00rootroot00000000000000typeset -ra sedfiles_ordered=( typos.sed grammar.sed inflected_forms.sed usage.sed usage_debatable.sed slashes.sed abbreviations.sed misc.sed quotes.sed additions.sed alter.sed drop.sed ) tools-0.6.0/importers/ding2tei/src/preprocess/de-en/quotes.sed000077500000000000000000000033741423006221500243740ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/quotes.sed - regularize quotes # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # ## Quotes # Notes: # * There is no clear distinction between single <'> and double <"> quotes. # Sometimes they are even used together (e.g. <"abc'>). # * TODO: It might be necessary to identify them. # * When revising this file for version 1.9, I got the impression that <'> # is the standard on the English side, while <„> and <“> are standard on # the German side. # * Sometimes (almost always?) '„“' is used on the german side # (e.g., „So für den Hausgebrauch“). # - Apparently new convention in version 1.9. # * Try to infer the "correct" quote variant from the surrounding. # * <"> may also be used as abbreviation for the unit inch. # * <'> may also be used in other contexts (e.g., , ). # * Usage as apostroph is quite frequent. s`"(Are you a good singer/player\?)'`'\1'` s`'(Pay per click)"`'\1'`g s`"(The Loyal Subject)'`'\1'`g s`"(The Hunchback of Notre-Dame), (1831)`"\1", \2`g # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/slashes.sed000077500000000000000000000217651423006221500245220ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/slashes.sed - fix wrong spacing around slahes # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Notes: # * See doc/syntax.slashes. # * TODO: Consider to generalize a few and in particular also catch wrongly # placed weak/strong slashes. # * TODO: Order this somehow. s`\((bei etw\.)/ (gegenüber etw\.)\)`(\1 / \2)`g s`\((in sth\.)/ (from sth\.)\)`(\1 / \2)`g s`\((von etw\.)/ (zu etw\.)\)`(\1 / \2)`g s`\((of sth\.)/(for sth\.)\)`(\1 / \2)`g s`\((eines) (Arguments)/ (Vorgehens) (usw\.)\)`(\1 \2/\3 \4)`g s`\((of a) (place)/ (situation)\)`(\1 \2/\3)`g s`(Kein) / (Keine) (\[Ös\.\] Ausschank)`\1/\2 \3`g s`\<(visual tree assessment) /(VTA) /`\1 /\2/` s`\<(this order/custom\.)/ (You have)\>`\1 / \2` s`/MFM$`&/` s`\<(to understand) /(recognise/grasp the)`\1/\2` s`\<(Kreuzschritt \{m\} vor)/ (zurück)\>`\1/\2` s`\<(on) (sb\.)/ (sth\.)`\1 \2/\3`g s`\<(centre \[Br\.\])/ (center \[Am\.\])`\1/\2`g s`\<(center \[Am\.\]) /(centre \[Br\.\])`\1/\2`g s`\<(easing) /(reduction) (in)\>`\1/\2 \3`g s`\<(Geräte \{pl\}) /(Ausrüstung \{f\})`\1/\2`g # TODO: generalize? s`\<(to) (approach)/ (consider)\>`\1 \2/\3`g s`\<(governor) (/Gov\./s)\>`\1s \2`g s`\<(mit jdm\.)/ (mit etw\.)`\1 / \2`g s`\<(with sb\.)/ (in sth\.)`\1 / \2`g s`(\(Plectranthus coleoides/forsteri)/ (glabratus\))`\1/\2` s`\<(timber \[Br\.\])/ (lumber \[Am\.\])`\1/\2`g s`\<(zurückbekommen)/ (zurückerhalten)\>`\1/\2`g s`\<(of sth\.)/ (in )`\1 / \2`g s`\<(fibre \[Br\.\])/ (fiber \[Am\.\])`\1/\2`g s`\<(skivvy \[Br\.\])/ (slave)`\1/\2`g s`\<(armoured \[Br\.\])/ (armored \[Am\.\])`\1/\2`g s`\<(to take)/ (to carry out) (a measure)\>`\1 / \2 \3`g s`\<(action for possession \[Br\.\])/ (action of eviction \[Am\.\])`\1 / \2`g s`\<(text-book example of how)/ (an object lesson in how)\>`\1 / \2`g s`\<(photovoltaics) /(PV)\; (solar)\>`\1 /\2/\; \3` s`\<(jdn\.)/ (etw\.)`\1/\2`g s`\<(Mehrzweckeinsatzstock \{m\}) /(MES) /`\1 /\2/`g s`\((of) /(showing sth\.)\)`(\1 / \2)`g s`\<(and don't get into mischief) /(and keep out of mischief)\>`\1 / \2`g s`\<(limitation) /(lapse) (of time)\>`\1/\2 \3`g s`\<(Abschluss \{m\}) / (Abschließen \{n\})`\1/\2`g s`\<(wind power unit) / (WPU)/`\1 /\2/`g s`\<(in) (Bestzustand) /(1a-Zustand)\>`\1 \2/\3`g s`\<(to hold the floor)/ (to address the meeting)\>`\1 / \2`g s`\<(balsam)/ (myrobalan)\>`\1/\2`g s`\<(Zeitungen) /(Zeitschriften)\>`\1/\2`g s`\<(are at a critical juncture)/ (have reached a critical juncture)\>`\1 / \2` s`\((of sb\.)/ (of), (from) (sth\.)\)`(\1 / \2/\3 \4)`g s`\<(set up) /(discontinue)\>`\1 / \2`g s`\<(als etw\.)/(zugunsten von jdm\.)`\1 / \2`g s`\<(as sb\.)/ (in favour of sb\.)`\1 / \2`g s`\<(gun) /(missile)\>`\1/\2`g s`\<(into sth\.)/ (into doing sth\.)`\1 / \2`g s`\<(to proffer) (sb\. sth\.) /(sth\. to sb\.)`\1 \2 / \3`g s`\<(of) (sb\.)/ (sth\.)`\1 \2/\3`g s`\<(für jdn\.)/ (als) jd\.`\1 / \2 jdn.`g s`\<(interpreted)/ (understood)\>`\1/\2`g s`\<(in favour) (of)/ (against) (sth\.)`\1 \2/\3 \4`g s`\<(on/upon/against) (sb\.)/ (sth\.)`\1 \2/\3`g s`\<(etwas zu bedeuten haben)/ (etwas bedeuten)\>`\1 / \2`g s`\<(sth\.)/ (doing sth\.)`\1 / \2`g s`\<(einen Ort entlang) /(durch einen Ort)\>`\1 / \2`g s`\<(about sth\.)/ (for doing sth\.)`\1 / \2`g s`\<(shop \[Br\.\]) /(store \[Am\.\])`\1/\2`g s`\<(von etw\.)/ (gegen etw\.)`\1 / \2`g s`\<(in die) (Normalform)/(kanonische Form)/? (gebracht|bringend?)\>`\1 \2 / \3 \4`g s`\<(zerwuzelt \[Ös\.\])/ (einen Schranz in den Bauch gelacht)\>`\1 / \2`g s`\<(look after)/ (take care of)\>`\1 / \2`g s`\<(zu) (bedienen)/ (handhaben) (sein)\>`\1 \2/\3 \4`g s`\<(home economics)/ (family and consumer sciences)\>`\1 / \2`g s`\<(to sth\.)/ (to do sth\.)`\1 / \2`g s`\<(renovieren)/ (erneuern)`\1/\2`g s`\<(take on) /(lay off)\>`\1 / \2`g s`(sb\.)/ (in)`\1 / \2`g # Note that there is a meaning in the different spacing, which is dropped here. s`\<(tangled up)/(entangled)/ (snarled up)/(ensnared)`\1 / \2 / \3 / \4`g s`\<(zum Erfolg)/ (zu seinem Glück)\>`\1 / \2`g s`\<(to enable) (sb\.)/ (sth\.)`\1 \2/\3`g s`\<(to transmogrify) (sb\.)/(sth\.)/ (into sth\.)`\1 \2/\3 / \4`g s`\<(wenn/wiewohl es schon lange her ist\.) /(wenn es auch schon lange her ist\.)`\1 / \2`g s`\<(threw) /(set) (back)\>`\1/\2 \3`g s`\<(attached)/ (screwed on)\.`\1 / \2.`g s`\((Kfz:) /([A-Z]+)\)`(\1 /\2/)`g s`\<(Rutherfordium)/ (Kurtschatovium)\>`\1/\2`g # Note: Cake is distinct from biscuit, thus the -repetition is deemed ok. # - The -repetition, however, is deemed too much. s`\<(the biscuit \[Br\.\])/ ?(the cake \[Am\.\])`\1 / \2`g s`\<(really) (takes) (the biscuit \[Br\.\]) / \2 (the cake \[Am\.\])`\1 \2 \3 / \4`g s`\((by Pilcher)/ (work title)\)`(\1 / \2)`g s`\<(Mikro…)(/µ/)`\1 \2` s`/I/O/`/ I/O /`g s`/a/s/l\?/`/ a/s/l? /`g s`\<(diameter for fixing the disc \[Br\.\])/ (disk \[Am\.\] to the brackets)\>`\1/\2` s`\<(to make a run on the shops \[Br\.\]) /(stores \[Am\.\] \[fig\.\])`\1/\2` s`\<(receivables) / (Rec\.)/`\1 /\2/` s`\<(mit jdm\./etw\.)/ (zwischen jdm\./etw\.)`\1 / \2`g s`\<(necessary) (adaptations) /(adjustments)\>`\1 \2/\3`g s`\<(an etw\.)/ (bei etw\.)`\1 / \2`g s`\<(by sth\.)/ (by saying/doing sth\.)`\1 / \2`g s`\<(bei einer) (Versteigerung)/ (Ausschreibung)\>`\1 \2/\3`g s`\<(in an auction) /(in a call for tender)`\1 / \2`g s`\<((in|of) the) (estate \[Br\.\])/ (inheritance \[Am\.\])`\1 \3/\4`g s`\<(about sb\./sth\.) /(for sb\./sth\.)`\1 / \2`g s`\<(to) (issue)/(establish)/ (open) (a credit)\>`\1 \2/\3/\4 \5`g s`(knit) / (knitted) / (knit-wool) (pullover) / (jumper \[Br\.\])/ (jersey \[Br\.\])/(sweater \[Am\.\])`\1/\2/\3 \4/\5/\6/\7` s`\<(zu etw\.)/ (in Bezug auf eine Sache)\>`\1 / \2`g s`\<(one) (could) / (may) /(might) (be)\>`\1 \2/\3/\4 \5`g s`\<(mit jdm\.) /(bei etw\.)`\1 / \2`g s`\<(zwischen jdm\.) /(unter jdm\.)`\1 / \2`g s`^(jdn\.) /(etw\.)`\1/\2` s`\<(This can) (achieve) /(accomplish) (a lot)\>`\1 \2/\3 \4`g s`\<(I/he/she/it) (smelled) / (smelt \[Br\.\])`\1 \2/\3`g s`\<(he/she has/had) (smelled) / (smelt \[Br\.\])`\1 \2/\3`g s`\<(descend) (on) /(upon) (sth\.)`\1 \2/\3 \4`g s`\<(jdm\.) (einen Fußtritt) /(Tritte) (versetzen)\>`\1 \2 / \3 \4`g s`\<(erbost) /(erzürnt) / (verärgert) / (wütend)\>`\1/\2/\3/\4`g s`\<(keinen Bissen) (hinunterbringen) / (hinunterbekommen \[geh\.\])/ (runterbringen \[ugs\.\]) / (runterkriegen \[Dt\.\] \[ugs\.\])`\1 \2/\3/\4/\5`g s`\<(Die Nachmittagssonne brannte) (heiß) / (unbarmherzig)\>`\1 \2/\3`g s`\<(The wind was blowing) (fiercely) / (harshly)\>`\1 \2/\3`g s`\<(The afternoon sun was burning) (fiercely) /(harshly)\>`\1 \2/\3`g s`\<(stehe ich völlig im Wald \[Dt\.\])/ (stehe ich völlig an \[Ös\.\])`\1 / \2`g s`\<(defer \(an event\)) \((to) /(until) / (till)\)`\1 (\2/\3/\4)`g s`\<(denigrate) (sb\.)/ (sth\.)`\1 \2/\3`g s`\<(den Platz) /(die Lage) (wechseln)\>`\1 / \2 \3`g s`\<(Januar) /(Jänner)\>`\1/\2`g s`\<(to look forward to sth\.)/ (to expect sth\.)`\1 / \2`g ## Abbreviations # - See also `abbreviations.sed'. s`\<(serious adverse reaction) / (SAR)\>/`\1 /\2/` # TODO: Unrelated to slashes: [rare] (and [Dt.]?) seems to not apply to the # whole unit. # Note: This was possibly intended to be "\1 /\2/ / /\3/". # The parser would currently create a very unexpected result from that, # though. s`\<(Doktor der Naturwissenschaften) /(Dr\. rer\. nat\.)/ / (Dr phil\. nat\.)/ (\[Dt\.\] \[rare\])`\1 /\2/ /\3/ \4`g s`\<(Inter-American Development Bank) /(IADB)/ (IDB)/`\1 /\2/ /\3/` s`\<(drei feste Maschen zusammenhäkeln / zusammen abmaschen / zusammenarbeiten) /(3 Fm zsm\.)/ / (3 Fm abm\.)/`\1 /\2/ /\3/` s`\<(extended single crochet) /(esc) /`\1 /\2/`g ## Missing space after abbreviation, before (.). s`\<(Folio \{n\} /fo/ /2°/)\((Buchformat)\)`\1 (\2)` s`\<(folio /fo/ /2°/)\((book size)\)`\1 (\2)` # See also `abbreviations.sed'. s`\<(secondary hyperparathyroidism) /(SHPT)/\((2-HPT)\)`\1 /\2/ (\3)` s`\<(logical inferences per second) /(LIPS)/\((expert system)\)`\1 /\2/ (\3)` ## Special abbreviations s`\<(schließende runde Klammer) /(\))/`\1 / \2 /` s`\<(right parenthesis \[Am\.\]) /(\))/`\1 / \2 /` s`\<(öffnende eckige Klammer) /(\[)/`\1 / \2 /` s`\<(schließende eckige Klammer) /(\])/`\1 / \2 /` s`\<(öffnende geschweifte Klammer) /(\{)/`\1 / \2 /` s`\<(schließende geschweifte Klammer) /(\})/`\1 / \2 /` s`\<(öffnende spitze Klammer) /(<)/`\1 / \2 /` s`\<(schließende spitze Klammer) /(>)/`\1 / \2 /` # Note: The following is broken in other ways (TODO). s`\((\+Gen\.)/ (über etw\.)\)`(\1 / \2)`g # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/typos.sed000077500000000000000000000041261423006221500242260ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/typos.sed - fix some typos in the en-de Ding source # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # ### Characters # Quote characters from the Windows-1252 charset (convert to the Unicode # version). See . # The unicode values are 0x91 and 0x92 while the UTF-8 encodings are 0xC291 and # 0xC292, respectively. s`\xC2\x91`‘`g s`\xC2\x92`’`g ### Simple typos ## Initial character to upper case # Search regexes: # * '\b[a-zäöüß]+ \{(f|m|n|pl|no pl)\>.*::' (many false positives) # Note that '\<' and '\>' do not generally suffice due to e.g. "...händler". s` (instandhaltungseinschränkungen \{pl\})` \u\1`g s` (tönung \{f\})` \u\1`g s` (kleine) (auseinandersetzung \{f\})` \1 \u\2`g s` (standringe \{pl\}) ` \u\1`g s` (gleichrichten \{n\})` \u\1`g s` (checksummen \{pl\})` \u\1`g ## Diacritics # In v1.8.1, the ń was written as HTML code (ń), in version 1.9 it was # changed to plain n. The english Wikipedia uses the diacritic [0]. s`Poznan`Poznań` ## Misc s`\`Erhöhung`g s`/(Jun\.\; jun\.)\; (Jnr)\; (Jr),/`/\1\; \2.\; \3./`g # Dative, not accusative. s`\<(zwischen) jdn\./etw\. und jdn\./etw\.`\1 jdm./etw. und jdm./etw.`g s`\| <> sth\. (verspielend)\;`| \1\;` ## References # [0] https://en.wikipedia.org/w/index.php?title=Pozna%C5%84&oldid=1066570232 # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/update_help.bash000077500000000000000000000233601423006221500255050ustar00rootroot00000000000000#!/usr/bin/env bash # # preprocessed/de-en/update_help.bash - help updating to new Ding version # # Copyright 2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # When updating ding2tei for a new Ding version, the sed scripts should also # be revised. # * Some replacements may become obsolete. # * Some replacements may need changing. # * Note: It may be worthwile to keep some generic replacements even if they # do not apply to anything anymore. # * The course for adapting to a new Ding version is to simply run the main # ding2tei program on the preprocessed Ding dictionary and act upon any # errors. # * Also, ideally, non-critical syntax errors/inconsistencies (those that # do not break the parser), should be adressed (see util/). # # This script can also be helpful whenever amending the sed files to check # that they actually applied. # # Precondition: # * The sed scripts contain only single line commands. # # How to use this script: # * See help() / run `$0 help`. # 1. Run `init'. # 2. `apply' all old scripts to the old source. # * This creates, for each command line of each script a new file, which # are ordered in sequence. # 3. Copy all scripts to a folder `new/'. # 4. for each sedfile (in order specified by $order_conf_file_relative) # 1. `apply' this script to the new source (use script index). # * Again, this yields for each sed command in the current script a # new file. # 2. Open the current sed script in `new/` in an editor (still identical # to the old sed script). # 3. Run your favourite `diff' program (I suggest `vimdiff') for each # subsequent pair of newly created files, both for the old and new # Ding source file. # * A small bash for loop is handy (TODO: include in this script). # - "a=(SEDFILE_INDEX_PADDED.*) # for (( i=0; i < ${#a[@]} - 1; i++ )) # do # vimdiff "${a[i]}" "${a[i+1]}" # done # " # 1. Compare the `diff's manually, with the current sed command in # mind. # 2. Adapt the current sed script in `new/`. # * Note: The scripts are identical as of now, so there is a direct # correspondence between the diffed old and new files. # 4. `apply' this script again (if anything changed). # 5. Verify that all changes applied correctly. # * Ideally manually. A cmp(1) may be deemed sufficient, though. # * A `diff' of the old and new script files is helpful. # 6. (optional) Check the diffs again (if anything changed). # * Note: The scripts are distinct now, so there is not necessarily # a direct correspondence between the old and new files anymore. # * This can be repeated. One can also change the order (in particular, # change things in an "earlier" sed file later. # * One should be aware, however, that the changes may build upon # another; therefore, one would ideally check whether all the later # changes still apply as expected. # 5. (optional) `apply` the whole new script list again (in one run), check # everything. # 6. Replace the old scripts by the new scripts. # # See also: # * src/preprocess/de-en/README # * src/preprocess/de-en/all.bash typeset -r script_path="$(realpath "$0")" typeset -r script_dir="${script_path%/*}" typeset -r base_dir="$script_dir" order_conf_file_relative='order.conf.bash' typeset -r auxdir_relative='aux' typeset -r auxdir_absolute="${base_dir}/${auxdir_relative}" typeset -rA sedscriptdir_relative=( [old]='.' [new]='new' ) typeset -rA sedscriptdir_absolute=( [old]="${base_dir}/${sedscriptdir_relative[old]}" [new]="${base_dir}/${sedscriptdir_relative[new]}" ) # Get ${sedfiles_ordered[@]}. source "${base_dir}/${order_conf_file_relative}" function help() { cat << EOF Syntax: \$0 init OLD_DING_SRCFILE NEW_DING_SRCFILE \$0 apply old|new I_START I_END \`init' must be run before the first \`apply'. When running \`apply', I_START must not be larger than the last I_END. The first \`apply' must be run with 0 as I_START. \`apply' drops any possibly existing output (of previous runs of \`apply') with I larger than I_START before starting. The old sed scriptfiles must be located in \`${sedscriptdir_relative[old]}/', the new sed scriptfiles in \`${sedscriptdir_relative[new]}/'. The new sedfiles should initially just be a copy of the old ones and can then be adapted. Only sedfiles specified in \`${order_conf_file_relative}' are used, and all these are expected to exist. Results of this script will be found in \`${auxdir_relative}/'. All relative paths above relative to: \`${base_dir}' EOF } # Implementation notes: # * sed file indices and sed file line numbers, when part of a string (e.g., # in filenames) are always padded to be 3 and 5 characters long. # - This is expected to be sufficient. # - The 3 and 5 are hard-coded in many places. # - The intention behind padding is to have the numerical and alphabetical # sort order of filenames coincide. # - An error is issued if the number of files or lines is too big. set -o noclobber set -o errexit set -o nounset shopt -s extglob function main() { [[ $# -ge 1 ]] || { help; exit 1; } local -r cmd="$1" shift 1 case "$cmd" in init) init "$@" ;; apply) apply "$@" ;; help) help ;; *) help exit 1 esac } function init() { [[ $# -eq 2 ]] || { help; exit 1; } local -rA ding_srcfile=( [old]="$1" [new]="$2" ) for oldnew in old new do mkdir -p -- "${auxdir_absolute}/sed/${oldnew}" mkdir -p -- "${auxdir_absolute}/data/${oldnew}" cp "${ding_srcfile[$oldnew]}" "${auxdir_absolute}/${oldnew}.ding.txt" ln -sf -- "../../${oldnew}.ding.txt" \ "${auxdir_absolute}/data/${oldnew}/000.00000.txt" done } function apply() { [[ $# -eq 3 ]] || { help; exit 1; } local -r oldnew="$1" local -r i_start="$2" local -r i_end="$3" verify_string_old_or_new "$oldnew" verify_is_sedfile_index "$i_start" verify_is_sedfile_index "$i_end" verify_less_or_equal "$i_start" "$i_end" # Delete all data that is to be rewritten or "later" s.t. the state remains # consistent. clean "$oldnew" "$i_start" inifinity # i_* sed file index local -i i_cur i_next local si_cur si_next for (( i_cur = i_start; i_cur <= i_end; i_cur++ )) do local sedfile="${sedfiles_ordered[i_cur]}" printf -v si_cur '%03i' "$i_cur" printf -v si_next '%03i' "$(( i_cur + 1 ))" sed -E -- '/^\s*$/ d; /^#/ d' \ "${sedscriptdir_absolute[$oldnew]}/${sedfile}" \ > "${auxdir_absolute}/sed/${oldnew}/${si_cur}.${sedfile}" # j_*: sed file line index local lines mapfile -t lines < "${auxdir_absolute}/sed/${oldnew}/${si_cur}.${sedfile}" local -i j_cur=0 j_next local sj_cur sj_next for line in "${lines[@]}" do j_next=j_cur+1 printf -v sj_cur '%05i' "$j_cur" printf -v sj_next '%05i' "$j_next" sed -E -- "$line" \ < "${auxdir_absolute}/data/${oldnew}/${si_cur}.${sj_cur}.txt" \ > "${auxdir_absolute}/data/${oldnew}/${si_cur}.${sj_next}.txt" j_cur=j_next done ln -s -- "${si_cur}.${sj_next}.txt" \ "${auxdir_absolute}/data/${oldnew}/${si_next}.00000.txt" done } function clean() { [[ $# -eq 3 ]] || { help; exit 1; } local -r oldnew="$1" local -r i_start="$2" local i_end="$3" verify_string_old_or_new "$oldnew" verify_type_int "$i_start" if [[ "$i_end" == inifinity ]] then # No, this is not exactly efficient (TODO). i_end=999 else verify_type_int "$i_end" fi local -r i_end verify_less_or_equal "$i_start" "$i_end" local -i i_cur for (( i_cur = i_start; i_cur <= i_end; i_cur++ )) do local si_cur si_next printf -v si_cur '%03i' "$i_cur" printf -v si_next '%03i' "$((i_cur + 1))" if [[ -v sedfiles_ordered[i_cur] ]] then local sedfile="${sedfiles_ordered[i_cur]}" rm -f "${auxdir_absolute}/sed/${oldnew}/${si_cur}.${sedfile}" fi rm -f "${auxdir_absolute}/data/${oldnew}/${si_cur}".!(00000).txt rm -f "${auxdir_absolute}/data/${oldnew}/${si_next}.00000.txt" done } function verify_type_int() { if [[ "$1" =~ ^[0-9]+$ ]] then local -i i="$1" if [[ "$i" == "$1" ]] then return 0 fi fi printf 'Error: Not an int: %s\n' "$1" >&2 exit 1 } function verify_is_sedfile_index() { verify_type_int "$1" if ! [[ "$1" -ge 0 && "$1" -lt "${#sedfiles_ordered[@]}" ]] then printf 'Error: sedfile index out of range: %s not in [%s,%s)\n' \ "$1" '0' "${#sedfiles_ordered[@]}" >&2 exit 1 fi } function verify_less_or_equal() { if ! [[ "$1" -le "$2" ]] then printf 'Error: %s greater than %s.\n' "$1" "$2" >&2 exit 1 fi } function verify_sedfile_count() { # 999 is already bad because there would be created a final symlink with # 999+1 = 1000. if [[ "$1" -ge 999 ]] then printf 'Error: Number of sed files too large: %s\n' "$1" >&2 exit 1 fi } function verify_line_count() { local -r file="$1" local -r lc="$2" if [[ "$lc" -ge 100000 ]] then printf 'Error: Number of lines too large: %s: %s\n' "$file" "$lc" >&2 exit 1 fi } function verify_string_old_or_new() { if [[ "$1" != @(old|new) ]] then printf 'Error: Invalid argument: %s\n' "$1" >&2 printf "Must be \`old' or \`new'\\n" fi } main "$@" tools-0.6.0/importers/ding2tei/src/preprocess/de-en/usage.sed000077500000000000000000000071611423006221500241560ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/usage.sed - fix errors in usage annotations # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # ### () -> [] # TODO: Consider to catch this in the parser. s`\((rarely used|rare)\)`[rare]`g ### Consistent spaces and dotting. # Note: There's probably more of these (TODO). # - alternatively, let the parser take care of it. # Missing <.>. # - Note: First expression has no matches in version 1.9; keeping # nonetheless. Similarly, the second espression could be shortened. s`\[(Ös|Br|Am|Norddt|Mittelwestdt|Mitteldt|Dt|Bayr)\]`[\1.]`g s`\[(ugs|cook|naut|photo|econ|coll|humor|adm|fig|stud|fin|ornith|meteo|geh|zool|textil|techn|pol|envir|bot|telco|statist|soc|sci|poet|mil|med|ling|hist|aviat|chem|zool|adm|biochem)\]`[\1.]`g # Superfluous < > before <.>. # - Note: Only matched in version 1.9. s`\[(soc|Am) \.\]`[\1.]`g # Superfluous <.> (if necessary, decided by count that the dotless variant is # correct). s`\[(sport|print|auto|slang|dated)\.\]`[\1]`g # <,> -> <.> s`\[(Br),\]`[\1.]`g ### Normalization # Differently abbreviated annotations. # Usually, the predominant version (by count) of two is taken. ## All except languages # Note: In version 1.9, all fixed except [stone], [milit.]; keeping all # regardless. s`\[rel.\]`\[relig.]`g s`\[environ\.\]`[envir.]`g s`\[TM\]`[tm]`g s`\[(technical|tech\.)\]`[techn.]`g s`\[stone\]`[min.]`g s`\[Statistik\]`[statist\.]`g s`\[sl\.\]`[slang]`g s`\[milit\.\]`[mil.]`g s`\[hum\.\]`[humor.]`g s`\[gramm\.\]`[ling.]`g s`\[finan\.\]`[fin.]`g s`\[bio\.\]`[biol.]`g s`\[const\.\]`[constr.]`g s`\[Kunst\]`[art]`g # [astr.] Could also mean [astrol.] but does not for all occurences. # - Note: Fixed in version 1.9; keeping regardless. s`\[(astr\.|aston\.|atron\.)\]`[astron.]`g # Note that the 'or' is converted in usage_debatable.sed. s`\[(formal) or humorous\]`[\1 or humor.]`g ## Languages # Note: In version 1.9, all fixed except [Sächs.], [Irl.]; keeping all # regardless. s`\[Scot.\]`[Sc.]`g s`\[(NZ)\.\]`[\1]`g s`\[New Zealand\]`[NZ]`g s`\[Liecht\.\]`\[Lie.]`g s`\[Sächsisch\]`[Sächs.]`g s`\[(irisch|Irl\.)\]`[Ir\.]`g # Debatable. The replacement value occurs more frequently. # - Note: Fixed in version 1.9; keeping regardless. s`\[ZA\]`[South Africa]`g # Debatable (Is there a semantic difference?). # The replaced values occurs once, the replacing value 11 times. s`\[Northern England\]`[Northern English]`g # The replacing value does not occur. It just should be an englisch # expression / abbreviation, in my opionion. Analogous to [Northern English]. s`\[Nordirl\.\]`[Northern Irish]`g # All of the below versions of [Lat.] appear exactly once. Use [Lat.] as it # seems to match the general convention for dialects / languages the best. # - Note: In version 1.9; only [lat.] remaining. s`\[(lateinisch|lat\.|Latin)\]`[Lat.]`g # Replacing value does not occur. Adapt to common naming scheme. s`\[arabisch\]`[Arab.]`g # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/de-en/usage_debatable.sed000077500000000000000000000033061423006221500261360ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # preprocess/de-en/usage_debatable.sed - fix debatable errors in usage # annotations # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # ### Normalize separator (<,>) # Notes: # * In all but very few cases, the separator is a comma or a slash. # * Slash has a distinct meaning, the others (<;>, ) not. s`\[(relig\.) und (Schw\.)\]`[\1, \2]`g ## -> # Notes: # * Cannot be generalized (as of now (2020-09-03), since [.] may contain free # text. # * Otherwise, it could be delegated to the parser. s`\[(formal) or (humor\.)\]`[\1/\2]`g ## Separation of extra info # [] should conly contain well defined values, as opposed to (). # Unfortunately, a little information gets lost this way. # Too rare to consider in the parser. s`\[(French) for (a female singer, especially in a nightclub)\]`[\1] (\2)` s`\[(übtr\.) für (eine große, schlanke Person)\]`[\1] (\2)`g s`\[(obs\.) für (Küster, Kirchendiener)\]`[\1] (\2)`g # vi: noet tools-0.6.0/importers/ding2tei/src/preprocess/es-de/000077500000000000000000000000001423006221500223525ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/src/preprocess/es-de/syntax.sed000077500000000000000000000045251423006221500244060ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # es-de/syntax.sed - modify the es-de dictionary to fit the Ding syntax # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Notes: # * This script is not a true solution. # * This concerns the header, "/..." suffixes and the "|" -> ";" # transformation. # * Instead, the parser should be adapted to separately allow for the # slightly different syntax of the es-de dictionary. # * The main program has some hard coded values pertaining to the German # English Ding dictionary, therefore even with this modifications, the # resulting TEI will have pretty wrong metadata. # Grammar. s`\{prn\}`\{pron\}`g s` \{Demonstrativpronomen\}`` s`:: ::`::` s`\{s\}`\{sing\}`g s`\{prep\}`\{prp\}`g s`\{([fnm])$`\{\1\}` s`\;$`` s`\{mf\}`\{m,f\}`g s`\{adv\.\}`{adv}`g # Remove a superfluous <(>. s`\<(Besetzung) \((\{f\}) (\(Film\))`\1 \2 \3` s`^(retroceder \{v\} :: zurückziehen)\|$`\1` /^monstruoso \{adj\} :: \{adj\}$/ d # Adapt header \`^# Spanish :: German word list$` d s`^(# Version :: 0\.0i) Mon Mar 5 18:36:47 2012$`\1 2012-05-03` s`^(# Copyright \(c\) :: .*) (2003-2012)$`\1,\n# \2` \`^# License ::` { N; N s/\n# License :: ([^\n]+)/; \1/g } \`^# [0-9]+ entries$` d # Debatable. s` /([[:alpha:]]*)$` (\1)` # Cheating... s`\|`\;`g # Remaining problems: # * <|> is always (and seemingly only) used to distinguish # different genders. Sometimes, the respective other side has only one word # for both genders, i.e. the number of groups on both sides to not match. # . Ex.: "llorón {m}|llorona {f} :: Heulsuse {f} [coloq.]" # . Ex.: "logopeda {m,f} :: Logopäde {m} | Logopädin {f}" # * The header. # vi: noet tools-0.6.0/importers/ding2tei/testsuite/000077500000000000000000000000001423006221500204325ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/testsuite/test-ding.sed000077500000000000000000000022061423006221500230300ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # test-ding.sed - validate the Ding input to a certain degree # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Note: This may give incorrect guesses when stumbling upon "/ | /". # - which is not present in Ding v1.8.1. h # Check for different count of <|> around <::>. s/[^:|]//g s/::/_/ s/://g /_/ { /^(\|*)_\1/ ! { g s/^.*$/Unbalanced <\|> separators:\n&\n/ p } } #g -- add before next test # vi: noet tools-0.6.0/importers/ding2tei/todo/000077500000000000000000000000001423006221500173465ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/todo/README000066400000000000000000000005211423006221500202240ustar00rootroot00000000000000README ====== Todo files, grouping several related tasks within. The idea is for some of them to wander into doc/, once the respective things are done. Some TODO are also to be found in the FreeDict Wiki [0]. Syntax ------ See: doc/META References ---------- [0] https://github.com/freedict/fd-dictionaries/wiki/discussion-TEI tools-0.6.0/importers/ding2tei/todo/combine.wikdict000066400000000000000000000004341423006221500223430ustar00rootroot00000000000000Piotr Bański suggested [1] to combine certain parts of the WikDict data with that of the Ding. Q: Should inflected forms from the Ding be retained? - Likely yes. - Presumably, not all words are in the Wiktionary (with inflected forms) References: [1] FreeDict ML (2020-05-09) tools-0.6.0/importers/ding2tei/todo/ding.devel-version000066400000000000000000000006411423006221500227740ustar00rootroot00000000000000Not only support version 1.8.1, but also "devel" - split preprocess/*.sed files# Note that "devel" is ever-changing, there is no (known) way of accessing old "devel" versions. Some words have disappeared in the devel version of the Ding - ex.: "Aggregationsreagenz" (and surrounding) - The web version of the Ding seems to be based on a third version of the Ding - has "Aggregationsreagens" (final "s", not "z") tools-0.6.0/importers/ding2tei/todo/ding.groups.empty000066400000000000000000000007101423006221500226630ustar00rootroot00000000000000Empty groups ============ Such do exist. Probably not intentionally. Differentiate three cases (of the corresponding other language's group) a) empty -> Drop. b) a phrase -> keep as example without translation c) a headword ? Drop? ? Referencing from other words on the same side of the same line? . No ID available to reference to. . The same may be the case for <~>-references. ? Keep without any translation? tools-0.6.0/importers/ding2tei/todo/doc.haddock000066400000000000000000000004701423006221500214330ustar00rootroot00000000000000Haddock markup TODO =================== See: https://haskell-haddock.readthedocs.io/en/latest/markup.html Haddock syntax is used for comments, albeit untested. * module description * top-level / exported functions (partially done) - arguments * top-level / exported datatypes (partially done) - arguments tools-0.6.0/importers/ding2tei/todo/enrich.from-prefixes000066400000000000000000000036601423006221500233330ustar00rootroot00000000000000Ex.: "von etw. absehen {vi}" Ex.: "etw. unterlassen {vt} [adm.]" Ex.: "etw. zu unterlassen haben [jur.]" - potentially not a headword, but a phrase, see todo/parsing.phrase-identification Ex.: "sein Revier markieren {vt} [zool.]" Ex.: "ich bin" - also: link to "sein" as inflected form. (see todo/enrich.inflected-forms) Ex.: "ich bin nicht" - This one should be considered a phrase, no dropping required. Ex.: "you are; you're" - Second form does not permit dropping anything Ex.: "jdm. nicht gehorchen" - "nicht" is not to be dropped. Ex.: "in Geschwindigkeit oder Leistung" * Drop some prefixes. ? Which? * Only when possible for all elements of group and translation group. * Infer information from prefixes. TODO: Identify important first words (count, shortness) - the same for second, third words - the same for the first n words (n <= ?) Counterex.: "von Adel sein" * "von" alone would likely not be considered a proper prefix to be made into a collocation annotation. Similarly: suffixes . potential counterexample: "on {prp; +Dat.} jdm./etw. (Ursprung)" . potential counterexample: "to hit on sb." (do not drop the "on" ?) . potential counterexample: "to hit {hit; hit} off" ~ "sich gut verstehen" . potential problem: "gegenüber jdm./einer Sache {prp} (in Bezug auf)" . special case: "to stave {staved, stove; staved, stove} off <> sth." . special case: "to hit {hit; hit} off" . Ex.: "losziehen {vi} gegen" . Ex.: "to hit out at sb." Potential counterexamles: . "in einigen Regionen alternativer Begriff zu Sauerkraut" . "to bump off <> sb." * Ignoring "<>", should "off" be part of the headword? . "at dusk" Possible problem: creation of duplicates inside a single group -------------------------------------------------------------- * Note: This may in theory also happen before stripping any prefix/suffix. * Consider: "to eat; eat" (invented example) ? Consider this an error? ? Merge. tools-0.6.0/importers/ding2tei/todo/enrich.grammar.inferral000066400000000000000000000010471423006221500237710ustar00rootroot00000000000000Grammar annotation inferral =========================== See doc/ding.annotation.brace on the current state. TODO: ----- * From the presence of inflected forms, infer `verb'. Ideas: ------ * Infer annotations from auxiliary words like . "jdm.", "jdn.", ... - See also todo/parsing.collocation-literals * Grammatical gender of words in plural . Often only {pl} annotated. ? Infer from possibly accompanying singular form ? See also -------- * doc/ding.annotation.brace * doc/enrich.grammar * todo/enrich.grammar.transferral tools-0.6.0/importers/ding2tei/todo/enrich.grammar.transferral000066400000000000000000000016011423006221500245060ustar00rootroot00000000000000Grammar annotation transferral ============================== See doc/enrich.grammar on the current state. Transferral between groups in a line: ------------------------------------- * {n,f,m,pl,no pl}: no - ex.: "ökologische/biologische Landwirtschaft {f}; ökologisch wirtschaftende Betriebe [agr.]" * {pron} . Ex.: "diese; dieser; dieses {pron} | diese {pl}" * Seems difficult. * ... Consideratations: ----------------- * Move some annotations to the level of an Entry or a Group. ? Only transfer annotations to the other side that match the whole group? ? Only transfer annotations where the other group is composed of only one unit ? - Alternatively: some restrictions - only alphabetic chars ? - only single word (depending on the annotation ?) ? See also -------- * doc/enrich.grammar * todo/parsing.scope-of-annotations * doc/ding.annotation.* tools-0.6.0/importers/ding2tei/todo/enrich.hyph-syllab000066400000000000000000000001061423006221500227710ustar00rootroot00000000000000Hyphenation and syllabification See also: todo/enrich/pronounciation tools-0.6.0/importers/ding2tei/todo/enrich.inflected-forms000066400000000000000000000006451423006221500236260ustar00rootroot00000000000000See also todo/enrich.from-prefixes Ex.: "sein {vi} | seiend | gewesen | ich bin | ..." - "ich bin": identify prefix - participles: difficult (unless maybe for regular verbs) - TODO: investigate further Ex.: "to be {...} | ... | you are; you're" - problem: "you're" - solution: retain original, but additionally extract inflected forms. - i.e.: "be | you are; you're" -> "be" (infl: "are") | "you are; you're" tools-0.6.0/importers/ding2tei/todo/enrich.link-plural-form000066400000000000000000000052561423006221500237430ustar00rootroot00000000000000Linking singular and plural forms ================================= * Differently gendered forms (m, f) of the same word seem to be always in the same group, often the only two. * Corresponding plural forms are usually, when available, in the second group, provided that the singular forms are in the first group. * Often, there are only these two groups. Problem: several (synonymous) units in a group. - Ex.: "Abbieger {m}; Abbiegende {m,f}; Abbiegender [auto] | Abbieger {pl}; Abbiegenden {pl}; Abbiegende" - In this case, the plural forms follow in the same order. - Unsure whether to rely on that. - Further investigation required. - Note that in the above example, there could also be added "Abbiegerin {f}" between the first and second unit of the first group. - Further, "Abbiegerinnen {f,pl}" (likely with only {pl} annotated) could be annotated, but it does not need to be (since the male plural is considered by some to be usable as a genderless one). Techniques to verify/identify semantic link ------------------------------------------- * German * f ~ m - Usually, ${x}in ~ ${x} . Does not apply to many foreign, imported words. * Note: Not all relations need to be caught. - prefix match [of a certain (relative) length] (heuristic) - potential conversion of vocals to umlauts . Ex.: "Graf" ~ "Gräfin", "Arzt" ~ "Ärztin" - edit distance * sing/n/m/f ~ pl - potential conversion of vocals to umlauts . Ex.: "Garten" ~ "Gärten" - suffixes (different ones) . Ex.: "Birne" ~ "Birnen" . Ex.: "Brot" ~ "Brote" - combination of the above two . Ex.: "Wort" ~ "Wörter" (or "Worte") * n ~ f/m ? Does this exist? * English * Note: annotations are mostly to be infered from the german side. * sing ~ pl - suffix "s" * Both sides - If X translates to Y, and Xs to Ys, and Xs is the plural of X, Ys is likely to be a plural of Y. - This is not certain though. There may be a different translation for a plural form, it may actually be a singular form. - In that case, there is a semantic relation between Y and Ys, where Ys means several of Y, but not a grammatical relation. - Hence, this must only serve as a hint, further verification is required, e.g. Y being a prefix of Ys or a certain maximal edit distance between Y and Ys. Specialties ----------- * {sing} and {pl} do not need to refer to nouns. . Ex.: "diese; dieser; dieses {pron} | diese {pl}" . Ex.: "Wir freuen uns über euer/Ihr {pl} zahlreiches Erscheinen." . Ex.: "that; those {pl}" See also -------- * doc/ding.annotation.brace tools-0.6.0/importers/ding2tei/todo/enrich.usage.transferral000066400000000000000000000001531423006221500241650ustar00rootroot00000000000000Usage transferral ================= May some kinds of usages be transferred? (Most cannot; e.g., [Br.].) tools-0.6.0/importers/ding2tei/todo/es-de000066400000000000000000000002641423006221500202700ustar00rootroot00000000000000There is also a spanish-enlish dictionary in the Ding format. (Much less entries) https://savannah.nongnu.org/projects/ding-es-de Review grammar inferral and transferral rules. tools-0.6.0/importers/ding2tei/todo/misc.consider000066400000000000000000000035511423006221500220350ustar00rootroot00000000000000Write a proper TEI library for Haskell. Identification of the grammatical gender of words annotated as plural - Note: The annotation is mostly on the german side, so I focus on that. - If, separated by <|> (or also <;>?), there is a noun (with annotated gender) that is a prefix of the plural word in question, transfer the gender. - also allow for conversion to umlauts (e.g. "Blatt" ~ "Blätter"). - does not catch certain loanwords (e.g. "Forum" ~ "Foren" (or "Fora" - not contained in the Ding)) - Use some regexes to figure out classes of words with such special plural form. (Just match for significant common prefixes allowing umlauts and possibly other small diversions - possibly only where no maximal matching prefix is found) -- use sed (or else HappyParser.y) - see also: https://de.wikipedia.org/wiki/Plural#Spezielle_Pluralformen_von_Fremdw%C3%B6rtern Identification of german nouns by Capitalisation - useful mostly for {pl} annotation, which may also be given for other POS - use regexes ? -- difficult due to sentences and loan-doublewords. Add stuff that I stumble upon - different sedfile - examples - "Fora" as plural of "Forum" Separate sedfiles - categories - typos (in regular words) - incorrect formatting - incorrect annotations - ... - problem: categories overlap at times Reversing routine(s) - AST -> Ding - TEI -> AST ? - rather obligatory when writing a proper TEI library - and probably not much extra work Identify flected forms - remove pronouns, exclamation marks Generalize `mergeWs' (in `AlexScanner') somehow. - Note: The usage of `foldr' does not really make it better. - `Either' is probably to be used to do generalization upon. - alternative: a `foldr' with a lookahead of 1. Consider exportable intermediate language (XML?) that allows several keys per entry. tools-0.6.0/importers/ding2tei/todo/old/000077500000000000000000000000001423006221500201245ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/todo/old/alex.wrapper-choice000066400000000000000000000002111423006221500237010ustar00rootroot00000000000000Lexer (Alex) - choice of wrapper - posn: allows for better error messages - monad, monadUserState - might simplify whitespace handling tools-0.6.0/importers/ding2tei/todo/old/any-ast.grammar-representation000066400000000000000000000013631423006221500261130ustar00rootroot00000000000000The (seemingly) simplest way is to just annotate grammar in both the Ding and TEI AST by means of lists of single grammar annotations. It may however be considered advantageous to at some point store a set of grammar information in a tree, annotated to any single element that may be annotated with grammar information. The potential benefit of this approach lies therin, that no contradictory grammar information can be stored. * Note however, that in both the Ding and TEI, grammar is annotated in lists. * Note also, that the seemingly contradictory annotation {m,f} is possible, if a headword can have both genders. * One might argue though that such annotations should be treated special anyways. (to be found out how to represent in TEI) tools-0.6.0/importers/ding2tei/todo/old/choice.versioning000066400000000000000000000011151423006221500234610ustar00rootroot00000000000000Versioning - current freedict-deu-eng versions are ahead of the Ding version - option: -fd (like Debian) - Is it guaranteed, that a named version does nerver change ? - One site says version 1.8, 2014-08-18, but provides version 1.8.1, 2016-09-06. - https://ftp.tu-chemnitz.de/pub/Local/urz/ding/de-en/ - Consider to use the provided date as (part of) version. - The download providing only the dictionary data does not have the version in its name. - Consider to suggest downloading the full Ding source (incl. a frontend program). tools-0.6.0/importers/ding2tei/todo/old/parsing.literal.tilde000066400000000000000000000002721423006221500242460ustar00rootroot00000000000000<~> signifies a succeeding reference to another entry. Parsing this requires either a) splitting by word at the lexer stage, or b) identifying "~" as lexeme at the lexer stage. tools-0.6.0/importers/ding2tei/todo/old/parsing.tool-choice000066400000000000000000000002241423006221500237140ustar00rootroot00000000000000Parser - choice of library / tool - Options - Happy (parser generator, like yacc, LALR-parsing) - (Mega-)Parsec (Parser combinator, LL-parsing) tools-0.6.0/importers/ding2tei/todo/parsing.annotations.angle-brackets000066400000000000000000000034271423006221500261570ustar00rootroot00000000000000Annotations in angle brackets ============================= According to the outdated Ding spec [0], they indicate wrong, old forms of the same word/expression. In practice: a) alternative (valid) form / synonym * Ex.: "Supplemental Restraint System /SRS/ " * Ex.: "Acethylen {n} [chem.] " * In fact, "Acethylen" seems to be a misspelling of the other. * Ex.: "Ausrichtung {f} (nach) " b) corresponding singular form * Ex.: "Lurche {pl} (Amphibia) (zoologische Klasse) [zool.] " * quite frequent c) wrong form * Ex.: "occurrence " * Ex.: "I love you. /ILU/ " * seemingly rare * difficult to distinguish from a) in particular. Ideas ----- * When the main word is annotated with {pl}, do some pl~sing correspondence testing (see todo/enrich.link-plural-form). * Careful, not to match obsolete an obsolete form (small edit distance!). Issue 1): plural "s" -------------------- only Ex.: "s" - fixed in preprocessor (see there) Special cases ------------- * "Geschütze {pl} [mil.] " * Synonym? Singular? (not grammatically!) Both? * <>-annotation applies to group / first unit. * "Binsen {pl}; Simsen {pl} (Juncus) (botanische Gattung) [bot.] " * "Chrysanthemen {pl}; Margeriten {pl} (Chrysanthemum) (botanische Gattung) [bot.] " * "digitization; digitizing; digitisation; digitising [Br.] " - not actually a special case. Rather, shows that <.> in fact applies to groups (TODO). * "hoar ", "to clepe {clept; clept} [obs.] " * Besides the semicolon: None of the above categories matches. [0] http://dict.tu-chemnitz.de/doc/syntax.html tools-0.6.0/importers/ding2tei/todo/parsing.annotations.braces000066400000000000000000000013741423006221500245330ustar00rootroot00000000000000Differentiation (grammar vs. inflected forms) ? Use the fact that inflected forms appear only on the English side. ? Issue a warning on partial grammar keyword matches. . e.g. "{prp; +Genn}" ? Use the fact that conjugated forms only appear for verbs. . Information usually not trivial to obtain - verb annotation is usually only on the german side. Allow for unknown content, e.g. {Quantifikator}; log that though. - Potentially require a command line flag. Special cases: . "{adj, usually not used before a noun}" . "{prp; + Fall des jeweiligen Bezugsworts}" . "{+Gen./bei}", "{prp; +Dat. / von jdm./etw.}" . "{idiom}" . "{Quantifikator}" ? Equivalent to {num}? . "{prp; +Gen.; +Dat. [ugs.]}" -- <[]> ? Cannot be represented in TEI? tools-0.6.0/importers/ding2tei/todo/parsing.annotations.brackets000066400000000000000000000017311423006221500250670ustar00rootroot00000000000000Bracket (<[]>) annotations ========================== General ------- ? Differentiate German and English side in Parsing - match language-specific keywords (e.g. "[ugs.]") - Complete the categorization of usage annotations, with the help of * util/results/bracketexps.* - Amend the parser to log all unknown usages and log these. Scope ----- . Some annotations apply to units. . ex.: "[Am.]", "[Ös.]" . Others usually (?) to groups, potentially to prefixes of them. . ex.: "[cook.]" . Some refer to part of a unit only, usually a word. . Usually indicates the annotations being an optional alternative form, which currently is to be converted to a slash-alternative in the preprocessing. . Potential Counterex. "reddish [log.] sucker" (No idea what [log.] means here.) Special examples ---------------- * "[only plural]" See also -------- * util/results/bracketexps.* * src/Data/NatLang/Usage.hs * src/Language/Ding/Read/Usage.hs tools-0.6.0/importers/ding2tei/todo/parsing.annotations.inline000066400000000000000000000015041423006221500245450ustar00rootroot00000000000000Handling of annotations on phrases / composed expressions (in particular gender annotations) - a) allow only to apply to whole expression (and ditch any inner annotation) - b) allow only inner annotations ("word {annot} word") - How to represent in the TEI data? (TODO: inform on whether it is possible to label parts of entries -- I highly doubt so.) - c) allow both - problems: - How to distinguish inner annotations at the end from annotations applying to the whole? - Many annotations are incorrectly placed (in particular, annotations applying to a true inner part are placed at the end). - solutions: - Manually check annotations inside expressions and after expressions composed of more than one word. What about [Br.] et al.? - ex.: "to tuck in / tuck up [Br.] <> a child (in bed)" tools-0.6.0/importers/ding2tei/todo/parsing.annotations.parentheses000066400000000000000000000014511423006221500256110ustar00rootroot00000000000000Annotations between <()>. ? In longer expressions / phrases, just keep them as part of the string. - longer expressions are unlikely to be found by exact search. Differentiate things like (of ...) from regular notes. * The former should be considered suffixing collocates. Use subtype on * Seen in eng-pol: (inside cit[@type=trans]) * related: Should be used in translations in place of ? ? At least for collocates? ? Use in the TEI output? ? In some contexts only? - No. Problem: scope of annotation (sometimes whole group, sometimes just the unit) - scope of whole group usually only, when after that whole group ? preferably after any other annotation? ? Consider some of them as instead of ? tools-0.6.0/importers/ding2tei/todo/parsing.collocation-literals000066400000000000000000000040021423006221500250520ustar00rootroot00000000000000Collocation literals ==================== Many units contain certain keywords such as "etw.", "sth.". The should be considered collocations and encoded in TEI as elements (not ). They may occur anywhere in units, however mostly as prefixes and suffixes. When occurring as infixes, the representation in TEI is questionable. Notes: * Only occuring as suffixes. * "to", "in" also are modifiers, but need to be matched separately. Syntax analysis: * utils/results/words.* * utils/results/wordspairs.* * utils/results/keywordprefs.* * utils/results/keywordsufs.* * utils/results/keywordswithprefs.* Classification: --------------- Notes: * While the below is expressed in Alex syntax, the identification of such collocations can not effectively happen in Alex, because * some potential collocations (e.g., "for sth.") may also occur as individual unit (only "sth." would be the collocation), * the collocations's constituents can be combined in many ways, including with slashes, whose scopes can not be generally determined. * This classification is incomplete. * The most frequent prefixes and suffixes that are considered collocations of the regarded type (>= 100 occs) should be included though. @en_kw = (sth|sb|so)\.|oneself @en_lmod = of|for|on|with|at|by|from|off|as|into|over|about|through|under|against|without|around|after|before|than|past @kw_special_in = "in" -- Note: "jmd." occurs both in nominative and accusative case. @de_kw_nom = (etw|jm?d)\. @de_kw_gen = (etw|jm?ds)\. @de_kw_acc = (etw|jm?dn|jmd)\. @de_kw_dat = (etw|jm?dm)\. -- TODO: "ohne" - really? -- Note: "vor +acc" usually (always?) requires another object (in acc.). @de_lmod_nom = als|wie @de_lmod_gen = wegen @de_lmod_acc = an|auf|für|über|vor|um|durch|unter|ohne|gegen @de_lmod_dat = mit|an|auf|von|zu|aus|nach|bei|über|vor|unter|"bis zu"|gleich @de_kw_special_sich = sich (\ + selbst)? @de_kw_special_zwischen = zwischen @de_exp_dat_zwischen = zwischen \ + @kw_de_dat \ + und \ + @kw_de_dat tools-0.6.0/importers/ding2tei/todo/parsing.conflicting-annotations000066400000000000000000000006251423006221500255700ustar00rootroot00000000000000Conflicting annotations ======================= Ex.: "einwandfrei {adj} {adv}" Does TEI allow such? . In a way: superEntry/form with several superEntry/entry and different superEntry/entry/gramGrp ? divide into two entries? - treat like "einwandfrei {adj} | einwandfrei {adv}" ? Which Step? ? Ding -> AST ? AST -> TEI ? See also: https://hal.inria.fr/hal-01757108/document : 2.1 (end) tools-0.6.0/importers/ding2tei/todo/parsing.elimination000066400000000000000000000017751423006221500232550ustar00rootroot00000000000000Elimination of certain parts of the input ========================================= Ex.: "Der Apfel {m} ist grün" -> "Der Apfel ist grün" . not yet decided whether such a case in particular should be handled like this. If it is to be done, it remains the question which spacing to keep. ? Remove the spacing preceding the removed element. . Matches the idea of a removed element becoming a neutral element, since spacing is stored as an attribute of the succeding token. . Works fine with commata. . ex.: "Der Apfel {m}, der ..." . This makes sense for removed postfix annotations. . Prefix annotations may be problematic, iff the annotation is not at the very beginning of a unit, i.e. inline. . ex. "..., (grüne) Äpfel" ? I cannot think of a case where this causes a single space being swapped for an empty string. ? Remove the spacing succeding the removed element. . Conversely to the former. ? Just assume a single space. . Usually breaks with commata. tools-0.6.0/importers/ding2tei/todo/parsing.empty-units000066400000000000000000000000471423006221500232320ustar00rootroot00000000000000They exist. One containing only "<>". tools-0.6.0/importers/ding2tei/todo/parsing.literal.Kfz000066400000000000000000000000641423006221500231200ustar00rootroot00000000000000Ex.: "Niger {m} /NE/ (Kfz: /RN/) [geogr.] :: Niger" tools-0.6.0/importers/ding2tei/todo/parsing.literal.to000066400000000000000000000006071423006221500230130ustar00rootroot00000000000000The `to' prefix should be removed and the annotated unit considered a verb. This is done, however some corner cases are not caught. Also, the implementation is no very elegant. Room for improvement. See src/Language/Ding/AlexScanner.hs Likely, PartialUnit should be amended. There was a debate on the FreeDict ML around the necessity of words like such. - assumed first half of 2020 tools-0.6.0/importers/ding2tei/todo/parsing.misc000066400000000000000000000046661423006221500217020ustar00rootroot00000000000000# > Abbrucharbeiter {m} | Abbrucharbeiter {pl} :: wrecker [Am.] | wreckers - [Am.] annotation applies to both sigular and plural - singular and plural separated by <|> (often <;> I believe). - recognise german plurals using some grammar checker // external source plus {pl} # Consider [alt] -> specifies alternate spelling (e.g., using <ß>) . usual case: units common_unit alt_unit units ? maybe do nontheless some comparison (least edit distance?) # {,;} - english side to leap {leaped, leapt; leaped, leapt} (to a place) | leaping | leaped; leapt | leaps | leaped; lept | to leap to safety Vergnügungsfahrt {f}; Spritztour {f} | eine Spritztour machen :: joyride | to joyride {joyrode; joyridden} -- apparently only on the english side (with this semantic) -- <;> separates simple past, pefect -- <,> separates alternatives for the same time edge case(s): - see preprocess/*.sed - embedded "[...]" and "(...)" - e.g.: "{bet, betted [Br.] (rarely used); bet, betted [Br.](rarely used)}" # (edge) cases: - {prp; wo? +Dat.; wohin? +Akk.} - {prp; wohin?, bis wann? +Akk.} - {prp; wann? +Dat.} - {pron interrog} - {Quantifikator} - {prp; wohin? +Akk.} - {+Gen./bei} - {prp +Dat.} - many like this (usually there is a semicolon in between) - {ppron pl} - {adj.} - {relativ} - always right after {pron} - Convert to {pron relativ} (similar to {pron interrog} ? - {vi/vt} - preprocessor; done - {vt;vi} - should be: {vt,vi} - preprocessor; done - {idiom} - {sing} - {num} - (+Gen/ über etw.) - ({f} [Ös.]) - ({n} [Ös.]) - "deadlocked {adj, usually not used before a noun}" - "ausgenommen {prp; + Fall des jeweiligen Bezugsworts}" - {prep.} - occurs once - should be without dot ? # (...) "(+ {adj})" "(Kfz: {n} /RI/)" "(f Buddhist structure)" - f =~ famous ? - common sematics - a) explanation - "versenkt eingelassen (mit glatter Oberfläche)" - "eingerollt {adj} (Blatt)" - b) context (similar to <[]>-annotations) - "Arbeitslampe {f} (Beleuchtungstechnik)" - c) possible continuation / interjection - "(beidseitig) eingespannt" - mixture - "to own (up) to (having done) sth. (old-fashioned)" - TODO: Does the location w.r.t <{}>-annotations matter ? # "Tiffany (Seidengaze f) [textil.]" - f -> {f} ? - applying to which ? Both ? "Sakko {n} ({m} [Schw.]);" "...(n)" -- plural or so "Adress rechnung {f} [comp.]" "die Meinigen / die meinigen {pl}" et al. (same line) - Plural related to both - Semantics of ? tools-0.6.0/importers/ding2tei/todo/parsing.misc.from-ding-spec000066400000000000000000000007471423006221500245070ustar00rootroot00000000000000https://dict.tu-chemnitz.de/doc/syntax.html - ... - "Zusatzangaben" - ... - "~" - sometimes a space is succeeding - sometimes annotated ("[...]") - seems to relate to the "~" annotated word - "Alternative Schreibweisen" - "-!-" not occuring - devel: occuring twice, in one line (between "<>") - "--" occuring in one line, twice (in "<...>" context) - in the devel version: occuring in another line (also between "<>") - "-+-" not occuring tools-0.6.0/importers/ding2tei/todo/parsing.phrase-construction000066400000000000000000000065001423006221500247460ustar00rootroot00000000000000Phrase construction =================== Currently, in building a phrase (i.e., expression), I use the Token monoid. It would be nice to be able to keep potentially dropped parts. - "a {b} c d {e}" -> Token _ "a b c d", Token _ OBrace, ... ? Really? Suggestion: Use a type like the following (1): data Expression = SimpleExp Token | ComposedExp Token SpecialExp Expression | ComposedExpR Token SpecialExp | ComposedExpL SpecialExp Expression data SpecialExp = HiddenExp Token | MultiExp (Multi Expression) | SwitchExp Token Token data Multi a = Single a | Multi a [a] (Multi a) | MultiR a [a] | MultiL [a] (Multi a) data Exp a b = Single a | Exp a b (Exp a b) | ExpR a b | ExpL b (Exp a b) data Expression' = Exp Token SpecialExp data Multi' a = Exp a [a] Problem: The list-like structure makes the monoid operation inefficient. - Note that most Expression's will be SimpleExp's. - Even otherwise: Its still only linear-time list concatenation. Porentially better (2): data Exp a b = Single a | Exp (Exp a b) b (Exp a b) | ExpR (Exp a b) b | ExpL b (Exp a b) Drawback: There are several trees that represent the same data. Another option (3): data Expression = [ExpUnit] -- or NonEmpty ExpUnit data ExpElement = SimpleExp Token | HiddenExp Expression | MultiExp (Multi Expression) | SwitchExp Token Token - simple - most Expression's will be [SimpleExp $ Token _] Yet Another Option (4): data Expression = SimpleExp Token | HiddenExp Expression | MultiExp [Expression] | SwitchExp Token Token | ComposedExp Expression Expression - simple and compact - still amiguous (SimpleExp "abc def" ~ ComposedExp (SE "abc") (SE "def")) Next Option -- drops HiddenExp (5): newtype Expression = Expression [Token] switchExp e f = Expression [a <> b, b <> a] simpleExp e = Expression [e] instance Semigroup Expression where Expression toks1 <> Expression toks2 = Expression $ [ tok1 <> tok2 | tok1 <- toks1, tok2 <- toks2 ] - instead of hidden exp, there could be an annotation of the whole expression including the hidden parts. - Duplicates data (There will be some sharing though). - Loss of information. - Probably OK for the source side of the resulting TEI dictionary. - The target side would be blown up (in case of many multi-exps). Questions: - Prohibit totally hidden expressions on the type level? - In which context would such composed expressions be allowed? - Is agressive splitting desirable at all? - Non-trivial expressions are not expected to be found by exact match anyways, in the resulting dictionary. - Units like "etw./jdn. meiden" should probably be treated differently (extract prefix into annotation). - Units like "meiden/vermeiden" should not occur at all. Considerations: - Simply use option (3) or similar and do not join SimpleExp's. - Does complicate things everywhere but on the unit level. - Only do this at the unit level. - potentially also in between <()>. tools-0.6.0/importers/ding2tei/todo/parsing.phrase-identification000066400000000000000000000066621423006221500252160ustar00rootroot00000000000000It would be nice to identify phrases or other non-trivial expression, such that they may be considered examples for associated keywords, and not keywords themselves. NOTE: This has been implemented, although there remains room for improvement. See Language/Ding/Enrich/Example* ? Test for subsequence match (should be delimited by whitespace or similar). * Potential counterexamples: . "etw. unterlassen {vt} [adm.] | etw. zu unterlassen haben [jur.]" . "Adel" ~ "niederer Adel" . "tongue" ~ "little tongue" ~ "Zünglein" ? require a certain maximum number of words in the key unit? * Note that capitalization may change. ? Require a certain minimum number of words in the examined unit. * positive signs ? a comma ? a period (at the end?) ? Consider quotes (<">, <'>) ? Which side should match? ? Any? ? Both? ? The side that currently is the "key"-side? ? The side that currently is the "value"-side? Obstacles * plural form / form of different gender (m/f) * Simple heuristics should do. * Inflected verbs * Note: Ideally, a lot of inflected forms are to be imprted from the Wiktionary in the future. (There is ongoing work from Karl Bartel.) * This would however be accessible in TEI, so t is either to be parsed separately or merged post translation. * Note: Sometimes inflected forms are listed individually. (TODO) * Some regular forms could likely be caught with a heuristic. * Unsure, if worth the effort. ? Ignore? * special Ex.: "ausreizen" ~ "... reizt ... aus ..." * Plural forms * Take care not to match "Menschen" as an example for "Mensch". Some examples: > Angeklagte {m,f}; Angeklagter [jur.] | Angeklagten {pl}; Angeklagte | Hauptangeklagter; Erstangeklagter | Zweitangeklagter | "Angeklagter!" | anonymer Angeklagter oder anonymes Opfer (in einer Anklageschrift) | anonyme Angeklagte oder anonymes Opfer (in einer Anklageschrift) :: accused; defendant | the accused (persons/parties); defendants | primary accused | second defendant; second accused | "Prisoner at the bar!" | (the person of) John Doe (in a bill of indictment) [Am.] | (the person of) Jane Doe (in a bill of indictment) [Am.] * "anonyme Angeklagte oder anonymes Opfer" vs "Jane Doe" * the first is likely to be considered an example, the latter not. * "accused" ~ "the accused" * Is that an example? ? Be conservative in such cases? > Abenddämmerung {f}; Niedergang {m}; Ausklang {m} | Lebensabend {m} :: sere and yellow leaf [fig.] | the sere and yellow leaf (of life/age) [fig.] * latter is to be considered an example, even though only slightly longer > echte Aale {pl}; Flussaale {pl}; Süßwasseraale {pl} (Anguilla) (zoologische Gattung) | Aal grün; grüner Aal [cook.] | Aal blau; blauer Aal [cook.] | Aal in Aspik; Aal in Gelee [cook.] :: freshwater eels; anguilla (zoological genus) | Boiled Eel served with Parsely Sauce | Eel au bleu; Eel steamed and served with Butter | Jellied Eel > Abstand {m}; Zwischenraum {m} (zwischen) | Abstände {pl}; Zwischenräume {pl} | in 25 Meter Abstand | im Abstand von 5 Metern | lichter Abstand | den gebührenden Abstand halten | Abstand halten! | Ich folgte ihm mit einigem Abstand.; Ich folgte ihm in einiger Entfernung. :: distance (between) | distances | at a distance of 25 metres | 5 metres apart | clear distance | to keep the proper distance | Keep a distance! | I followed him at a distance. * two synonymous phrases, only on has an infix tools-0.6.0/importers/ding2tei/todo/parsing.removal_of_auxiliary_words000066400000000000000000000001331423006221500263660ustar00rootroot00000000000000E.g.: "jdn./etw. in etw. verwandeln {vt}" -> "verwandeln" (possibly with some annotation) tools-0.6.0/importers/ding2tei/todo/parsing.scope-of-annotations000066400000000000000000000025751423006221500250120ustar00rootroot00000000000000Scope of annotations ==================== - ex.: "obligatorisch; verpflichtend {adj}; Pflicht...; Zwangs... :: compulsory" - Probably, "{adj}" is supposed to apply to both of the first two adjectives. ? assume that a generic part of speech annotation (e.g. "{adj}") applies to all preceding units in that group. - seems to fit quite well - TODO: more rigorous searching (e.g. try to find counterexamples with (capitalised) nouns or expressions composed of several words; also search for conflicting annotations) - conflicting annotations should also be detected in the main program - Module Language.Ding.Enrich - Is there a point in per-group annotations ? - Any annotation following a group / prefix-set annotation is also a group / prefix-set annotation. This applies for both <[]> and <{}> annotations, which may interact in this regard. - Note that conversely, an annotation preceding a unit-level annotation must also be a unit-level annotation. There should be some rules to solve corresponding conflicts. ? <()>-annotations relevant here? - probable counterexample: "überrannt; überlastet {adj} (WWW-Server) [comp.]" - Repeated annotations indicate that these do not form part of a prefix set. - Ex.: "accusation; plaint [Br.] [jur.]; libel [Sc.] [jur.]" See also: * doc/ding.annotation.* * todo/enrich.annotation.transferral tools-0.6.0/importers/ding2tei/todo/parsing.slashes000066400000000000000000000025351423006221500224020ustar00rootroot00000000000000Parsing of single slashes ========================= Issue 1): Recognizing the scope of weak slashes ----------------------------------------------- Examples. * "(mit jdm. / mit etw.)" * "| Auslandsdeutsche / Auslandsfranzosen ::" * "Kein / Keine [Ös.] Ausschank" * Converted to strong slash in preprocessing * There are many more similar occurences. * "| über dem / überm [ugs.] Durchschnitt |" Issue 2): partial words ----------------------- Examples: * "An-/Durchbohren" * "Jugendstil-/Kunst" (before preprocessing: "Jugendstil- [Kunst]") Issue 3) Comma separated abbrevs -------------------------------- Examples: * "Gesellschaft mit beschränkter Haftung /GmbH/, /GesmbH/" * "bürgerlichen Rechts /GbR/; /GdbR/, BGB-Gesellschaft" * not really an example (not strongly related) Consider to make all abbreviations their own entry. Remainder (single slashes) -------------------------- Not to be confused with slash-enclosed annotations. examples - "auf/in etw. surfen" - "eine Droge, die schnell süchtig macht / von der man schnell abhängig wird" subcategory pronouns - "er/sie surft" Resolving the problems: ? keep a state for the current preceding spaces (Bool possibly suffices) ? match different entities depending on the availability of preceding or succeding space / separator. See also -------- * doc/ding.slashes tools-0.6.0/importers/ding2tei/todo/parsing.spacing000066400000000000000000000001311423006221500223520ustar00rootroot00000000000000Consider not to store the amount of spacing, but rather whether there was any (boolean). tools-0.6.0/importers/ding2tei/todo/parsing.special000066400000000000000000000003271423006221500223550ustar00rootroot00000000000000If "/ /" should be allowed for all special characters, the class of special characters needs to be expanded. In the current dictionary (v. 1.8.1), there is only "%" besides the common special characters. tools-0.6.0/importers/ding2tei/todo/parsing.whitespace000066400000000000000000000002511423006221500230650ustar00rootroot00000000000000Currently, all whitespace except for \n is considered equivalent. Consider to change this to only allow ' ' as regular whitespace. - Use $printable for the remainder. tools-0.6.0/importers/ding2tei/todo/parsing.wordswitch000066400000000000000000000012051423006221500231260ustar00rootroot00000000000000syntax: '<>' semantic: surrounding words interchangeable possible solutions - ignore - Alex: separate by WS (i.e., identify words) - consider chars [-_'] - potential special case: "(n)" suffix on one of the words problem: sometimes, its not a single word, but instead for example a composed noun - ex. "a child" problem: combination with "/" . ex. "to tuck in / tuck up [Br.] <> a child (in bed)" - "/" needs good heuristics attention: annotations - ex. "to tuck in / tuck up [Br.] <> a child (in bed)" Note: <> is apparently only on the english side. - If a de->en dictionary is sufficient for now, it can be probably ignored. tools-0.6.0/importers/ding2tei/todo/preprocess.find-irregularities000066400000000000000000000004351423006221500254270ustar00rootroot00000000000000Ding irregularities & misspellings ================================== How to find? - Regexes for assumed possible errors - Errors upon parsing See - todo/parsing.misc - src/preprocess/*.sed Examples - () instead of {}: - '\([^{]{0,10}\b(adj|adv|prp|n|m|f|...)\b' (`less' regex) tools-0.6.0/importers/ding2tei/todo/preprocess.fix-misspellings000066400000000000000000000001011423006221500247420ustar00rootroot00000000000000Use a spellchecker, e.g. /usr/share/dict/{words,ogerman,ngerman} tools-0.6.0/importers/ding2tei/todo/preprocess.quotes000066400000000000000000000004251423006221500227760ustar00rootroot00000000000000Quotes <"> Special cases: * '<">' * '1/4"' <'> Examples: * "'Zur Aktivierung klicken'" * "'Die Qual der Wahl. Ich kann mich nicht entscheiden, welches ich nehmen soll.'" Problem: * Most <'> are apostrophs. Solution: ? Analogous to '/' (left <'>, strong <'>, ...)? tools-0.6.0/importers/ding2tei/todo/preprocess.tabs000066400000000000000000000002151423006221500224040ustar00rootroot00000000000000Are tabs permitted ? - there is exactly one in the devel version of de-en.txt (none in version 1.8.1) Adjust AlexScanner.x accordingly. tools-0.6.0/importers/ding2tei/todo/reduce-memory-consumption000066400000000000000000000022041423006221500244200ustar00rootroot00000000000000Currently (2020-10-14), 4.5 GiB are taken up by parsing the Ding source (which is 20 MiB in size). The main issue is that the program cannot start writing to disk before essentially the whole dictionary is processed, because the (prefixing) TEI header wants to know the number of TEI entries. -- Obsolete: This huge memory usage probably is due to something in the Happy-generated code. The usage can likely be drastically be reduced by having Happy only parse single lines. This requires parsing the header separately, though (which should not be difficult). Testing with restricted memory: $ ulimit -S -d #N_KB * result: restricting to below 2.6 GiB crashes the program. Testing repeated inspection: > putStrLn $ show $ length ls > threadDelay $ 20 * 1000 * 1000 > putStrLn $ show $ length $ show ls * second counting finishes fast * no memory reduction after first counting done noticeable. Results of improvement (scan & parse by line) * scan&parse time: 20s -> 13s * scan time: 4s -> 10s (likely I did something wrong in measuring) * memory usage: 2.6 GiB -> 2.7 MiB * Note: lazyness and garbage collection have a huge impact here tools-0.6.0/importers/ding2tei/todo/setup-mechanism000066400000000000000000000005071423006221500223750ustar00rootroot00000000000000Installation / Compilation / Setup mechanism - options: - Cabal - recommended by (seemingly all) Haskell people - probably does not (easily) allow for non-Haskell parts - restrict to only the Haskell part - Makefile - Shell script - Combine the execution of sed scripts and the compiled Haskell program tools-0.6.0/importers/ding2tei/todo/tei.alternative-spellings000066400000000000000000000005241423006221500243660ustar00rootroot00000000000000? Use a separate or ? - Ask at Freedict first. For different US/UK spellings (or similar): - ex. from TEI doc colour US color - https://www.tei-c.org/release/doc/tei-p5-doc/en/html/DI.html : 9.3.5.2 tools-0.6.0/importers/ding2tei/todo/tei.homograph-grouping000066400000000000000000000006521423006221500236700ustar00rootroot00000000000000Options (that are valid FreeDict TEI): * superEntry/entry * superEntry/entry/sense * entry/sense * entry/sense/sense All known information collected in the FreeDict Wiki (11 - Grouping of homographs): See also: * Language/TEI/Syntax/Body.hs * https://dariah-eric.github.io/lexicalresources/pages/TEILex0/TEILex0.html#nested-entries-vs-multiple-senses tools-0.6.0/importers/ding2tei/todo/tei.lines000066400000000000000000000017361423006221500211720ustar00rootroot00000000000000How to represent lines in the TEI output ======================================== - Create two dictionaries, one for each direction (de->en, en->de). NOTE: Mostly done. Let one direction be fixed. ? Group Ding groups in a ? - No. They do not function as "a single unit". ? For each line, separate by the source language's keywords. . "a; b :: c; d" -> "a :: c; d", "b :: c, d" - similar for <|> ? Group units of a group in a ? . They do funtion as "a single unit", so ok. - Group homographs using . ? Group new (more numerous) lines by homographs on the source side. ? Group in a with children. ? Group in s instead. - Do not use here. - see https://www.tei-c.org/release/doc/tei-p5-doc/en/html/DI.html - 9.1, 9.2.1 ? Are all translation units in the same group considered synonymous? - Otherwise: Use separate entries. ? Use sortKey's? See also: todo/linking.lines tools-0.6.0/importers/ding2tei/todo/tei.usage-tag000066400000000000000000000006641423006221500217340ustar00rootroot00000000000000 ===== . Created (mostly?) from []-annotations. - Differentiate, as described in the TEI Lex-0 doc: * https://dariah-eric.github.io/lexicalresources/pages/TEILex0/TEILex0.html#index.xml-body.1_div.7_div.2 ? Usage of the @norm attribute? . Standardization of values, e.g. "ugs." ~ "colloquial". - See example: * https://dariah-eric.github.io/lexicalresources/pages/TEILex0/TEILex0.html#index.xml-body.1_div.7_div.1 tools-0.6.0/importers/ding2tei/todo/teiaddphonetics.bugs000066400000000000000000000006771423006221500234110ustar00rootroot00000000000000teiaddphonetics: * Unit: "Angeklagter!" * '[[:letter:]] *[!?:,]"' * in words: a letter, whitespace, [!?:,], <"> * [*.\\;-] are ok * Whitespace before <"> also ok. $ fdtools-git/teiaddphonetics --infile deu-eng.tei --outfile build/tei/deu-eng-phonetics.tei --espeak-count 1 last written word: ''Sie sagt sie ist rechtzeitig da'-'Nie im Leben ' / 'Denkste!' / 'Keine Chance!'' See also: preprocess/de-en/adjust_for_teiaddphonetics.sed tools-0.6.0/importers/ding2tei/todo/testing.idempotence000066400000000000000000000007011423006221500232370ustar00rootroot00000000000000Assuming a Pretty instance for Syntax.Dict, it should hold that (parse . scan . pretty) is the identity on Syntax.Dict. A slightly weaker condition is that (pretty . parse . scan) is idempotent on valid textual dictionaries (those that parse.scan does not emit an error for). For testing, one shall either use the idempotence condition or (parse . scan . pretty . parse . scan) x = (parse . scan) x for valid textual dictionaries x. tools-0.6.0/importers/ding2tei/todo/testing.quickcheck000066400000000000000000000000741423006221500230600ustar00rootroot00000000000000Quickcheck ? Enrichment for instance could well be tested. tools-0.6.0/importers/ding2tei/util/000077500000000000000000000000001423006221500173565ustar00rootroot00000000000000tools-0.6.0/importers/ding2tei/util/extract.bash000077500000000000000000000040071423006221500216730ustar00rootroot00000000000000#!/usr/bin/env bash # # extract.bash - a small script to extract different expressions # # Copyright 2020,2022 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # dingfile=../dict/ding/de-en.txt.pp types=( {brace,bracket,paren,dot,switch}exps nonalpha words wordpairs parenprefs{1,2} keyword{prefs,sufs} keywordswithprefs ) dir="$(dirname "$(realpath "$0")")" outdir="${dir}/results" # Do not overwrite existing files (using '>'). set -o noclobber function main() { cd "$dir" mkdir -p "$outdir" if [ $# -ge 1 ] then extract "$1" else for type in "${types[@]}" do printf 'Extracting %s...\n' "$type" extract "$type" done fi } function extract() { local type="$1" local shfile="extract_${type}.sh" local sedfile="extract_${type}.sed" local script if [ -e "$shfile" ] then script="$shfile" else if ! [ -e "$sedfile" ] then printf 'Error: %s does not exist.\n' "$sedfile" 1>&2 exit 1 fi script="$sedfile" fi exps_sorted="$(./"$script" < "$dingfile" | sort)" # Sort by name. uniq <<< "$exps_sorted" > "${outdir}/${type}.by_name" # Sort by number of occurences. uniq -c <<< "$exps_sorted" | sort -nr > "${outdir}/${type}.by_count" # Sort by length. awk '{ print length, $0 }' < "${outdir}/${type}.by_name" | sort -n \ | cut -d ' ' -f 2- > "${outdir}/${type}.by_length" } main "$@" # vi: ts=2 sw=2 noet tools-0.6.0/importers/ding2tei/util/extract_braceexps.sed000077500000000000000000000023221423006221500235630ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # extract_braceexps.sed - a small script to extract brace expressions # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Only innermost brace expressions ("{...}") are to be matched. # (Actually, there are no nested brace expressions in the Ding source.) # Several brace expressions in the same line are to be split into several # lines. /\{.*\}/ { # Reduce to (roughly) "{[^{}]*}*" s/[^{]*[^}]*(\{[^}]*\})([^{]*[^}]*$)?/\1/g # Separate by newlines. s/\}\{/\}\n\{/g # Print p } # vi: noet tools-0.6.0/importers/ding2tei/util/extract_bracketexps.sed000077500000000000000000000023301423006221500241210ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # extract_bracketexps.sed - a small script to extract bracket expressions # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Only innermost brace expressions ("{...}") are to be matched. # (Actually, there are no nested brace expressions in the Ding source.) # Several brace expressions in the same line are to be split into several # lines. /\[.*\]/ { # Reduce to (roughly) "{[^\[\]]*}*" s/[^[]*[^]]*(\[[^]]*\])([^[]*[^]]*$)?/\1/g # Separate by newlines. s/\]\[/\]\n\[/g # Print p } # vi: noet tools-0.6.0/importers/ding2tei/util/extract_dotexps.sed000077500000000000000000000016061423006221500233010ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # extract_dotexps.sed - a small script to extract dot-terminated words # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # s/[^.]*(^| )([^ ]*\.)/\2\n/g s/^((.*)\.).*/\1/p tools-0.6.0/importers/ding2tei/util/extract_keywordprefs.sed000077500000000000000000000021441423006221500243350ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # extract_keywordprefs.sed - extract words prefixing certain keywords # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Find all prefixing words (use ł as separator) :loop s`^(.*ł)? *([^ ł]+) +((etw|sth|sb|jm?d[nms]?|so)\.|oneself)`\1\2ł` t loop s`ł *$`` t print s`^(.*ł)? *([^ ł]+)`\1` t loop # Print all found words from hold space. :print s` +$`` /^$/ d s`ł`\n`g p # vi: noet tools-0.6.0/importers/ding2tei/util/extract_keywordsufs.sed000077500000000000000000000021431423006221500241750ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # extract_keywordsufs.sed - extract words suffixing certain keywords # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Find all prefixing words (use ł as separator) :loop s`^(.*ł)? *((etw|sth|sb|jm?d[nms]?|so)\.|oneself) +([^ ł]+)`\1\4ł` t loop s`ł *$`` t print s`^(.*ł)? *([^ ł]+)`\1` t loop # Print all found words from hold space. :print s` +$`` /^$/ d s`ł`\n`g p # vi: noet tools-0.6.0/importers/ding2tei/util/extract_keywordswithprefs.sed000077500000000000000000000022451423006221500254160ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # extract_keywordswithprefs.sed - extract certain keywords together with # prefixing words # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Find all prefixing words (use ł as separator) :loop s`^(.*ł)? *([^ ł]+) +((etw|sth|sb|jm?d[nms]?|so)\.|oneself)([^ł]|$)`\1\2 \3ł` t loop s`ł *$`` t print s`^(.*ł)? *([^ ł]+)`\1` t loop # Print all found words from hold space. :print s` +$`` /^$/ d s`ł`\n`g p # vi: noet tools-0.6.0/importers/ding2tei/util/extract_nonalpha.sed000077500000000000000000000023341423006221500234120ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # extract_nonalpha.sed - a small script to extract non-alphabetic characters # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Only innermost brace expressions ("{...}") are to be matched. # (Actually, there are no nested brace expressions in the Ding source.) # Several brace expressions in the same line are to be split into several # lines. # Remove alphabetic characters. s/[[:alpha:]]//g # Remove whitespace. s/\s//g # Remove empty lines. /^$/ d # Add trailing newlines. s/./&\n/g s/\n$// # vi: noet tools-0.6.0/importers/ding2tei/util/extract_parenexps.sed000077500000000000000000000022141423006221500236140ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # extract_parenexps.sed - a small script to extract paren-expressions # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Only innermost paren-expressions ("(...)") are to be matched. # Several paren-expressions in the same line are to be split into several # lines. /\(.*\)/ { # Reduce to (roughly) "\([^()]*\)*" s/[^(]*[^)]*(\([^)]*\))([^(]*[^)]*$)?/\1/g # Separate by newlines. s/\)\(/\)\n\(/g # Print p } # vi: noet tools-0.6.0/importers/ding2tei/util/extract_parenprefs1.sed000077500000000000000000000017771423006221500240520ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # extract_parenprefs.sed - a small script to extract prefixes inside parens # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Notes: # * This script should receive lines with parenthese-expressions. # * Prefixes consisting of a single word extracted. s`^\( *([^ )]+).*$`\1` # vi: noet tools-0.6.0/importers/ding2tei/util/extract_parenprefs1.sh000077500000000000000000000000761423006221500237000ustar00rootroot00000000000000#!/bin/sh ./extract_parenexps.sed | ./extract_parenprefs1.sed tools-0.6.0/importers/ding2tei/util/extract_parenprefs2.sed000077500000000000000000000021131423006221500240340ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # extract_parenprefs.sed - a small script to extract prefixes inside parens # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Notes: # * This script should receive lines with parenthese-expressions. # * Prefixes consisting of a two words extracted. # Drop lines containing single words inside parens /^\( *[^ ]* *\)/ d s`^\( *([^ )]+ [^ )]+).*$`\1` # vi: noet tools-0.6.0/importers/ding2tei/util/extract_parenprefs2.sh000077500000000000000000000000761423006221500237010ustar00rootroot00000000000000#!/bin/sh ./extract_parenexps.sed | ./extract_parenprefs2.sed tools-0.6.0/importers/ding2tei/util/extract_switchexps.sed000077500000000000000000000020441423006221500240110ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # extract_switchexps.sed - a small script to extract "<>"-expressions. # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Note: Only the succeeding word is taken with, to prevent the list of results # from becoming too long. # (TODO:) Do the inverse, separately. s/.*(<> *[^ ;|:]+)/\1\n/g s/(<> *[^ ;|:]+).*/\1/p tools-0.6.0/importers/ding2tei/util/extract_wordpairs.sed000077500000000000000000000021341423006221500236220ustar00rootroot00000000000000#!/usr/bin/env -S sed -nEf # # extract_word.sed - a small script to extract words # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # # Notes: # * This script is very slow. # * The generated data is not overly useful, since <::> <|> et al. are # considered words. /^#/ d h s/ *([^ ]+) +([^ ]+)/\1 \2\n/g s/(^|\n) *[^ ]*$// /^$/ d p x s/^ *[^ ]+// s/ *([^ ]+) +([^ ]+)/\1 \2\n/g s/(^|\n) *[^ ]*$// /^$/ d p tools-0.6.0/importers/ding2tei/util/extract_words.sed000077500000000000000000000015241423006221500227500ustar00rootroot00000000000000#!/usr/bin/env -S sed -Ef # # extract_word.sed - a small script to extract words # # Copyright 2020 Einhard Leichtfuß # # This file is part of ding2tei-haskell. # # ding2tei-haskell is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ding2tei-haskell 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 Affero General Public License for more details. # # You should have received a copy of the GNU Affero General Public License # along with ding2tei-haskell. If not, see . # /^#/ d s/ +/\n/g tools-0.6.0/importers/epo-eng/000077500000000000000000000000001423006221500162265ustar00rootroot00000000000000tools-0.6.0/importers/epo-eng/README.md000066400000000000000000000023771423006221500175160ustar00rootroot00000000000000This importer is for a single dictionary format only, as used by the epo-eng dictionary from . It is (c) 2015 Paul Denisowski, under the terms of the Creative Commons Attribution 3.0 Unported License. Usage ----- The usage of this script is extremely simple: $ python3 epo-eng-import.py - `UPSTREAM_DICT`: path to the text dictionary, as distributed by the upstream project - `TEI_SKELETON`: header information; should be taken from the last release and extended with the changes with the newer version. - `OUT_DIR`: output directory, must not exist. Python3 is the name of the Python interpreter. It might be that your systems calls a Python version > 3.x just `python`, then you need to alter the command. Sources ------- The markup of the original Esperanto-English dictionary is hard to parse. The current parser is far from perfect, but it does a fairly good job. I've contacted upstream regarding a patch (Jul 2017), but haven't got a response. Therefore, the changes are recorded in [epo-eng.patch](epo-eng.patch) and can be applied to the epo-eng dictionary released in 2015. The file README.dict is copied as README to the output directory. tools-0.6.0/importers/epo-eng/epo-eng-import.py000066400000000000000000000277701423006221500214570ustar00rootroot00000000000000"""This file imports the epo-eng dictionary from . Please see the README for more information.""" import enum import os import sys import urllib.request import xml.sax.saxutils as saxutils # allow tokenizer import from fd_import.tokenizer import ChunkType, tokenize import fd_import class WordType(enum.Enum): Full = '' # normal words Prefix = 'pref' # word prefixes Suffix = 'suff' # :) Part = 'part' # "-x-", so an infix class Definition: #pylint: disable=too-few-public-methods """Only relevant for headwords with no direct translations, they define the headword.""" def __init__(self, definition): self.definition = definition def as_xml(self): return '\n%s\n' % saxutils.escape(self.definition) class Word: #pylint: disable=too-few-public-methods """This is either a head word or a translation.""" def __init__(self, word, usage_info=None): self.word = word # gramGrp is a list of ready-to-use xml strings, basically *one* node self.gramGrp, self.usage_info = self.__get_gram_info(usage_info) self.word_type = type def __repr__(self): return ('%s (%s)' % (self.word, self.usage_info) if self.usage_info else self.word) def __get_gram_info(self, usage_info): """Figure out, whether text is a collocating word or a usage hint. Return tuple ready-to-use XML. """ # special cases: catch collocations collocations = ['for', 'of', 'up', 'to', 'in', 'on', 'by', 'upon', 'ke', 'de', "do"] # esperanto if not usage_info: return (None, None) elif usage_info in collocations: return (['%s' % saxutils.escape(usage_info)], None) else: return (None, '%s' % saxutils.escape(usage_info)) def as_xml(self): xml = ['\n', saxutils.escape(self.word), ''] if self.usage_info: xml.append('\n' + self.usage_info) # already escaped if self.gramGrp: xml.append('\n\n%s\n' % '\n'.join(self.gramGrp)) return ''.join(xml + ['\n']) #pylint: disable=redefined-variable-type class HeadWord(Word): #pylint: disable=too-few-public-methods def __init__(self, word, usg=None): if word.startswith('-') and word.endswith('-'): self.type = WordType.Part elif word.startswith('-'): self.type = WordType.Suffix elif word.endswith('-'): self.type = WordType.Prefix else: self.type = WordType.Full self.word = word.lstrip('-').rstrip('-') super().__init__(self.word, usg) def as_xml(self): extent = '' if not self.type == WordType.Full: extent = ' extent="%s"' % self.type.value orth = '%s' % (extent, saxutils.escape(self.word)) if self.gramGrp: orth += '\n\n%s' % '\n'.join(self.gramGrp) if self.usage_info: orth += '\n' + self.usage_info return '
\n%s\n
' % orth def structure_translations(unordered_list): """Take a list of chunks (see docs of parse_meanings) and return a list of list of words. The outer lists holds different meanings (homonyms) and has in most cases only one entry. The inner list contains objects of type Word.""" translations = [[]] # initialize list with one sense while unordered_list: chunk = unordered_list.pop(0) # test for type of chunk if chunk[0] == ChunkType.Paren: # is this an additional info for a word: if unordered_list and unordered_list[0][0] == ChunkType.Word: translations[-1].append(Word(unordered_list.pop(0)[1], chunk[1])) else: # no next chunk or no word # this is either a parenthesized expression with no word before # or after, then it's a definition; otherwise it's an unhandled # case and a bug if (len(translations) == 1 and not translations[0]) and \ (not unordered_list or unordered_list[0][0] == ChunkType.Comma): # definition translations[-1].append(Definition(chunk[1])) else: print(',',repr(translations)) raise ValueError(("Couldn't parse translations; tokens " "parsed: %s\nCurrent token: %s\nTokens left: %s") % \ (translations,chunk,unordered_list)) elif chunk[0] == ChunkType.Word: # word with usage info / colloc if unordered_list and unordered_list[0][0] == ChunkType.Paren: # in some cases definitions are written as "to (be in) a # position", this is poor markup. ignore those parenthesis and # add it as a whole word: if len(unordered_list) >= 2 and unordered_list[1][0] == ChunkType.Word: translations[-1].append(Word(chunk[1] +" " + \ unordered_list.pop(0)[1] + " " + \ unordered_list.pop(0)[1])) else: translations[-1].append(Word(chunk[1], unordered_list.pop(0)[1])) else: translations[-1].append(Word(chunk[1])) elif chunk[0] == ChunkType.Comma: # new word starts, ignore pass elif chunk[0] == ChunkType.Semicolon: # homonym, new sense translations.append([]) # add brackets and braces verbatim elif chunk[0] in (ChunkType.Bracket, ChunkType.Brace): chars = ('[]' if chunk[0] == ChunkType.Bracket else '{}') # readd translation in bracket/brace, verbatim if translations[-1]: # at least one word found translations[-1][-1].word += ' ' + chars[0] + chunk[1] + chars[1] else: # new word translations[-1].append(Word(chars[0] + chunk[1] + chars[1])) else: raise NotImplementedError("Unhandled case: " + repr(chunk)) return translations def guess_grammar_details(translations): """This function inspects the English translations to infer grammatical information. Definitions starting with "to " are verbs in the infinitive form, etc. The translations might get altered, for instance, to strip the "to " from the actual definition.""" gram = '' firstword = translations[0][0] if isinstance(firstword, Definition): return (None, translations) # don't fiddle around with def's if firstword.word.startswith('to '): gram = 'v' for deflist in translations: for word in deflist: if word.word.startswith('to '): word.word = word.word[3:] # remove wrongly parsed colloc's collocs = ['%s' % c for c in ['to', 'a', 'an']] for deflist in translations: for word in deflist: if word.gramGrp: # it need to have a colloc, otherwise no checks for colloc in collocs: if colloc in word.gramGrp: idx = collocs.index(colloc) del word.gramGrp[idx] return (gram, translations) def translations_to_xml(translations): """Translate nested definition / translation (word) structure into a TEI:sense structure.""" xml = [] for definition_list in translations: if not definition_list: raise ValueError("Found empty translation list; all translations: " \ + repr(translations)) xml.append('') xml.extend(x.as_xml() for x in definition_list) xml.append('') return xml def write_output(input_file, base_dir, tei_skeleton, body_xml): """This writes the dictionary import into the specified directory.""" print("Writing TEI dictionary…") with open(tei_skeleton, 'r', encoding='utf-8') as f: header = f.read() body_start = header.find('') + 6 tei_file = os.path.join(base_dir, 'epo-eng.tei') if not os.path.exists(base_dir): os.mkdir(base_dir) with open(tei_file, 'w', encoding='utf-8') as f: f.write(header[:body_start] + '\n') f.write(body_xml) f.write(header[body_start+1:].lstrip().rstrip() + '\n') fd_import.output.reindent_xml(tei_file) if not os.path.exists(os.path.join(base_dir, 'COPYING')): # retrieve copyright information print("Downloading CC unported 3.0 license") with open(os.path.join(base_dir, 'COPYING'), 'wb') as f: req = req = urllib.request.Request( 'https://creativecommons.org/licenses/by/3.0/legalcode.txt', data=None, headers = { 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0' }) with urllib.request.urlopen(req) as u: f.write(u.read()) # write Makefile fd_import.output.mk_makefile(base_dir, ['epo-eng.tei', 'epo-eng.patch']) def main(input_file, tei_skeleton, output_directory): print("Parsing dictionary…") with open(input_file, 'r', encoding='utf-8') as f: # gnerator with word pairs; ignore indented lines (only file header, # ATM) words = (wp for wp in f.read().split('\n') if wp.strip() and not wp.startswith(" ")) word_list = [] for word_pair in words: head, trans = word_pair.split(' : ') translations = tokenize(trans) translations = structure_translations(translations) head = tokenize(head) if len(head) == 2: # headword with definition head = HeadWord(head[0][1], head[1][1]) else: head = HeadWord(head[0][1]) word_list.append((head, translations)) # enrich words with grammatical info, strip "to" and other words *and* # convert to XML xml = [] for head, trans in word_list: # translations might get altered, e.g. "to" is stripped gram, trans = guess_grammar_details(trans) xml += ['', head.as_xml()] if gram: xml.append('\n%s\n' % gram) xml += translations_to_xml(trans) + [''] write_output(input_file, output_directory, tei_skeleton, '\n'.join(xml)) print("Done. Now it's time to copy DTD, CSS and RNG and validate the dictionary.") def check_args(): """command line parameter validation""" if len(sys.argv) != 4: print("Error: invalid command line parameters.") import textwrap print("Usage: %s \n "\ % sys.argv[0], end="") print('\n '.join(textwrap.wrap(("TEI SKELETON has to be a FreeDict TEI file, with an empty " "body tag. The opening and closing body tags have to be on " "separate lines.\n" "The output directory may exist and files will be overwritten, " "as necessary."), 78))) sys.exit(1) if not os.environ['FREEDICT_TOOLS']: print("The environment variable FREEDICT_TOOLS is not set.") print("This is mandatory, so that the necessary scripts can be located.") print("Have a look at the FreeDict wiki for more information:") print("https://github.com/freedict/fd-dictionaries/wiki") sys.exit(4) for file in sys.argv[1:3]: if not os.path.exists(file): print('Sorry, but the file "%s" does not exist.' % file) sys.exit(2) dictdir = os.path.basename(os.path.abspath(sys.argv[3])) if not len(dictdir) == 7 or '-' not in dictdir: print("The output directory has to be named with the usual FreeDict naming scheme. Otherwise, the Make rules will fail.") sys.exit(5) if __name__ == '__main__': check_args() main(sys.argv[1], sys.argv[2], sys.argv[3]) tools-0.6.0/importers/epo-eng/epo-eng.patch000066400000000000000000000101241423006221500205770ustar00rootroot00000000000000--- epo-eng.orig 2017-07-09 11:40:36.611728286 +0200 +++ epo-eng 2017-07-03 15:58:11.599154598 +0200 @@ -189,7 +189,7 @@ Abisenujo : Abyssinia, Ethiopia abisma : abysmal abisme : abysmally -abismejo : (the) abyss +abismejo : abyss abismo : abyss, chasm, gulf, precipice, oblivion abiŝag : Abishag abiturienta : baccalaureate; related to high school graduation @@ -1515,7 +1515,7 @@ agordiĝemo : manageability, tractability agordiĝi : to be tuned (up) agordilaro : control panel -agordilo : configurator, tuner (device), tuning fork, setup program, (software), wizard (software), assistant (installation) +agordilo : configurator, tuner (device), tuning fork, setup program, wizard (software), assistant (installation) agordindikoj al la fenestroadministrilo : window manager hints agordinformoj : configuration information agordisto : tuner (person) @@ -7751,7 +7751,7 @@ batraka : batrachian batrako : batrachian batrakoj : anura -batsono : (sound of) striking (of a clock) +batsono : striking (sound of a clock) batuo : battue, hunt, shoot Batuo : Batu baŭdo : baud @@ -11769,7 +11769,7 @@ ĉonmago : chonmage ĉopsuo : chop suey ĉoto : bullhead, sculpin -ĉu : either, if, is it, whether, (asks a question) +ĉu : either, if, is it, whether ĉu ... aŭ : whether ... or ĉu ... ĉu : whether ... or, whether ... whether ĉu aŭ ne danĝere : dangerous or not @@ -26216,7 +26216,7 @@ inviti : to invite invitiĝi : to be invited invitilo : invitation, prompt -invito : bidding, invitation, (command), prompt +invito : bidding, invitation, prompt involucia : involuntary involucie : involuntarily involucio : involution @@ -32568,7 +32568,7 @@ kutimiĝo : adjustment kutimita : accustomed to, used to kutimo : custom, habit, way, practice, routine -kutimulo : (a) regular (guest, customer, etc.) +kutimulo : regular (guest, customer, etc.) kutro : cutter kuvajta : Kuwaiti kuvajtano : Kuwaiti @@ -37771,7 +37771,7 @@ melopepo : spaghetti squash, summer squash, yellow squash melopsitako : budgerigar melotrono : mellotron -mem : self, (puts emphasis on the noun or pronoun it follows) +mem : self (puts emphasis on the noun or pronoun it follows) mem-malkompaktiga : self-extracting mem-malkompaktiga arkivo : self-extracting archive mem-malkompaktiga exe-dosiero : self-extracting EXE file @@ -38625,7 +38625,7 @@ minidisko : MD, MiniDisc minikamerao : minicamera minimuma : minimal, minimum -minimume : at (the) least +minimume : at least minimumejo : point of minimum minimumiganto : point of minimum minimumigi : to minimize @@ -38670,7 +38670,7 @@ minuso : minus (sign) minussigno : minus sign minuto : minuet (time) -mio : (the) self, one’s self +mio : self, one’s self Mioceno : Miocene miogalo : desman, muskrat miopa : myopic, near-sighted, short-sighted @@ -42407,7 +42407,7 @@ Okeano : Oceanus okedro : octahedron okej : agreed, okay -okera voko : (the) eightfold path (of Buddhism) +okera voko : eightfold path (of Buddhism) Okinavo : Okinawa oklahoma : Oklahoman oklahomano : Oklahoman @@ -44859,7 +44859,7 @@ pezoforto : gravitation, gravity Pfalzo : Palatinate pfenigo : pfennig -PGKD : (plej granda komuna divizoro) GCD (greatest common denominator) +PGKD (plej granda komuna divizoro) : GCD (greatest common denominator) pĝ : paĝo pia : pious, righteous piamatro : pia mater @@ -46314,7 +46314,7 @@ porko : hog, pig, swine porkocervo : babirusa, deer-hog porkograso : lard -porkomerca : (for) commercial (use, purposes) +porkomerca : commercial (use, purposes) porkostalo : sty porkstalo : sty porkviro : boar, hog @@ -47192,7 +47192,7 @@ pridiskuti : to discuss pridisputi : to argue, dispute, query, question pridubebla : doubtful -pridubi : to (cast) doubt (upon) +pridubi : to doubt, to cast doubt (upon) prienketi : to investigate prierara : error, about errors prierara raportilo : error report tool @@ -50975,7 +50975,7 @@ ronda hoketo : breve ronda kanao : hiragana ronda krampo : parenthesis, round bracket -rondaj krampoj : round parentheses, () +rondaj krampoj : round parentheses rondbuŝuloj : cyclostomes rondcifere : round numbers (in) ronde : roundly tools-0.6.0/importers/fd_import/000077500000000000000000000000001423006221500166575ustar00rootroot00000000000000tools-0.6.0/importers/fd_import/README000066400000000000000000000002341423006221500175360ustar00rootroot00000000000000This is a common module for tokenizing text-only dictionaries. It is written in Python and documented in the source code. Please start reading __init__.py. tools-0.6.0/importers/fd_import/__init__.py000066400000000000000000000004021423006221500207640ustar00rootroot00000000000000"""This is a dummy file, so that this directory can be used as a module. The actual magic should *always* happen in tokenizer.py. Tests.py is a stand-alone test suite and does, really, tests.""" from . import output, tokenizer from . import output, output tools-0.6.0/importers/fd_import/output.py000066400000000000000000000070531423006221500205760ustar00rootroot00000000000000"""This module contains functions to write all accompanying files for the dictionary, e.g. license or README.""" import os import re import shutil import sys import textwrap import xml.dom.minidom class OutputError(Exception): pass def copy_readme(input_file, output_directory): """When a README is found in the same directory as `input_file`, the file is copied to OUTPUT_DIRECTORY. The input file has to be called "README.xxx-yyy" or "README.xxx-yyy.EXT", where ext is anarbitrari file extension and xxx-yyy a ISO 6639-3 three-letter language code.""" full_input = os.path.abspath(input_file) input_file = os.path.basename(input_file) input_dir = os.path.dirname(full_input) iso_code = re.search('^([a-z]{3}-[a-z]{3})', input_file) if not iso_code: raise OutputError(('%s does not follow the "xxx-yyy.EXT" ' 'naming convention.') % input_file) iso_code = iso_code.groups()[0] readme = None for match in (re.search('^README.([a-z]{3}-[a-z]{3}).*', f) for f in os.listdir(input_dir)): if not match: continue if match.groups()[0] == iso_code: readme = match.string break if readme: shutil.copy(os.path.join(input_dir, readme), os.path.join(output_directory, 'README.md')) else: raise OutputError("No README found.") def mk_makefile(output_path, additional_files=None): readme = [f for f in os.listdir(output_path) if f.lower().startswith('readme')] if readme: if not additional_files: additional_files = [] additional_files.append(readme[0]) additional_files = ('' if not additional_files else ' '.join(additional_files)) with open(os.path.join(output_path, 'Makefile'), 'w', encoding='utf-8') as f: f.write("""# The line below is really just a fallback and only works if you have got a copy # of the tools directory at this location. It's better to set the environment # variable in your shell. FREEDICT_TOOLS ?= ../../tools DISTFILES = COPYING %s \ freedict-P5.xml freedict-P5.rng freedict-P5.dtd freedict-dictionary.css\ INSTALL Makefile NEWS # do not generate phonemes supported_phonetics = include $(FREEDICT_TOOLS)/mk/dicts.mk """ % additional_files) def reindent_xml(file_name): """Reindent an XML file with Python's minidom package. Please note that this will likely use a large amount of main memory.""" try: tree = xml.dom.minidom.parse(file_name) with open(file_name, 'w', encoding='utf-8') as f: reindented = tree.toprettyxml(indent=" ") # avoid multiple empty lines, inserted by minidom f.write('\n'.join(l for l in reindented.split('\n') if l.strip())) except MemoryError: print("Unable to reindent file using Python's minidom module.") if shutil.which('xmllint'): print("Using xmllint for reindentation.") ret = os.system('xmllint --format {0} > {0}.tmp'.format(file_name)) if ret: print("Aborting.") sys.exit(21) with open(file_name + '.tmp', encoding='utf-8') as infile: with open(file_name, 'w', encoding='utf-8') as out: out.write(infile.read()) os.remove(file_name + '.tmp') else: print('\n'.join(textwrap.wrap(("Please install xmllint as a fallback (this " "script will automatically use it) or use some other magic to " "increase the available main memory."), 78))) sys.exit(9) tools-0.6.0/importers/fd_import/tests.py000066400000000000000000000121251423006221500203740ustar00rootroot00000000000000#pylint: disable=too-many-public-methods,import-error,too-few-public-methods,missing-docstring,unused-variable,multiple-imports import unittest import tokenizer as tk from tokenizer import ChunkType class tests(unittest.TestCase): def test_commas_are_detected(self): chunks = tk.tokenize('foo, bar') self.assertEqual(len(chunks), 3) self.assertEqual(chunks[0][0], ChunkType.Word) self.assertEqual(chunks[1][0], ChunkType.Comma) self.assertEqual(chunks[2][0], ChunkType.Word) def test_semicolons_are_detected(self): chunks = tk.tokenize('foo; bar') self.assertEqual(len(chunks), 3) self.assertEqual(chunks[0][0], ChunkType.Word) self.assertEqual(chunks[1][0], ChunkType.Semicolon) self.assertEqual(chunks[2][0], ChunkType.Word) def test_vert_bar_detected(self): chunks = tk.tokenize('foo | bar') self.assertEqual(len(chunks), 3) self.assertEqual(chunks[0][0], ChunkType.Word) self.assertEqual(chunks[1][0], ChunkType.VerticalBar) self.assertEqual(chunks[2][0], ChunkType.Word) def test_parenthesized_expressions_parsed_corretly(self): tks = tk.tokenize('test (the) behaviour') self.assertEqual(len(tks), 3) self.assertEqual(tks[0][0], ChunkType.Word) self.assertEqual(tks[1][0], ChunkType.Paren) self.assertEqual(tks[1][1], 'the') self.assertEqual(tks[2][0], ChunkType.Word) # now paren at beginning tks = tk.tokenize('(the) behaviour') self.assertEqual(len(tks), 2) self.assertEqual(tks[0][0], ChunkType.Paren) self.assertEqual(tks[0][1], 'the') self.assertEqual(tks[1][0], ChunkType.Word) # now at the end tks = tk.tokenize('fond (of)') self.assertEqual(len(tks), 2) self.assertEqual(tks[0][0], ChunkType.Word) self.assertEqual(tks[1][0], ChunkType.Paren) self.assertEqual(tks[1][1], 'of') def test_braces_recognized(self): tks = tk.tokenize('test {blah} behaviour') self.assertEqual(len(tks), 3) self.assertEqual(tks[0][0], ChunkType.Word) self.assertEqual(tks[1][0], ChunkType.Brace) self.assertEqual(tks[1][1], 'blah') self.assertEqual(tks[2][0], ChunkType.Word) # now paren at beginning tks = tk.tokenize('{schnurp} xyz') self.assertEqual(len(tks), 2) self.assertEqual(tks[0][0], ChunkType.Brace) self.assertEqual(tks[1][0], ChunkType.Word) self.assertEqual(tks[0][1], 'schnurp') # now at the end tks = tk.tokenize('Fleisch {n}') self.assertEqual(len(tks), 2) self.assertEqual(tks[0][0], ChunkType.Word) self.assertEqual(tks[1][0], ChunkType.Brace) self.assertEqual(tks[1][1], 'n') def test_brackets_recognized(self): tks = tk.tokenize('test [Br.] behaviour') self.assertEqual(len(tks), 3) self.assertEqual(tks[0][0], ChunkType.Word) self.assertEqual(tks[1][0], ChunkType.Bracket) self.assertEqual(tks[1][1], 'Br.') self.assertEqual(tks[2][0], ChunkType.Word) # now paren at beginning tks = tk.tokenize('[zool.] xyz') self.assertEqual(len(tks), 2) self.assertEqual(tks[0][0], ChunkType.Bracket) self.assertEqual(tks[1][0], ChunkType.Word) self.assertEqual(tks[0][1], 'zool.') # now at the end tks = tk.tokenize('Fleisch [bot.]') self.assertEqual(len(tks), 2) self.assertEqual(tks[0][0], ChunkType.Word) self.assertEqual(tks[1][0], ChunkType.Bracket) self.assertEqual(tks[1][1], 'bot.') def test_enclosing_chars_have_precedence_over_delimiters(self): tks = tk.tokenize('(a,b;c|d/e) {a,b;c|d/e} [a,b;c|d/e]') self.assertEqual(len(tks), 3) for chunk in tks: self.assertEqual(chunk[1], 'a,b;c|d/e') # test slash tks = tk.tokenize('x /a,b;c|d/ y', parse_slash=True) self.assertEqual(len(tks), 3) self.assertEqual(tks[1][1], 'a,b;c|d') def test_paren_or_slashes_in_words_ignored(self): tks = tk.tokenize('house(s), Häuser/innen', parse_slash=True) self.assertEqual(len(tks), 3) self.assertEqual(tks[0][0], ChunkType.Word) self.assertEqual(tks[2][0], ChunkType.Word) def test_slash_within_paren_works(self): tks = tk.tokenize('(my/example)') self.assertEqual(len(tks), 1) self.assertEqual(tks[0][0], ChunkType.Paren) tks = tk.tokenize('(my/example)', parse_slash=True) self.assertEqual(len(tks), 1, "expected one element, got: " + repr(tks)) self.assertEqual(tks[0][0], ChunkType.Paren) def test_only_expressions_with_no_spaces_withing_slash_slash_parsed(self): print("===") tks = tk.tokenize('/AB/', parse_slash=True) print("---") self.assertEqual(len(tks), 1) self.assertEqual(tks[0][0], ChunkType.Slash) tks = tk.tokenize('A / B/', parse_slash=True) self.assertEqual(len(tks), 1) self.assertEqual(tks[0][0], ChunkType.Word) if __name__ == '__main__': unittest.main() tools-0.6.0/importers/fd_import/tokenizer.py000066400000000000000000000136441423006221500212530ustar00rootroot00000000000000"""Tokenizer for text-only dictionary formats. To use this library, add your dictionary importer as a subdirectory to importers/. Assuming you have an immporter in importers/foo, you would add the following snippet to your foo.py to use this module: from os.path import abspath, dirname import sys sys.path.append(dirname(dirname(abspath(sys.argv[0])))) import tokenizer For the usage, please see the functions and classes defined in this module.""" import enum import inspect class ChunkType(enum.Enum): """The tokenizer parses individual chunks and emits tuples with type information and chunk content. The first entry is the chunk type, as defined by this enum.""" Word = 0 Paren = 1 # parenthesized expressions () Bracket = 2 # brackets [] Brace = 3 # "embraced" expressions: {} Semicolon = 4 # delimiter, e.g. homonyms Comma = 5 # word/definition boundary VerticalBar = 6 # has to be surrounded by spaces, is a delimiter as e.g. comma Slash = 7 # optional; example usage include abbreviations "/ETC/" def __repr__(self): return '<%s.%s>' % (self.__class__.__name__, self.name) def space_before_slash(string, i): """The tokenizer can recognize chunks enclosed by slashes. To avoid to many false positives, only expressions with no space are supported, ATM. This functions checks whether a space is found before the next slash.""" for ch in string[i+1:]: # skip first character, is the very slash if ch.isspace(): return True elif ch == '/': return False return True # end of string reached, is like a space FRAMING = {'(': ChunkType.Paren, '[': ChunkType.Bracket, '{': ChunkType.Brace} CLOSING = {')':'(', '}':'{', ']':'['} def tokenize(source, parse_slash=False): #pylint: disable=redefined-variable-type,too-many-branches """Tokenize a line (of translations) into chunks, where each chunk is a tuple of (ChunkType, content). This only works with either the headword or the translation part, so instead of: abc - a, b; c It either parses "abc" or "a, b; c". Chunk types with no value (as for instance Comma) return a tuple with None as second element. Parenthesis have precedence over commas and ;. So a, b (c,;d) will be tokenized as [(Word, 'a'), (Comma, None), (Word, 'b'), (Paren, 'c,;d')] The prefix ChunkType has been omitted for readibility reasons. This precedence rule also means that nested structures are *not* recognized, the outer wins. This won't work: ([(blah)]) will result in [(Paren, '[(blah'), (Word, '])')] """ chunks = [] tmp_storage = [] # for unfinished chunks def save_parsed_chunk(ctype): """Save tmp_storage in chunks with the given type. This function is a short-hand.""" nonlocal chunks, tmp_storage if not all(c.isspace() for c in tmp_storage): chunks.append((ctype, ''.join(tmp_storage).strip())) tmp_storage = [] state = ChunkType.Word # current state of parser (ChunkType) prevchar = '' is_new_word = lambda: prevchar == '' or prevchar.isspace() for idx, ch in enumerate(source): if state in FRAMING.values() or (parse_slash and state == ChunkType.Slash): if ch in CLOSING and state == FRAMING[CLOSING[ch]]: save_parsed_chunk(FRAMING[CLOSING[ch]]) state = ChunkType.Word elif ch == '/' and state == ChunkType.Slash and parse_slash: save_parsed_chunk(ChunkType.Slash) state = ChunkType.Word else: # within the brace/bracket/parethesis tmp_storage.append(ch) else: # ChunkType.Word # separate a enclosed expression if preceded by whitespace / # beginning of string; handle slash separately if is_new_word() and (ch in FRAMING or (parse_slash and \ ch == '/' and not space_before_slash(source, idx))): # above tests that / is followed by characters only, no spaces # and hence no straying slashes are detected if ch == '/' and parse_slash: state = ChunkType.Slash else: state = FRAMING[ch] if tmp_storage: # add word before, if any save_parsed_chunk(ChunkType.Word) # only recognize /foo/ if parse_slash and if space in front elif ch == ',': # comma outside of parens, new word if tmp_storage: # not empty save_parsed_chunk(ChunkType.Word) chunks.append((ChunkType.Comma, None)) elif ch == ';': if tmp_storage: # not empty save_parsed_chunk(ChunkType.Word) chunks.append((ChunkType.Semicolon, None)) elif ch == '|' and (prevchar == '' or prevchar.isspace()): # space around? if tmp_storage: # not empty save_parsed_chunk(ChunkType.Word) chunks.append((ChunkType.VerticalBar, None)) else: tmp_storage.append(ch) prevchar = ch if tmp_storage: save_parsed_chunk(ChunkType.Word) return chunks def split_list(mylist, delim): """Split a list into chunks with the given delimiter. If a delimiter is a function, it will get the list item as function and has to return true if a split should occur. Returned is an iterator, yielding a chunk at a time. Examples: >>> list(split_list([1,2,3,2,4,5,2,2,10], 2)) [[1],[3],[4,5],[],10]] >>> list(split_list([])) [] >>> list(split_list([1,2,3,4], 20)) [[1,2,3,4]]""" if not mylist: return [] gathered = [] is_splitpoint = (delim if inspect.isfunction(delim) else lambda e: e == delim) for elem in mylist: if is_splitpoint(elem): yield gathered gathered = [] else: gathered.append(elem) yield gathered tools-0.6.0/importers/setup.py000066400000000000000000000006511423006221500164100ustar00rootroot00000000000000# This script installs fd_import, it does not and should not install the importer # scripts. from setuptools import setup setup(name='fd_import', version='0.2.0', description='FreeDict parser and dictionary import helpers', author='The FreeDict Developers', author_email='freedict@freelists.org', url='https://github.com/freedict/tools', license='GPL3', py_modules=['fd_import'], ) tools-0.6.0/importers/tei-p4/000077500000000000000000000000001423006221500157765ustar00rootroot00000000000000tools-0.6.0/importers/tei-p4/README000066400000000000000000000003601423006221500166550ustar00rootroot00000000000000This directory contains importer scripts which are not used anymore. These convert into the old TEI p4 format. If you feel they could be useful again, please update the output bits in those scripts and give them a directory above this one. tools-0.6.0/importers/tei-p4/dict2tei.py000077500000000000000000000200611423006221500200610ustar00rootroot00000000000000#!/usr/bin/python # Written by Petergozz, Jan 2004 # # micha137: renamed from dict2xml.py # ## todo GPL 2+ ## ##### THIS IS ALPHA LEVEL NOT FOR PRODUCTION USE ########### ####### Requires Python2.3 or later ########################### ## TODO will need TEI header > proper tei stuff too !! ## d2X_write_tei_header() (not here) ## TODO add detect for .dz files and uncompress ## (if tools on board if not .. explore the gzip modules :) ## dz is a modded version of gzip so _might_ be doable ? import sys import time import os import string import re #cool new way to do getopts :) #import optparse from optparse import OptionParser, OptionGroup # # Globals # VERSION = "-0.1.1" chatty = None app = os.path.basename(sys.argv[0]) start_time = time.asctime() # # regex defs (pre-compiles) these are used in d2x_format_xml # rex_hdwd = re.compile('^\w.*$') #Headword starts with anything not a white space rex_descpt = re.compile('^\s\s+.*$') #Description starts with more than one white space ## TODO add matches for parts of speech pronunciation etc. here hmm more command line options ... ## TODO add matches for file names here (to autogen out names) ## TODO add matches for 00-data etc for dictd headers (possibly) def d2x_getInput(): d2x_usage = '%prog -f dictfile [options]\n\n Defaults are provided for _everything_ except the dictfmt FILE to read from ' cl_parser = OptionParser(d2x_usage, version="%prog"+VERSION ) cl_parser.add_option("-f", "--file", type="string", action="store", dest="readfile", help="read dictfmt file from FILENAME" ) cl_parser.add_option("-v", "--verbose", action="store_true", dest="verbose", help="Tell me whats going on. ") cl_parser.add_option("-o", "--out", type="string", action="store", dest="writefile", default="dicttei.xml", help="write TEI/XML format file to FILENAME" ) groupDocT = OptionGroup( cl_parser, "Advanced Options for changing the DOCTYPE", "Use these to set a doctype string that works for your system") groupDocT.add_option("-s", "--dtdsys", type="string", action="store", dest="DTDsys",default='http://www.tei-c.org/Guidelines/DTD/tei2.dtd' , help="set System DTD to PATH. NB: If your not using an XML/SGML catalog system you should set this to: /your/path/to/tei2.dtd" ) groupDocT.add_option("-p", "--dtdpub", type="string",action="store", dest="DTDpub",default='-//TEI P4//DTD Main Document Type//EN', help="set public DTD to \"Formal Public Identifier\" NB: You _will_ need to quote it" ) groupDocT.add_option("-t", "--dtdtype", type="string", action="store", dest="DTDtype", default="TEI.2", help="set non default DOCTYPE [TEI.2] " ) cl_parser.add_option_group( groupDocT ) groupXML = OptionGroup( cl_parser, "Advanced options for altering the default XML header.", "Use these if you need to change the defaults. There are no single switch options for these." ) groupXML.add_option("--xmlver" , type="string", action="store", dest="XMLver", default='1.0', help="Set XML version attribute. [\"1.0\" ]" ) groupXML.add_option("--xmllang", type="string", action="store", dest="XMLlang", default='en', help="set the XML code language attribute. [en]") groupXML.add_option("--xmlstand", type="string", action="store", dest="XMLstand", default='no', help="set the XML \"standalone\" attribute. [no]") groupXML.add_option("--xmlenc", type="string", action="store", dest="XMLenc", default='utf-8', help="set the XML character ISO code attribute. [utf-8] \n ") cl_parser.add_option_group( groupXML ) ## TODO a really quiet option and a logging option and a dotfile prefs section and group the options so they don't scare the crap out of innocent bystanders. (cl_options, cl_args) = cl_parser.parse_args() #pull the exports out of the "getopt" dictFileIN = cl_options.readfile teiFileOut = cl_options.writefile dtdType = cl_options.DTDtype dtdSys = cl_options.DTDsys dtdPub = cl_options.DTDpub chat = cl_options.verbose xml_v = cl_options.XMLver xml_lang = cl_options.XMLlang xml_stand = cl_options.XMLstand xml_enc = cl_options.XMLenc # catch-me's here if len(cl_args) << 1: ## this still broken i will fix later cl_parser.error("We need at least one thing to do.\n\n Have you supplied a file name for reading ?\n <::For help type::> "+ app +" -h") elif dictFileIN == None : print app +" ::> No input file <::\n" cl_parser.print_help() sys.exit(0) else: print app +" Reading from:::> "+ dictFileIN + " <::\n" print app +" Writing to: ::> "+ teiFileOut +" <::\n" #Test for verbosity # (damm and blast this is clunky) print app+" REMINDER ::> This is Alpha level software ! <::" print app+ VERSION +" !!!!!!!!!!!! not for production use !!!!!!!!!!!!!!!!" if chat == True : print "command line options :", cl_options chatty = "Y" print "Chat mode is on" +chatty else : chatty = "N" print app +" Chat mode off" # #Now get to work #call the workhorses up # d2x_write_prolog( app, teiFileOut, dtdType, dtdPub, dtdSys, xml_v, xml_lang, xml_stand, xml_enc, chatty ) d2x_format_xml( dictFileIN, teiFileOut, chatty ) return() def d2x_write_prolog( this_app, fout, doc_t, doc_type_pub, doc_type_sys, xml_v, xml_lang, xml_stand, xml_enc, chatty ): if chatty == "Y": print "entered write prolog function" xmlfile = file(fout, "w+") if chatty == "Y": print "Writing to ::> ", xmlfile # prolog is just a concat of all the following: doc_type = '\n \n]>\n ' xml_head = '' # #So putting it all together we get # prolog = xml_head+'\n'+doc_type+'\n\n' if chatty == "Y" : print(prolog) xmlfile.write( prolog ) xmlfile.close() # this seems safer and dumps the buffer (we need a lot of ram for big files) def d2x_format_xml(fin, fout, chatty ) : """d2x_format_xml() takes a dictd format file and wraps it in TEI print dictionary xml tags. Command line options exist for most sgml, XML attributes and file in and out names. Defaults are supplied for all but the file in name. """ dictfmt = file( fin, 'r+',1 ) #open file in read and write mode line buffed only xmlfile = file( fout, 'a' ) # reopen the output file for appending # read all of dictfmt file to a list (as it only has new lines to differentiate with) dictlist = dictfmt.read() ##TODO break into 100 line (+/- 8K) blocks use seek to increment through the whole file? # now split the buffer by "2 or more new lines" dictarray = dictlist.split('\n\n') # TODO make a spinner or % readout(after you improve cache and speed) for record in dictarray: recSub1 = re.sub('^\n', "" , record)#tidy any leading newlines recSub2 = re.sub('\t', ' ', recSub1) # replace tabs with 4 spaces recSub3 = recSub2+'\n'+'
' # tag the true end sub_string = recSub3.split('\n') # #note do not strip leading space from defs (yet) # # it should now be the case that headwords start on "col one" and defs etc don't xmlfile.write('\n') for field in sub_string: if chatty == "Y": print "found field" match_H = rex_hdwd.match( field ) match_D = rex_descpt.match( field ) match_End = re.search('', field) if match_H : if chatty == "Y": print 'Headword Match found: ', match_H.group() xmlfile.write('\n
' ) xmlfile.write(match_H.group()) xmlfile.write('
') elif match_D : if chatty == "Y": print 'Description Match found: ', match_D.group() xmlfile.write( '\n') xmlfile.write( match_D.group() ) xmlfile.write ('') elif match_End : if chatty == "Y": print 'end entry' xmlfile.write ('\n') else: if chatty == "Y": print 'No match' # #detect mode of operation and gather an environment etc #we actually start from here if called to execute # if __name__ == "__main__": d2x_getInput() # NTS this is not C print app+ ": End Run" tools-0.6.0/importers/tei-p4/hd2tei.pl000077500000000000000000000031561423006221500175220ustar00rootroot00000000000000#!/usr/bin/perl # # 2002 Michael Bunk # only sgml tei! $hdfile = shift @ARGV; if ($hdfile eq "") { print stderr "Usage: hd2dict \n"; print stderr " To convert headword definition dict files to TEI format. Output\n"; print stderr " is on stdout.\n"; die; } die "Can't find file \"$hdfile\"" unless -f $hdfile; open(HDHANDLE, "<".$hdfile); while () { push(@zeilen,$_); } print " \]>\n"; print "<\?xml version='1\.0' encoding='ISO-8859-1'>\n"; print "\n\n\n".$hdfile."\n"; print " converted withhd2tei\.pl\n"; print " \n

freedict\.de\n"; # Statement aus Quelldatei uebernehmen (Zeilen vor dem ersten %h) for(@zeilen) { if (/^%h/) {last;}# Schleife abbrechen print $_; } print "

\n

".$hdfile."

\n"; print "\n"; $openentry = 0; for(@zeilen) { chop; if(/^%h/) { if ($openentry == 1) { print "\n"; $openentry = 0; } print "
".substr($_,3)."
\n "; next; }; if(/^%d/) { $openentry = 1; next; } if ($openentry == 1) { print $_;} else { print stderr "Unsinn: ".$_."\n";} } print "
\n"; close(HDHANDLE); tools-0.6.0/importers/tei-p4/tab2tei.pl000077500000000000000000000113371423006221500176750ustar00rootroot00000000000000#!/usr/bin/perl # 4/2002 Michael Bunk # This code generates sgml tei by formatting a tab delimited text file. # It is specific for khasi language, but may be a starting point for you. # features: # + Khasi-Artikel u/ka, Deutsche Artikel der/die/das # + Pluralangaben im Dt. ("--" == gleich Sg./Pl., wird bernommen) # (Kodierung in TEI ok?) # + Zusatzangabe Wortart -> half-hardcoded # wishlist: # - recognize/encode things: # - uebersetzungalternativen # - Kommentare in runden Klammern () # - what about unicode? im falle khasi-deutsch hinterher mglich # Parameter: # alle Worte der Eingangsdatei sind von dieser Wortart # ("v" = Verben, "n" - Substantive, "" gemischte Wortliste $PART_OF_SPEECH = "n"; # bersetzungsrichtung der Wortliste ("deu" oder "kha") $FIRST_LANG = "kha"; ########################################################## sub erkenne_artikel_deu { # Deutsche Artikel erkennen # Parameter: $1 - Referenz auf String ", " # $2 - Referenz auf TEI-Ausgabestring, ist "", wenn kein # Artikel erkannt wurde my $wortref = shift; my $artikelerkanntref = shift; if(${$wortref} =~ /, *(der|die|das) */) { if (${$wortref} =~ /der */) { ${$artikelerkanntref}=" m\n";} elsif (${$wortref} =~ /die */) { ${$artikelerkanntref}=" f\n";} elsif (${$wortref} =~ /das */) { ${$artikelerkanntref}=" n\n";} ${$wortref} =~ s/, *(der|die|das) *//; } else {${$artikelerkanntref}="";} } sub erkenne_artikel_kha { # Khasi-Artikel erkennen # Parameter: $1 - Referenz auf String ", " # $2 - Referenz auf TEI-Ausgabestring, ist "", wenn kein # Artikel erkannt wurde my $wortref = shift; my $artikelerkanntref = shift; if(${$wortref} =~ /, *(u|ka) */) { if (${$wortref} =~ /u */) { ${$artikelerkanntref}=" m\n";} elsif (${$wortref} =~ /ka */) { ${$artikelerkanntref}=" f\n";} ${$wortref} =~ s/, *(u|ka) *//; } else {${$artikelerkanntref}="";} } # Main $tabfile = shift @ARGV; if ($tabfile eq "") { print stderr "Usage: tab2tei \n"; print stderr " To convert tab delimited wordlists to TEI format. Output\n"; print stderr " is on stdout.\n"; die; } die "Can't find file \"$tabfile\"" unless -f $tabfile; open(TABHANDLE, "<".$tabfile); while () { push(@zeilen,$_); } print " \]>\n"; print "<\?xml version='1\.0' encoding='ISO-8859-1'>\n"; print "\n\n\n".$hdfile."\n"; print " converted withtab2tei\.pl\n"; print " \n

freedict\.de\n"; # Statement aus Quelldatei uebernehmen (Zeilen vor der ersten mit TAB) for(@zeilen) { if (/[\t]/) {last;}# Schleife abbrechen print $_; } print "

\n

".$tabfile."

\n"; print "\n"; for(@zeilen) { chomp;# \n entf. s/\r\Z//;# evtl. CR entfernen (DOS-Datei) if(/\t/) { @felder = split(/\t/,$_,2); # deu/kha-spezifisch! # "/" decodieren @sgplur = split(/\//,$felder[0]); if($sgplur[1] ne "") { $felder[0] = $sgplur[0]; } if ($FIRST_LANG eq "deu") { erkenne_artikel_deu(\$felder[0],\$artikelerkannt); erkenne_artikel_kha(\$felder[1],\$artikeltrerkannt); } else { erkenne_artikel_deu(\$felder[1],\$artikeltrerkannt); erkenne_artikel_kha(\$felder[0],\$artikelerkannt); } # remove leading/trailing whitespace $felder[0] =~ s/\A\s*//; $felder[0] =~ s/\Z\s*//; $felder[1] =~ s/\A\s*//; $felder[1] =~ s/\Z\s*//; $sgplur[1] =~ s/\A\s*//; $sgplur[1] =~ s/\Z\s*//; print "\n
".$felder[0]."\n"; print "
\n"; print " \n"; if ($PART_OF_SPEECH ne "") { print " $PART_OF_SPEECH\n"; } print $artikelerkannt; print " \n"; # give german plural form if($sgplur[1] ne "") { print "
\n"; print " ",$sgplur[1],"\n"; print "
\n"; } print " \n"; print " ",$felder[1],"\n"; print $artikeltrerkannt; print " \n
\n"; next; }; print stderr "Zeile ohne TAB: ".$_."\n"; } print "
\n"; close(TABHANDLE); tools-0.6.0/importers/tei-p4/xdf2tei.pl000077500000000000000000000116451423006221500177120ustar00rootroot00000000000000#!/usr/bin/perl -w # $Id$ # (c) Oct 2004 - July 2005 Michael Bunk # This is GPL software. # use strict; if(-t) { # we are connected to a terminal - print help print STDERR "Call me as: $0 outputfile.tei\n"; print STDERR <<'ENDOFDOCS'; This script converts an xdf file from stdin into a TEI file on stdout. You will need to prepend a TEI header and append some closing TEI tags to generate a complete TEI file. xdf was documented here: http://fdicts.com/xdf.php Now that they moved to http://dicts.info, it seems the xdf format was abandoned, since I didn't find documentation for it anymore. Short Summary ------------- # comment line word1 [tabulator] word2 [tabulator] note1 [tabulator] note2 [tabulator] translator The first note column contains information about word class. Standard are: m - masculine noun f - feminine noun n - neuter noun pl - plural noun n: - noun v: - verb adj: - adjective adv: - adverb prep: - preposition conj: - conjunction interj: - interjection Or information about the sphere (domain) where it is commony used if it is not a common word. Standard are: [abbr.] - abbreviation [fin.] - finance [myt.] - mythology [agr.] - agricultural [geo.] - geographical [phra.] - phrase [astr.] - astronomy [geol.] - geology [phy.] - physics [aut.] - automobile industry [hist.] - history [polit.] - politics [bio.] - biology [it.] - information technologies [rel.] - religion [bot.] - botany [law.] - law term [sex.] - sexual term [chem.] - chemistry [mat.] - mathematics [slang.] - slang term [chil.] - children speech [med.] - medicine [sport.] - sport term [col.] - colloquial [mil.] - military [tech.] - technology [el.] - electrotechnics [mus.] - musical term [vulg.] - vulgar term Or special notes which are specified in each xdf file they are used in. Special notes are in () braces. Example: # Comment. Special note (dv) used for derived verb work some word (dv) note for this translation John Smith Mapping into TEI ---------------- We split each line at the tabulator into 5 variables. $note1 is further analyzed: $word1 -> $word2 -> $note1 = note1a + note1b => + $note2 -> $translator ->
dog
note1a note1b word2 note2
ENDOFDOCS exit 0; } sub htmlencode { my $s = shift; $s =~ s/&/&/g; $s =~ s/\"/"/g; $s =~ s//>/g; return $s; } sub mywarn { my $headword = shift; print STDERR "$headword [$.]:\t", @_, "\n"; } my $entries = 0; while(<>) { # remove DOS line ending s/\r\n//; # remove Unicode Byte Order Mark if(/\xef\xbb\xbf/) { print STDERR "removing Byte Order Mark\n"; s/\xef\xbb\xbf//; } if(/^\s*#(.*)/) { # it would be best to put xdf comments into the header # but for this we should extract them with some grep command # and put them into the header manually print "\n"; next; } my($word1, $word2, $note1, $note2, $translator) = split /\t/; if(!defined($word1)) { mywarn '', "Empty headword (word1). Skipping."; next; } if(!defined($word2)) { mywarn '', "Empty translation (word2). Skipping."; next; } my($pos, $number); undef $pos; my $domain = ""; undef $number; my $genus = ""; if($note1) { my @notes1 = split /\s/, $note1; foreach(@notes1) { if(/^(\w+):$/) { mywarn $word1, "Part-of-Speech already set: $1 (had: $pos; note='$_')" if $pos; $pos = $1; next; } if(/^(pl)\.?$/) { mywarn $word1, "Number already set: $1 (had: $number; note='$_')" if $number; $number = $1; next; } if(/^(m|f|n)$/) { mywarn $word1, "Genus already set: $1 (had: $genus; note='$_')" if $genus; $genus = $1; next; } if(/^\[([^\.]+)\.\]$/) { mywarn $word1, "Domain already set: $1 (had: $domain; note='$_')" if $domain; $domain = $1; next; } mywarn $word1, "Unmatched part of note1: '$_'"; } } my $pos1 = ""; my $number1 = ""; my $gen1= ""; $pos1 = "$pos" if $pos; $number1 = "$number" if $number; $gen1 = "$genus" if $genus; print " \n"; print "
\n"; print " ". htmlencode($word1) ."\n"; print "
\n"; print " $pos1$gen1$number1\n" if $pos || $number || $genus; print " \n"; print " $domain\n" if $domain; print " ". htmlencode($word2) ."\n"; print " \n"; my $r=''; $r = " resp=\"". htmlencode($translator) ."\"" if $translator; print " ". (defined($note2)?htmlencode($note2):'') ."
\n" if $translator || $note2; print " \n"; $entries++; } print STDERR "Wrote $entries entries.\n"; tools-0.6.0/importers/wikdict/000077500000000000000000000000001423006221500163325ustar00rootroot00000000000000tools-0.6.0/importers/wikdict/README.md000066400000000000000000000010311423006221500176040ustar00rootroot00000000000000# WikDict TEI importer Import TEI files generated by [WikDict] into FreeDict. These dictionaries contain data originally created at Wiktionary. [WikDict]: http://www.wikdict.com ## Usage Go to the root directory where all the generated dictionaries reside (configured under the section `[generated]` in your freedictrc) and execute $FREEDICT_TOOLS/importers/wikdict/import_wikdict.py This creates new directories (or replaces existing ones) for the imported dictionaries. Then use make as for all other FreeDict dictionaries. tools-0.6.0/importers/wikdict/import_wikdict.py000077500000000000000000000201531423006221500217400ustar00rootroot00000000000000#!/usr/bin/env python3 #vim: set expandtab sts=4 ts=4 sw=4 autoindent ft=python: """This script downloads all (already approved) dictionary from the WikDict project to be included in FreeDict's repository. This script requires a FreeDict API file to figure out which dictionaries are from WikDict and which not. It searches first for a local file referenced by the FreeDict configuration. This is the preferred way. Then it downloads a version from freedict.org. """ #pylint: disable=multiple-imports import enum import html.parser import json import multiprocessing import os import re import sys import urllib.request, urllib.parse import shutil import xml.etree.ElementTree as ET from datetime import date SOURCE_URL = 'https://download.wikdict.com/dictionaries/tei/recommended/' # minimal number of words to consider a dictionary for inclusion MIN_WORD_COUNT = 10000 DOWNLOAD_PREFIX = 'http://{0.netloc}{0.path}'.format(urllib.parse.urlsplit(SOURCE_URL)) def get_fd_api(): """Read local FreeDict API file or load from given path from configuration or download it from freedict.org as fallback. It is advised to generate a fresh API copy locally. This function will try to load a virtual environment, if configured.""" js = None #pylint: disable=bare-except api_file = lambda c: os.path.join(os.path.expanduser( c['DEFAULT']['api_output_path']), 'freedict-database.json') try: import fd_tool.config as config cnf = config.discover_and_load() js = json.load(open(api_file(cnf))) except ImportError: # try to activate virtual env, if configured but not sourced yet paths = [os.path.join(os.path.expanduser("~"), '.config/freedict/freedictrc')] if os.environ.get('LOCALAPPDATA'): paths.append(os.path.join(os.environ['LOCALAPPDATA'], 'freedict/freedict.ini')) conffile = [path for path in paths if os.path.exists(path)] if conffile: import configparser cnf = configparser.ConfigParser() cnf.read_file(open(conffile[0])) if 'DEFAULT' in cnf and 'api_output_path' in cnf['DEFAULT']: js = json.load(open(api_file(cnf))) except KeyboardInterrupt: # fd_tool.config.ConfigurationError -- but we don't know whether it wasbe imported before pass if not js: from urllib.request import urlopen with urlopen('https://freedict.org/freedict-database.json') as u: js = json.loads(u.read().decode('utf-8')) # strip all non-dictionary nodes (e.g. 'software' return [node for node in js if 'name' in node] class LinkExtractor(html.parser.HTMLParser): """Extract all URL's from a HTML file. Use extract_links for a more easy-to-use function.""" def __init__(self): self.links = [] super().__init__() def handle_starttag(self, tag, attrs): if tag == 'a': attrs = dict(attrs) if 'href' in attrs: self.links.append(attrs['href']) def extract_links(from_string): """Return a list with all links contained in the HTML page passed as input parameter.""" parser = LinkExtractor() parser.feed(from_string) return parser.links def download(link): """Download given link and decode the bytes.""" try: with urllib.request.urlopen(link) as u: return u.read().decode('UTF-8') except urllib.error.HTTPError as h: if int(h.code) == 404: reason = '%s; url: %s' % (str(h), link) raise urllib.error.URLError(reason) else: raise h from None def assert_correct_working_directory(): """Check that this script is executed from the correct directory.""" num_files = sum(1 for fn in os.listdir('.') if re.search('^[a-z]{3}-[a-z]{3}$', fn)) if num_files < 2: # less than two dictionaries, probably not a dictionary root print("Error: must be run from a FreeDict source root, i.e. where all generated dictionaries are stored.") sys.exit(9) def make_changelog(path): tmpl_vars = { 'date': date.today(), 'dict': path, } with open(os.path.join(path, 'ChangeLog'), 'w') as f: f.write(""" {date} * automatic import of {dict} dictionary from WikDict """.strip().format(**tmpl_vars) + '\n') def update_dict_files(path, shared_file_path): dir_template = os.path.join( os.path.dirname(os.path.realpath(__file__)), 'template' ) shutil.rmtree(path, ignore_errors=True) os.makedirs(path, exist_ok=True) def copy(files): for file in files: try: shutil.copy2(file, path) except shutil.SameFileError: pass copy(os.path.join(shared_file_path, f) for f in ('freedict-dictionary.css', 'freedict-P5.dtd', 'INSTALL', 'freedict-P5.rng', 'freedict-P5.xml')) copy(os.path.join(dir_template, f) for f in os.listdir(dir_template)) def dict_exists_from_other_source(api_dump, dictname): dictionary = [d for d in api_dump if d['name'] == dictname] return dictionary and ('sourceURL' not in dictionary[0] or \ 'wikdict' not in dictionary[0]['sourceURL']) def enough_headwords(tei): """This function parses the TEI header and returns true if the given dictionary contains more than the minimal required number of headwords.""" tei = ET.fromstring(tei) node = tei.find('*//{http://www.tei-c.org/ns/1.0}extent') count = re.search(r'(\d+\s*,?\.?\d*)\s+.*word', node.text).groups()[0] return int(count.strip(' ,.')) >= MIN_WORD_COUNT def parse_links(): with urllib.request.urlopen(SOURCE_URL) as src: try: data = src.read().decode('utf-8') except UnicodeEncodeError: raise ValueError("Could not encode page with encoding UTF-8; please adjust manually") return (l for l in extract_links(data) if l.endswith('.tei')) class DictionaryStrategy(enum.Enum): TooSmall = 0 ManuallyEdited = 1 Imported = 2 Rubbish = 3 def import_dictionary(api, link): """Import a dictionary from WikDict and prepare dictionary directory for a release.""" if not urllib.parse.urlsplit(link)[1]: # no host in URL link = urllib.parse.urljoin(DOWNLOAD_PREFIX, link) base_name = os.path.splitext(link.split('/')[-1])[0] # name without .tei if not re.match(r'\w{3}-\w{3}', base_name): return (DictionaryStrategy.Rubbish, None) if dict_exists_from_other_source(api, base_name): return (base_name, DictionaryStrategy.ManuallyEdited) tei = download(link) if not enough_headwords(tei): return (base_name, DictionaryStrategy.TooSmall) print('Importing', base_name) if not os.path.exists(base_name): os.makedirs(base_name) # erase old files, write new ones update_dict_files(base_name, sys.argv[1]) with open(os.path.join(base_name, base_name + '.tei'), 'w', encoding='utf-8') as file: file.write(tei) make_changelog(base_name) return (base_name, DictionaryStrategy.Imported) def main(): assert_correct_working_directory() if len(sys.argv) != 2: print("Error, path to shared FreeDict files required as first argument.") sys.exit(1) if not os.path.exists(sys.argv[1]): print("Error, path does not exist",sys.argv[1]) sys.exit(2) # statistics too_small, manual = [], [] api = get_fd_api() with multiprocessing.Pool(5) as p: res = p.starmap(import_dictionary, # ↓ pair with api, see import_dictionary ((api, l) for l in sorted(parse_links()))) for action in res: dictname, action = action # what has been done with which dictionary if action is DictionaryStrategy.TooSmall: too_small.append(dictname) elif action == DictionaryStrategy.ManuallyEdited: manual.append(dictname) print("The following dictionaries were skipped because:") print("… a non-WikDict version exists:",', '.join(manual)) print("… they were too small:", ', '.join(too_small)) if __name__ == '__main__': main() tools-0.6.0/importers/wikdict/template/000077500000000000000000000000001423006221500201455ustar00rootroot00000000000000tools-0.6.0/importers/wikdict/template/COPYING000066400000000000000000000536731423006221500212160ustar00rootroot00000000000000License ======= CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS LICENSE DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE INFORMATION PROVIDED, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM ITS USE. \ THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED. BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS. 1. Definitions -------------- : a\. "**Adaptation**" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License. : b\. "**Collection**" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(f) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined below) for the purposes of this License. : c\. "**Creative Commons Compatible License**" means a license that is listed at that has been approved by Creative Commons as being essentially equivalent to this License, including, at a minimum, because that license: (i) contains terms that have the same purpose, meaning and effect as the License Elements of this License; and, (ii) explicitly permits the relicensing of adaptations of works made available under that license under this License or a Creative Commons jurisdiction license with the same License Elements as this License. : d\. "**Distribute**" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership. : e\. "**License Elements**" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, ShareAlike. : f\. "**Licensor**" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License. : g\. "**Original Author**" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast. : h\. "**Work**" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work. : i\. "**You**" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation. : j\. "**Publicly Perform**" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images. : k\. "**Reproduce**" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium. 2. Fair Dealing Rights ---------------------- Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws. 3. License Grant ---------------- Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below: : a\. to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections; : b\. to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified."; : c\. to Distribute and Publicly Perform the Work including as incorporated in Collections; and, : d\. to Distribute and Publicly Perform Adaptations. : e\. For the avoidance of doubt: : i\. **Non-waivable Compulsory License Schemes.** In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; : ii\. **Waivable Compulsory License Schemes.** In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor waives the exclusive right to collect such royalties for any exercise by You of the rights granted under this License; and, : iii\. **Voluntary License Schemes.** The Licensor waives the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License. The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved. 4. Restrictions --------------- The license granted in Section 3 above is expressly made subject to and limited by the following restrictions: : a\. You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(c), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(c), as requested. : b\. You may Distribute or Publicly Perform an Adaptation only under the terms of: (i) this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-ShareAlike 3.0 US)); (iv) a Creative Commons Compatible License. If you license the Adaptation under one of the licenses mentioned in (iv), you must comply with the terms of that license. If you license the Adaptation under the terms of any of the licenses mentioned in (i), (ii) or (iii) (the "Applicable License"), you must comply with the terms of the Applicable License generally and the following provisions: (I) You must include a copy of, or the URI for, the Applicable License with every copy of each Adaptation You Distribute or Publicly Perform; (II) You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License; (III) You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform; (IV) when You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License. : c\. If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and (iv) , consistent with Ssection 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(c) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties. : d\. Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise. 5. Representations, Warranties and Disclaimer --------------------------------------------- UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO SUCH EXCLUSION MAY NOT APPLY TO YOU. 6. Limitation on Liability. --------------------------- EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 7. Termination -------------- : a\. This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License. : b\. Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above. 8. Miscellaneous ---------------- : a\. Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License. : b\. Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License. : c\. If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. : d\. No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent. : e\. This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You. : f\. The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law. \ Creative Commons Notice Creative Commons is not a party to this License, and makes no warranty whatsoever in connection with the Work. Creative Commons will not be liable to You or any party on any legal theory for any damages whatsoever, including without limitation any general, special, incidental or consequential damages arising in connection to this license. Notwithstanding the foregoing two (2) sentences, if Creative Commons has expressly identified itself as the Licensor hereunder, it shall have all rights and obligations of Licensor. Except for the limited purpose of indicating to the public that the Work is licensed under the CCPL, Creative Commons does not authorize the use by either party of the trademark "Creative Commons" or any related trademark or logo of Creative Commons without the prior written consent of Creative Commons. Any permitted use will be in compliance with Creative Commons' then-current trademark usage guidelines, as may be published on its website or otherwise made available upon request from time to time. For the avoidance of doubt, this trademark restriction does not form part of the License. Creative Commons may be contacted at [their website](http://www.creativecommons.org). tools-0.6.0/importers/wikdict/template/Makefile000066400000000000000000000006411423006221500216060ustar00rootroot00000000000000# The line below is really just a fallback and only works if you have got a copy of the tools directory at this location. It's better to set the environment variable in your shell. FREEDICT_TOOLS ?= ../../tools DISTFILES = COPYING ChangeLog INSTALL Makefile README $(wildcard *.tei) \ freedict-P5.dtd freedict-P5.rng freedict-P5.xml freedict-dictionary.css supported_phonetics= include $(FREEDICT_TOOLS)/mk/dicts.mk tools-0.6.0/importers/wikdict/template/README000066400000000000000000000000001423006221500210130ustar00rootroot00000000000000tools-0.6.0/irc/000077500000000000000000000000001423006221500134255ustar00rootroot00000000000000tools-0.6.0/irc/README000066400000000000000000000002611423006221500143040ustar00rootroot00000000000000This is the module for the Sopel IRC bot, found on freenode/#freedict. It is not the most feature-rich and nicest bot, but it's a nice starting point. It *should* be extended. tools-0.6.0/irc/dict_lookup.py000066400000000000000000000112461423006221500163170ustar00rootroot00000000000000"""This is a quickly hacked module for the Sopel IRC bot. It executes the dict command line client to query available dictionaries. Using a proper dict library would be far better and has not been done due to time constraints. (C) 2017 Sebastian Humenda, licensed under the terms of the GPL, version 3, or any later version, published by the FSF.""" import os import random import re import subprocess import sys from sopel import module dict_pattern = re.compile(r"^[a-z]{3}-[a-z]{3}$") dict_file_pattern = re.compile(r'^freedict-([a-z]{3}-[a-z]{3}).index$') def answer(bot, trigger, msg): if trigger.is_privmsg: bot.say(msg) else: bot.reply(msg) def error(bot, msg): """Print an error message""" bot.reply(msg) def lookup(bot, language, word): if language: if not dict_pattern.search(language): error(bot, "Invalid dictionary specifier.") return if len(word) > 20: error(bot, "Sorry, this word is too long.") return proc = None if language: proc = subprocess.Popen(['dict', '-d', 'fd-' + language, word.encode(sys.getdefaultencoding())], stdout=subprocess.PIPE, stderr=subprocess.PIPE) else: proc = subprocess.Popen(['dict', word.encode(sys.getdefaultencoding())], stdout=subprocess.PIPE, stderr=subprocess.PIPE) output = proc.communicate() if proc.wait(): # try to get alternate suggestions stderr = output[1].decode(sys.getdefaultencoding()) if 'perhaps you mean' in stderr: suggestions = '; '.join(stderr.split('\n')[1:]) suggestions = suggestions[suggestions.find(':')+2:] bot.say("Sorry, entry not found, try: " + suggestions) else: bot.say("Sorry, but unfortunately this entry is not available.") return else: # strip first two lines text = output[0].decode(sys.getdefaultencoding()).lstrip() text = (l for l in text.split('\n')[3:] if l) text = [line for line in text if not line.lstrip().startswith("From ")] if len(text) > 5: # ToDo: use paste service text = text[:6] text[-1] += ' … a few more lines omitted.' if not language: text[-1] = text[-1][:-1] + ', try restricting the search by ' +\ 'specifying a dictionary.' for line in text: bot.say(line) ################################################################################ # actual triggers @module.rate(server=0.4) @module.commands('dict') def dict_lookup(bot, trigger): def report_invalid_input(): answer(bot, trigger, ("Usage: .dict [DICTIONARY] word…; try .help dict " "for an explanation.")) if not trigger.group(2): report_invalid_input() return data = trigger.group(2).split(' ') word = language = None if len(data) == 0: report_invalid_input() return elif len(data) == 1: word = data[0] else: language = data[0] word = ' '.join(data[1:]) lookup(bot, language, word) @module.rate(server=0.2) @module.commands('list') def list_dicts(bot, trigger): files = ', '.join(sorted(map(lambda d: d.groups()[0], filter(bool, map(dict_file_pattern.search, os.listdir('/usr/share/dictd')))))) answer(bot, trigger, "Available databases: " + files) @module.commands('help') def help_user(bot, trigger): args = trigger.group(2) if args: if args == '.list' or args == 'list': answer(bot, trigger, "Type .list and I will present you with a list of all FreeDict dictionaries.") elif args == '.dict' or args == 'dict': answer(bot, trigger, ("Type .dict to " "look up something in the desired dictionary (see .list).")) answer(bot, trigger, "Alternatively, you can try .dict , " + \ "but I'll only output the first matches, if too many dictionaries contain this word.") answer(bot, trigger, "For instance, why not try: " + \ random.choice(['.dict lat-deu sapientia', '.dict deu-eng Hase', '.dict fra-eng ordinateur', '.dict eng-spa house'])) else: answer(bot, trigger, "I am afraid this is something that " + \ "I am not entitled to know.") else: # general help answer(bot, trigger, ("I'm serving your dictionary queries. Try " "the command .list to get a list of dictionaries and the command " ".dict to look up a word. Use .help to get information on the usage.")) tools-0.6.0/irc/sopel.cfg000066400000000000000000000005621423006221500152330ustar00rootroot00000000000000[core] nick = fd-dict name = Helpful FreeDict Dictionary Bot host = irc.oftc.net use_ssl = true port = 6697 owner = FreeDict.org channels = #freedict enable=dict_lookup,spellcheck,wikipedia logdir=/var/lib/sopel/log db_filename=/var/lib/sopel/default.db pid_dir=/var/lib/sopel/ homedir = /var/lib/sopel [db] userdb_type = sqlite userdb_file = /var/lib/sopel/default.db tools-0.6.0/lib/000077500000000000000000000000001423006221500134165ustar00rootroot00000000000000tools-0.6.0/lib/Dict.pm000066400000000000000000000167361423006221500146540ustar00rootroot00000000000000# V1.5 5/2004 # * generate warnings on empty headwords that appear after # punctuation removal # V1.4 4/2004 # * trim leading spaces after punctuation removal from headwords # * made warnings of non-acsii characters in non utf8 index work # V1.3 2/2004 # * use LC_ALL instead of LANG environment variable # for setting locale for 'sort' # * don't use -df switches to 'sort' when generating # 00-database-utf8 headword # * do headword mangling in the same way (hopefully) like dictd # does in its tolower_alnumspace_utf8() function # V1.2 5/2003 # * gave optional argument 'locale' to open_dict() # V1.1 improved by Michael Bunk, # * This API is undocumented & cruel! But at least I added # some comments here. # # V1.0 Copyright (C) 2000 Horst Eyermann # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, visit # package Dict; use strict; use Text::Wrap;# for fill() or wrap() use Encode 'decode_utf8'; use utf8; our ($headwords, $prev_word, $start_article, $head, $fill2, $end_article, @hwords, $text, $all_text, $utf8mode); # there is CPAN module MIME::Base64 for this! but anyway, # without it we are independent our $b64_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; sub b64_encode { my $val = shift @_; my $result = ""; my $tempo = ($val & 0xC0000000) >> 30; if ( $tempo != 0) { $result = substr $b64_list, $tempo, 1; } $tempo = ($val & 0x3f000000) >> 24; if ( $result ne "" || $tempo != 0) { $result .= substr $b64_list, $tempo, 1; } $tempo = ($val & 0x00fc0000) >> 18; if ( $result ne "" || $tempo != 0) { $result .= substr $b64_list, $tempo, 1; } $tempo = ($val & 0x0003f000) >> 12; if ( $result ne "" || $tempo != 0) { $result .= substr $b64_list, $tempo, 1; } $tempo = ($val & 0x00000fc0) >> 6; if ( $result ne "" || $tempo != 0) { $result .= substr $b64_list, $tempo, 1; } $tempo = ($val & 0x0000003f); $result .= substr $b64_list, $tempo, 1; return $result; } sub open_dict{ my ($this, $name, $locale, $d, $sortcmd); $this = shift @_; $name = shift @_; $locale = shift @_; $utf8mode = shift @_; # set locale # otherwise collating sequence (sorting oder) might be # wrong (space after letters and things like that) printf STDERR "Dict.pm: Using LC_ALL: $locale\n"; $ENV{'LC_ALL'}=$locale; # open file where definitions will go open DATA, ">$name.dict"; # open a pipe and output the index in a sorted way # dictfmt uses only -df (and no options in utf8 / 8bit mode) # in bash we enter: sort -t $'\t' -k 1,1bdf # the sort options: # -t: give field separator, the TAB # -k: only use first field for sorting # b: ignore trailing blanks (might not be needed or even harmful) # df: as usual (dictionary order, ignore case) # don't give -df option when we generate 00-database-utf8 index $d = ($utf8mode) ? "" : "df"; $sortcmd = "|sort -t \"\t\" -k 1,1b".$d." >$name.index"; warn "using sort command: '$sortcmd'\n"; open INDEX, $sortcmd; if ($utf8mode) { binmode(INDEX, ":utf8"); } warn "\nopened: $name.dict and \n $name.index\n"; $headwords = 0; $prev_word = ""; $start_article = 0; $head = 0; $fill2 = ""; } sub write_newline { # directly write into data file # newlines in the INDEX don't make sense anyway print DATA "\n"; } sub set_headword { # Here a marker is set for the next entries being headwords # But how does it work for the first entry? You cannot call set_headword # before having one?? # For the first time set_headword is called, NOTHING is written to index! # At second call, first entry's headword is written. # this also means that for the very last entry to appear in the index, # after outputting the last entry we once again have to call set_headword! # couldn't this be done in a better way? yes, new api! $head = 1; $end_article = tell DATA; # Output status if (($headwords % 100) == 0) { printf STDERR "%8d Headwords.\r", $headwords } # that hwords is an array means multiple headwords # can reference the same definition! good. if(@hwords != 0) { foreach (@hwords) { # warn about non-ascii chars if not in utf8 mode if(!$utf8mode && /\P{IsASCII}/) { warn "Non ASCII char in headword: \"$_\"\n"; # btw, dictfmt quits here! } # make perl believe it is utf8 # see 'man perluniintro' if($utf8mode) { $_ = decode_utf8($_); } # generate two headwords for "00-database-*": # first with, second without "-" characters if(/^00-database-/) { print INDEX "$_\t" . b64_encode($start_article); print INDEX "\t" . b64_encode($end_article-$start_article) . "\n"; } # do headword mangling in the same way (hopefully) like dictd # does in its str.c:tolower_alnumspace_utf8() function: # if not in allchars mode (we can't be since we don't support it), # - any whitespace characters are translated to simple space # - only alphanumeric characters are considered # AlexeyCheusov> Minus should also be removed unless you use "allchars" mode. # AlexeyCheusov> In last case, ALL characters from the query should be matched. s/\s/ /g; s/[^\s\p{IsAlnum}]//g; # thereafter characters are translated to lower case (required # even in allchars mode!) $_ = lc $_; # sanity checks if(/^\s+(.+)/) { warn "\nWarning: Trimmed leading space(s) from headword '$_'\n"; $_ = $1; warn "Now it is '$_'\n"; } if(/^\s*$/) { warn "\nSkipping empty or whitespace-only headword!\n"; warn "\t\@hwords: ", @hwords, "\n"; } else { print INDEX "$_\t" . b64_encode($start_article); print INDEX "\t" . b64_encode($end_article-$start_article) . "\n"; } } } $start_article = $end_article; @hwords = (); } sub end_headword { $head = 0; } sub add_text{ $text = shift @_; # FIXME api <-> layout not independent! # connect multiple headwords with comma in dict file if (($head) && (defined $hwords[0])) { $all_text .= ", ". $text} # usual output else { $all_text .= $text}; } sub write_text{ $Text::Wrap::columns = 70; print DATA fill("", $fill2, $all_text); $all_text = ""; return if ((!defined $text) || ($text eq "")); if ($head == 1) { push @hwords, $text;# FIXME $text is an ugly global var! $headwords += 1; } # FIXME $text is an ugly global var! $text = ""; } sub add_headword{ $text = shift @_; return if ((!defined $text) || ($text eq "")); if ($head == 1) { push @hwords, $text;# FIXME $text is an ugly global var! $headwords += 1; } $text = ""; } sub write_direct{ $text = shift @_; print DATA $text; } 1; tools-0.6.0/lib/TEIHandlerxml_xml.pm000066400000000000000000000265161423006221500173060ustar00rootroot00000000000000# SAX handler module for tei2dictxml_xml converter # V1.3 10/2006 Michael Bunk # * removed trailing white space # * added error handler, so XML:ESISParser doesn't die() on warnings # V1.2 5/2004 Michael Bunk # * added option to generate 00-database-allchars header # * if reverse index is generated, 00-database-short will indicate it now # V1.1 4/2004 Michael Bunk # * when headwords contain entities, characters() # is called multiple times for the same element: # now only one call to add_headword() # will be done # V1.0 6/2003 Michael Bunk # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, visit # # thinking about event & calling order of the Dict subs (there is no other doc?): # 1. set_file_name() -> open_dict() # 2. -> set_headword() # 3. characters() -> $current_headword .= $data; # 4. end_tag -> add_headword($current_headword) # 5. -> end_headword() # 6. several start_tags -> ( write_direct() | add_text() ) # 7. several end_tags -> write_text() # 8. -> write_newline() # 9. continue with 2. for all other entrys # 10. closing the filehandles not needed # for encoding tei header we need some extra: # V1.1.: # 1. same # 2. -> set_headword(); write_text("00-database-info"); end_headword(); # 3. header start_tags -> add_text(" Info: "); # 4. tag content (if any) -> add_text($data); and occasionally save to some $var # 5. header end_tags -> add_text("\n"); # 6. -> write_text(); # set_headword(); write_text("00-database-short"); end_headword(); # write_direct($database-short); # set_headword(); write_text("00-database-url"); end_headword(); # write_direct($database-url); # 7. go on with entrys like in V1.0 (go on at 2. there) package lib::TEIHandlerxml_xml; use strict; use warnings; use lib::Dict; use utf8; # for filtering: use FileHandle; use IPC::Open2; # for sablotron: use XML::Sablotron; use XML::Sablotron::DOM; our ($header, $preLastStartTag, $lastStartTag, $database_short, $database_url, @higherElements, $file_name, $skip_header, $state, $quoted, $filtercmd, $sit, $sab, $reverse_index, $sab_templ, $current_headword); # i copied _escape() from XML::Handler::CanonXMLWriter # for escaping special chars in HTML/XML/SGML my %char_entities = ( "\x09" => ' ', "\x0a" => ' ', "\x0d" => ' ', '&' => '&', '<' => '<', '>' => '>', '"' => '"', ); sub _escape { my $string = shift; $string =~ s/([\x09\x0a\x0d&<>\"])/$char_entities{$1}/ge; return $string; } sub new { my ($type) = shift; $header = 0; $state=""; return bless {}, $type; } sub dumpTag { my $element = shift; #dump attributes my $attr=""; if(!defined $element) { die "Please make sure the input file is valid!"; } my %attrhash = %{$element->{Attributes}}; foreach(keys %attrhash) { $attr .= ' '.$_.'="'._escape($attrhash{$_}).'"'; } return "<".$element->{Name}.$attr.">"; } sub apply_filter_cmd { my ($filtercommand, $quoted) = @_; if($filtercmd ne "") { print STDERR "."; #print "calling open2 with '$filtercmd'..."; #TODO: catch errors from executed filtercmd # according to 'man perlipc' my $pid = open2(*Reader, *Writer, $filtercmd ); #print "handing over...";flush STDOUT; print Writer "$quoted\n"; close(Writer); #print "reading...";flush STDOUT; $quoted = ; while(!eof(Reader)) { $quoted .= ; }; #print "\n";flush STDOUT; waitpid $pid, 0; } elsif ($sab) { # print STDERR "+"; $sab->addArg($sit, 'input', $quoted); # print STDERR "\ncalling addArgTree..."; $sab->addArgTree($sit, 'template', $sab_templ); # print STDERR "\ncalling process..."; $sab->process($sit, 'arg:/template', 'arg:/input', 'arg:/output'); # print STDERR "\ncalling getResultArg..."; $quoted = $sab->getResultArg('arg:/output'); # print STDERR "\ngot: $quoted\n\n\n\n"; } return $quoted; } sub set_options { my ($self, $filename, $askip_header, $aGenerate00DatabaseUtf8, $aLocale, $aFilterCmd, $aStyleSheet, $aReverseindex, $aAllchars) = @_; $file_name = $filename; $file_name =~ s/^(\w*\/)+//g; $file_name =~ s/(\S*)\.\w*/$1/; $skip_header=$askip_header; $filtercmd=$aFilterCmd; $reverse_index=$aReverseindex; if ($aStyleSheet) { print STDERR "initializing Sablotron...\n"; $sab = new XML::Sablotron; $sit = new XML::Sablotron::Situation; $sab_templ = XML::Sablotron::DOM::parseStylesheet($sit, $aStyleSheet); } Dict->open_dict($file_name,$aLocale,$aGenerate00DatabaseUtf8); if($aGenerate00DatabaseUtf8) { print STDERR "Generating 00-database-utf8\n"; Dict::set_headword(); Dict::add_text("00-database-utf8"); Dict::write_text(); Dict::end_headword(); # might not be needed at all, but we give a hint Dict::write_direct("\n "); Dict::write_direct("This dictionary is UTF-8 encoded. If you use dictd, ". "make sure to start it with the appropriate --locale option."); Dict::write_newline(); } if($aAllchars) { print STDERR "Generating 00-database-allchars\n"; Dict::set_headword(); Dict::add_text("00-database-allchars"); Dict::write_text(); Dict::end_headword(); Dict::write_newline(); } } sub start_element { my ($self, $element) = @_; my $part = $element->{Name}; $preLastStartTag = $lastStartTag; $lastStartTag = $part; # for multiple orths at beginning of
if (!$reverse_index && ($Dict::head) && ($part ne "orth")) { Dict::end_headword() }; # see what tag we have (& remember XML is case sensitive!) # most frequent tags on top if (($part eq "entry") || ($part eq "teiHeader")) { $quoted = ""; $state = "quoting"; } elsif ($part eq "orth" && !$reverse_index) { if ($preLastStartTag eq "form") {Dict::set_headword()} $state = "orth";$current_headword=''; } elsif ($part eq "tr" && $reverse_index) { if (!$Dict::head) {Dict::set_headword()} $state = "tr";$current_headword=''; } if ($part eq "revisionDesc") { # add myself as # # now # tei2dict_xml.pl # converted TEI database into .dict format # my $now_string = gmtime(); my $s .= "\n $now_string\n"; $s .= " tei2dictxml_xml.pl\n"; $s .= " converted TEI database into .dict format\n"; $quoted .= dumpTag($element) . $s; # printf STDERR "new revisionDesc: $quoted"; } elsif (($state eq "quoting") || ($state eq "orth") || ($state eq "tr")) { $quoted .= dumpTag($element); } elsif (($part eq "text") || ($part eq "body") || ($part eq "TEI.2")) { # skip these tags without warnings } else { print STDERR "warning: skipping start tag: $part\n" } } sub characters { my ($self, $element) = @_; my $data = $element->{Data}; # FIXME: maybe with other parser than ESISParser? # my guess: sometimes our loved ESISParser calls this handler # while it shouldn't: to notify us of a line break and white # space. there are handlers end_record() and white_space() for # this. anyway: since we don't have
-element where newlines
    # are important inside, we just replace all \n by ' '
    # then all multiple white space by single ' '
    # then remove all ' ' at beginning or end
    # and skip everything if we get empty $data

    # do some whitespace stripping:
    return if ($data =~ /\A\s*\Z/);

    $data =~ s/\n/ /;
    $data =~ s/\s+/ /;

    if ($data eq "") {
       # can happen because of newlines
       return;
       }

    if (($state eq "quoting") ||
        ($state eq "orth") ||
	($state eq "tr")) {
	$quoted .= _escape($data);
	}

    if    (($preLastStartTag eq "titleStmt") &&
           ($lastStartTag    eq "title")) { $database_short = $data }
    elsif ($lastStartTag     eq "pubPlace") { $database_url=$data }

    if ($reverse_index && ($lastStartTag eq "tr")) {
	$current_headword .= $data;
	}

    elsif (!$reverse_index && ($lastStartTag eq "orth")) {
       if (($preLastStartTag eq "form") ||
           ($preLastStartTag eq "orth")) {
	      #warn "headwords add_text: '$data'\n";
	      $current_headword .= $data;
	     }
       else {
         print STDERR "multiple -s only at beginning of  supported. Skipping the others!\n" }
       }
}


sub end_element {
    my ($self, $element) = @_;
    my $part  = $element->{Name};

    pop @higherElements;



    if (($state eq "quoting") || ($state eq "orth") || ($state eq "tr")) {
	$quoted .= "\n";
	}

    if ($part eq "orth") {
	Dict::add_headword($current_headword);
	# Dict::end_headword() has to be called later, because we don't know now
	# if there is another orth coming (multiple headwords for one entry)
	if($state eq "orth") {$state = "quoting"};
	}

    if ($part eq "tr") {
	Dict::add_headword($current_headword);
	# Dict::end_headword() has to be called later, because we don't know now
	# if there is another tr coming
	if($state eq "tr") {$state = "quoting"};
	}

    elsif ($part eq "entry") {
        if ($reverse_index && $Dict::head) { Dict::end_headword() };

	$quoted = apply_filter_cmd($filtercmd,$quoted);

	Dict::write_direct($quoted);
	Dict::write_newline();
	$state = "";
	}

    elsif (($part eq "teiHeader")) {
	return if $skip_header;

	$state = "";

	Dict::write_text();
	Dict::write_newline();

	# 00-database-short
	Dict::set_headword(); Dict::add_text("00-database-short");
	Dict::write_text(); Dict::end_headword();
	Dict::write_direct("\n  ");
	Dict::write_direct($database_short);
	if($reverse_index)
	{
	  Dict::write_direct(' [reverse index]');
	}
	Dict::write_newline();

	## 00-database-info
	Dict::set_headword();
	$Dict::text="00-database-info";
	Dict::write_text();
	Dict::end_headword();

	$quoted = apply_filter_cmd($filtercmd,$quoted);

	Dict::add_text($quoted);# will do wrapping
	Dict::write_text();
	Dict::write_newline();

	# FIXME who defined 00-database-url and where is it used? it seems not in kdict
	Dict::set_headword(); Dict::add_text("00-database-url");
	Dict::write_text(); Dict::end_headword();
	Dict::write_direct(" ");
	Dict::write_direct($database_url);
	Dict::write_newline();

	} # ^header

    elsif ($part eq "body") {
	Dict::set_headword();# to have the last headword appear in INDEX
	};

}

sub error
{
  my ($self, $args) = @_;
  if ($args->{'Message'} =~ /^.+:.+:\d+:W:/)
  {
    # print warnings
    print STDERR "Warning from XML::ESISParser: " . $args->{'Message'} . "\n";
    return
  }
  use Data::Dumper;
  print STDERR Dumper \@_;
  print STDERR "Error from XML::ESISParser: " . $args->{'Message'} . "\n";
  exit 3;
}

1;
tools-0.6.0/mark_dubious_translations.pl000066400000000000000000000055741423006221500205050ustar00rootroot00000000000000#!/usr/bin/perl

use strict;
use utf8;
use LWP::Simple;
use Getopt::Std;

our($opt_l, $opt_s, $opt_m,$opt_h);
getopts('l:s:m:h');


sub HELP_MESSAGE
{
  print < [-s ] [-m ] [-h] [in_file [out_file]]

This script scans .tei dictionary for translations values marked with 
tags, and tries to query http://en.wiktionary.org online dictionary for articles
titled as value found between . If article is found and has a
section  inside, then this translation is considered to be ok.
Otherwise translation is considered to be dubious and marked with a  sign
at the beginning to the line.

Then you can use `diff -u10` and deal with dubious translation only.

This script can be useful if you start maintaining buggy dictionary and want to
eliminate typos and misspelling.


Options:

-h	help & exit
-l	language name as used in en.wiktionary.org section names. I.e. "Russian"
-s	sleep time between fetches in seconds, in order not to DDoS wiktionary.
	Default is 3
-m	a symbol for marking dubious translations. Default is '☹'

Usage examples:

  $0 -l Russian eng-rus.tei eng-rus.tei.marked
  
  $0 -l Russian eng-rus.tei.marked

Author:
  Nikolay Shaplov , 2014

License:
  GNU General Public License ver. 2.0 and any later version.

EOT
  exit
}

HELP_MESSAGE if $opt_h or (!$opt_l);

my $target_lang = $opt_l;
my $sleep = $opt_s || 3;
my $error_mark = $opt_m || '☹'; # do not use symblos that might be insde your dictionary!!!

my $in;
if ($ARGV[0])
{
  open $in, "<:utf8" , $ARGV[0] or die "cannot open ".$ARGV[0]." $!";
} else
{
  binmode(STDIN, ":utf8");
  $in = *STDIN;
}
my $out;
if ($ARGV[1])
{
  open $out, "<:utf8" , $ARGV[1] or die "cannot open ".$ARGV[1]." $!";
} else
{
  binmode(STDOUT, ":utf8");
  $out = *STDOUT;
}

binmode(STDERR, ":utf8");

my $buf = "";

my $text = "";
while (my $s = <$in>)
{
  $text.= $s;
}

my $res = "";
while ($text=~s{^(.*?)()(.*?)()}{}s)
{
  my $ok = 1;
  my $header = $1;
  my $open = $2;
  my $def = $3;
  my $close = $4;
  
  print STDERR $def;
  
  my $content = get("http://en.wiktionary.org/wiki/$def");
  
  if ( !($content =~/

$target_lang/s )) { $ok = 0; $open =~ s/ 1) { my $s = shift @l; if ($s =~ s/$error_mark//g) { $s=$error_mark.$s; } print $out $s, "\n"; } $buf = shift @l; }tools-0.6.0/mk/000077500000000000000000000000001423006221500132575ustar00rootroot00000000000000tools-0.6.0/mk/README.md000066400000000000000000000033551423006221500145440ustar00rootroot00000000000000# Build System Explanation ----------- This directory contains make include files: - `config.mk`: This file contains common variables, used within all other make files. It also defines the help system, invoked with `make help`. - `dicts.mk`: This file is included by every dictionary Makefile and defines all the targets to work with a dictionary, e.g. building a release tar ball, validating its contents or exporting to other formats. - `dictroot.mk`: Only the top-level makefile of a directory with multiple dictionaries should include this file (e.g. the root of the dictionary git repository). It defines rules to build all dictionaries at once and to install and uninstall them. More information about these files can be found in chapter 8 of the Freedict HOWTO. Platforms --------- To add a new platform or delete an existing one, the following parts have to be touched: - The variable `available_platforms` lists all defined platforms, so adding or removing a a name from there will automatically remove the dictionary from the build and the release target (and from a few more). - Each platform needs to provide a build-PLATFORM and a release-PLATFORM rule, where PLATFORM is replaced by the name present in `available_platforms`.\ Ideally, the release-PLATFORM target depends on the distribution archive, so that the build system can infer whether a release has been build. It's best to use `$(BUILD_DIR)/PLATFORM/freedict-lg1-lg2-VERSION-PLATFORM.extension``. lg1-lg2 is the identifier for the dictionary and the VERSION is available in the variable with the same name.\ Note: an exception is the dictd format, which builds to without the -PLATFORM suffix for historical reasons. tools-0.6.0/mk/config.mk000066400000000000000000000124301423006221500150550ustar00rootroot00000000000000# This file defines common configuration variables shared across all Makefiles. # Furthermore, it defines a help target which makes the buildsystem # self-documenting. SHELL=bash # set all as default target, so that help is not executed by default .DEFAULT_GOAL := all # `xsltproc' comes with libxml/libxslt, see xmlsoft.org XSLTPROCESSOR ?= xsltproc XSLTPROCESSORARGS += --novalid --xinclude DICTFMTFLAGS ?= --utf8 # set default value FREEDICT_TOOLS ?= . # where built files should get installed PREFIX?=/usr/local DESTDIR ?= # name of the nsgmls command NSGMLS ?= onsgmls # the file xml.soc file comes with sp/opensp/openjade # and is required by nsgmls XMLSOC ?= /usr/share/xml/declaration/xml.soc # xml.soc normally points to this file XMLDECLARATION ?= /usr/share/xml/declaration/xml.dcl #XMLDECLARATION ?= /usr/share/xml/declaration/xml1n.dcl # you can also set this in ~/.bashrc SGML_CATALOG_FILES ?= /etc/sgml/catalog DICTFMT = $(shell command -v dictfmt 2> /dev/null) ifeq ($(DICTFMT),) $(warnrning The command dictfmt was not found, you will not be able to convert into the dictd format.) endif charlint_in_path := $(shell which charlint.pl 2>/dev/null) ifneq ($(charlint_in_path), ) CHARLINT := $(charlint_in_path) else # if this doesn't exist it will be downloaded CHARLINT := $(FREEDICT_TOOLS)/charlint.pl endif CHARLINT_DATA := $(dir $(CHARLINT))charlint-unicodedata MBRDICO_PATH = / BUILDHELPERS_DIR=$(FREEDICT_TOOLS)/buildhelpers/ # Directory containing all build files, usually a tree like build/ # within the current working directory. BUILD_DIR=build RELEASE_DIR=$(BUILD_DIR)/release # script to restart the dictd daemon, if installed DICTD_RESTART_SCRIPT = $(BUILDHELPERS_DIR)dict_restart_helper.sh ################################################################################ # FreeDict configuration # Use FREEDICTRC to discover a configuration. If the variable is empty, no # configuration was found. FREEDICTRC = $(wildcard $(HOME)/.config/freedict/freedictrc $(LOCALAPPDATA)/freedict/freedict.ini) ################################################################################ # Python special handling # First, the interpreter is queried for the correct version (and name). Then the # setup for a virtual environment is done. For the "end-user", all that matters # should be the exc_pyscript function, e.g.: # # foo: # $(exc_pyscript) fd_api --what-ever # find interpreter with matching name, python3 or python? PYTHON := $(shell command -v python3 2> /dev/null) ifeq ($(PYTHON),) PYTHON := $(shell command -v python 2> /dev/null) ifeq ("$(PYTHON)","") $(error No Python executable found, please make sure that a recent Python is in the search path) endif endif # query version PYTHON_VERSION_FULL := $(wordlist 2,4,$(subst ., ,$(shell $(PYTHON) --version 2>&1))) PYTHON_VERSION_MAJOR := $(word 1,${PYTHON_VERSION_FULL}) ifneq ($(PYTHON_VERSION_MAJOR),3) $(error a python Version >= 3.4 is required, current python major is $(PYTHON_VERSION_MAJOR)) endif # exc_pyscript is meant to either call a system-wide installed version or one # from a virtual env. The latter needs to be specified in the FD configuration, # documented in the wiki. # This is a fallback for system-wide script usage: exc_pyscript = $(1) $(2) $(3) $(4) $(5) $(6) $(7) $(8) $(9) # find a (python) virtual env # ToDo: look at detection algorithm FREEDICTRC = $(HOME)/.config/freedict/freedictrc ifneq ($(wildcard $(FREEDICTRC)),) VIRTUAL_ENV=$(shell grep -E 'virtual_env.*=.*' < $(FREEDICTRC) | cut -d = -f 2\ |tr -d ' ') # if virtual env found ifneq ($(VIRTUAL_ENV),) ifneq ($(wildcard $(VIRTUAL_ENV)),) # call python from the virtual environment exc_pyscript = source $(VIRTUAL_ENV)/bin/activate; \ $(1) $(2) $(3) $(4) $(5) $(6) $(7) $(8) $(9) endif endif endif ################################################################################ # Remote file handling # These two functions are meant to be used within a rule (combined as *one* # shell line) to remember state. If remote volumes were mounted before, don't # umount them, otherwise, do. Example: # # foo: # $(call mount_or_reuse); mycommand; $(call umount_or_keep) mount_or_reuse=$(call exc_pyscript,fd_file_mgr,-m); \ if [ $$? -eq 201 ]; then \ STAY_MOUNTED=1; \ else \ STAY_MOUNTED=0; \ fi umount_or_keep = \ if [ $$STAY_MOUNTED -eq 0 ]; then \ $(call exc_pyscript,fd_file_mgr,-u); \ fi ################################################################################ # Define the help system, use #! after the colon of a rule to add a # documentation string help: @echo "Usage: make " @echo "The following commands are defined:" @echo @IFS=$$'\n'; \ help_lines=`fgrep -h "#!" $(MAKEFILE_LIST) | fgrep -v fgrep | fgrep -v help_line | grep -v 'Define the' | sed -e 's/\\$$//' | sort`; \ for help_line in $${help_lines[@]}; do \ help_command=`echo $$help_line | sed -e 's/^\(.*\): .*/\1/' -e 's/^ *//' -e 's/ *$$//' -e 's/:$$//'`; \ help_info=`echo $$help_line | sed -e 's/.*#!\(.*\)$$/\1/' -e 's/^ *//' -e 's/ *$$//'`; \ printf "%-19s %s\n" $$help_command $$help_info; \ done;\ if [ -n `echo $$HELP_SUFFIX|wc -w` ]; then \ echo;\ printf "%s\n" $$HELP_SUFFIX; \ fi # NOTE: If you want to add a HELP_SUFFIX, you have to export the variable as # environment variable. # Note II: wc -w is necessary, because HELP_SUFFIX might be a multi-line string tools-0.6.0/mk/dictroot.mk000066400000000000000000000031151423006221500154370ustar00rootroot00000000000000# This makefile snippet contains all commands which can be performed in a # directory containing many dictionary (a dictionary root). # The subdirectories have to be called "lg1-lg2" (so the usual dictionary naming # conventions). FREEDICT_TOOLS ?= . include $(FREEDICT_TOOLS)/mk/config.mk # this shows that this makefile include may only be used for a directory # containing many dictionaries DICTS=$(shell find . -maxdepth 1 -name '???-???' -printf "%P\n"|sort|xargs echo) # A target per dictionary, i.e. validate-DICTNAME. VALIDATION_TARGETS=$(foreach DICT,$(DICTS),validate-$(DICT)) # Calls default target for each dictionary module. # Note: This is a conflict if you wanted to call # the 'all' target of each dictionary module. all: #! build all dictionaries (default) all: build_all build_all: $(DICTS) $(DICTS): @$(MAKE) -C $@ install: #! install the built files, without attempting to restart any applications using them install: build_all echo Installing $(DICTS) @set -e; for dict in $(DICTS); do \ $(MAKE) -C $$dict install; \ done install-restart: #! install built dictionaries and attempt to restart applications using them install-restart: install-core @$(DICTD_RESTART_SCRIPT) || echo "Please make sure to run this command as root" # ToDo uninstall: $(BUILD) clean:: for DICT in $(DICTS); do \ $(MAKE) -C $$DICT clean; \ done validation: #! validate all dictionaries in this directory validation: $(VALIDATION_TARGETS) $(VALIDATION_TARGETS): @$(MAKE) -C $(patsubst validate-%,%,$@) validation .PHONY: install uninstall api all clean build_all $(DICTS) $(VALIDATION_TARGETS) tools-0.6.0/mk/dicts.mk000066400000000000000000000363071423006221500147270ustar00rootroot00000000000000# This file contains all targets defined for a dictionary. It is sourced by its # Makefile. # It defines targets to convert (build) the TEI # files to the supported output formats. It also features some release targets # used for making a release in FreeDict. "install" and "uninstall" targets are # provided, too. include $(FREEDICT_TOOLS)/mk/config.mk export HELP_SUFFIX define HELP_SUFFIX = NOTE: For the targets `release` and `build`, there are targets called `release-PLATFORM` and `build-PLATFORM` available, if you just want to build one platform. endef ####################### #### Common variable definitions ####################### ifeq ($(origin UNSUPPORTED_PLATFORMS), undefined) UNSUPPORTED_PLATFORMS = evolutionary endif available_platforms := src dictd slob dictname ?= $(shell basename "$(shell pwd)") xsldir ?= $(FREEDICT_TOOLS)/xsl TEIADDPHONETICS := $(FREEDICT_TOOLS)/teiaddphonetics XMLLINT := /usr/bin/xmllint XSLTPROCESSORARGS += $(strip --stringparam dictname $(dictname) \ --path $(dir $(abspath $(dictname).tei))) version1 := $(shell sed -e '100q;//!d;s/.*\(.*\)<\/edition>.*/\1/;q'\ $(wildcard $(dictname).tei)) version := $(subst $(space),,$(version1)) # these files are included in each of the platform releases which are *not* a # source release. DISTFILES_BINARY = $(foreach f, README README.md README.txt README.rst \ COPYING COPYING.txt COPYING.md COPYING.rst LICENSE \ LICENSE.txt LICENSE.md LICENSE.rst \ INSTALL INSTALL.md INSTALL.rst INSTALL.txt, \ $(wildcard $(f))) # only consider these files if they do exist PREFIX ?= /usr DESTDIR ?= ################ # Common Function Definitions ################ # Helper function to retrieve the release path. We cannot declare the value # statically, because it is only required for the deploy target and this is only # executed by admins. The first argument is "optional". deploy_to = $(shell $(MAKE) --no-print-directory -C $(FREEDICT_TOOLS) release-path)/$(1) # This function assists the release-% rules. It generates the release path for # each platform; Arg1: platform gen_release_path = $(RELEASE_DIR)/freedict-$(dictname)-$(version).$(if \ $(findstring slob,$(1)),slob,$(1).tar.xz) gen_release_hashpath = $(call gen_release_path,$(1)).sha512 # dictionary source file -- normally just $(dictname).tei, but can be # overwritten e.g. by the phonetics generator dict_tei_source = $(dictname).tei ####################### #### Phonetics import #### #### This needs to come before all others, so that the relevant functions are #### defined correctly. ####################### source_lang = $(firstword $(subst -, ,$(dictname))) # dictionary authors may set `supported_phonetics_lang = 2` and skip the check; # this should only be used in circumstances where the build system fails to work # with the generated phonetics ifeq ($(supported_phonetics_lang),) phoneme_generation_supported=$(shell $(TEIADDPHONETICS) --supports-lang $(source_lang) 2> /dev/null;echo $$?) ifneq ($(phoneme_generation_supported),0) $(warning Unable to run teiaddphonetics, phoneme generation disabled. Check installed dependencies.) endif ifeq ($(phoneme_generation_supported),0) # supported language dict_tei_source = build/tei/$(dictname)-phonetics.tei $(BUILD_DIR)/tei: mkdir -p $@ # Create a copy containing IPA phonetic information $(call dict_tei_source): $(dictname).tei mkdir -p $(dir $@) $(TEIADDPHONETICS) -o $@ $< endif endif ################ # General targets (default target, maintenance targets) ################ all: #! convert the TEI XML source into all supported output formats (see list-platforms) all: build build: #! same as all, build all available output formats build: $(foreach platform,$(available_platforms),build-$(platform) ) $(RELEASE_DIR): mkdir -p $@ changelog-help: @$(call exc_pyscript,fd_changelog,-h) changelog: #! launch a script to assist in updating a TEI header for next release, try changelog-help for usage help changelog: $(dictname).tei @$(call exc_pyscript,fd_changelog,${E},$<) # This is a "double colon rule", allowing you to extend this rule in your own # makefile. # For example: # # clean:: # -rm -f delete_this_file.too clean:: #! clean build files rm -rf build rm -f valid.stamp deploy: #! deploy all platforms of a release to the remote file hosting service deploy: $(foreach r, $(available_platforms), release-$(r)) @MOUNTED=0; \ if command -v mountpoint &> /dev/null; then \ if mountpoint -q "$(deploy_to)"; then \ echo "Remote file system mounted, skipping this step."; \ else \ $(MAKE) --no-print-directory -C $(FREEDICT_TOOLS) mount; \ MOUNTED=1; \ fi; \ else \ $(MAKE) --no-print-directory -C $(FREEDICT_TOOLS) mount; fi; \ if [ ! -d "$(call deploy_to,$(dictname))" ]; then \ echo "Creating new release directory for first release of $(dictname)"; \ mkdir -p $(call deploy_to,$(dictname)); fi; \ if [ -d $(call deploy_to,$(dictname)/$(version)) ]; then \ if [ "${FORCE}" = "y" ]; then \ echo "Enforcing deployment…"; \ else \ echo "Release $(version) has been deployed already. Use \`make FORCE=y deploy\` to enforce the deployment."; \ exit 2; fi; \ else \ mkdir -p $(call deploy_to,$(dictname)/$(version)); fi; \ chmod a+r $(foreach p,$(available_platforms), $(call gen_release_path,$(p))); \ echo "Copying files…";\ cp $(foreach p,$(available_platforms), $(call gen_release_path,$(p))) \ $(call deploy_to,$(dictname)/$(version)); \ cp $(foreach p,$(available_platforms), $(call gen_release_hashpath,$(p))) \ $(call deploy_to,$(dictname)/$(version)); \ if [ $$MOUNTED -eq 1 ]; then \ $(MAKE) --no-print-directory -C $(FREEDICT_TOOLS) umount; \ fi find-homographs: #! find all homographs and list them, one per line find-homographs: $(dictname).tei @export HOMS="`cat $< | grep orth | sed -e s:'[ ]*':'':g -e s:'<\/orth>':'':g | sort -f | uniq -i -d`"; \ if [ -n "$$HOMS" ]; then \ echo "Found `echo "$$HOMS"|wc -l` homographs"; \ echo "$$HOMS" | tr '\n' ',' | sed 's/,/, /g' | fold -s; fi list-platforms: #! list all available platforms, AKA output formats @echo -n $(available_platforms) %.sha512: % cd $(dir $^); \ sha512sum $(notdir $^) > $(notdir $@) print-unsupported: #! print unsupported platforms @echo -n $(UNSUPPORTED_PLATFORMS) pos-statistics: #! print statistics about the number of the different part-of-speech tags used pos-statistics: $(dictname).tei @grep -o ".*" $< | perl -p -e 's/(.*)<\/pos>/$$1/;' | sort | uniq -c |sort -b -g # Query platform support status # This yields an exit status of # 0 for dict supported on this platform # 1 for dict unsupported on this platform # 2 FOR unknown platform query-%: #! query platform support status; 0=dictd supported, 1=dictd unsupported, 2=UNKNOWN platform @if [ -z "$(findstring $*,$(available_platforms))" ]; then \ echo "Unknown platform: $*"; exit 2; fi @if [ -n "$(findstring $*,$(UNSUPPORTED_PLATFORMS))" ]; then \ echo "Platform $* does not support this dictionary module."; exit 1; fi @echo "Platform $* supports this dictionary module." release: #! build releases for all available platforms release: $(foreach platform,$(available_platforms),release-$(platform)) version: #! output current (source) version number @echo $(version) ################################################################################ #### Quality Assurance Helpers ################################################################################ # Each helper HAS TO output an error code and each of the error codes need to # have an explanation in $FREEDICT_TOOLS/mk/qa. describe: #! `make describe E=` explains CODE, as printed by `make qa` @if [ -f "$$FREEDICT_TOOLS/mk/qa/${E}.md" ]; then \ cat "$$FREEDICT_TOOLS/mk/qa/${E}.md"; \ else \ echo "No such error code."; \ fi has_venv = $(if $(wildcard $(VIRTUAL_ENV)),,has-venv) has-venv: @echo "No python virtual environment found (possibly causing the errors above)."; \ echo "Read $$FREEDICT_TOOLS/README or execute "; \ echo 'make -C $$FREEDICT_TOOLS mk_venv-help' # This is a makefile-internal rule. It detects problems (duplicated entries or # empty nodes) within TEI files and prints a warning, if appropriate. By default # it does not fail when an issue is encountered to allow the continuation of # parent targets as `qa`. However, if used from a script, the a non-zero exit # code can be enforced on error by supplying EXIT=y report-duplicates: report-duplicates: $(dictname).tei @$(call exc_pyscript,rm_duplicates,-s,$<) || \ (if [ "${EXIT}" = "y" ]; then exit 1; fi) qa: #! execute quality assurance helpers, use make explain E='NUM' if an error occurs qa: report-duplicates validation | $(call has_venv) rm_duplicates: #! remove duplicated entries and empty XML nodes and present a diff of the changes rm_duplicates: $(dictname).tei @$(call exc_pyscript,rm_duplicates,$<) validation: #! validate dictionary with FreeDict's TEI XML subset validation: $(dictname).tei @$(XMLLINT) --noout --xinclude --relaxng freedict-P5.rng $< ###################################################################### #### Dict(d) format as used by the Dictd server and other programs ###################################################################### BUILD_DICTD=$(BUILD_DIR)/dictd $(BUILD_DICTD)/$(dictname).c5: $(call dict_tei_source) \ $(xsldir)/tei2c5.xsl $(xsldir)/inc/teientry2txt.xsl \ $(xsldir)/inc/teiheader2txt.xsl \ $(xsldir)/inc/indent.xsl @mkdir -p $(dir $@) $(XSLTPROCESSOR) $(XSLTPROCESSORARGS) $(xsldir)/tei2c5.xsl $< >$@ build-dictd: $(BUILD_DICTD)/$(dictname).dict.dz $(BUILD_DICTD)/$(dictname).index $(BUILD_DICTD)/%.dict $(BUILD_DICTD)/%.index: $(BUILD_DICTD)/%.c5 query-dictd cd $(BUILD_DICTD) && \ dictfmt --without-time -t --headword-separator %%% $(DICTFMTFLAGS) $* < $(notdir $<) $(BUILD_DICTD)/%.dict.dz: $(BUILD_DICTD)/%.dict dictzip -k $< # prevent make from removing our precious file .PRECIOUS: $(BUILD_DICTD)/$(dictname).dict # build release archive $(call gen_release_path,dictd): $(BUILD_DICTD)/$(dictname).dict.dz $(BUILD_DICTD)/$(dictname).index tar --dereference --transform='s/build.dictd.//' -C .. -cJf $@ \ $(addprefix $(notdir $(realpath .))/, $^) \ $(addprefix $(notdir $(realpath .))/, $(DISTFILES_BINARY)) release-dictd: $(RELEASE_DIR) $(call gen_release_path,dictd) \ $(call gen_release_hashpath,dictd) ###################################### #### targets for evolutionary platform ###################################### date=$(shell date +%Y-%m-%d) install-base: $(BUILD_DICTD)/$(dictname).dict.dz $(BUILD_DICTD)/$(dictname).index install -d $(DESTDIR)/$(PREFIX)/share/dictd install -m 644 $^ $(DESTDIR)/$(PREFIX)/share/dictd install: #! install the dictionary install: install-base @echo -n 'Sucessfully installed the dictionary. Use `sudo dictdconfig -w` and ' @if command -v systemctl 2>&1 > /dev/null; then \ echo -n '`systemctl restart dictd`'; \ elif command -v service 2>&1 > /dev/null; then \ echo -n '`service dictd restart`'; \ else \ echo -n 'restart your dictd server'; \ fi @echo ' to make use of the new dictionary.' install-restart: #! same as install, but also restart the dict daemon install-restart: install-base sh $(FREEDICT_TOOLS)/buildhelpers/dict_restart_helper.sh uninstall: #! uninstall this dictionary -rm $(DESTDIR)/$(PREFIX)/share/dictd/$(dictname).dict.dz $(DESTDIR)/$(DESTDIR)/$(dictname).index $(DICTD_RESTART_SCRIPT) ################################################################################ #### Source 'platform' ################################################################################ # put all sources of a dictionary module into a tarball for release # ("distribution"). this only includes the .tei file if it doesn't have to be # generated from other sources $(call gen_release_path,src): $(RELEASE_DIR) $(DISTFILES) tar --dereference -C .. -cJf $@ \ $(addprefix $(notdir $(realpath .))/, $(DISTFILES)) # empty rule to fit into build system (build-) build-src: $(dictname).tei release-src: $(call gen_release_path,src) $(call gen_release_hashpath,src) ################################## #### targets for StarDict platform ################################## # This tool comes with stardict DICTD2DIC ?= dictd2dic # This is hardcoded into dictd2dic :( stardict_prefix = dictd_www.dict.org_ # idxhead is required to preexist by dictd2dic. The reason is not documented. $(dictname).idxhead: echo -n "" > $@ $(stardict_prefix)$(dictname).idx $(stardict_prefix)$(dictname).dict.dz \ dictd2dic.out: $(dictname).index $(dictname).dict $(dictname).idxhead $(DICTD2DIC) $(dictname) >dictd2dic.out gzip -9 $(stardict_prefix)$(dictname).idx # $(wordcount) and $(idxfilesize) are a target-specific variables $(stardict_prefix)$(dictname).ifo: \ wordcount=$(word 2, $(shell tail -n1 dictd2dic.out)) $(stardict_prefix)$(dictname).ifo: \ idxfilesize=$(strip $(shell zcat $(stardict_prefix)$(dictname).idx | wc -c)) $(stardict_prefix)$(dictname).ifo: $(stardict_prefix)$(dictname).idx \ dictd2dic.out authorresp.out title.out sourceurl.out @echo "Generating $@..." @echo "StarDict's dict ifo file" > $@ @echo "version=2.4.2" >> $@ @echo "wordcount=$(wordcount)" >> $@ @echo "idxfilesize=$(idxfilesize)" >> $@ @echo "bookname=$(shell cat title.out)" >> $@ @echo "author=$(shell sed -e "s/ <.*>//" > $@ @echo "email=$(shell sed -e "s/.* <\(.*\)>/\1/" > $@ @echo "website=$(shell cat sourceurl.out)" >> $@ @echo "description=Converted to StarDict format by freedict.org" >> $@ @echo "date=$(shell date +%Y.%m.%d)" >> $@ @echo "sametypesequence=m" >> $@ @cat $@ stardict: $(stardict_prefix)$(dictname).ifo $(BUILD_DIR)/stardict/freedict-$(dictname)-$(version)-stardict.tar.bz2: \ $(stardict_prefix)$(dictname).ifo \ $(stardict_prefix)$(dictname).dict.dz \ $(stardict_prefix)$(dictname).idx.gz @if [ ! -d $(BUILD_DIR)/stardict ]; then \ mkdir $(BUILD_DIR)/stardict; fi tar -C .. -cvjf \ $(BUILD_DIR)/stardict/freedict-$(dictname)-$(version)-stardict.tar.bz2 \ $(addprefix $(notdir $(realpath .))/, $^) release-stardict: \ $(BUILD_DIR)/stardict/freedict-$(dictname)-$(version)-stardict.tar.bz2 clean:: rm -f $(dictname).idxhead $(stardict_prefix)$(dictname).idx.gz \ $(stardict_prefix)$(dictname).dict.dz $(stardict_prefix)$(dictname).ifo \ $(BUILD_DIR)/stardict/freedict-$(dictname)-$(version)-stardict.tar.bz2 \ dictd2dic.out authorresp.out title.out sourceurl.out ####################### #### Slob format for the Aard Android dictionary client ####################### build-slob: $(BUILD_DIR)/slob/$(dictname)-$(version).slob $(BUILD_DIR)/slob/$(dictname)-$(version).slob: $(call dict_tei_source) @mkdir -p $(dir $@) rm -f $@ $(call exc_pyscript,tei2slob,-w,$(BUILD_DIR)/slob,-o,$@,$<) $(call gen_release_path,slob): $(BUILD_DIR)/slob/$(dictname)-$(version).slob $(RELEASE_DIR) cp $< $@ # make the hash depend on the release file $(call gen_release_hashpath,slob): $(call gen_release_path,slob) release-slob: $(call gen_release_path,slob) $(call gen_release_hashpath,slob) ####################### #### Makefile-technical ####################### # should be default, but is not for make-historic reasons .DELETE_ON_ERROR: .PHONY: all build-dictd build-slob build-src clean dist find-homographs \ install pos-statistics print-unsupported query-% releaase-src release release-dictd \ test test-reverse tests uninstall validation version tools-0.6.0/mk/qa/000077500000000000000000000000001423006221500136605ustar00rootroot00000000000000tools-0.6.0/mk/qa/1.md000066400000000000000000000002571423006221500143460ustar00rootroot00000000000000The rm_duplicates script detected either empty XML nodes or completely doubled translations. You can use `make rm_duplicates` to remove them and to see a diff of the changes. tools-0.6.0/requirements.txt000066400000000000000000000005661423006221500161430ustar00rootroot00000000000000-e git://github.com/freedict/tools.git#egg=fd_tool&subdirectory=fd_tool -e git://github.com/freedict/tools.git#egg=fd_import&subdirectory=importers semver==2.8.1 PyICU>=2.3 -e git://github.com/ovalhub/pyicu@3614198d130273ec4acc029bd7ded083e6309224#egg=pyicu ankisync==0.1.6.3 -e git://github.com/itkach/slob.git#egg=slob -e git://github.com/itkach/tei2slob.git#egg=tei2slob tools-0.6.0/teiaddphonetics000077500000000000000000000276351423006221500157620ustar00rootroot00000000000000#!/usr/bin/env python3 # (c) 2020 Sebastian Humenda; Licence: GPL-3+ # pylint: disable=global-statement """Restrictions: - Escaping within elements could be "lost" since the parser considers ' and other characters not to be worth escaping. - Name spacing is ignored. This script depends on eSpeakNG and /usr/share/isocodes. On Debian-based systems, install the packages isocodes and espeak-ng.""" import argparse import json import os import re import shutil import subprocess import sys import xml.sax as sax ISO_639_TABLE = "/usr/share/iso-codes/json/iso_639-3.json" CHARACTER_REPLACEMENTS = { '"', "(", ")", "{", "}", ",", "...", ".", ":", ";", "!", "?", "-", "…", "'", "[", "´", "&", "°", "$", "]", "®", "-", "̩", "£", "%", "\\", "§", "­", "/", "+", "‘", "̃", "²", "³", "\n", "\r", chr(0x60C), # from ara-eng "−", "–", "↑", "→", "←", "@", ".", "〜", "×", "+", "&", "~", "↓", "、", } ESPEAKNG_LANG_CORRECTIONS = { "swh": "sw", # eSpeak 1.49 uses "sw" that stands for "swa", while FD uses "swh" } DEBUG = None # pylint: disable=too-few-public-methods class Orth: """A representation of a element.""" def __init__(self, attrs): self.attrs = attrs self.text = [] class Entry: """A (mostly) serialised entry. An entry may contain strings or Orth nodes. It also mimics an output stream using the write method.""" def __init__(self, attrs): attrs = "".join(f' {k}="{v}"' for k, v in attrs.items()) self.xml = [f""] def write(self, item): """ToDo: text or orth.""" self.xml.append(item) def contains_pronunciation(self): return any( token for token in self.xml if isinstance(token, str) and "= EspeakNg.RESTART_AFTER: self.reset() headword = "".join(orth.text) headword = self.escape(headword) self.non_alpha.update( c for c in headword if not (c.isalpha() or c.isdigit() or c.isspace()) ) self.proc.stdin.write(headword.encode(sys.getdefaultencoding()) + b"\n") self.proc.stdin.flush() buf = self.proc.stdout.readline() word = buf.decode(sys.getdefaultencoding()).strip() if DEBUG: open(DEBUG, "a").write(f"{headword} :: {word}\n") self.headwords_transcribed += 1 return word def escape(self, word): """Escape any punctuation, see ipa().""" for ch in CHARACTER_REPLACEMENTS: word = word.replace(ch, " ") return word.replace(" ", " ") def close(self): remainder = self.proc.communicate()[0] if remainder: import textwrap non_alpha = "".join(self.non_alpha) print( "\n".join( textwrap.wrap( "Error: {} lines found that were not " "recognised by this script during IPA transcription of " "eSpeak-NG. This is a bug due to special characters that lead " " to unexpected newlines in the eSpeak-NG output. Expected is " "a line per headword. Use --debug to save the transcriptions to a " "separate file." ) ).format(len(remainder.split(b"\n"))) + f"\nPotentially problematic characters: {non_alpha}" ) sys.exit(9) ret = self.proc.wait() if ret != 0: raise OSError(f"Espeak-ng gave non-zero exit code {ret}.") def reset(self): """Reset eSpeak-NG to avoid filling the OS stdin buffer.""" self.close() self._start() class TeiConsumer(sax.handler.ContentHandler): """A streaming SAX parser that calls a registered handler function to process elements.""" def __init__(self, file_handle, ipa_generator): # This is either None or a list. If it is a list, all text data will be # appended. self.current_orth = None self.outstream = file_handle self.outstream_backup = None super().__init__() self.ipa_generator = ipa_generator def endDocument(self): self.outstream.write("\n") self.outstream.close() self.ipa_generator.close() def startElement(self, name, attrs): """Signals the start of an element in non-namespace mode.""" if not attrs: attrs = {} if name == "entry": self.outstream_backup = self.outstream self.outstream = Entry(attrs) elif name == "orth": self.current_orth = Orth(attrs) else: attrs = attrs.items() self.outstream.write(f"<{name}") if attrs: for k, v in attrs: self.outstream.write(f' {k}="{v}"') self.outstream.write(">") def endElement(self, name): """Signals the end of an element in non-namespace mode.""" if name == "orth": # add the Orth to the Entry self.outstream.write(self.current_orth) self.current_orth = None elif name == "entry": entry = self.outstream self.outstream = self.outstream_backup for item in entry.xml: if isinstance(item, Orth): attrs = "".join(f' {k}="{v}"' for k, v in item.attrs.items()) text = sax.saxutils.escape("".join(item.text)) self.outstream.write(f"{text}") # It could be that one orth has transcriptions, another # doesn't, these cases are ignored in this version if not entry.contains_pronunciation(): # transcriptions are rarely empty transcribed = sax.saxutils.escape(self.ipa_generator.ipa(item)) if transcribed: self.outstream.write(f"\n{transcribed}") else: self.outstream.write(item) self.outstream.write(f"") else: self.outstream.write(f"") def characters(self, content): """Receive notification of character data.""" if self.current_orth: self.current_orth.text.append(content) else: self.outstream.write(sax.saxutils.escape(content)) def ignorableWhitespace(self, whitespace): """Receive notification of ignorable whitespace in element content.""" self.outstream.write(whitespace) def espeakng_voice(iso_639_3_code): """Return the Espeak-NG voice from the given ISO 639-3 code. StopIteration is raised if language is unknown.""" # Read all available languages first if not os.path.exists(ISO_639_TABLE): print("Error: failed to load {ISO_639_TABLE}.") print("This file can be found in the isocodes package on Debian-based systems.") sys.exit(3) proc = subprocess.run(["espeak-ng", "--voices"], capture_output=True) espeakng_voices = set() for voice in proc.stdout.decode(sys.getdefaultencoding()).split("\n"): voice = re.search(r"^\s*[0-9]*\s*([a-z]+)(-[0-9]*[a-z]*)?\s.*$", voice) if voice: espeakng_voices.add(voice.group(1)) all_iso_639 = json.load(open(ISO_639_TABLE))["639-3"] # Let StopIteration propagate lang = next(l for l in all_iso_639 if l["alpha_3"] == iso_639_3_code) # get the ISO-639-1 code (alpha_2) and afterwards, try to detect whether # there's a correction that maps from a code used by FD to one used by # eSpeakNG lang = lang.get("alpha_2", iso_639_3_code) lang = ESPEAKNG_LANG_CORRECTIONS.get(lang, lang) if lang not in espeakng_voices: print(f'Error, espeak did not recognise language "{lang}".') sys.exit(6) return lang def xml_preamble(tei_file): """Extract everything until = 0: lines.append(line[:idx]) break else: lines.append(line) tei_file.seek(0) return "".join(lines) if found else "" def handle_cmdline(): global DEBUG parser = argparse.ArgumentParser( description="Enrich a TEI file with pronunciation info." ) parser.add_argument( "tei_dict", metavar="TEI_DICT", nargs="?", help="input file name" ) parser.add_argument( "-o", dest="outpath", metavar="OUTPUT_PATH", nargs=1, help="write processed TEI to given output file. default is \ xxx-yyy-phoneme.tei", ) parser.add_argument( "--supports-lang", dest="supports_lang", metavar="LANG", nargs=1, help="test wheter given language (ISO 639-3) is supported by eSpeak", ) parser.add_argument( "--debug", dest="debug", metavar="DEBUGFILE", nargs=1, help="write phoneme transcription to specified file. The\ file will have a headword per line, separated by colons from \ the transcription. This helps to find the problematic \ headword if transcriptions start to mismatch.", ) args = parser.parse_args() teipath = None # Handle --supports-lang that aborts after the check, return results # otherwise if args.supports_lang: try: espeakng_voice(args.supports_lang[0]) except StopIteration: # raised to propagate missing language sys.exit(4) else: sys.exit(0) elif not args.tei_dict: print("Error, need to provide input TEI file") sys.exit(9) teipath = args.tei_dict if args.outpath: outpath = args.outpath[0] else: outpath = f"{os.path.splitext(teipath)[0]}-phonetics.tei" if args.debug: DEBUG = args.debug[0] return (teipath, outpath) def main(): teipath, outpath = handle_cmdline() # Guess eSpeak-NG voice to use lang = os.path.splitext(os.path.basename(teipath))[0].split("-")[0] ipa_gen = EspeakNg(espeakng_voice(lang)) # Extract preamble (not parsed by SAX parser), write and call SAX parser # afterwards with open(teipath) as teifile: outfile = open(outpath, "w") # closed by TeiConsumer outfile.write(xml_preamble(teifile)) parser = sax.make_parser() teihandler = TeiConsumer(outfile, ipa_gen) parser.setContentHandler(teihandler) parser.parse(teifile) if __name__ == "__main__": main() tools-0.6.0/teidiff000077500000000000000000000360371423006221500142210ustar00rootroot00000000000000#!/usr/bin/perl # $Revision$ use warnings; use strict; use Getopt::Long; use Pod::Usage; use XML::DOM; use Text::Diff; my ($verbose, $help); my $count = 1; my $base; my $output; GetOptions ( "verbose" => \$verbose, "help|?" => \$help, "count" => \$count, "base=s" => \$base, "output=s" => \$output) or exit 1; pod2usage(-exitval => 1, -verbose => 2) if $help; if (scalar @ARGV != 2) { print STDERR "I need exactly 2 filenames to operate. For help use --help.\n"; exit 1 } if (defined $output) { $output = uc $output; unless ($output =~ /^FILES|UNMODIFIED|ADDED|DELETED|MODIFIED_(LA1|LA2|MULTIMATCH)$/) { print STDERR "Invalid category: $output\n"; exit 1 } } my $parser = new XML::DOM::Parser; print STDERR "Parsing $ARGV[0]... " if $verbose; my $doc1 = $parser->parsefile ($ARGV[0]); my @entries1 = $doc1->getElementsByTagName ("entry"); my $entries1 = scalar @entries1; print STDERR $entries1, " entries\nParsing $ARGV[1]... " if $verbose; my $doc2 = $parser->parsefile ($ARGV[1]); undef $parser; my @entries2 = $doc2->getElementsByTagName ("entry"); my $entries2 = scalar @entries2; print STDERR $entries2, " entries\n" if $verbose; sub pretty_print { my ($node, $indent) = @_; my $type = $node->getNodeType; return $node->getNodeValue if $type == TEXT_NODE; if ($type == ELEMENT_NODE) { $indent = 0 unless defined $indent; my $contents = undef; for my $c ($node->getChildNodes) { my $cpp = pretty_print ($c, $indent+2); if (defined $contents) { $contents .= $cpp; next } $contents = $cpp } my $attrs = ''; for my $a ($node->getAttributes->getValues) { $attrs .= ' ' . $a->getNodeName . '=' . $a->getNodeValue } return ' ' x $indent . '<' . $node->getNodeName . $attrs . '/>' unless defined $contents; return ' ' x $indent . '<' . $node->getNodeName . $attrs . '>' . ($node->getFirstChild->getNodeType == ELEMENT_NODE ? "\n" : '') . $contents . ($node->getLastChild->getNodeType == ELEMENT_NODE ? ' ' x $indent : '') . 'getNodeName . ">\n" } die "Unhandled node type: ", $node->getNodeType } my (@unmodified, @added, @deleted, @modified_form, @modified_senses, @modified_multimatch, %entry2form, %entry2senses, %entry2complete, %complete2entry1, %form2entry1, %senses2entry1, %complete2entry2, %form2entry2, %senses2entry2); # adds an entry to an arrayref sub put { my ($hashref, $index, $entry) = @_; if (exists $hashref->{$index}) { push @{ $hashref->{$index} }, $entry } else { $hashref->{$index} = [ $entry ] } } # deletes an entry from an arrayref # the entry may occur only once sub remove { my ($hashref, $index, $entry) = @_; die join ' ', caller unless defined $hashref && defined $index; return unless exists $hashref->{$index}; my @entries = @{ $hashref->{$index} }; my $count = scalar @entries; if ($count == 1) { return unless $entries[$#entries] eq $entry; delete $hashref->{$index}; return } for (my $i=0; $i <= $count ; $i++) { next unless exists $entries[$i]; next unless $entries[$i] == $entry; delete $entries[$i]; return } warn "No entry removed"; } # Removes the entries in $entry2arrayref from the $x2entryhashrefs given. # Each $x2entryhashref argument must be followed by an $entry2xhashref. sub removeall { my $entryarrayref = shift; while(my $x2entryhashref = shift) { my $entry2x = shift; die unless defined $entry2x; for my $entry (@$entryarrayref) { remove $x2entryhashref, $entry2x->{$entry}, $entry } } } sub find_unmodified_or_modified { my $found = 0; for (keys %complete2entry1) { next if 1 < scalar @{ $complete2entry1{$_} }; next unless exists $complete2entry2{$_}; next if 1 != scalar @{ $complete2entry2{$_} }; my $entry1 = $complete2entry1{$_}->[0]; my $entry2 = $complete2entry2{$_}->[0]; push @unmodified, $entry1; delete $complete2entry1{$_}; delete $complete2entry2{$_}; remove \%form2entry1, $entry2form{$entry1}, $entry1; remove \%form2entry2, $entry2form{$entry1}, $entry2; remove \%senses2entry1, $entry2senses{$entry1}, $entry1; remove \%senses2entry2, $entry2senses{$entry1}, $entry2; $found++ } for (keys %form2entry1) { next if 1 < scalar @{ $form2entry1{$_} }; next unless exists $form2entry2{$_}; next if 1 != scalar @{ $form2entry2{$_} }; my $entry1 = $form2entry1{$_}->[0]; my $entry2 = $form2entry2{$_}->[0]; push @modified_senses, [ $entry1, $entry2 ] ; remove \%complete2entry1, $entry2complete{$entry1}, $entry1; remove \%complete2entry2, $entry2complete{$entry2}, $entry2; delete $form2entry1{$_}; delete $form2entry2{$_}; remove \%senses2entry1, $entry2senses{$entry1}, $entry1; remove \%senses2entry2, $entry2senses{$entry2}, $entry2; $found++ } for (keys %senses2entry1) { next if 1 < scalar @{ $senses2entry1{$_} }; next unless exists $senses2entry2{$_}; next if 1 != scalar @{ $senses2entry2{$_} }; my $entry1 = $senses2entry1{$_}->[0]; my $entry2 = $senses2entry2{$_}->[0]; push @modified_form, [ $entry1, $entry2 ] ; remove \%complete2entry1, $entry2complete{$entry1}, $entry1; remove \%complete2entry2, $entry2complete{$entry2}, $entry2; remove \%form2entry1, $entry2form{$entry1}, $entry1; remove \%form2entry2, $entry2form{$entry2}, $entry2; delete $senses2entry1{$_}; delete $senses2entry2{$_}; $found++ } print STDERR "$found entries matched\n" if $verbose; return $found } sub find_added_deleted { print STDERR "Finding added and deleted entries... " if $verbose; # an entry was deleted/added when no match # for it exists in the other file # since single matches should be sorted out by now, # only multimatches remain to be skipped for (keys %complete2entry1) { # the next line should never apply next if exists $complete2entry2{$_}; my $skip = 0; for my $entry1 (@{ $complete2entry1{$_} }) { next unless exists $form2entry2{ $entry2form{$entry1} } || exists $senses2entry2{ $entry2senses{$entry1} }; $skip = 1; last } next if $skip; push @deleted, @{ $complete2entry1{$_} }; removeall $complete2entry1{$_}, \%form2entry1, \%entry2form, \%senses2entry1, \%entry2senses; delete $complete2entry1{$_} } for (keys %complete2entry2) { next if exists $complete2entry1{$_}; my $skip = 0; for my $entry2 (@{ $complete2entry2{$_} }) { next unless exists $form2entry1{ $entry2form{$entry2} } || exists $senses2entry1{ $entry2senses{$entry2} }; $skip = 1; last } next if $skip; push @added, @{ $complete2entry2{$_} }; removeall $complete2entry2{$_}, \%form2entry2, \%entry2form, \%senses2entry2, \%entry2senses; delete $complete2entry2{$_} } print STDERR "\n" if $verbose; } # Actually there is nothing more to find. We just put together the remains # of the lists. sub find_modified_multimatch { print STDERR "Collecting the multimatches... " if $verbose; my (@from1, @from2); for (values %complete2entry1) { push @from1, @$_ } for (values %complete2entry2) { push @from2, @$_ } push @modified_multimatch, [ @from1 ], [ @from2 ]; print STDERR "\n" if $verbose; } sub cache_toStrings { my ($entries, $complete2entry, $form2entry, $senses2entry) = @_; for (@$entries) { # a complete comparison key is easy (if canonicalised XML is used) my $scomplete = $_->toString (); put $complete2entry, $scomplete, $_; $entry2complete{$_} = $scomplete; # comparison key for form: concat and child nodes # comparison key for senses: concatenate all other child nodes my @children = $_->getChildNodes (); my $ssenses = my $sforms = ''; for my $child (@children) { if ($child->getTagName () =~ /^form|gramGrp$/) { $sforms .= $child->toString (); next } $ssenses .= $child->toString () } put $form2entry, $sforms, $_; $entry2form{$_} = $sforms; put $senses2entry, $ssenses, $_; $entry2senses{$_} = $ssenses } } print STDERR "Caching results of toString()..." if $verbose; cache_toStrings \@entries1, \%complete2entry1, \%form2entry1, \%senses2entry1; undef @entries1; cache_toStrings \@entries2, \%complete2entry2, \%form2entry2, \%senses2entry2; undef @entries2; print STDERR "\n" if $verbose; my $i = 1; do { print STDERR "Comparison iteration ", $i, "... " if $verbose; $i++ } while find_unmodified_or_modified; find_added_deleted; find_modified_multimatch; print STDERR "Finished comparison.\n" if $verbose; undef %complete2entry1; undef %form2entry1; undef %senses2entry1; undef %complete2entry2; undef %form2entry2; undef %senses2entry2; if ($count) { my $unmodified = scalar @unmodified; my $added = scalar @added; my $deleted = scalar @deleted; my $modified_form = scalar @modified_form; my $modified_senses = scalar @modified_senses; my $mm1 = scalar @{ $modified_multimatch[0] }; my $mm2 = scalar @{ $modified_multimatch[1] }; print "Unmodified:\t\t$unmodified\n"; print "Added:\t\t\t$added\n"; print "Deleted:\t\t$deleted\n"; print "Modified :\t$modified_form\n"; print "Modified s:\t$modified_senses\n"; print "Modified multimatch:\t$mm1 + $mm2\n"; my $sum1 = $unmodified + $deleted + $modified_form + $modified_senses + $mm1; my $sum2 = $unmodified + $added + $modified_form + $modified_senses + $mm2; print STDERR "file1: unmod + del + mod_form + mod_sense + mm1 = ", $sum1, ($sum1 == $entries1 ? ' OK' : ' MISMATCH') . " \n" if $verbose; print STDERR "file2: unmod + add + mod_form + mod_sense + mm2 = ", $sum2, ($sum2 == $entries2 ? ' OK' : ' MISMATCH') . " \n" if $verbose; } sub print_category_fh { my ($fh, $category) = @_; print STDERR "Outputting category $category... " if defined $verbose and $verbose>1; if ($category eq 'UNMODIFIED') { print $fh pretty_print $_ for (@unmodified) } elsif ($category eq 'ADDED') { print $fh pretty_print $_ for (@added) } elsif ($category eq 'DELETED') { print $fh pretty_print $_ for (@deleted) } elsif ($category eq 'MODIFIED_LA1') { for (@modified_form) { my $pretty1 = pretty_print $_->[0]; my $pretty2 = pretty_print $_->[1]; my %options = (); my $diff = diff \$pretty1, \$pretty2, \%options; print $fh $diff } } elsif ($category eq 'MODIFIED_LA2') { for (@modified_senses) { my $pretty1 = pretty_print $_->[0]; my $pretty2 = pretty_print $_->[1]; my %options = (); my $diff = diff \$pretty1, \$pretty2, \%options; print $fh $diff } } elsif ($category eq 'MODIFIED_MULTIMATCH') { # output entry sets print $fh "\n"; print STDERR "file1... " if $verbose; print $fh pretty_print $_ for (@{ $modified_multimatch[0] }); print $fh "\n\n"; print STDERR "file2... " if $verbose; print $fh pretty_print $_ for (@{ $modified_multimatch[1] }); print $fh "\n" } else { warn "Invalid category: $category" } print STDERR "done.\n" if defined $verbose and $verbose>1 } sub print_category_file { my ($filename, $category) = @_; my $fh; unless (open $fh, '>:utf8', $filename) { print STDERR "Could not open $filename for writing: $!\n"; return } print STDERR "Writing $filename... " if $verbose; print_category_fh $fh, $category; close $fh; print STDERR "OK\n" if $verbose } exit unless defined $output; $base = $ARGV[0] . '-' . $ARGV[1] unless defined $base; if ($output eq 'FILES') { print_category_file $base . '-unmodified.teipart', 'UNMODIFIED'; undef @unmodified; print_category_file $base . '-added.teipart', 'ADDED'; undef @added; print_category_file $base . '-deleted.teipart', 'DELETED'; undef @deleted; print_category_file $base . '-modified_la1.udiff', 'MODIFIED_LA1'; undef @modified_form; print_category_file $base . '-modified_la2.udiff', 'MODIFIED_LA2'; undef @modified_senses; print_category_file $base . '-modified_multimatch.xml', 'MODIFIED_MULTIMATCH'; exit } # print only category in $output to STDOUT open my $fh, '>:utf8',' -' or die; print_category_fh $fh, $output; close $fh; # __DATA__, keep a file1 and a file2 and the outfiles # XXX testcases, call with a commandline switch __END__ =head1 NAME teidiff - Compare the entries in two TEI XML dictionary files =head1 SYNOPSIS teidiff [options] file1 file2 Options: --base Basename for output files --[no]count Print number of added/modified/deleted entries to STDOUT --help This manual page --output Entry category to print to STDOUT --verbose Tell what I'm doing =head1 OPTIONS =over 8 =item B<--base BASENAME> Basename of output files. Default: F =item B<--[no]count> Print number of added/modified/deleted entries to STDOUT. Default: on. =item B<--help> Print this manual page and exit. =item B<--verbose> Print information about the current work performed to STDERR. =item B<--output CATEGORY> Can be set to the string "FILES" or a category of entries to output to STDOUT. By default nothing is output. If "FILES" is used, the filenames will follow the scheme F The following categories are allowed: =over 4 =item UNMODIFIED Unmodified entries. They appear exactly the same in both versions of the dictionary. =item ADDED Entries not present in file1, but in file2. They were added in F. =item DELETED Entries present in file1, but not in file2. They were deleted in F. =item MODIFIED_LA1 Entries where the contents of the element were modified, but the contents of the elements are unmodified. =item MODIFIED_LA2 Entries where the contents of the element(s) were modified, but the contents of the elements are unmodified. =item MODIFIED_MULTIMATCH Entries where the contents of the or elements were modified (but not both at the same time), but a corresponding entry was not found, because multiple matches exist ("multimatch"). This category is output as two sets - the entries from file1 and the entries from file2. One could build finer sets, consisting of only the entries between which the multimatch relationship exists. But this information is not useful enough for implementation currently. =back =back =head1 DESCRIPTION This program parses the given input files and compares the entries in them, putting them in B. One or all categories can be output. =head1 NOTES Normally ignorable blank space should not account for differences. C can be used as a filter to drop ignorable blank spaces: xmllint --noblanks infile.xml >outfile.xml C also comes with a C<--c14n> option, but using it is unadvisable, because it adds default attributes, bloating the file submerging relevant differences in a sea of explicitness. =head1 TODO Integrating the functionality of this command line tool into FreeDict-Editor would facilitate comfortable browsing of the differences. =head1 AUTHOR AND LICENCE Author: Michael Bunk, 2006 This is free software, licensed under GPL. =cut tools-0.6.0/teimerge/000077500000000000000000000000001423006221500144515ustar00rootroot00000000000000tools-0.6.0/teimerge/TEIMergeMainHandler.pm000066400000000000000000000074731423006221500205260ustar00rootroot00000000000000# module for teimerge # Teile entnommen aus CanonXMLWriter.pm # Copyright (C) 2002 Michael Bunk # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, visit # package TEIMergeMainHandler; # ESISParser zu verwenden ist tdlich, denn der fgt alle # #IMPLIED-Attribute hinzu!! #use XML::ESISParser; # der hier ist aber eher fr XML als SGML! use XML::Parser::PerlSAX; # Ausgabe ist in UTF-8, da das vom parser geliefert wird!!! use TEIMergeOtherHandler; %char_entities = ( "\x09" => ' ', "\x0a" => ' ', "\x0d" => ' ', '&' => '&', '<' => '<', '>' => '>', '"' => '"', ); sub set_otherfilenames { my $self = shift; $filenamesref = shift; } sub new { my ($type) = @_; return bless {}, $type; } sub start_document { print " ]>\n"; } sub characters { my ($self, $element) = @_; $data = $element->{Data}; #print($self->_escape($data)); print($data); } sub ignorable_whitespace { my ($self, $element) = @_; $data = $element->{Data}; print($data); } sub processing_instruction { my $self = shift; my $pi = shift; if (exists $pi->{Data}) { print('{Target} . ' ' . $pi->{Data} . '?>'); } else { print('{Target} . '?>'); } } sub comment { my $self = shift; my $comment = shift; print(''); } sub start_element { my ($self, $element) = @_; $part = $element->{Name}; print('<' . $part); my $key; my $attrs = $element->{Attributes}; foreach $key (sort keys %$attrs) { print(" $key=\"" . $self->_escape($attrs->{$key}) . '"'); } print('>'); } sub end_element { my ($self, $element) = @_; my $part = $element->{Name}; if ( $part eq "BODY") { # nun sind alle entrys von mainfile ausgegeben worden, es folgen die otherfiles warn "Mainfile done.\n"; my $my_handler = TEIMergeOtherHandler->new; my @additional_args; # push (@additional_args, IsSGML => 1); # my $parser = XML::ESISParser->new; my $parser = XML::Parser::PerlSAX->new; foreach my $otherfile (@{$filenamesref}) { do { warn "Can't find file \"$otherfile\"\n"; next;} unless -f $otherfile; warn "Mache mich an $otherfile...\n"; $parser->parse(Source => { SystemId => $otherfile }, Handler => $my_handler, @additional_args); } }; print ""; } sub record_end { # print "\n"; } sub external_entity_ref { my ($self, $name) = @_; warn "external_entity_reference\n"; } sub notation_decl { my ($self, $name, $sysid, $pubid, $genid) = @_; warn "notation_decl\n"; } sub subdoc_entity_decl { my ($self, $name) = @_; warn "subdoc_entity_decl\n"; } sub external_sgml_entity_decl { my ($self, $name, $sysid, $pubid, $genid) = @_; warn caller, "external_sgml_entity_decl\n"; } sub _escape { my $self = shift; my $string = shift; $string =~ s/([\x09\x0a\x0d&<>"])/$char_entities{$1}/ge; return $string; } 1; tools-0.6.0/teimerge/TEIMergeOtherHandler.pm000066400000000000000000000037671423006221500207250ustar00rootroot00000000000000# module for teimerge # # Copyright (C) 2002 Michael Bunk # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, visit # package TEIMergeOtherHandler; sub new { my ($type) = @_; return bless {}, $type; } sub characters { my ($self, $element) = @_; # wenn inside -> print, sonst nicht if (!$insideentry) {return;}; $data = $element->{Data}; chomp $data; $data =~ s/\s+$//; print $data; } sub start_element { my ($self, $element) = @_; $part = $element->{Name}; $insideentry = 1 if ( ($part eq "ENTRY") || ($part eq "entry")); if ($insideentry) { print('<' . $part); my $key; my $attrs = $element->{Attributes}; foreach $key (sort keys %$attrs) { print(" $key=\"" . $self->_escape($attrs->{$key}) . '"'); } print('>'); } } sub end_element { my ($self, $element) = @_; $part = $element->{Name}; print "\n" if ($insideentry); $insideentry = 0 if (($part eq "ENTRY") || ($part eq "entry")); } %char_entities = ( "\x09" => ' ', "\x0a" => ' ', "\x0d" => ' ', '&' => '&', '<' => '<', '>' => '>', '"' => '"', ); sub _escape { my $self = shift; my $string = shift; $string =~ s/([\x09\x0a\x0d&<>"])/$char_entities{$1}/ge; return $string; } 1; # mu stehenbleiben, damit perl nicht meckert tools-0.6.0/teimerge/teimerge.pl000077500000000000000000000026251423006221500166170ustar00rootroot00000000000000#!/usr/bin/perl -w # V1.0 3/2002 Michael Bunk # * sgml output only, not xml yet # * no sorting of any kind # Vorgehen: # zwei parser+handler werden benoetigt: mainfile/otherfiles # 1. mainfile ausgeben (header + alle entries, aber nicht footer) # 2. otherfilesparser auf file1 ansetzen # 3. header des otherfile ueberlesen # 4. entries ausgeben # 5. otherfile schlieen # 6. schritt 2 fuer file[2..N]... # 7. footer ausgeben () #use XML::ESISParser; use XML::Parser::PerlSAX; use TEIMergeMainHandler; my $mainfile = shift @ARGV; if ($mainfile eq "") { warn "Usage: teimerge []\n"; warn " To merge TEI encoded dict files. Output is on stdout.\n"; warn " The header of the mainfile is taken, others discarded.\n"; die; } die "Can't find file \"$mainfile\"" unless -f $mainfile; #push (@additional_args, IsSGML => 1); # an dieser stelle sollten die IMPLIED-Attribute auzuschalten sein # ? ist das mglich mit nsgmls? #$XML::ESISParser::NSGMLS_FLAGS_sgml = $XML::ESISParser::NSGMLS_FLAGS_sgml." -D/usr/share/sgml -cCATALOG.tei-3 "; my $mergemainhandler = TEIMergeMainHandler->new; $mergemainhandler->set_otherfilenames(\@ARGV); #XML::Parser::PerlSAX #XML::ESISParser XML::Parser::PerlSAX->new->parse(Source => { SystemId => $mainfile }, Handler => $mergemainhandler, @additional_args); # EOF tools-0.6.0/teisort.pl000077500000000000000000000140621423006221500147040ustar00rootroot00000000000000#!/usr/bin/perl -w # V1.1 6/2002 by Guido Ostkamp, # * more dictd-like sorting with by_dict_sort() # # V1.0 4/2002 by Michael Bunk, # * does some sorting of a tei-file, but without parser (worked out on xml, # but might be fine with sgml - i didn't try it) # * problem: xml-parser would not tell position, would it? # * so let's do without parser ;-) # * using some undefined collation order # * i wrote this to be able to merge double entrys # * sort with an in-memory index of # - keyword (first orth) # - byte-start-offset of entry in tei file (end is found by # outputting until ) # * we could try DB_File (see manpage), but let's try simple hash first # key: -characters # value: struct -offset # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, visit # use strict; sub by_dict_sort { my $x = $a; my $y = $b; $x =~ s/[^\d\w\s]//gi; # remove all non-alphanumeric and non-whitespace $x =~ s/(.*)/\L$1/gi; # turn lowercase $y =~ s/[^\d\w\s]//gi; # remove all non-alphanumeric and non-whitespace $y =~ s/(.*)/\L$1/gi; # turn lowercase $x cmp $y } my $file = shift; unless(defined $file) { print STDERR "\nteisort - sort tei file without using any xml parser (not safe)\n"; print STDERR "\n The inputfile is expected in XML or SGML TEI format, see http://www.tei-c.org/\n"; print STDERR " Output is on stdout.\n\n"; print STDERR " Usage: teisort \n"; print STDERR " : name of tei inputfile\n\n"; die } die "Can't find file \"$file\"" unless -r $file; open HANDLE, "<".$file; print STDERR "Generating index in memory...\n"; my ($headend, $footstart, $offset, $entry, %orths, $orth, $counter, $tell_now, $tell_lastline); # when we find "", # but save everything inside a $entry (we could keep the whole text # in the hash!!! [this is just a notice for future reference # for myself]). then we look for the "$1" and use # $1 as key my $todo = ""; my $offsetOnLine = 0; my $searchmode = 0; # 0 = find end of header by finding "" # 2 = find end of entry by looking for "" # 3 = nothing more to find, we are inside the footer, break; # input is taken from $todo ! readfile: while () { $tell_lastline = $tell_now; $tell_now = tell; $todo .= $_; while ($todo ne "") { # find end of header if (($searchmode == 0) && ($todo =~ //i)) { $searchmode = 3; my $eoffset = index("<\/body>", $todo); $footstart = $tell_lastline + $eoffset; print STDERR "footoffset: $footstart\n"; #$offsetOnLine -= $eoffset; $todo = "";#substr($todo,0,$eoffset); last readfile #exit that loop } # find end of entry if (($searchmode == 2) && ($todo =~ /<\/entry>/i)) { $searchmode = 1; my $eoffset = index "", $todo; $entry .= substr($todo, 0, 8+$eoffset) . "\n"; $offsetOnLine -= $eoffset; $todo = substr $todo, 0, $eoffset; # find orth # /s modifies to treat $entry as single line if ($entry =~ /(.*)<\/orth>/s) { $orth = $1; #print STDERR "orth: '$orth'\n"; } else { #print STDERR "."; warn "no orth found in entry!!! there is something wrong! Entry is <$entry>" } # we may not overwrite any pair in the hash that we already have # but since the entry-elements are read from the tei file again, # the " *" is never seen in the output :) while(defined $orths{$orth}) { $orth .= " " } # save in hash $orths{$orth}=$offset; next } # else $entry .= $todo; $todo = ""; $offsetOnLine = 0 } } print STDERR " $counter entries\n"; ############################################################### print STDERR "Outputting sorted entries...\n"; # output header my $header; die unless sysseek HANDLE, 0, 0; sysread HANDLE, $header, $headend; print $header; $counter = 0; # this one simple sort call does the keywork! foreach $orth (sort by_dict_sort keys %orths) { $counter++; if ($counter % 100 == 0) { print STDERR " $counter entries\n" } $offset = $orths{$orth}; #print STDERR "offset: $offset\n"; sysseek HANDLE, $offset, 0; # output until my $stopword = ""; my $stopwordpos = 0; my $stopwordlength = length $stopword; my $c; do { sysread HANDLE, $c,1;# maybe sysread with more than one byte would be faster... print $c; if ($c eq substr($stopword, $stopwordpos, 1)) { $stopwordpos++ } else { $stopwordpos = 0 } } until $stopwordpos == $stopwordlength; } # foreach # output footer my $footer; die unless sysseek HANDLE, $footstart, 0; my @stats = stat HANDLE;# fetch tei filesize die unless @stats; sysread(HANDLE, $footer, $stats[7]-$footstart); print $footer; print STDERR " $counter entries\n"; close HANDLE tools-0.6.0/testing/000077500000000000000000000000001423006221500143255ustar00rootroot00000000000000tools-0.6.0/testing/find-lonely-braces.xsl000066400000000000000000000241021423006221500205310ustar00rootroot00000000000000 <!DOCTYPE TEI.2 PUBLIC "-//TEI P4//DTD Main DTD Driver File//EN" "http://www.tei-c.org/P4X/DTD/tei2.dtd" [ <!ENTITY % TEI.XML "INCLUDE" > <!ENTITY % TEI.dictionaries "INCLUDE" > <!ENTITY % TEI.linking "INCLUDE" > <!ATTLIST xptr url CDATA #IMPLIED > <!ATTLIST xref url CDATA #IMPLIED > ]> Processed check-lonely-nodeset returned . false check-lonely-nodeset with nodes called. true true true true true false check-lonely-node-for-bracetype with node='' and open-braces= char-open='' called. false true true true tools-0.6.0/testing/lonely-braces-testcases.tei000066400000000000000000000107441423006221500215710ustar00rootroot00000000000000 ]> Lonely Braces Testcases Michael Bunk <kleinerwurm@gmx.net> 0.1 only a handful of headwords FreeDict

Copyright (C) 2005 Michael Bunk <kleinerwurm@gmx.net>

GNU GENERAL PUBLIC LICENSE

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

2005 http://freedict.org/
free dictionaries testing

Home:

This dictionary comes to you through nice people making it available for free and for good. It is part of the FreeDict project, http://www.freedict.org / http://freedict.de. This project aims to make available many translating dictionaries for free. Your contributions are welcome!

2005-06-23 FreeDict Maintainer Michael Bunk Wrote these testcases.
( This entry should be found.
a { without counterpart
This entry should be found as well.
a valid headword
But a broken translation. <
a valid headword
But an as well broken translation. >
valid headword without any braces
This entry should not be found.
valid headword (with allowed corresponding braces)
This entry should not be found. But since braces should be avoided, we could emit a warning...
This is a pathologic example (BTW, phrases as headwords are something disputable). The problem (with this element {content)} ist, that different brace types nest incorrectly.
This entry should be found, but cannot.
Did I mention it would be nice (to be able to automatically determine) test success or failure?
This entry should be found.
Many elements
The bug can be found only in the last "tr" element {
tools-0.6.0/testing/ungrouped-homographs.tei000066400000000000000000000050461423006221500212120ustar00rootroot00000000000000 ]> Test FreeDict Dictionary Author Michael Bunk <micha@luetzschena.de> Maintainer Michael Bunk <micha@luetzschena.de> 0.1 1 testcase FreeDict

GNU GENERAL PUBLIC LICENSE

2005 http://freedict.org/
free dictionaries used only for testing purposes

Home:

This dictionary comes to you through nice people making it available for free and for good. It is part of the FreeDict project, http://www.freedict.org / http://freedict.de. This project aims to make available many translating dictionaries for free. Your contributions are welcome!

2005-03-02 Michael Bunk Created this file to test group-homographs.xsl, an XSLT stylesheet to group homographs using the hom element.
homograph1 homograph2
n m The first and second homograph. They are nouns. homograph1 should become grouped with its homograph1 from the next entry. homograph2 should go into its own entry.
homograph1
adj This homograph1 is an adjective.
tools-0.6.0/xmltei2xmldict.pl000077500000000000000000000124461423006221500161700ustar00rootroot00000000000000#!/usr/bin/perl #xmltei2xmldict.pl requires: # #- nsgmls from SP 1.3.4 from http://www.jclark.com/jade/ # or OpenSP from http://openjade.sourceforge.net/ #- ESISParser.pm from libxml-perl-0.07, available via # (su) # perl -MCPAN -e shell # install K/KM/KMACLEOD/libxml-perl-0.07.tar.gz #- TEI P4 DTDs, available from http://www.tei-c.org/Guidelines2/index.html #- make SGML_CATALOG_FILES point to the right location(s), e.g. # export SGML_CATALOG_FILES="/usr/share/doc/packages/sp/\ #html-xml/xml.soc:/var/lib/sgml/CATALOG.tei_4xml:/var/lib/sgml/\ #CATALOG.iso_ent" #- Sablotron library and Perl module: # # $Id$ # V1.5 10/2006 Michael Bunk # * removed -l option completely # * cosmetic changes / rewrite # * new option -n to set NSGMLS option of XML:ESISParser # # V1.4 5/2004 Michael Bunk # * added option to generate 00-database-allchars header # # V1.3 4/2004 Michael Bunk kleinerwurm-at-gmx.net # * finally used FindBin # * use warnings; instead of #!perl -w to avoid warnings # in foreign code (Sablotron module) # # V1.1 2/2004 Michael Bunk kleinerwurm-at-gmx.net # * put .pm files into lib/ # # V1.0 6/2003 Michael Bunk kleinerwurm-at-gmx.net # * based on tei2dict_xml.pl # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, visit # use warnings; use strict; use FindBin; use lib "$FindBin::Bin"; use XML::ESISParser; use Getopt::Std; use XML::Sablotron; use XML::Sablotron::DOM; use lib::TEIHandlerxml_xml; our ($opt_f, $opt_h, $opt_s, $opt_c, $opt_u, $opt_i, $opt_r, $opt_t, $opt_a, $sab, $sit, $opt_n, $opt_d); getopts 'surai:f:t:n:d:'; unless (defined $opt_f) { print STDERR < elements The TEI input file is expected as XML in TEI P4 format, see http://www.tei-c.org/ Outputs .index and .dict file. The index is sorted with sort(1). This help is printed, because there was no TEI file given. Usage: $0 -f [-sura] [-i |-t ] [-n ] [-d ] -s skip TEI header: do not treat it to generate 00-database-info & co special entrys (good to convert adapted SGML TEI files) -u generate headword 00-database-utf8 in index file to mark the database being in UTF-8 encoding. When this is given, 'sort' is called without -d option, ie. all characters are used in comparisons. -r generate reverse index (use instead of ) -a generate headword 00-database-allchars (but no change in index mangling!) This converter cannot generate the 00-database-alphabet entry, so -a is required for non-latin scripts. You should prefer to use dictfmt(1) then. -i \t execute filtercmd for each entry (eg. 'sabcmd style.xsl') -t \t use an XSLT stylesheet for filtering the entries with the Sablotron library. Excludes -i. -n \tSets the NSGMLS option of XML::ESISParser. By default 'nsgmls' is used. To use OpenSP, set this option to 'onsgmls'. -d \tSets the Declaration option of XML::ESISParser. XML::ESISParser expects the SGML declaration by default in /usr/lib/sgml/declaration/xml.decl. In Debian /usr/share/xml/declaration/xml1n.dcl works best with nsgmls from the SP XML parser. \t name of TEI input file END exit 1 } our $file = $opt_f; unless (-f $file) { print STDERR "Can't find file \"$file\""; exit 2 } if ($opt_i && $opt_t) { print STDERR "Only one of -i and -t may be given"; exit 2 } our $my_handler = lib::TEIHandlerxml_xml->new(); $my_handler->set_options( $file, # hand over name for .dict and .index output files $opt_s ? 1 : 0, # skip TEI header $opt_u ? 1 : 0, # generate 00-database-utf8 "C", # locale $opt_i ? $opt_i : "", # filter command $opt_t, # stylesheet for Sablotron $opt_r ? 1 : 0, # generate reverse index $opt_a ? 1 : 0); # generate 00-database-allchars unless (defined $ENV{SGML_CATALOG_FILES}) { $ENV{SGML_CATALOG_FILES} = "/var/lib/sgml/CATALOG.tei_4xml:/var/lib/sgml/CATALOG.iso_ent"; print STDERR <new->parse(Source => { SystemId => $file }, Handler => $my_handler, %additional_args); print STDERR "Created $Dict::headwords headwords (including multiple -s / from the -s).\n"; tools-0.6.0/xquery/000077500000000000000000000000001423006221500142055ustar00rootroot00000000000000tools-0.6.0/xquery/simple_dump.xquery000066400000000000000000000043201423006221500200010ustar00rootroot00000000000000(: simple_dump.xquery - perform a simple dump of the contents of a FreeDict dictionary Originally by Piotr Bański (bansp at o2.pl), 01-nov-2010. License: GNU GPL ver. 3.0 or any later version. $Id$ This script expects an external parameter $lg_pair but you may safely set that to '' and manipulate the contents of $my_lg_pair pair instead. Initially, it was only supposed to match the headword(s) with their equivalents, but I got slightly carried away. Still, this is supposed to be a simple dump, so it skips a lot of potential details. :) declare default element namespace "http://www.tei-c.org/ns/1.0"; declare namespace functx = "http:///www.functx.com"; declare option saxon:output "method=text"; (:the following variable is system-internal :) declare variable $my_svn_id as xs:string := "$Id$"; (: set this to the pair of languages that you want to process :) declare variable $my_lg_pair as xs:string := "srp-eng"; (: reset this to true() for an even simpler dump :) declare variable $skip_gram as xs:boolean := false(); declare variable $lg_pair as xs:string external; declare function functx:get_lg_pair() as xs:string { let $lgs := if ($lg_pair) then $lg_pair else $my_lg_pair return $lgs }; declare function functx:get_filename() as xs:string { let $lgs := functx:get_lg_pair() return concat('../../',$lgs,'/',$lgs,'.tei') }; declare function functx:header() as xs:string { let $ret := concat('Dump of ',functx:get_lg_pair(),'.tei on ',substring-before(string(xs:date(current-dateTime())),'+'),' at ',substring-before(string(xs:time(current-dateTime())),'.'),' ') return $ret }; declare function functx:process() as xs:string+ { for $entry in doc(functx:get_filename())/TEI/text/body/entry let $hdwd := $entry/form/orth let $gram := for $any in $entry/gramGrp/* return normalize-space($any) let $gloss := $entry//cit[@type='trans']/quote | $entry//sense/def order by lower-case($hdwd[1]) return concat(string-join($hdwd,', '),if (count($gram) and not($skip_gram)) then concat(' [',string-join($gram,'|'),'] ') else ' -- ',normalize-space(string-join($gloss,', '))) }; let $ret := functx:process() return concat(functx:header(),string-join($ret,' ')) tools-0.6.0/xsl/000077500000000000000000000000001423006221500134565ustar00rootroot00000000000000tools-0.6.0/xsl/freedict_P4toP5.xsl000066400000000000000000000170351423006221500171140ustar00rootroot00000000000000 Converter for FreeDict.org databases: from P4 to P5

The input dictionaries vary in some details, so expect to tweak this script. In some cases the tweaks may be worth porting back to the project, in some cases you will probably judge them as specific to the dictionary at hand. In general, this is going to be a one-time script: it should do most of the conversion work for you, and you will be left with the remaining details, hopefully only within the header. Please make sure to indicate in the revisionDesc that conversion has taken place! And then check if the FreeDict build system converts your TEI source to c5 properly.

Piotr Bański the author(s), 2010; license: GPL v3 or any later version (http://www.gnu.org/licenses/gpl.html). $Id$ Convert trans to as many (sense/)cit as there are tr elements inside it. If the original dictionary has no sense elements under entry, create them around each old trans. Convert gen elements *inside* trans. Be careful: this is a very specific case and you may have to tweak this template for your database (probably by removing the gramGrp layer) Convert pos elements *inside* trans. Be careful: this is a very specific case and you may have to tweak this template for your database (possibly by removing the gramGrp layer; mind also the template for trans/gen - if more such elements occur in a trans, it might be better to group them in a more nifty way) YOUR_NAME_HERE: Conversion of TEI P4 source into P5 via tools/xsl/freedict_P4toP5.xsl; manual clean-up. http://freedict.org/

This dictionary comes to you through nice people making it available for free and for good. It is part of the FreeDict project, http://freedict.org/. This project aims to make translating dictionaries available for free. Your contributions are welcome!

for the freedict database Maintainer [up for grabs] $Id:$

Copyright (C) 1999-2010 by various authors listed below.

Available under the terms of the GNU General Public License ver. 2.0 and any later version.

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 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 General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

eat the default or unnecessary attributes tools-0.6.0/xsl/getauthor.xsl000066400000000000000000000014761423006221500162200ustar00rootroot00000000000000 tools-0.6.0/xsl/getedition.xsl000066400000000000000000000017401423006221500163430ustar00rootroot00000000000000 tools-0.6.0/xsl/getmaintainer.xsl000066400000000000000000000006061423006221500170370ustar00rootroot00000000000000 tools-0.6.0/xsl/getsourceurl.xsl000066400000000000000000000013101423006221500167240ustar00rootroot00000000000000 tools-0.6.0/xsl/getstatus.xsl000066400000000000000000000012271423006221500162330ustar00rootroot00000000000000 tools-0.6.0/xsl/gettitle.xsl000066400000000000000000000011301423006221500160220ustar00rootroot00000000000000 tools-0.6.0/xsl/group-homographs-sorted.xsl000066400000000000000000000211121423006221500210020ustar00rootroot00000000000000 Processing entry Skipping already handled homograph ' '. Transforming using <hom>: ' ' has ? homographs. Warning: Can't handle multiple <orth> elements: '' - Results undefined. They should be separated into their own entries. tools-0.6.0/xsl/group-homographs.xsl000066400000000000000000000117761423006221500175230ustar00rootroot00000000000000 Skipping already handled homograph ''. Transforming using <hom>: ' ' has homographs. Can't handle multiple <orth> elements (yet?): '' - They should be separated into their own entries. tools-0.6.0/xsl/inc/000077500000000000000000000000001423006221500142275ustar00rootroot00000000000000tools-0.6.0/xsl/inc/indent.xsl000066400000000000000000000114301423006221500162370ustar00rootroot00000000000000 tools-0.6.0/xsl/inc/teientry2txt.xsl000066400000000000000000000433161423006221500174530ustar00rootroot00000000000000 $Id: teientry2txt.xsl 1166 2011-09-10 20:02:34Z bansp $ ( , , ) , ( ) < , > [sg=pl] 0 - , ( ) [ ] See also: Synonyms: Synonym: Antonyms: Antonym: Inflection of: Derived from: (imp: ) (pl: ) (past: ) ( ) , Entry edited by: ( ) tools-0.6.0/xsl/inc/teiheader2txt.xsl000066400000000000000000000254231423006221500175410ustar00rootroot00000000000000 $Id: teiheader2txt.xsl 1095 2011-01-06 14:57:17Z bansp$ : Edition: Size: I understand this is needed for cases where we merely redistribute stuff. For upstream publishers, I guess. Shouldn't this always be the FreeDict URL? Id #, currently we only use the SVN Id. Availability: Series: Notes: * Database Status: Source(s): The Project: Changelog: : tools-0.6.0/xsl/re-indent.xsl000066400000000000000000000016211423006221500160730ustar00rootroot00000000000000 Source XML reformatter

This is just an indentity transform with an added instruction to reformat the source and add the XML declaration. Small and silly, but useful.

Distributor: FreeDict.org (http://freedict.org/)

public domain $Id:$
tools-0.6.0/xsl/sort.xsl000066400000000000000000000022601423006221500151750ustar00rootroot00000000000000 tools-0.6.0/xsl/tei-wrapper.xml000066400000000000000000000010701423006221500164350ustar00rootroot00000000000000 ]> &e; tools-0.6.0/xsl/tei2c5-reverse.xsl000066400000000000000000000033121423006221500167510ustar00rootroot00000000000000 _____ 00-database-short [reverse index] _____ %%% tools-0.6.0/xsl/tei2c5.xsl000066400000000000000000000116611423006221500153060ustar00rootroot00000000000000 _____ 00-database-info _____ 00-database-short Use either the ref of type='home' or possibly make a mistake and use the first ref under sourceDesc. _____ 00-database-url _____ Warning! Empty headword for entry # %%% : Processed of entries (%). There is nothing special about the '=' characters, it's just a piece of quasi-aesthetic pseudomagic. ===================================================================== ===================================================================== An important note: tables within paragraphs will not render nice at all. | tools-0.6.0/xsl/tei2dic.xsl000066400000000000000000000334301423006221500155340ustar00rootroot00000000000000 \en id= char-precedence= { -,!/.()?}{a}{AäÄ}{b}{B}{c}{C} {d}{D}{e}{E}{f}{F}{g}{G}{h}{H}{i}{I}{j}{J}{k}{K} {l}{L}{m}{M}{n}{N}{o}{OöÖ}{p}{P}{q}{Q}{r}{R}{s}{Sß} {t}{T}{u}{UüÜ}{v}{V}{w}{W}{x}{X}{y}{Y}{z}{Z} Warning: No preset char-precedence for language. Using modified Wikipedia char-precedence. {a}{AäâàáåãæÀÁÂÃÄÅÆ}{b}{B}{c}{CçÇ}{d}{DðÐ}{e}{EèéêëÈÉÊ} {f}{F}{g}{G}{hH}{i}{IíîìïÍÎ}{j}{J}{k}{K}{l}{L£}{m}{Mµ} {n}{NñÑ}{o}{OôøòõóöÓÔÕÖ}{p}{P}{q}{Q}{r}{R}{s}{Sß}{t}{T} {u}{UüùúûÙÚÜ}{v}{V}{w}{W}{x}{X}{y}{Yýÿ}{z}{Z}0123456789 -,;:!?/.`~'()}@$*\&%=×ØÞ­´¸§°·²½±¡³ºª»«þ¼¿ description= maintainer= copyright=Publisher: Year: Place: comment00=Dictionary Source URL: comment01=XSLT processor used for TEI->bedic conversion: comment02=Stylesheet used for TEI->bedic conversion: V comment =Note: comment =ChangLog: : \0 Warning: Skipping entry without or with empty orth element(s). Warning: Skipping entry with only form child(ren). form contents: '' Warning: Ignoring additional orth elements in entry ''. \0 Stylesheet assertion failed: Template 'format-homograph' called without input {s} {/s} {ps} {/ps} {pr}{/pr} {ss} -- -- See also: , {/ss} {ct} .{/ct} , () , ( ) {ex}" {hw/} " ( ) {/ex} {sa} {/sa} tools-0.6.0/xsl/tei2dictxml.xsl000066400000000000000000000017371423006221500164460ustar00rootroot00000000000000 _____ tools-0.6.0/xsl/tei2ding.xsl000066400000000000000000000063161423006221500157210ustar00rootroot00000000000000 $Id$ Warning: Skipping entry without <orth> children. Warning: Skipping entry without <tr> or <def> children: { } ; :: [ .] ; Warning: Word contains languages separator (' :: '): Warning: Word contained semicolon: Result: tools-0.6.0/xsl/tei2haali.xsl000066400000000000000000000117771423006221500160650ustar00rootroot00000000000000 NEWLINE :: 40 0 [] ( )

40 2 *
; ( )
tools-0.6.0/xsl/tei2htm.xsl000066400000000000000000000045511423006221500155670ustar00rootroot00000000000000

[] .
;
See also:
. , ()
"" = "" ,
tools-0.6.0/xsl/tei2txt.xsl000066400000000000000000000006621423006221500156150ustar00rootroot00000000000000 tools-0.6.0/xsl/tei2vok.xsl000066400000000000000000000204271423006221500155760ustar00rootroot00000000000000 $Id$ ;/‐ĞğİıŞşćčđřăČąĆĐŭŵŷŌōēĈĉďěĜĝĥĵľňŘŝťůșț ,+-GgIiSsccdraCaCDuwyOoeCcdeGghjlnRstust ˙ [words] [phrases] [notes] Warning: Skipping entry without <orth> children. Warning: Skipping entry without <tr> or <def> children: ; { } / Warning! Ignoring translation alternatives exceeding 16 for entry: ; Warning: Truncating to 127 characters: Result: tools-0.6.0/xsl/tei2webster.xsl000066400000000000000000000025531423006221500164520ustar00rootroot00000000000000 [] ( ) ; ( )

It presupposes that your working copy of Freedict starts at (SVN/)freedict/trunk/ (you really don't need the other directories besides the trunk/) and that there is a copy of Sebastian Rahtz's TEI Stylesheets located in (SVN/)TEI/Stylesheets/ (this is only relevant for the xsl:import statement; but without that statement this script won't do its job).