demokritos-0.3.7/0000755000076500007650000000000010403514611015020 5ustar jtauberjtauber00000000000000demokritos-0.3.7/basic_server.py0000755000076500007650000000174210401233074020047 0ustar jtauberjtauber00000000000000#!/usr/bin/env python # # Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution """ This creates a simple server with two collections in a single workspace. The first collection is an entry collection and the second one is a media collection. This server should be sufficient for basic interop testing. """ import sys sys.path.append("lib") from store import AtomStore import server atom_store = AtomStore("http://localhost:8000", "test/REPOS") workspace = atom_store.create_workspace("Demokritos Test Workspace") collection1 = atom_store.create_collection("Blog Entry Collection", "/blog", "entry") collection2 = atom_store.create_collection("Media Collection", "/media", "media") workspace.add_collection(collection1) workspace.add_collection(collection2) atom_store.save_collection(collection1) atom_store.save_collection(collection2) server.atom_store = atom_store # @@@ hack for now server.AtomServer().serve_forever() demokritos-0.3.7/GPL0000644000076500007650000004313110366122044015372 0ustar jtauberjtauber00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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 Library General Public License instead of this License. demokritos-0.3.7/lib/0000755000076500007650000000000010403514611015566 5ustar jtauberjtauber00000000000000demokritos-0.3.7/lib/app/0000755000076500007650000000000010403514610016345 5ustar jtauberjtauber00000000000000demokritos-0.3.7/lib/app/__init__.py0000644000076500007650000000046610366122044020470 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution from parse import parse_introspection, parse_entry, AtomProtocolException from generate import generate_introspection from model import Service, Workspace, CollectionReference, Collection, Memberdemokritos-0.3.7/lib/app/generate.py0000644000076500007650000000167110366122044020522 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution from atom import generate_entry def generate_introspection(wfile, service): wfile.write("""\n""") for workspace in service.workspaces: generate_workspace(wfile, workspace) wfile.write("""\n""") def generate_workspace(wfile, workspace): wfile.write("""\n""" % workspace.title) for collection_reference in workspace.collections: generate_collection_reference(wfile, collection_reference) wfile.write("""\n""") def generate_collection_reference(wfile, collection_reference): wfile.write("""\n%s\n\n""" % (collection_reference.title, collection_reference.href, collection_reference.member_type)) demokritos-0.3.7/lib/app/model.py0000644000076500007650000000512710374615226020040 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution """ This module models the objects in the Atom Publication Protocol (APP). The objects are designed to be usable on both the server and client side. One implication of this is that a Workspace object doesn't actually contain Collection objects but rather just CollectionReference objects which contain enough to produce the introspection document. Likewise, a Member object only contains enough content to produce the collection feed. Any out-of-line content is referenced but not included. As can be seen in store.py, an actual server implementation has to extend these objects to provide full APP functionality. """ from atom import Entry class Extensible: """ base class of an object that can have extension elements. """ def __init__(self): self.children = [] def add(self, child): self.children.append(child) class Service(Extensible): # workspaces[] def __init__(self): Extensible.__init__(self) self.workspaces = [] class Workspace(Extensible): # title # collections[] - the first collection is the primary one (APP-07 7.2.2) def __init__(self, title=None): Extensible.__init__(self) if title: self.title = title self.collections = [] def add_collection(self, title, href, member_type): self.collections.append(CollectionReference(title, href, member_type)) class CollectionReference(Extensible): # title # href # member_type def __init__(self, title=None, href=None, member_type=None): # note that if arguments are not provided here, the attributes must # be set individually. Extensible.__init__(self) self.title = title self.href = href self.member_type = member_type def updated_sort_key(member): return member.updated.value # @@@ need to normalize timezone class Collection: # members[] def __init__(self): self.members = [] def add_member(self, member): self._remove_member(member) self.members.insert(0, member) self.members.sort(key=updated_sort_key, reverse=True) # underscore variant exists so add_member above doesn't call version # on subclass def _remove_member(self, member): for m in self.members[:]: if m.id == member.id: self.members.remove(m) def remove_member(self, member): self._remove_member(member) class Member: pass demokritos-0.3.7/lib/app/parse.py0000644000076500007650000001061410373002741020036 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution from pyworks.xml import HandlerBase, parse, Generic_ElementHandler, NAMESPACE_SEPARATOR from atom.parse import Atom_EntryHandler, atom_ns from model import * APP_NAMESPACE = "http://purl.org/atom/app#" def app_ns(name): return APP_NAMESPACE + NAMESPACE_SEPARATOR + name class AtomProtocolException(Exception): pass class AtomProtocol_HandlerBase(HandlerBase): def no_children(self, name, attribute): raise AtomProtocolException("%s not allowed here" % name) ## APP-07 7.2.1 class Introspection_RootHandler(AtomProtocol_HandlerBase): def child(self, name, attributes): if name == app_ns("service"): return Introspection_ServiceHandler else: raise AtomProtocolException("%s not allowed here" % name) ## APP-07 7.2.1 - app:service class Introspection_ServiceHandler(AtomProtocol_HandlerBase): def init(self): self.product = Service() def child(self, name, attributes): if name == app_ns("workspace"): return Introspection_WorkspaceHandler else: # @@@ extensionElement return Generic_ElementHandler def end(self, name): if len(self.product.workspaces) == 0: raise AtomProtocolException("service must have at least one workspace") self.parent_handler.product = self.product ## APP-07 7.2.2 - app:workspace class Introspection_WorkspaceHandler(AtomProtocol_HandlerBase): def init(self): self.product = Workspace() if "title" not in self.attributes: raise AtomProtocolException("workspace must have a title") self.product.title = self.attributes["title"] def child(self, name, attributes): if name == app_ns("collection"): return Introspection_CollectionHandler else: # @@@ extensionElement return Generic_ElementHandler def end(self, name): # @@@ APP-07 requires there be at least one collection in a workspace self.parent_handler.product.workspaces.append(self.product) ## APP-07 7.2.3 - app:collection class Introspection_CollectionHandler(AtomProtocol_HandlerBase): def init(self): self.product = CollectionReference() if "title" not in self.attributes: raise AtomProtocolException("collection must have 'title' attribute") if "href" not in self.attributes: raise AtomProtocolException("collection must have 'href' attribute") for name, value in self.attributes.items(): if name == "title": self.product.title = value elif name == "href": self.product.href = value else: raise AtomException("%s not allowed here" % name) def child(self, name, attributes): if name == app_ns("member-type"): return Introspection_MemberTypeHandler else: # @@@ extensionElement return Generic_ElementHandler def end(self, name): if self.product.member_type is None: raise AtomProtocolException("collection must have a member-type") self.parent_handler.product.collections.append(self.product) ## APP-07 7.2.4 - app:member-type class Introspection_MemberTypeHandler(AtomProtocol_HandlerBase): def end(self, name): # @@@ should appTypeValue = "entry" | "media" be enforced? self.parent_handler.product.member_type = self.char_data # this is version of Atom_RootHandler for the more lax entries that can be # POSTed to a collection class LaxEntry_RootHandler(AtomProtocol_HandlerBase): def child(self, name, attributes): if name == atom_ns("entry"): return LaxEntry_EntryHandler else: raise AtomProtocolException("document element must be entry") # this is version of Atom_EntryHandler for the more lax entries that can be # POSTed to a collection class LaxEntry_EntryHandler(Atom_EntryHandler): def validate(self): # unlike superclass, this doesn't have restrictions on id, updated if not self.got_title: raise AtomException("entry must have title element") ## MAIN FUNCTIONS IN THIS MODULE def parse_introspection(xml): return parse(xml, Introspection_RootHandler) def parse_entry(xml): return parse(xml, LaxEntry_RootHandler)demokritos-0.3.7/lib/atom/0000755000076500007650000000000010403514610016525 5ustar jtauberjtauber00000000000000demokritos-0.3.7/lib/atom/__init__.py0000644000076500007650000000054410366122525020651 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution from parse import parse_atom, AtomException from generate import generate_atom, generate_entry, generate_feed from model import Feed, Entry from model import Link, Date, Person from model import PlainText, InlineTextContent, OutOfLineContentdemokritos-0.3.7/lib/atom/generate.py0000644000076500007650000001344410401250522020674 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution # @@@ dates and IRIs must not be surrounded by whitespace import re from model import Feed, Entry, InlineXHTMLContent, InlineTextContent, OutOfLineContent from pyworks.xml import generate_xml def generate_atom(wfile, atom_object): if isinstance(atom_object, Feed): generate_feed(wfile, atom_object) elif isinstance(atom_object, Entry): generate_entry(wfile, atom_object, top=True) else: raise AtomException("unknown object '%s'" % atom_object) def generate_feed(wfile, feed, top=True): if top: wfile.write("""\n""") if top: wfile.write("""\n""") else: wfile.write("""\n""") generate_text("title", wfile, feed.title) if hasattr(feed, "subtitle"): generate_text("subtitle", wfile, feed.subtitle) generate_id(wfile, feed.id) generate_date("updated", wfile, feed.updated) generate_links(wfile, feed.links) generate_people("author", wfile, feed.authors) generate_people("contributor", wfile, feed.contributors) if hasattr(feed, "rights"): generate_rights(wfile, feed.rights) if hasattr(feed, "generator"): # @@@ should this actually just be us? generate_generator(wfile, feed.generator) generate_entries(wfile, feed.entries) wfile.write("""\n""") def generate_text(name, wfile, text_construct): if hasattr(text_construct, "type_"): typ = text_construct.type_ if typ in ["text", "html"]: value = re.sub("&", "&", text_construct.value) value = re.sub("<", "<", value) wfile.write("""<%s type="%s">%s\n""" % (name, typ, value, name)) else: pass # @@@ else: value = re.sub("&", "&", text_construct.value) value = re.sub("<", "<", value) wfile.write("""<%s>%s\n""" % (name, value, name)) def generate_id(wfile, id): wfile.write("""%s\n""" % id) def generate_date(name, wfile, date_construct): wfile.write("""<%s>%s\n""" % (name, date_construct.value, name)) def generate_links(wfile, links): for link in links: attrs = " href=\"%s\"" % link.href # note underscore in rel_ to get explicit value if hasattr(link, "rel_"): attrs += " rel=\"%s\"" % link.rel_ if hasattr(link, "type"): attrs += " type=\"%s\"" % link.type if hasattr(link, "hreflang"): attrs += " hreflang=\"%s\"" % link.hreflang if hasattr(link, "title"): attrs += " title=\"%s\"" % link.title if hasattr(link, "length"): attrs += " length=\"%s\"" % link.length wfile.write("""\n""" % attrs) def generate_people(name, wfile, people): for person in people: wfile.write("""<%s>\n""" % name) wfile.write("""%s\n""" % person.name) if hasattr(person, "uri"): wfile.write("""%s\n""" % person.uri) if hasattr(person, "email"): wfile.write("""%s\n""" % person.email) wfile.write("""\n""" % name) def generate_rights(wfile, rights): wfile.write("""%s\n""" % rights) def generate_generator(wfile, generator): attrs = "" if hasattr(generator, "uri"): attrs += " uri=\"%s\"" % generator.uri if hasattr(generator, "version"): attrs += " version=\"%s\"" % generator.version wfile.write("""%s\n""" % (attrs, generator.value)) def generate_entries(wfile, entries): for entry in entries: generate_entry(wfile, entry) def generate_entry(wfile, entry, top=False): if top: wfile.write("""\n""") if top: wfile.write("""\n""") else: wfile.write("""\n""") # @@@ rights # @@@ source generate_text("title", wfile, entry.title) generate_id(wfile, entry.id) generate_date("updated", wfile, entry.updated) if hasattr(entry, "published"): generate_date("published", wfile, entry.published) generate_links(wfile, entry.links) generate_people("author", wfile, entry.authors) generate_people("contributor", wfile, entry.contributors) if hasattr(entry, "summary"): generate_text("summary", wfile, entry.summary) generate_categories(wfile, entry.categories) if hasattr(entry, "content"): generate_content(wfile, entry.content) wfile.write("""\n""") def generate_categories(wfile, categories): for category in categories: attrs = " term=\"%s\"" % category.term if hasattr(category, "scheme"): attrs += " scheme=\"%s\"" % category.scheme if hasattr(category, "label"): attrs += " label=\"%s\"" % category.label wfile.write("""\n""" % attrs) def generate_content(wfile, content): if isinstance(content, InlineXHTMLContent): wfile.write("""\n""") generate_xml(wfile, content.children[0]) wfile.write("""\n""") elif isinstance(content, OutOfLineContent): wfile.write("""\n""" % content.src) elif isinstance(content, InlineTextContent): escaped_content = content.content escaped_content = re.sub("&", "&", escaped_content) escaped_content = re.sub("<", "<", escaped_content) wfile.write("""%s""" % (content.type, escaped_content)) else: pass # @@@ demokritos-0.3.7/lib/atom/model.py0000644000076500007650000000602610366723567020230 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution class Feed: def __init__(self): self.links = [] self.authors = [] self.contributors = [] self.categories = [] self.entries = [] class Entry: # content # id # published # rights # source # summary # title # updated def __init__(self): self.links = [] self.authors = [] self.contributors = [] self.categories = [] # extension elements self.children = [] def add(self, child): self.children.append(child) class Category: # term # scheme # label def __init__(self): # extension elements self.children = [] def add(self, child): self.children.append(child) class Source: # generator # icon # id # logo # rights # subtitle # title # updated def __init__(self): self.links = [] self.authors = [] self.contributors = [] self.categories = [] # extension elements self.children = [] def add(self, child): self.children.append(child) class PlainText(object): def __init__(self, value=None): if value: self.value = value def get_type(self): if hasattr(self, "type_"): return self.type_ else: return "text" def set_type(self, type): self.type_ = type type = property(get_type, set_type) class XHTMLText: def __init__(self): self.children = [] def add(self, child): self.children.append(child) class Link(object): def __init__(self, rel=None, href=None): if rel: self.rel_ = rel if href: self.href = href def get_rel(self): if hasattr(self, "rel_"): return self.rel_ else: return "alternate" def set_rel(self, rel): self.rel_ = rel rel = property(get_rel, set_rel) class Generator: pass class Person: def __init__(self, name=None, uri=None, email=None): if name: self.name = name if uri: self.uri = uri if email: self.email = email # extension elements self.children = [] def add(self, child): self.children.append(child) class Date: def __init__(self, value=None): if value: self.value = value class InlineTextContent: def __init__(self, content=None): if content: self.content = content class InlineXHTMLContent: def __init__(self): self.children = [] def add(self, child): self.children.append(child) class InlineOtherContent: pass class OutOfLineContent: def __init__(self, src=None): if src: self.src = src demokritos-0.3.7/lib/atom/parse.py0000644000076500007650000005576310401250522020226 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution from pyworks.xml import HandlerBase, parse, Generic_ElementHandler, NAMESPACE_SEPARATOR from model import * ATOM_NAMESPACE = "http://www.w3.org/2005/Atom" def atom_ns(name): return ATOM_NAMESPACE + NAMESPACE_SEPARATOR + name def xhtml_ns(name): return "http://www.w3.org/1999/xhtml" + NAMESPACE_SEPARATOR + name class AtomException(Exception): pass class Atom_HandlerBase(HandlerBase): def no_children(self, name, attribute): raise AtomException("%s not allowed here" % name) # @@@ dsig support (5.1) # @@@ language sensitive # @@@ xml:base class Atom_RootHandler(Atom_HandlerBase): def child(self, name, attributes): if name == atom_ns("feed"): return Atom_FeedHandler elif name == atom_ns("entry"): return Atom_EntryHandler else: raise AtomException("%s not allowed here" % name) class Atom_FeedHandler(Atom_HandlerBase): def init(self): self.product = Feed() self.got_author = False self.got_generator = False self.got_icon = False self.got_id = False self.got_logo = False self.got_rights = False self.got_subtitle = False self.got_title = False self.got_updated = False self.got_entries = False def child(self, name, attributes): if self.got_entries: if name == atom_ns("entry"): return Atom_EntryHandler else: raise AtomException("once entries start in feed, only further entries can follow, not '%s'" % name) else: if name == atom_ns("author"): self.got_author = True return Atom_AuthorHandler elif name == atom_ns("category"): return Atom_CategoryHandler elif name == atom_ns("contributor"): return Atom_ContributorHandler elif name == atom_ns("generator"): if self.got_generator: raise AtomException("feed can only have one generator element") else: self.got_generator = True return Atom_GeneratorHandler elif name == atom_ns("icon"): if self.got_icon: raise AtomException("feed can only have one icon element") else: self.got_icon = True return Atom_IconHandler elif name == atom_ns("id"): if self.got_id: raise AtomException("feed can only have one id element") else: self.got_id = True return Atom_IdHandler elif name == atom_ns("link"): return Atom_LinkHandler elif name == atom_ns("logo"): if self.got_logo: raise AtomException("feed can only have one logo element") else: self.got_logo = True return Atom_LogoHandler elif name == atom_ns("rights"): if self.got_rights: raise AtomException("feed can only have one rights element") else: self.got_rights = True return Atom_RightsHandler elif name == atom_ns("subtitle"): if self.got_subtitle: raise AtomException("feed can only have one subtitle element") else: self.got_subtitle = True return Atom_SubtitleHandler elif name == atom_ns("title"): if self.got_title: raise AtomException("feed can only have one title element") else: self.got_title = True return Atom_TitleHandler elif name == atom_ns("updated"): if self.got_updated: raise AtomException("feed can only have one updated element") else: self.got_updated = True return Atom_UpdatedHandler elif name == atom_ns("entry"): self.got_entries = True return Atom_EntryHandler else: raise AtomException("%s not allowed here" % name) def end(self, name): if not self.got_id: raise AtomException("feed must have id element") elif not self.got_title: raise AtomException("feed must have title element") elif not self.got_updated: raise AtomException("feed must have updated element") else: self.parent_handler.product = self.product class Atom_TextConstructHandlerBase(Atom_HandlerBase): # ABSTRACT BASE def init(self): self.product = None for name, value in self.attributes.items(): if name == "type": if value in ["text", "html"]: # plain text construct self.product = PlainText() self.product.type = value elif value in ["xhtml"]: # xhtml text construct self.product = XHTMLText() self.product.type = value else: raise AtomException("type must be one of 'text', 'html' or 'xhtml', not '%s'" % value) else: raise AtomException("%s not allowed here" % name) if self.product is None: self.product = PlainText() def child(self, name, attributes): if self.product.type in ["text", "html"]: raise AtomException("child elements not allowed when type is '%s'" % self.product.type) elif name != xhtml_ns("div"): raise AtomException("must contain single xhtml:div element, not '%s'" % name) else: return Generic_ElementHandler class Atom_TitleHandler(Atom_TextConstructHandlerBase): def end(self, name): self.product.value = self.char_data.strip() self.parent_handler.product.title = self.product class Atom_SubtitleHandler(Atom_TextConstructHandlerBase): def end(self, name): self.product.value = self.char_data.strip() self.parent_handler.product.subtitle = self.product class Atom_SummaryHandler(Atom_TextConstructHandlerBase): def end(self, name): self.product.value = self.char_data.strip() self.parent_handler.product.summary = self.product class Atom_LinkHandler(Atom_HandlerBase): def init(self): self.product = Link() # @@@ atom:feed elements MUST NOT contain more than one atom:link # element with a rel attribute value of "alternate" that has the # same combination of type and hreflang attribute values. if "href" not in self.attributes: raise AtomException("link must have 'href' attribute") for name, value in self.attributes.items(): if name == "href": self.product.href = value elif name == "rel": self.product.rel = value elif name == "type": self.product.type = value elif name == "hreflang": self.product.hreflang = value elif name == "length": self.product.length = value elif name == "title": self.product.title = value else: raise AtomException("%s not allowed here" % name) child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.links.append(self.product) class Atom_CategoryHandler(Atom_HandlerBase): def init(self): self.product = Category() if "term" not in self.attributes: raise AtomException("category must have term attribute") for name, value in self.attributes.items(): if name == "term": self.product.term = value elif name == "scheme": self.product.scheme = value elif name == "label": self.product.label = value # @@@ language sensitive else: raise AtomException("%s not allowed here" % name) def child(self, name, attributes): return Generic_ElementHandler def end(self, name): self.parent_handler.product.categories.append(self.product) class Atom_DateConstructHandlerBase(Atom_HandlerBase): def init(self): self.product = Date() child = Atom_HandlerBase.no_children # @@@ should validate date (see section 3.3) class Atom_UpdatedHandler(Atom_DateConstructHandlerBase): def end(self, name): self.product.value = self.char_data self.parent_handler.product.updated = self.product class Atom_PublishedHandler(Atom_DateConstructHandlerBase): def end(self, name): self.product.value = self.char_data self.parent_handler.product.published = self.product class Atom_PersonConstructHandlerBase(Atom_HandlerBase): # ABSTRACT BASE def init(self): self.product = Person() self.got_name = False self.got_uri = False self.got_email = False def child(self, name, attributes): if name == atom_ns("name"): if self.got_name: raise AtomException("can only have one name element in a person construct") else: self.got_name = True return Atom_NameHandler elif name == atom_ns("uri"): if self.got_uri: raise AtomException("can only have one uri element in a person construct") else: self.got_uri = True return Atom_UriHandler elif name == atom_ns("email"): if self.got_email: raise AtomException("can only have one email element in a person construct") else: self.got_email = True return Atom_EmailHandler else: return Generic_ElementHandler def end(self, name): if not self.got_name: raise AtomException("person construct must have a name") # @@@ If an atom:entry element does not contain atom:author elements, then # the atom:author elements of the contained atom:source element are # considered to apply. In an Atom Feed Document, the atom:author # elements of the containing atom:feed element are considered to apply # to the entry if there are no atom:author elements in the locations # described above. class Atom_AuthorHandler(Atom_PersonConstructHandlerBase): def end(self, name): self.parent_handler.product.authors.append(self.product) class Atom_ContributorHandler(Atom_PersonConstructHandlerBase): def end(self, name): self.parent_handler.product.contributors.append(self.product) class Atom_NameHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.name = self.char_data class Atom_UriHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.uri = self.char_data class Atom_EmailHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.email = self.char_data class Atom_IdHandler(Atom_HandlerBase): # @@@ 4.2.6 child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.id = self.char_data class Atom_IconHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.icon = self.char_data class Atom_LogoHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.icon = self.char_data class Atom_RightsHandler(Atom_HandlerBase): child = Atom_HandlerBase.no_children def end(self, name): self.parent_handler.product.rights = self.char_data.strip() class Atom_GeneratorHandler(Atom_HandlerBase): def init(self): self.product = Generator() for name, value in self.attributes.items(): if name == "uri": self.product.uri = value elif name == "version": self.product.version = value else: raise AtomException("%s not allowed here" % name) child = Atom_HandlerBase.no_children def end(self, name): self.product.value = self.char_data.strip() self.parent_handler.product.generator = self.product class Atom_EntryHandler(Atom_HandlerBase): def init(self): self.product = Entry() self.got_author = False self.got_content = False self.got_id = False self.got_published = False self.got_rights = False self.got_source = False self.got_summary = False self.got_title = False self.got_updated = False def child(self, name, attributes): if name == atom_ns("author"): self.got_author = True return Atom_AuthorHandler elif name == atom_ns("category"): return Atom_CategoryHandler elif name == atom_ns("content"): if self.got_content: raise AtomException("entry can only have one content element") else: self.got_content = True return Atom_ContentHandler elif name == atom_ns("contributor"): return Atom_ContributorHandler elif name == atom_ns("id"): if self.got_id: raise AtomException("entry can only have one id element") else: self.got_id = True return Atom_IdHandler elif name == atom_ns("link"): return Atom_LinkHandler elif name == atom_ns("published"): if self.got_published: raise AtomException("entry can only have one published element") else: self.got_published = True return Atom_PublishedHandler elif name == atom_ns("rights"): if self.got_rights: raise AtomException("entry can only have one rights element") else: self.got_rights = True return Atom_RightsHandler elif name == atom_ns("source"): if self.got_source: raise AtomException("entry can only have one source element") else: self.got_source = True return Atom_SourceHandler elif name == atom_ns("summary"): if self.got_summary: raise AtomException("entry can only have one summary element") else: self.got_summary = True return Atom_SummaryHandler elif name == atom_ns("title"): if self.got_title: raise AtomException("entry can only have one title element") else: self.got_title = True return Atom_TitleHandler elif name == atom_ns("updated"): if self.got_updated: raise AtomException("entry can only have one updated element") else: self.got_updated = True return Atom_UpdatedHandler else: return Generic_ElementHandler # @@@ atom:entry elements that contain no child atom:content element # MUST contain at least one atom:link element with a rel attribute # value of "alternate". # @@@ atom:entry elements MUST contain an atom:summary element in either # of the following cases: # # * the atom:entry contains an atom:content that has a "src" # attribute (and is thus empty). # # * the atom:entry contains content that is encoded in Base64; i.e. # the "type" attribute of atom:content is a MIME media type # [MIMEREG], but is not an XML media type [RFC3023], does not # begin with "text/", and does not end with "/xml" or "+xml". def end(self, name): self.validate() if isinstance(self.parent_handler, Atom_FeedHandler): self.parent_handler.product.entries.append(self.product) else: self.parent_handler.product = self.product def validate(self): if isinstance(self.parent_handler, Atom_FeedHandler): if not self.got_author and not self.parent_handler.got_author: raise AtomException("entry may not omit author element if feed does") # if not in a feed, ignore the author requirement @@@ if not self.got_id: raise AtomException("entry must have id element") if not self.got_title: raise AtomException("entry must have title element") if not self.got_updated: raise AtomException("entry must have updated element") class Atom_ContentHandler(Atom_HandlerBase): def init(self): self.product = None if "src" in self.attributes: self.product = OutOfLineContent() self.product.src = self.attributes["src"] type_attr = self.attributes.get("type") if type_attr in ["text", "html", "xhtml"]: raise AtomException("content with src cannot have type of '%s'" % type_attr) if type_attr: self.product.type = type_attr else: type_attr = self.attributes.get("type") if type_attr in ["text", "html"]: self.product = InlineTextContent() elif type_attr == "xhtml": self.product = InlineXHTMLContent() elif type_attr == None: self.product = InlineTextContent() else: self.product = InlineOtherContent() if type_attr: self.product.type = type_attr def child(self, name, attributes): if isinstance(self.product, InlineTextContent): raise AtomException("child elements not allowed when type is inline text") elif isinstance(self.product, InlineXHTMLContent): if name != xhtml_ns("div"): raise AtomException("content of type 'xhtml' must contain single xhtml:div element, not '%s'" % name) else: return Generic_ElementHandler elif isinstance(self.product, OutOfLineContent): raise AtomException("child elements not allowed in out of line content") else: # InlineOtherContent pass # @@@ # @@@ # 4. If the value of "type" is an XML media type [RFC3023], or ends # with "+xml" or "/xml" (case-insensitive), the content of # atom:content MAY include child elements, and SHOULD be suitable # for handling as the indicated media type. If the "src" attribute # is not provided, this would normally mean that the "atom:content" # # element would contain a single child element which would serve as # the root element of the XML document of the indicated type. # # 5. If the value of "type" begins with "text/" (case-insensitive), # the content of atom:content MUST NOT contain child elements. # # 6. For all other values of "type", the content of atom:content MUST # be a valid Base64 encoding, as described in [RFC3548], section 3. # When decoded, it SHOULD be suitable for handling as the indicated # media type. In this case, the characters in the Base64 encoding # MAY be preceded and followed in the atom:content element by # white-space, and lines are separated by a single newline (U+000A) # character. # def end(self, name): if isinstance(self.product, InlineTextContent): self.product.content = self.char_data.strip() self.parent_handler.product.content = self.product Atom_HandlerBase.end(self, name) class Atom_SourceHandler(Atom_HandlerBase): def init(self): self.product = Source() self.got_author = False self.got_generator = False self.got_icon = False self.got_id = False self.got_logo = False self.got_rights = False self.got_subtitle = False self.got_title = False self.got_updated = False def child(self, name, attributes): if name == atom_ns("author"): self.got_author = True return Atom_AuthorHandler elif name == atom_ns("category"): return Atom_CategoryHandler elif name == atom_ns("contributor"): return Atom_ContributorHandler elif name == atom_ns("generator"): if self.got_generator: raise AtomException("source can only have one generator element") else: self.got_generator = True return Atom_GeneratorHandler elif name == atom_ns("icon"): if self.got_icon: raise AtomException("source can only have one icon element") else: self.got_icon = True return Atom_IconHandler elif name == atom_ns("id"): if self.got_id: raise AtomException("source can only have one id element") else: self.got_id = True return Atom_IdHandler elif name == atom_ns("link"): return Atom_LinkHandler elif name == atom_ns("logo"): if self.got_logo: raise AtomException("source can only have one logo element") else: self.got_logo = True return Atom_LogoHandler elif name == atom_ns("rights"): if self.got_rights: raise AtomException("source can only have one rights element") else: self.got_rights = True return Atom_RightsHandler elif name == atom_ns("subtitle"): if self.got_subtitle: raise AtomException("source can only have one subtitle element") else: self.got_subtitle = True return Atom_SubtitleHandler elif name == atom_ns("title"): if self.got_title: raise AtomException("source can only have one title element") else: self.got_title = True return Atom_TitleHandler elif name == atom_ns("updated"): if self.got_updated: raise AtomException("source can only have one updated element") else: self.got_updated = True return Atom_UpdatedHandler else: # @@@ extensionElement return Generic_ElementHandler def end(self, name): self.parent_handler.product.source = self.product def parse_atom(xml): return parse(xml, Atom_RootHandler) demokritos-0.3.7/lib/persistence.py0000644000076500007650000001376210401366654020507 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2006 James Tauber # See GPL file at top of distribution import cPickle as pickle from pyworks.svn import SvnRepository, SvnNode from store import ServerEntryCollection, ServerMediaCollection from store import ServerEntryMember, ServerMediaMember NONE = 0 COLLECTION = 1 MEMBER = 2 OTHER = 3 class Persistence: def __init__(self, repos_path, store): self.store = store self.svn_repository = SvnRepository(repos_path) def get_type(self, path): node = self.svn_repository.get_node(path) if node.node_kind() == SvnNode.NONE: return NONE if node.node_kind() == SvnNode.DIR: return OTHER # @@@ hack - could use dedicated property member_type = node.get_property("app:member-type") if member_type: return COLLECTION else: return MEMBER def get_collection(self, path): node = self.svn_repository.get_node(path) if node.node_kind() == SvnNode.NONE: return None else: if node.get_property("app:member-type") == "entry": collection = PersistedEntryCollection(path, node) else: collection = PersistedMediaCollection(path, node) collection.dereference_members(self) collection.store = self.store return collection def get_member(self, path): node = self.svn_repository.get_node(path) if node.node_kind() == SvnNode.NONE: return None else: if node.get_property("app:media-type") == "application/atom+xml": member = PersistedEntryMember(path, node) else: member = PersistedMediaMember(path, node) return member def remove_member(self, member): collection = self.get_collection(member.collection_path) collection.remove_member(member) txn = self.svn_repository.begin_txn() txn.delete(member.path) txn.commit() # @@@ could be done in same transaction self.save_collection(collection) def save_member(self, member, collection): node = self.svn_repository.get_node(member.path) txn = self.svn_repository.begin_txn() if node.node_kind() == SvnNode.NONE: txn.make_dirs(member.path) txn.make_file(member.path) txn.set_content(member.path, member.get_content()) txn.change_node_prop(member.path, "svn:mime-type", member.media_type) txn.change_node_prop(member.path, "app:media-type", member.media_type) txn.change_node_prop(member.path, "atom:href", str(member.href)) txn.change_node_prop(member.path, "atom:id", str(member.id)) txn.change_node_prop(member.path, "app:collection", str(member.collection_path)) txn.change_node_prop(member.path, "atom:title", pickle.dumps(member.title)) txn.change_node_prop(member.path, "atom:updated", pickle.dumps(member.updated)) txn.change_node_prop(member.path, "atom:authors", pickle.dumps(member.authors)) # @@@ other properties txn.commit() def save_collection(self, collection): node = self.svn_repository.get_node(collection.path) txn = self.svn_repository.begin_txn() if node.node_kind() == SvnNode.NONE: txn.make_dirs(collection.path) txn.make_file(collection.path) member_path_list = "\n".join(collection.member_path_list) txn.set_content(collection.path, str(member_path_list)) txn.change_node_prop(collection.path, "app:member-type", str(collection.member_type)) txn.change_node_prop(collection.path, "atom:id", str(collection.id)) txn.change_node_prop(collection.path, "atom:title", str(collection.title)) txn.change_node_prop(collection.path, "atom:updated", pickle.dumps(collection.updated)) txn.change_node_prop(collection.path, "atom:href", str(collection.href)) txn.commit() class PersistedCollection: def __init__(self, path, svn_node): self.path = path self.svn_node = svn_node self.member_type = svn_node.get_property("app:member-type") self.id = svn_node.get_property("atom:id") self.title = svn_node.get_property("atom:title") updated_pickle = svn_node.get_property("atom:updated") if updated_pickle: self.updated = pickle.loads(updated_pickle) self.href = svn_node.get_property("atom:href") content = self.svn_node.get_content() if content == "": self.member_path_list = [] else: self.member_path_list = content.split("\n") def dereference_members(self, persistence): self.members = [persistence.get_member(path) for path in self.member_path_list] class PersistedEntryCollection(PersistedCollection, ServerEntryCollection): pass class PersistedMediaCollection(PersistedCollection, ServerMediaCollection): pass class PersistedMember: def __init__(self, path, svn_node): self.path = path self.svn_node = svn_node self.media_type = svn_node.get_property("app:media-type") self.href = svn_node.get_property("atom:href") self.id = svn_node.get_property("atom:id") self.collection_path = svn_node.get_property("app:collection") title_pickle = svn_node.get_property("atom:title") if title_pickle: self.title = pickle.loads(title_pickle) updated_pickle = svn_node.get_property("atom:updated") if updated_pickle: self.updated = pickle.loads(updated_pickle) authors_pickle = svn_node.get_property("atom:authors") if authors_pickle: self.authors = pickle.loads(authors_pickle) # @@@ other properties def get_content(self): return self.svn_node.get_content() class PersistedMediaMember(PersistedMember, ServerMediaMember): pass class PersistedEntryMember(PersistedMember, ServerEntryMember): pass demokritos-0.3.7/lib/pyworks/0000755000076500007650000000000010403514611017304 5ustar jtauberjtauber00000000000000demokritos-0.3.7/lib/pyworks/__init__.py0000644000076500007650000000103010373002741021411 0ustar jtauberjtauber00000000000000# Pyworks Common Library # Copyright (C) 2001-2006 James Tauber # See GPL file at top of distribution """ This package provides a number of modules that are useful outside of Demokritos. pyworks.svn - object-oriented wrapper around subversion binding pyworks.web - http server with more RESTful approach pyworks.xml - xml parsing library """ import sys import pyworks_svn import pyworks_web import pyworks_xml sys.modules["pyworks.svn"] = pyworks_svn sys.modules["pyworks.web"] = pyworks_web sys.modules["pyworks.xml"] = pyworks_xmldemokritos-0.3.7/lib/pyworks/pyworks_svn.py0000644000076500007650000000675210373067070022304 0ustar jtauberjtauber00000000000000# pyworks.svn - object-oriented wrapper around subversion binding # Copyright (C) 2006 James Tauber # See GPL file at top of distribution """ This module provides a more object-oriented wrapper around the SWIG-based binding that comes with the version control system, Subversion. The main purpose of this module is to enable use of a local subversion repository for persistence. Subversion 1.3.0 or later is required. """ from svn import fs, repos, core assert (core.SVN_VER_MAJOR, core.SVN_VER_MINOR) >= (1, 3), "Subversion 1.3 or later required" class SvnRepository: """ A wrapper around a subversion repository and filesystem. """ def __init__(self, root_path): self.repos = repos.open(root_path) self.fs_ptr = repos.fs(self.repos) def latest_rev(self): return fs.youngest_rev(self.fs_ptr) def get_node(self, path, revision=None): revision = revision or self.latest_rev() revision_root = fs.revision_root(self.fs_ptr, revision) return SvnNode(self.fs_ptr, revision_root, path) def get_revision_property(self, name, revision=None): revision = revision or self.latest_rev() return fs.revision_prop(self.fs_ptr, revision, name) def begin_txn(self, revision=None): revision = revision or self.latest_rev() return SvnTransaction(fs.begin_txn(self.fs_ptr, revision)) class SvnTransaction: """ A wrapper around a subversion transaction and transaction root. """ def __init__(self, txn): self.txn = txn self.root = fs.txn_root(txn) def commit(self): return fs.commit_txn(self.txn) def abort(self): return fs.abort_txn(self.txn) def make_dirs(self, path): parts = path.split("/") for i in range(len(parts)): d = "/".join(parts[:i]) if d: if fs.check_path(self.root, d) == SvnNode.NONE: self.make_dir(d) def make_dir(self, path): fs.make_dir(self.root, path) def make_file(self, path): fs.make_file(self.root, path) def change_node_prop(self, path, name, value): fs.change_node_prop(self.root, path, name, value) def delete(self, path): fs.delete(self.root, path) def set_content(self, path, content): stream = fs.apply_text(self.root, path, None) core.svn_stream_write(stream, content) core.svn_stream_close(stream) class SvnNode: """ A subversion node (revision root / path pair). """ def __init__(self, fs_ptr, revision_root, path): self.fs_ptr = fs_ptr self.root = revision_root self.path = path # node kinds NONE, FILE, DIR, UNKNOWN = range(4) def node_kind(self): return fs.check_path(self.root, self.path) def get_stream(self): stream = fs.file_contents(self.root, self.path) return core.Stream(stream) def get_content(self): return self.get_stream().read() def get_entries(self): # @@@ could return nodes return fs.dir_entries(self.root, self.path).keys() def get_property(self, name): return fs.node_prop(self.root, self.path, name) def get_properties(self): return fs.node_proplist(self.root, self.path) def last_modified(self): created_rev = fs.node_created_rev(self.root, self.path) date = fs.revision_prop(self.fs_ptr, created_rev, "svn:date") return date # return core.svn_time_from_cstring(date) / 1000000 demokritos-0.3.7/lib/pyworks/pyworks_web.py0000644000076500007650000001513410373002741022237 0ustar jtauberjtauber00000000000000# pyworks.web - http server with more RESTful approach # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution """ This module provides a base for an HTTP server that encourages a RESTful resource-oriented approach. Server should be instantiated with a handler that subclasses RequestHandler. """ import traceback import BaseHTTPServer import urllib import cgi class Server: """ instantiate or subclass this and pass a subclass of RequestHandler to __init__ as the handler. """ def __init__(self, host, port, handler): self.httpd = BaseHTTPServer.HTTPServer((host, port), handler) def serve_forever(self): self.httpd.serve_forever() class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """ handles HTTP requests. subclass this and: - set server_version - implement a get_resource(self, request) method which returns the appropriate Resource subclass for the given request. """ ## server_version = "..." ## def get_resource(self, request): ## ...return appropriate Resource based on request... def do_HEAD(self): self._handle("HEAD") def do_GET(self): self._handle("GET") def do_POST(self): self._handle("POST") def do_PUT(self): self._handle("PUT") def do_DELETE(self): self._handle("DELETE") def _handle(self, method): try: request = Request(self) resource = self.get_resource(request) resource.request = request if not hasattr(resource, method): self.send_error(405, "Cannot %s this URI" % method) else: response = getattr(resource, method)() response.output(self) except HTTPError, e: code, message = e self.send_error(code, message) except: traceback.print_exc() self.send_error(500, "Server Got Exception") class Request: """ encapsulates an HTTP request. fields: full_path - full path including query string path - path without query string header - dictionary of headers content - body content (if any) """ def __init__(self, request_handler): self.full_path = request_handler.path self.path, self.query = urllib.splitquery(self.full_path) self.header = request_handler.headers length = request_handler.headers.get("Content-Length") if length: self.content = request_handler.rfile.read(int(length)) # @@@ only read content if there is a Content-Length header # @@@ seems to hang otherwise - need to investigate # @@@ could return a 411 in the mean-time def parse(self, types): """ parses the query string according to the required types, raising a ValueError if the query string doesn't conform. Currently only supports a single field. types should be a tuple of field_name and field_type where field_type must be able to take a string to generate the desired object, e.g. index = self.request.parse(("index", int)) will return an integer index if it the sole parameter in the query string otherwise a ValueError will be raised. """ if isinstance(types, list): pass # @@@ to implement else: field_name, field_type = types qsl = cgi.parse_qsl(self.query, strict_parsing=True) if len(qsl) != 1: raise ValueError if qsl[0][0] != field_name: raise ValueError field_value = field_type(qsl[0][1]) return field_value class Response: """ encapsulates the response to be given by the HTTP server. """ def __init__(self, code, header={}, content=None): self.code = code self.header = header self.content = content def output(self, handler): handler.send_response(self.code) for header in self.header: handler.send_header(header, self.header[header]) handler.end_headers() if self.content: handler.wfile.write(self.content) class Resource: """ an object identified by a URI (looked up by get_resource on RequestHandler) that responds to HTTP methods such as HEAD, GET, POST, PUT, DELETE. For convenience, HEAD and GET are already implemented (you'll just need to implement a content() method---see below). You may also implement one or more of POST, PUT, DELETE. The HEAD, GET, POST, PUT, DELETE methods of a Resource should either return one of these objects, e.g. return Response(200, {"Content-type": media_type}, content) return Response(201, {"Location": new_uri}) or raise an HTTPError: raise HTTPError(415, "Unsupported Media Type") raise HTTPError(404, "Resource Not Found") As well as passing the parts of the response in the constructor, you can also build them up individually, e.g. response = Response(200) response.header["Content-type"] = content_type response.header["Content-Length"] = str(len(content)) response.content = content return response Note that you should only implement those HTTP methods that are appropriate. The server will automatically return a 405 if a client uses a method your resource doesn't support. While you can implement your own HEAD and GET methods, it is easier to use the defaults and instead implement a content() method which either returns a tuple of strings (content_type, content) or raises an HTTPError. """ def HEAD(self): content_type, content = self.content() response = Response(200) response.header["Content-type"] = content_type response.header["Content-Length"] = str(len(content)) return response def GET(self): content_type, content = self.content() response = Response(200) response.header["Content-type"] = content_type response.header["Content-Length"] = str(len(content)) response.content = content return response ## def content(self): ## ...return content_type, content ## implement POST, PUT, DELETE if necessary class HTTPError(Exception): """ raise this for 4xx HTTP errors. You should pass in two args: the HTTP code (as an int) and an error message string, e.g. raise HTTPError(404, "Resource Not Found") """ pass demokritos-0.3.7/lib/pyworks/pyworks_xml.py0000644000076500007650000000727610373002741022272 0ustar jtauberjtauber00000000000000# pyworks.xml - xml parsing library # Copyright (C) 2001-2006 James Tauber # See GPL file at top of distribution """ This is a base module I use for writing handlers for building domain objects from expat parsing events. """ from xml.parsers import expat XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace" NAMESPACE_SEPARATOR = "^" def xml_ns(name): return XML_NAMESPACE + NAMESPACE_SEPARATOR + name class HandlerBase: def __init__(self, parser, parent_handler=None, name=None, attributes=None, inherited={}): self.parser = parser self.parent_handler = parent_handler self.name = name self.attributes = {} self.char_data = "" self.set_handlers() self.inherited = inherited if attributes: for name, value in attributes.items(): if name == xml_ns("lang"): self.inherited["xml:lang"] = value elif name == xml_ns("base"): self.inherited["xml:base"] = value else: self.attributes[name] = value self.init() def init(self): pass def set_handlers(self): self.parser.StartElementHandler = self.process_child self.parser.CharacterDataHandler = self.char self.parser.EndElementHandler = self.process_end def process_child(self, name, attributes): handler = self.child(name, attributes) handler(self.parser, self, name, attributes, self.inherited) def char(self, data): self.char_data += data def process_end(self, name): self.end(name) if self.parent_handler is not None: self.parent_handler.set_handlers() else: # must be root pass def end(self, name): pass class Element: def __init__(self, name, attributes): self.name = name self.attributes = attributes self.children = [] def add(self, child): self.children.append(child) class CharData: def __init__(self, chardata): self.chardata = chardata class Generic_ElementHandler(HandlerBase): def init(self): self.product = Element(self.name, self.attributes) def child(self, name, attributes): return Generic_ElementHandler def char(self, data): self.product.add(CharData(data)) def end(self, name): self.parent_handler.product.add(self.product) def parse(xml, root_handler_class): parser = expat.ParserCreate(namespace_separator=NAMESPACE_SEPARATOR) root_handler = root_handler_class(parser) parser.Parse(xml) return root_handler.product def make_prefix(uri): if uri == "http://www.w3.org/1999/xhtml": return "xhtml" else: pass # @@@ def generate_xml(wfile, element, namespaces = {}): attrs = "" for name, value in element.attributes.items(): # @@@ escaping attrs += " %s=\"%s\"" % (name, value) if NAMESPACE_SEPARATOR in element.name: # @@@ should really be pre-split uri, name = element.name.split(NAMESPACE_SEPARATOR) if uri not in namespaces: prefix = make_prefix(uri) attrs += " xmlns:%s=\"%s\"" % (prefix, uri) namespaces[uri] = prefix element_name = namespaces[uri] + ":" + name else: element_name = element.name wfile.write("<%s%s>" % (element_name, attrs)) for child in element.children: if isinstance(child, Element): generate_xml(wfile, child, namespaces) else: # chardata wfile.write(child.chardata) wfile.write("" % element_name)demokritos-0.3.7/lib/server.py0000755000076500007650000001245210403514366017464 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution """ The Atom HTTP server, built on webbase that provides the link between HTTP requests and the store itself. """ from cStringIO import StringIO import time from store import UnsupportedMediaTypeError, MissingMediaTypeError from app import generate_introspection from atom import generate_feed, generate_entry, AtomException from pyworks.web import Server, RequestHandler, Response, Resource, HTTPError ## Note that this module gets injected with the atom_store object. class AtomServer(Server): def __init__(self, host="", port=8000): Server.__init__(self, host, port, AtomRequestHandler) print "listening on port", port class AtomRequestHandler(RequestHandler): server_version = "Demokritos/0.3.7" def get_resource(self, request): if request.full_path == "/introspection": return IntrospectionResource() elif atom_store.is_collection(request.path): collection = atom_store.get_collection(request.path) return CollectionResource(collection) elif atom_store.is_member(request.path): member = atom_store.get_member(request.path) return MemberResource(member) else: raise HTTPError(404, "Resource Not Found") class IntrospectionResource(Resource): """ the Resource representing introspection documents. """ def content(self): content = StringIO() generate_introspection(content, atom_store.service) return "application/atomserv+xml", content.getvalue() class CollectionResource(Resource): """ the Resource representing collection feeds. """ def __init__(self, collection): self.collection = collection def content(self): if self.collection: if self.request.query: try: index = self.request.parse(("index", int)) except ValueError: # @@@ catches too much raise HTTPError(400, "Bad Query String") else: index = 0 try: feed = self.collection.make_feed(index) except AttributeError: # @@@ catches too much raise HTTPError(400, "Bad Query String") content = StringIO() generate_feed(content, feed) return "application/atom+xml", content.getvalue() else: raise HTTPError(404, "Resource Not Found") def POST(self): # add new resource to collection and return 201 with location if self.collection: suggested_title = self.request.header.get("Title") media_type = self.request.header.get("Content-type") content = self.request.content try: new_member = self.collection.create_member(content, media_type, suggested_title) entry = new_member.make_feed_entry() entry_content = StringIO() generate_entry(entry_content, entry, top=True) return Response(201, {"Location": new_member.href}, entry_content.getvalue()) except AtomException, e: raise HTTPError(400, e) except UnsupportedMediaTypeError: raise HTTPError(415, "Unsupported Media Type") except MissingMediaTypeError, e: raise HTTPError(400, "Missing Content Type") else: raise HTTPError(404, "Collection Not Found") ## Note you cannot PUT or DELETE a CollectionResource class MemberResource(Resource): """ the Resource representing individual members of a collection. """ def __init__(self, member): self.member = member def content(self): if self.member: if self.request.query: if self.request.query == "atom": entry = self.member.make_feed_entry() content = StringIO() generate_entry(content, entry, top=True) return "application/atom+xml", content.getvalue() else: raise HTTPError(400, "Bad Query String") else: return self.member.media_type, self.member.get_content() else: raise HTTPError(404, "Resource Not Found") ## Note you cannot POST to a MemberResource def PUT(self): # replace resource, return 200 with resource if self.member: media_type = self.request.header.get("Content-type") suggested_title = self.request.header.get("Title") content = self.request.content self.member = atom_store.replace_member(self.member, content, media_type, suggested_title) # @@@ this could also return a 204 with no content return Response(200, {"Content-type": self.member.media_type}, self.member.get_content()) else: raise HTTPError(404, "Resource Not Found") def DELETE(self): # delete resource, return 204 if self.member: atom_store.remove_member(self.member) else: raise HTTPError(404, "Resource Not Found") return Response(204) demokritos-0.3.7/lib/store.py0000644000076500007650000003042410401403311017267 0ustar jtauberjtauber00000000000000# Demokritos Atom Library and Server # Copyright (C) 2005-2006 James Tauber # See GPL file at top of distribution """ This is the core of the Atom store and ties together the service, workspace, collection and member domain objects. """ #import sys #sys.path.append("lib") from app import Service, Workspace, Collection, Member from atom import Feed, Entry from atom import Link, Date, Person from atom import PlainText, InlineTextContent, OutOfLineContent from app import parse_entry from atom import generate_entry from cStringIO import StringIO import re import time class UnsupportedMediaTypeError(Exception): pass class MissingMediaTypeError(Exception): pass class AtomStore: def __init__(self, root_uri, repos_path): """ instantiates a new Atom Store with the given root URI. """ self.root_uri = root_uri self.service = Service() # has to be here to avoid circularity from persistence import Persistence self.persistence = Persistence(repos_path, self) self.make_uri = UriMint(self, self.root_uri) def create_workspace(self, name): """ creates an empty workspace. The workspace will have a name and will be registered with the store. """ workspace = ServerWorkspace(name) self.service.workspaces.append(workspace) return workspace def create_collection(self, title, path, member_type): """ creates an empty collection. The collection will have an ID and HREF based on the path provided as well as a title. The collection will also be registered with the store. Valid member types are currently "media" and "entry". """ if member_type == "media": collection = ServerMediaCollection(title, path) elif member_type == "entry": collection = ServerEntryCollection(title, path) else: raise AttributeError, "unknown member_type" collection.href = self.root_uri + path collection.id = collection.href collection.store = self return collection def create_media_member(self, path): """ creates an empty media member. The object will have an ID and HREF based on the path provided and will be registered with the store. It will not yet be in a collection however, and all other attributes will still need to be set. """ member = ServerMediaMember(path) member.href = self.root_uri + path member.id = member.href return member def save_collection(self, collection): self.persistence.save_collection(collection) def save_member(self, member, collection): self.persistence.save_member(member, collection) # @@@ could be done explicitly # @@@ could be done in same transaction self.save_collection(collection) def create_entry_member(self, path): """ creates an empty entry member. The object will have an ID and HREF based on the path provided and will be registered with the store. It will not yet be in a collection however, and all other attributes will still need to be set. """ member = ServerEntryMember(path) member.href = self.root_uri + path member.id = member.href return member def is_collection(self, path): import persistence # @@@ return self.persistence.get_type(path) == persistence.COLLECTION def is_member(self, path): import persistence # @@@ return self.persistence.get_type(path) == persistence.MEMBER def exists(self, path): import persistence # @@@ return self.persistence.get_type(path) != persistence.NONE def get_collection(self, path): return self.persistence.get_collection(path) def get_member(self, path): return self.persistence.get_member(path) def remove_member(self, member): self.persistence.remove_member(member) def replace_member(self, old_member, content, media_type, suggested_title): collection = self.persistence.get_collection(old_member.collection_path) new_member = collection.create_member(content, media_type, suggested_title, old_member) return new_member class UriMint: """ This example of a URI Mint concatenates the collection path with yyyy/mm/dd with a modified suggested_name. """ def __init__(self, store, root_uri): self.store = store self.root_uri = root_uri def __call__(self, collection, suggested_name=None): if suggested_name: base_name = re.sub("[^A-Za-z0-9\-\.\,\(\)]", "_", suggested_name).lower()[:25] else: base_name = "resource" # @@@ date = "/%04d/%02d/%02d/" % tuple(time.localtime()[:3]) # @@@ doesn't check for duplicates yet # @@@ should support collection-specific prefix path = date + base_name orig_path = path count = 0 while self.store.exists(path): count += 1 path = orig_path + "_" + str(count) uri = self.root_uri + path return uri, path class ServerWorkspace(Workspace): def add_collection(self, collection): Workspace.add_collection(self, collection.title, collection.href, collection.member_type) class ServerCollection(Collection): def __init__(self, title, path): Collection.__init__(self) self.title = title self.path = path self.updated = Date(time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) # @@@ self.member_type = None self.member_path_list = [] def add_member(self, member): if not self.supported_media_type(member.media_type): raise UnsupportedMediaTypeError() Collection.add_member(self, member) member.collection_path = self.path # member.collections.add(self) self.updated = self.members[0].updated self.member_path_list = [member.path for member in self.members] def remove_member(self, member): Collection.remove_member(self, member) self.updated = Date(time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) self.member_path_list = [member.path for member in self.members] def make_feed(self, index=0): max_entries = 10 # @@@ magic number to = min(index + max_entries, len(self.members)) if len(self.members) > to: next = to else: next = 0 if index > 0: prev = min(0, index - max_entries) last = max(0, max_entries * ((len(self.members) - 1) / max_entries)) feed = Feed() feed.id = self.id # @@@ feed.title = PlainText(self.title) # @@@ perhaps Collection.title should be text construct feed.updated = self.updated if index: if index < 0 or index > len(self.members) - 1: raise AttributeError feed.links.append(Link(rel="first", href=self.href)) if prev: feed.links.append(Link(rel="previous", href="%s?index=%d" % (self.href, prev))) else: feed.links.append(Link(rel="previous", href=self.href)) if next: feed.links.append(Link(rel="next", href="%s?index=%d" % (self.href, next))) if last: feed.links.append(Link(rel="last", href="%s?index=%d" % (self.href, last))) else: # always include last feed.links.append(Link(rel="last", href=self.href)) feed.entries = [member.make_feed_entry() for member in self.members[index:to]] return feed class ServerMediaCollection(ServerCollection): # @@@ add support for restricting media types def __init__(self, title, path): ServerCollection.__init__(self, title, path) self.member_type = "media" def supported_media_type(self, media_type): return True def create_member(self, content, media_type, suggested_title, replace_member=None): if not self.supported_media_type(media_type): raise UnsupportedMediaTypeError() if replace_member: uri = replace_member.href path = replace_member.path else: uri, path = self.store.make_uri(self, suggested_title) member = self.store.create_media_member(path) member.title = PlainText(suggested_title or "no title") # @@@ member.updated = Date(time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) # @@@ member.authors = [Person(name="Sam Pell")] # @@@ eventually whoever is authenticated member.media_type = media_type member.raw_content = content self.add_member(member) self.store.save_member(member, self) return member class ServerEntryCollection(ServerCollection): def __init__(self, title, path): ServerCollection.__init__(self, title, path) self.member_type = "entry" def supported_media_type(self, media_type): if media_type is None: raise MissingMediaTypeError() # @@@ might need to be smarter than just startswith return media_type.startswith("application/atom+xml") def create_member(self, content, media_type, suggested_title, replace_member=None): # @@@ we don't currently do anything with a charset if it exists if not self.supported_media_type(media_type): raise UnsupportedMediaTypeError() # may thrown AtomException entry = parse_entry(content) if replace_member: uri = replace_member.href path = replace_member.path else: uri, path = self.store.make_uri(self, entry.title.value) member = self.store.create_entry_member(path) member.media_type = "application/atom+xml" entry.id = member.id entry.updated = Date(time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())) if not entry.authors: entry.authors = [Person(name="Sam Pell")] # @@@ eventually whoever is authenticated member.title = entry.title member.updated = entry.updated member.authors = entry.authors member.categories = entry.categories out = StringIO() generate_entry(out, entry, top=True) member.raw_content = out.getvalue() self.add_member(member) self.store.save_member(member, self) return member class ServerMember(Member): def __init__(self, path): # Member.__init__(self) self.path = path self.collections = set() class ServerMediaMember(ServerMember): def make_feed_entry(self): entry = Entry() entry.title = self.title entry.id = self.id entry.updated = self.updated # @@@ entry.published = self.published metadata_uri = self.href + "?atom" # @@@ entry.links.append(Link(rel="edit", href=metadata_uri)) entry.authors = self.authors # @@@ entry.contributors = self.contributors # @@@ entry.categories = self.categories entry.content = OutOfLineContent(src=self.href) return entry def get_content(self): return self.raw_content class ServerEntryMember(ServerMember): def make_feed_entry(self): entry = Entry() entry.title = self.title entry.id = self.id entry.updated = self.updated if hasattr(self, "published"): entry.published = self.published # @@@ entry.links.append(Link(rel="edit", href=self.href)) entry.authors = self.authors if hasattr(self, "contributors"): entry.contributors = self.contributors if hasattr(self, "categories"): entry.categories = self.categories if hasattr(self, "summary"): entry.summary = self.summary entry.content = OutOfLineContent(src=self.href) return entry def get_content(self): return self.raw_content demokritos-0.3.7/README0000644000076500007650000000045310403514366015711 0ustar jtauberjtauber00000000000000Demokritos Atom Server version 0.3.7 http://jtauber.com/demokritos ./basic_server.py provides a server set up with an entry collection and media collection in a single workspace. It should be sufficient for basic interoperability testing. There are more detailed tests in the ./test/ directory. demokritos-0.3.7/test/0000755000076500007650000000000010403514607016004 5ustar jtauberjtauber00000000000000demokritos-0.3.7/test/coverage.py0000644000076500007650000007324510316664521020170 0ustar jtauberjtauber00000000000000#!/usr/bin/python # # Perforce Defect Tracking Integration Project # # # COVERAGE.PY -- COVERAGE TESTING # # Gareth Rees, Ravenbrook Limited, 2001-12-04 # Ned Batchelder, 2004-12-12 # http://nedbatchelder.com/code/modules/coverage.html # # # 1. INTRODUCTION # # This module provides coverage testing for Python code. # # The intended readership is all Python developers. # # This document is not confidential. # # See [GDR 2001-12-04a] for the command-line interface, programmatic # interface and limitations. See [GDR 2001-12-04b] for requirements and # design. """Usage: coverage.py -x MODULE.py [ARG1 ARG2 ...] Execute module, passing the given command-line arguments, collecting coverage data. coverage.py -e Erase collected coverage data. coverage.py -r [-m] FILE1 FILE2 ... Report on the statement coverage for the given files. With the -m option, show line numbers of the statements that weren't executed. coverage.py -a [-d dir] FILE1 FILE2 ... Make annotated copies of the given files, marking statements that are executed with > and statements that are missed with !. With the -d option, make the copies in that directory. Without the -d option, make each copy in the same directory as the original. Coverage data is saved in the file .coverage by default. Set the COVERAGE_FILE environment variable to save it somewhere else.""" __version__ = "2.2.20041231" # see detailed history at the end of this file. import compiler import compiler.visitor import os import re import string import sys import types # 2. IMPLEMENTATION # # This uses the "singleton" pattern. # # The word "morf" means a module object (from which the source file can # be deduced by suitable manipulation of the __file__ attribute) or a # filename. # # When we generate a coverage report we have to canonicalize every # filename in the coverage dictionary just in case it refers to the # module we are reporting on. It seems a shame to throw away this # information so the data in the coverage dictionary is transferred to # the 'cexecuted' dictionary under the canonical filenames. # # The coverage dictionary is called "c" and the trace function "t". The # reason for these short names is that Python looks up variables by name # at runtime and so execution time depends on the length of variables! # In the bottleneck of this application it's appropriate to abbreviate # names to increase speed. # A dictionary with an entry for (Python source file name, line number # in that file) if that line has been executed. c = {} # t(f, x, y). This method is passed to sys.settrace as a trace # function. See [van Rossum 2001-07-20b, 9.2] for an explanation of # sys.settrace and the arguments and return value of the trace function. # See [van Rossum 2001-07-20a, 3.2] for a description of frame and code # objects. def t(f, w, a): #print w, f.f_code.co_filename, f.f_lineno if w == 'line': c[(f.f_code.co_filename, f.f_lineno)] = 1 return t class StatementFindingAstVisitor(compiler.visitor.ASTVisitor): def __init__(self, statements, excluded, suite_spots): compiler.visitor.ASTVisitor.__init__(self) self.statements = statements self.excluded = excluded self.suite_spots = suite_spots self.excluding_suite = 0 def doRecursive(self, node): self.recordNodeLine(node) for n in node.getChildNodes(): self.dispatch(n) visitStmt = visitModule = doRecursive def doCode(self, node): if hasattr(node, 'decorators') and node.decorators: self.dispatch(node.decorators) self.doSuite(node, node.code) visitFunction = visitClass = doCode def getFirstLine(self, node): # Find the first line in the tree node. lineno = node.lineno for n in node.getChildNodes(): f = self.getFirstLine(n) if lineno and f: lineno = min(lineno, f) else: lineno = lineno or f return lineno def getLastLine(self, node): # Find the first line in the tree node. lineno = node.lineno for n in node.getChildNodes(): lineno = max(lineno, self.getLastLine(n)) return lineno def doStatement(self, node): self.recordLine(self.getFirstLine(node)) visitAssert = visitAssign = visitAssTuple = visitDiscard = visitPrint = \ visitPrintnl = visitRaise = visitSubscript = \ visitDecorators = \ doStatement def recordNodeLine(self, node): return self.recordLine(node.lineno) def recordLine(self, lineno): # Returns a bool, whether the line is included or excluded. if lineno: # Multi-line tests introducing suites have to get charged to their # keyword. if lineno in self.suite_spots: lineno = self.suite_spots[lineno][0] # If we're inside an exluded suite, record that this line was # excluded. if self.excluding_suite: self.excluded[lineno] = 1 return 0 # If this line is excluded, or suite_spots maps this line to # another line that is exlcuded, then we're excluded. elif self.excluded.has_key(lineno) or \ self.suite_spots.has_key(lineno) and \ self.excluded.has_key(self.suite_spots[lineno][1]): return 0 # Otherwise, this is an executable line. else: self.statements[lineno] = 1 return 1 return 0 default = recordNodeLine def recordAndDispatch(self, node): self.recordNodeLine(node) self.dispatch(node) def doSuite(self, intro, body, exclude=0): exsuite = self.excluding_suite if exclude or (intro and not self.recordNodeLine(intro)): self.excluding_suite = 1 self.recordAndDispatch(body) self.excluding_suite = exsuite def doPlainWordSuite(self, prevsuite, suite): # Finding the exclude lines for else's is tricky, because they aren't # present in the compiler parse tree. Look at the previous suite, # and find its last line. If any line between there and the else's # first line are excluded, then we exclude the else. lastprev = self.getLastLine(prevsuite) firstelse = self.getFirstLine(suite) for l in range(lastprev+1, firstelse): if self.suite_spots.has_key(l): self.doSuite(None, suite, exclude=self.excluded.has_key(l)) break else: self.doSuite(None, suite) def doElse(self, prevsuite, node): if node.else_: self.doPlainWordSuite(prevsuite, node.else_) def visitFor(self, node): self.doSuite(node, node.body) self.doElse(node.body, node) def visitIf(self, node): # The first test has to be handled separately from the rest. # The first test is credited to the line with the "if", but the others # are credited to the line with the test for the elif. self.doSuite(node, node.tests[0][1]) for t, n in node.tests[1:]: self.doSuite(t, n) self.doElse(node.tests[-1][1], node) def visitWhile(self, node): self.doSuite(node, node.body) self.doElse(node.body, node) def visitTryExcept(self, node): self.doSuite(node, node.body) for i in range(len(node.handlers)): a, b, h = node.handlers[i] if not a: # It's a plain "except:". Find the previous suite. if i > 0: prev = node.handlers[i-1][2] else: prev = node.body self.doPlainWordSuite(prev, h) else: self.doSuite(a, h) self.doElse(node.handlers[-1][2], node) def visitTryFinally(self, node): self.doSuite(node, node.body) self.doPlainWordSuite(node.body, node.final) def visitGlobal(self, node): # "global" statements don't execute like others (they don't call the # trace function), so don't record their line numbers. pass the_coverage = None class coverage: error = "coverage error" # Name of the cache file (unless environment variable is set). cache_default = ".coverage" # Environment variable naming the cache file. cache_env = "COVERAGE_FILE" # A map from canonical Python source file name to a dictionary in # which there's an entry for each line number that has been # executed. cexecuted = {} # Cache of results of calling the analysis2() method, so that you can # specify both -r and -a without doing double work. analysis_cache = {} # Cache of results of calling the canonical_filename() method, to # avoid duplicating work. canonical_filename_cache = {} def __init__(self): global the_coverage if the_coverage: raise self.error, "Only one coverage object allowed." self.usecache = 1 self.cache = None self.exclude_re = '' def help(self, error=None): if error: print error print print __doc__ sys.exit(1) def command_line(self): import getopt settings = {} optmap = { '-a': 'annotate', '-d:': 'directory=', '-e': 'erase', '-h': 'help', '-i': 'ignore-errors', '-m': 'show-missing', '-r': 'report', '-x': 'execute', } short_opts = string.join(map(lambda o: o[1:], optmap.keys()), '') long_opts = optmap.values() options, args = getopt.getopt(sys.argv[1:], short_opts, long_opts) for o, a in options: if optmap.has_key(o): settings[optmap[o]] = 1 elif optmap.has_key(o + ':'): settings[optmap[o + ':']] = a elif o[2:] in long_opts: settings[o[2:]] = 1 elif o[2:] + '=' in long_opts: settings[o[2:]] = a else: self.help("Unknown option: '%s'." % o) if settings.get('help'): self.help() for i in ['erase', 'execute']: for j in ['annotate', 'report']: if settings.get(i) and settings.get(j): self.help("You can't specify the '%s' and '%s' " "options at the same time." % (i, j)) args_needed = (settings.get('execute') or settings.get('annotate') or settings.get('report')) action = settings.get('erase') or args_needed if not action: self.help("You must specify at least one of -e, -x, -r, or -a.") if not args_needed and args: self.help("Unexpected arguments %s." % args) self.get_ready() self.exclude('#pragma[: ]+[nN][oO] [cC][oO][vV][eE][rR]') if settings.get('erase'): self.erase() if settings.get('execute'): if not args: self.help("Nothing to do.") sys.argv = args self.start() import __main__ sys.path[0] = os.path.dirname(sys.argv[0]) execfile(sys.argv[0], __main__.__dict__) if not args: args = self.cexecuted.keys() ignore_errors = settings.get('ignore-errors') show_missing = settings.get('show-missing') directory = settings.get('directory=') if settings.get('report'): self.report(args, show_missing, ignore_errors) if settings.get('annotate'): self.annotate(args, directory, ignore_errors) def use_cache(self, usecache): self.usecache = usecache def get_ready(self): if self.usecache and not self.cache: self.cache = os.environ.get(self.cache_env, self.cache_default) self.restore() self.analysis_cache = {} def start(self): self.get_ready() sys.settrace(t) def stop(self): sys.settrace(None) def erase(self): global c c = {} self.analysis_cache = {} self.cexecuted = {} if self.cache and os.path.exists(self.cache): os.remove(self.cache) self.exclude_re = '' def exclude(self, re): if self.exclude_re: self.exclude_re += "|" self.exclude_re += "(" + re + ")" # save(). Save coverage data to the coverage cache. def save(self): if self.usecache and self.cache: self.canonicalize_filenames() cache = open(self.cache, 'wb') import marshal marshal.dump(self.cexecuted, cache) cache.close() # restore(). Restore coverage data from the coverage cache (if it # exists). def restore(self): global c c = {} self.cexecuted = {} assert self.usecache if not os.path.exists(self.cache): return try: cache = open(self.cache, 'rb') import marshal cexecuted = marshal.load(cache) cache.close() if isinstance(cexecuted, types.DictType): self.cexecuted = cexecuted except: pass # canonical_filename(filename). Return a canonical filename for the # file (that is, an absolute path with no redundant components and # normalized case). See [GDR 2001-12-04b, 3.3]. def canonical_filename(self, filename): if not self.canonical_filename_cache.has_key(filename): f = filename if os.path.isabs(f) and not os.path.exists(f): f = os.path.basename(f) if not os.path.isabs(f): for path in [os.curdir] + sys.path: g = os.path.join(path, f) if os.path.exists(g): f = g break cf = os.path.normcase(os.path.abspath(f)) self.canonical_filename_cache[filename] = cf return self.canonical_filename_cache[filename] # canonicalize_filenames(). Copy results from "executed" to # "cexecuted", canonicalizing filenames on the way. Clear the # "executed" map. def canonicalize_filenames(self): global c for filename, lineno in c.keys(): f = self.canonical_filename(filename) if not self.cexecuted.has_key(f): self.cexecuted[f] = {} self.cexecuted[f][lineno] = 1 c = {} # morf_filename(morf). Return the filename for a module or file. def morf_filename(self, morf): if isinstance(morf, types.ModuleType): if not hasattr(morf, '__file__'): raise self.error, "Module has no __file__ attribute." file = morf.__file__ else: file = morf return self.canonical_filename(file) # analyze_morf(morf). Analyze the module or filename passed as # the argument. If the source code can't be found, raise an error. # Otherwise, return a tuple of (1) the canonical filename of the # source code for the module, (2) a list of lines of statements # in the source code, and (3) a list of lines of excluded statements. def analyze_morf(self, morf): if self.analysis_cache.has_key(morf): return self.analysis_cache[morf] filename = self.morf_filename(morf) ext = os.path.splitext(filename)[1] if ext == '.pyc': if not os.path.exists(filename[0:-1]): raise self.error, ("No source for compiled code '%s'." % filename) filename = filename[0:-1] elif ext != '.py': raise self.error, "File '%s' not Python source." % filename source = open(filename, 'r') lines, excluded_lines = self.find_executable_statements( source.read(), exclude=self.exclude_re ) source.close() result = filename, lines, excluded_lines self.analysis_cache[morf] = result return result def get_suite_spots(self, tree, spots): import symbol, token for i in range(1, len(tree)): if type(tree[i]) == type(()): if tree[i][0] == symbol.suite: # Found a suite, look back for the colon and keyword. lineno_colon = lineno_word = None for j in range(i-1, 0, -1): if tree[j][0] == token.COLON: lineno_colon = tree[j][2] elif tree[j][0] == token.NAME: if tree[j][1] == 'elif': # Find the line number of the first non-terminal # after the keyword. t = tree[j+1] while t and token.ISNONTERMINAL(t[0]): t = t[1] if t: lineno_word = t[2] else: lineno_word = tree[j][2] break elif tree[j][0] == symbol.except_clause: # "except" clauses look like: # ('except_clause', ('NAME', 'except', lineno), ...) if tree[j][1][0] == token.NAME: lineno_word = tree[j][1][2] break if lineno_colon and lineno_word: # Found colon and keyword, mark all the lines # between the two with the two line numbers. for l in range(lineno_word, lineno_colon+1): spots[l] = (lineno_word, lineno_colon) self.get_suite_spots(tree[i], spots) def find_executable_statements(self, text, exclude=None): # Find lines which match an exclusion pattern. excluded = {} suite_spots = {} if exclude: reExclude = re.compile(exclude) lines = text.split('\n') for i in range(len(lines)): if reExclude.search(lines[i]): excluded[i+1] = 1 import parser tree = parser.suite(text+'\n\n').totuple(1) self.get_suite_spots(tree, suite_spots) # Use the compiler module to parse the text and find the executable # statements. We add newlines to be impervious to final partial lines. statements = {} ast = compiler.parse(text+'\n\n') visitor = StatementFindingAstVisitor(statements, excluded, suite_spots) compiler.walk(ast, visitor, walker=visitor) lines = statements.keys() lines.sort() excluded_lines = excluded.keys() excluded_lines.sort() return lines, excluded_lines # format_lines(statements, lines). Format a list of line numbers # for printing by coalescing groups of lines as long as the lines # represent consecutive statements. This will coalesce even if # there are gaps between statements, so if statements = # [1,2,3,4,5,10,11,12,13,14] and lines = [1,2,5,10,11,13,14] then # format_lines will return "1-2, 5-11, 13-14". def format_lines(self, statements, lines): pairs = [] i = 0 j = 0 start = None pairs = [] while i < len(statements) and j < len(lines): if statements[i] == lines[j]: if start == None: start = lines[j] end = lines[j] j = j + 1 elif start: pairs.append((start, end)) start = None i = i + 1 if start: pairs.append((start, end)) def stringify(pair): start, end = pair if start == end: return "%d" % start else: return "%d-%d" % (start, end) import string return string.join(map(stringify, pairs), ", ") # Backward compatibility with version 1. def analysis(self, morf): f, s, _, m, mf = self.analysis2(morf) return f, s, m, mf def analysis2(self, morf): filename, statements, excluded = self.analyze_morf(morf) self.canonicalize_filenames() if not self.cexecuted.has_key(filename): self.cexecuted[filename] = {} missing = [] for line in statements: if not self.cexecuted[filename].has_key(line): missing.append(line) return (filename, statements, excluded, missing, self.format_lines(statements, missing)) def morf_name(self, morf): if isinstance(morf, types.ModuleType): return morf.__name__ else: return os.path.splitext(os.path.basename(morf))[0] def report(self, morfs, show_missing=1, ignore_errors=0): if not isinstance(morfs, types.ListType): morfs = [morfs] max_name = max([5,] + map(len, map(self.morf_name, morfs))) fmt_name = "%%- %ds " % max_name fmt_err = fmt_name + "%s: %s" header = fmt_name % "Name" + " Stmts Exec Cover" fmt_coverage = fmt_name + "% 6d % 6d % 5d%%" if show_missing: header = header + " Missing" fmt_coverage = fmt_coverage + " %s" print header print "-" * len(header) total_statements = 0 total_executed = 0 for morf in morfs: name = self.morf_name(morf) try: _, statements, _, missing, readable = self.analysis2(morf) n = len(statements) m = n - len(missing) if n > 0: pc = 100.0 * m / n else: pc = 100.0 args = (name, n, m, pc) if show_missing: args = args + (readable,) print fmt_coverage % args total_statements = total_statements + n total_executed = total_executed + m except KeyboardInterrupt: raise except: if not ignore_errors: type, msg = sys.exc_info()[0:2] print fmt_err % (name, type, msg) if len(morfs) > 1: print "-" * len(header) if total_statements > 0: pc = 100.0 * total_executed / total_statements else: pc = 100.0 args = ("TOTAL", total_statements, total_executed, pc) if show_missing: args = args + ("",) print fmt_coverage % args # annotate(morfs, ignore_errors). blank_re = re.compile("\\s*(#|$)") else_re = re.compile("\\s*else\\s*:\\s*(#|$)") def annotate(self, morfs, directory=None, ignore_errors=0): for morf in morfs: try: filename, statements, excluded, missing, _ = self.analysis2(morf) self.annotate_file(filename, statements, excluded, missing, directory) except KeyboardInterrupt: raise except: if not ignore_errors: raise def annotate_file(self, filename, statements, excluded, missing, directory=None): source = open(filename, 'r') if directory: dest_file = os.path.join(directory, os.path.basename(filename) + ',cover') else: dest_file = filename + ',cover' dest = open(dest_file, 'w') lineno = 0 i = 0 j = 0 covered = 1 while 1: line = source.readline() if line == '': break lineno = lineno + 1 while i < len(statements) and statements[i] < lineno: i = i + 1 while j < len(missing) and missing[j] < lineno: j = j + 1 if i < len(statements) and statements[i] == lineno: covered = j >= len(missing) or missing[j] > lineno if self.blank_re.match(line): dest.write(' ') elif self.else_re.match(line): # Special logic for lines containing only # 'else:'. See [GDR 2001-12-04b, 3.2]. if i >= len(statements) and j >= len(missing): dest.write('! ') elif i >= len(statements) or j >= len(missing): dest.write('> ') elif statements[i] == missing[j]: dest.write('! ') else: dest.write('> ') elif lineno in excluded: dest.write('- ') elif covered: dest.write('> ') else: dest.write('! ') dest.write(line) source.close() dest.close() # Singleton object. the_coverage = coverage() # Module functions call methods in the singleton object. def use_cache(*args, **kw): return the_coverage.use_cache(*args, **kw) def start(*args, **kw): return the_coverage.start(*args, **kw) def stop(*args, **kw): return the_coverage.stop(*args, **kw) def erase(*args, **kw): return the_coverage.erase(*args, **kw) def exclude(*args, **kw): return the_coverage.exclude(*args, **kw) def analysis(*args, **kw): return the_coverage.analysis(*args, **kw) def analysis2(*args, **kw): return the_coverage.analysis2(*args, **kw) def report(*args, **kw): return the_coverage.report(*args, **kw) def annotate(*args, **kw): return the_coverage.annotate(*args, **kw) def annotate_file(*args, **kw): return the_coverage.annotate_file(*args, **kw) # Save coverage data when Python exits. (The atexit module wasn't # introduced until Python 2.0, so use sys.exitfunc when it's not # available.) try: import atexit atexit.register(the_coverage.save) except ImportError: sys.exitfunc = the_coverage.save # Command-line interface. if __name__ == '__main__': the_coverage.command_line() # A. REFERENCES # # [GDR 2001-12-04a] "Statement coverage for Python"; Gareth Rees; # Ravenbrook Limited; 2001-12-04; # . # # [GDR 2001-12-04b] "Statement coverage for Python: design and # analysis"; Gareth Rees; Ravenbrook Limited; 2001-12-04; # . # # [van Rossum 2001-07-20a] "Python Reference Manual (releae 2.1.1)"; # Guide van Rossum; 2001-07-20; # . # # [van Rossum 2001-07-20b] "Python Library Reference"; Guido van Rossum; # 2001-07-20; . # # # B. DOCUMENT HISTORY # # 2001-12-04 GDR Created. # # 2001-12-06 GDR Added command-line interface and source code # annotation. # # 2001-12-09 GDR Moved design and interface to separate documents. # # 2001-12-10 GDR Open cache file as binary on Windows. Allow # simultaneous -e and -x, or -a and -r. # # 2001-12-12 GDR Added command-line help. Cache analysis so that it # only needs to be done once when you specify -a and -r. # # 2001-12-13 GDR Improved speed while recording. Portable between # Python 1.5.2 and 2.1.1. # # 2002-01-03 GDR Module-level functions work correctly. # # 2002-01-07 GDR Update sys.path when running a file with the -x option, # so that it matches the value the program would get if it were run on # its own. # # 2004-12-12 NMB Significant code changes. # - Finding executable statements has been rewritten so that docstrings and # other quirks of Python execution aren't mistakenly identified as missing # lines. # - Lines can be excluded from consideration, even entire suites of lines. # - The filesystem cache of covered lines can be disabled programmatically. # - Modernized the code. # # 2004-12-14 NMB Minor tweaks. Return 'analysis' to its original behavior # and add 'analysis2'. Add a global for 'annotate', and factor it, adding # 'annotate_file'. # # 2004-12-31 NMB Allow for keyword arguments in the module global functions. # # C. COPYRIGHT AND LICENCE # # Copyright 2001 Gareth Rees. All rights reserved. # Copyright 2004 Ned Batchelder. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the # distribution. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH # DAMAGE. # # $Id: coverage.py 5 2004-12-14 12:08:23Z ned $ demokritos-0.3.7/test/data/0000755000076500007650000000000010403514607016715 5ustar jtauberjtauber00000000000000demokritos-0.3.7/test/data/07-introspection-out.xml0000644000076500007650000000077110362321566023401 0ustar jtauberjtauber00000000000000 entry media entry demokritos-0.3.7/test/data/07-introspection.xml0000644000076500007650000000117110362321566022567 0ustar jtauberjtauber00000000000000 entry media entry demokritos-0.3.7/test/data/bad-content1.atom0000644000076500007650000000046110366742564022074 0ustar jtauberjtauber00000000000000this says it's text2006-01-27T22:51:26-07:002006-01-27T22:51:26-07:00

but it's really XML

demokritos-0.3.7/test/data/beach.jpg0000644000076500007650000027453610401127126020474 0ustar jtauberjtauber00000000000000ÿØÿàJFIFHHÿþ AppleMark ÿâ ICC_PROFILEADBEmntrRGB XYZ Ð 3;acspAPPLnoneöÖÓ-ADBE cprtü2desc0kwtptœbkpt°rTRCÄgTRCÄbTRCÄrXYZÔgXYZèbXYZütextCopyright 2000 Adobe Systems IncorporateddescAdobe RGB (1998)XYZ óQÌXYZ curv3XYZ œO¥üXYZ 4 ,•XYZ &1/¾œÿÛC   ÿÛCÿÀLôÿÄ ÿÄG!1A"Qa2q #B‘R¡±$3ÁÑCbáð4r‚’%sñ&Sc¢ÿÄÿÄ=!1AQ"aq‘¡ð2±ÁÑáñ#BR3’b$Cr‚S¢ÿÚ ?ë»ÖÅêì*ÇØõôÁH¯9á ÕƒåÈ?qÑ % ¨6IÞ½ d3Û¢è"êúY²M%Ññ… ÃØãÏEÑ ”Î “dÓOPw¹ÇéÑ•t ¢‘$s–ÜqÇVZ€¾4^‘p¿qÑDh‚$ÙŠñÆz)æ„ÒÌl‡´0ÊŽ>‡¬uq`Y«}i(‘Þņyà¯â¹¨ì!Ù£ ·Û$ôº•Ȳkp¼Ñ‘Vbq¿ÿ6:¦ÔVp¤Ý8¥ÑÕ"Wo··M. S:Ì¢j*½yG>ýLÖ²±tª†pïŸ×Kn€‚„lv¶^ÉFfÈÀ œ*¶|ãz„¬#ƒJÄøè Â’% ú›‡ðÈ_°ê +ƒÉ¾¡à`/z¢ªÀ;,þv2‘ç>zЈ‰²9i,Qt¨¨SÝa©28Ž0~þ:6ÔÝHDY›;Œa>ÌsÑ6 ÙPcŠO%ƒ'˜cõèƒÁTv)°ÎŸø²~¤ž˜ X&uEŸTsëJ?V=J3yõ$$õP¢%ýcŒ³’:0B¤^&\á˜c«PƒK¿vò¥›ëõê ˜•é%¨\°Ê}½ºÖ¨e$üEhÈ2‹£l[TÎæ†••j?$l>ãÇTiµYs–Z²wÀ!"ÿé^z™²â‰»’L¯úu )—šñWgÌÊOƒõê‹ (Ö N>@z&ÊÃmNU€`öèMP,­´Ç$oàóƒ·Ÿ‡¡å1Ôbê ¯55¯JGa¥¯Õኻ¥r[¨§4¾ºŠ†®õçj§æ /±eÈê^9][(8˜\ÐøƒøŒ¤±Íf¦ÑrkÊz+E]-^©Hhb††hâÞ⢢å™cXAIPÆÕÉ •p¹ä㸨¦@ ¼íë+~ {ŸMül©ÊOÚ Û­c¢«{'¬u}“VTWPÔÎ.ÒOWAS<ž¿©ø)M3IüfŠPÑÍ ´l#hÙº]n'F³ 2è1±µd\hVÃÃab9}h+˜ÖÎëêû¶°¼v³»£Hiºjš¨)ä7‚#b»Åø™—甼–;T0Vbzñ”ñuÉsø‰åxçîJíŠRÌÑ.ÞÊ…ÓZËWÍu¯°Vßµ…žžº©gümšžžQD$žhøÿ†;€tˆãƒ·%vŠÂV/q¤âÛùòEuT/Ú,¯ˆ/†Í§ìÔKjð·*í7A[¾õq¦hõCHÌ'j'€•¢XfGOÃHw¾K¤CŒðçS|1×9‰:,Ô+Uq9…§Ëò/îRO,©KS¶·³òüÑ©\cùÇ¿Ÿ¯^aŒ¹"Ë AuÕwx·Àd–ýæÓ¨Í&ßLägžxàù=mmRcK¤¹ãb£Ç$‹JXHƒäoÜ?§ þ¼tm|éê¡«‹\nuÔ¬)¥Ž¤Ô2« ƒÈþütÜ–BàAnèn¦ Pµt›dÜI২Ãê|ñã Ä•@ 8'ˆkâ1ͺåL‘÷û{ò:[ip›.Ñ;ÑTE]25Dåb °“ÂùF9 ‘Ç¿úõY‚ «'MjÉ ”ÐÇS=Ê) F°Ë$¥•|üÛr£ÛÀã$޹¸œõŒøÔêê))¥”¬ó×u=\qí’¾7¸ÈÀÇ“ÒCŽ£ùP|þýÊ‹TÛdŸt¢@·”³‚¸YI,|Ø9€IöÇ[MP×@ÑC9Ü)ź³e ¬iW$[£-ÄL 瀜çéÓ(8<GqFÐ.ÔÑWI5;OM•ôé1G™âeF|ˆÀ lUÇ‚yç«eAû¥ˆdë }- Å-ÉPA_”àãØÿ6NN|qíÇXê2Mìª BZ—)ëªS ‰>y™³•ò=¾˜ã¥Œ.[Ì…)·pŸ¢£HૹÃáFá!þ¹?ô㥺û"éèTv³ø=~Œ ÀMÑ¢ܯÎ1Õfæ­­A4ÜŒüÝBåyP 8ÎÐp=ú©(ƒWž5…~o¨\¬°ì‰iWh<ýºŽrižÄA§Í†lyéY“ƒ`¢e\ý‡×¥¸¸Ù:‹¡aŒîo~²–JÜÚ¢.²fÈù[}º ¦pŠß“’㞘ºIºXˆÇæÝo§S8ÑnèõVÎ9'ß ž„ÔhÝX¦Œ½ƒŸ¯Žˆ¼*3.¼Áƒã¨bÝ+A.ßÌÇúùéF$èŽÃŽË~ž:¤BwAÜã#Û G,ÍÆÆ N—”n‹7$¥eð1éÐ åƒ#9?^ jÆP˜ç O·: 6Tn±´7.7}ñž¬;`†˜Ý%’ vÉ$ç>ËŽ˜*8*4Ú‘É 68 OéÓ˜ç$[d‘’>Æ<ý:9*¡ªªü« ?íÔð¨ÀD²†?2ñïÑJ è¡ÿÃÇ·RJ½èÄp6`ûÈꋎª4(O ANÕý:‘¦I R[ߎ‡:3NÅ+n' ЪÉ:%Bàëçªu@tU‘%ž•c*HÇ“Õ=É­jôQ³+mpÄž‡¦æÔïd†e9Fñõút“StÖµx¨Š6ôÙ ¶z’e• ¼7øºkíæOÔ\(CGTñFÓQú£c*;sðBœ¸Ž–þ³ƒˆº{pásû»U˜×°¿Zt…‹Oé™ ¼VÚ¥§£G§rEECDó@²Û¤ÞY›~Ò0 P±×õÔášhONe"´z/œMG¢jíº£Ri½3©mz£D-Þãi¶Ý(©!’–IJ»9‚&*ùE%Nݬ ' Ÿ ÀZ`‹ Þav)“—¬/öïZÿŸ ½E 2jG£ÕFuVü•Tõ ½ýZ‡`K+#xŒÁ˜â=¸*kC®ÓõO.âÞ*²žõZR8¥­KŒ<­G¢0nX¦ßÀ*< ýIë3ªÄsL÷JÛU]íÉAA$.‘>+Ѧݾ@øþ*ò1»oŸ|g¬XŒî ¸›+Î (eòïp®/WM-D•fÎNs´þSÎ:WBy$“¸P›âãõ="e(Wì>£Œÿ׫e3ªŽƒ¡Qó2°â¤a… Œ¶|dõ£,¶ $]%«‚ž6§¬|ISe ‡áóàd~ž?×£p&n…ÔÁ6Ù0~îüDþ¾×JA‚c䲟|çÏÈè`4Ü%5€Ù,hÚY¤£‹|a™O>=ó÷óÿn˜=Ss\è4G½D("ˆ,‰„üƒ’ª9 Ïôçߪ›|…0³Ime<­Øìò3„XÂárÄr'òqïÒj”’'—5¤4››mÇ(¡‰b‚ª+ E’úNNïlqŒã{uÈ1‚!F‰T*ålÔ¶Û™ºRËL)—!£Ý¹™=ÊÞ¶±¢?¼*¿‹ r®ÃaíùTÿCÇN¦Ü§(MPšÅLÒ…¨«‚J€¹ & GóãÜãïíã¢u!µèd© ǨWÑÝüÇ?o@ã°D[+>‹¯¸ûsç¡Î4(G%XcõêÍp4ÊP{n$uB°TêGTI¡ù¿0®„Ò>+߀#’þ9ÇTj©Ð•B8å¸óÔéCýÝÈ &>ëо¼¢4·D­¸ƒÄŸ.}ÇBk}ñ§aáÉãéÔ5ˆ_‡˜r¬?n¬°°%Ôj[œ4qéû½ L_ðá’†FŽ@‘ rdÆsà®a†©Ñ™ä>Z[zèÖ®×OÂÖcÛ®þY´î²»Qöß^KÛ›e?ïJ©mÙx-3)UZ‰å_ž-ì’p–$3Ö‡±·icxôL~&•¤ÁÙR:×G5ʶ¢¶Ù©ìWë*[ –3MK4^´›Œ#zâ#|îNâ’c:ÊÒéÊä—»µCL‰IJ•.æ¢Fn~`xò~‡žžÒL„ C[tCÎ^E2N‰/æÜdçûtE ¦¡ºe?ÅÝæ-bÇkaIöÇ×ß¡€ S ÆÙl³44é%AÆB+‘È?CãŸn…ï¶–DàwRËE]2[Irm,ÏæL>ç¤2°Ž¯ð¯Sp‡´ãi^¿v~@½òNp£9ãôé®i™ct%9µ%µèi Z4ަ&Þ$I 1å˜ùÏôéÎkÌ” Rz;©¥`€oðÆL /sœà~£¡4å$hŸ"»RJ· ˆšIâÁ2ªOËÎàGœžqà}8é.¥Õô"èÆIC-dƒ1´dB’gå$¸FpI#>ÿÛ¡mBÙ”@Œ¶PÙ`®‘ÞJŠ©à¥•-Ë3)>àçr~ýkdi'Ñ-ΘSh¦²Ò5h4ÕRÈû)"+´LòØóƒÈÝQj°à;‘›Ã 8,?¯BXˆ?µR0ITN¨3uN¨…è@Þûuq)Ÿ`ŽˆƒÒ»‘”¤%9à~VRˆPÄ1xR|õ è¡ iâqçûô3 ÃB3`8±÷úuŠ…³p†"a”ý:…óª™eå=,»uW¶¡>ýX•V *#û†ûõNV £#~Rſߪ$‹¨@B1¨îÝïÕš“ª3ÒFÎw–ýz°ó:¨ÖˆXXÕ@ÛÏý:²ùWÑ¡[£ý¹è:@­­€Ù‚CÐŽƒ1*È‹áÇq÷êó¡ÊŒÌ|0VÏCÒ¢ ^4íà2>½u:; , ù†Üõf¢°°S<™è3#ËÉ`Ó1ñ´±êúD9F)ØŸÐôaò‚ ñܾξüŒŽ†y(K4è‰&Ù2øàcß«’¿ËDí s*Ž/‘Ó<ÈNÐÌþ,«xàg sRäsGéJÒüßeñÕev°ˆ–î“™¡çç,ŒôyIÙaÍ&–H˜g-ýGVP— IÏ ËÎAð£ß¤U¢æØ]RE“õL’<™œ`«IƒýycòØ ðOkšïà¿E!IX£þKŸ¾:ý*µ|üÓw$Rð`Ó©§t%ŽJÊáóäñÑÝ Kp°ýz†Í–K7<’}ñÕM•—ã¿èÙóЂ!WzʾNHêªJñv†=MÑL£c‘ÿ™‰þ½QvËÆI8!ÏëÇV!QwjÏ æê‹Ýs¢P³HO,?÷ÿ^«(DR…wÇ8ée£daü’ÄU*Ù+¿<}ºI%«Žáá[íÕç•‘‚Rpÿ^:Qi™Fúrƒœ§ÛŽ©¯ ò” ·Øô:(ö^sÕD¨]²1Xw?רDÝ@èF+`ç‘Õ`òFú„ðOß©–ÈKÖ >X#ÇCbBÊ’À†;Ž~I:£ÕùÎOUhP›¡ÀÎìž‚ £gÖL ä~F´¨]ÍeeRS»ìz¢Å@‚‚YØ—1Ç»ìz¨Ù\$æY7œ}ó㪠*ºl#,NXŸ|õ•y·Gúé€ ‘Žƒ)P>è°ÁÛ¹¿^‹*"ôšM²0%HprÐôMÉIš ÀŒ~§ß©;¨QQÑ$`»Ï·×£Ì…ÂuGŠJpÀ–‘}ú®‘Ê -‰I½%• üg$ù=P{·S¡iÑRz„±cc€9ÏWÒ§B2ÇÌ’®|¡¨J®’E4ŠÅ´òc$nŠNª VºW·¦HÝ/Óóý:^iO¦mõÙX2äcÛªi Ý9Í ;xÖvkSÕÃr¾Ø¨+ €Ô´5U© ˆAÇ©´Å3ÆBžxòqÑB¶ÑfiÄ—ÄíÒ不ŸYiKQfýíMYI%uÔxØË êí.ÅwT+Mðq‡ÄZÒZMÆÄOßð§ƒÍ$mÏù\øøƒºôÂÒÝè²_5jUÖTþçE§7=!X6JãÒâIâdõǦÍ"±?ªIãÏã_F³x6 G³Û e.XsUÆ®ùé-=§¨4ÝÇOë;®¥ü];NÑTRÍÛ¡Îb„‰ @-—RPžÇ‹^ŽG ®“é‡Þ Ô¤¨üZÿ“"ýy;qÉ÷ÇQ¢××Ýѵ¡ãµ;IH•¸Þ‘ª®o’o1Œ¨?æ íïïút:J"€"é¶h^ij¦F ¹ñž=ÿ¿ž­Ã`U?(16Fþ*Áæ$‘&ð7r0|ž<€µNI<Ô5+%+URKN;Ÿ†}ñãÇý@/"ÈžË\ë’‘ä7Gý~Ã=X\!1:§ kàŠ)C´…rP©“<l1Àúty& è±LÐVº;Ï5A1± žÉ£"ÖV&ä%íu†'S$±þ!øÂ¹äàþŸß¥Š]ˆÀ½“•’Z[m%=MÁ`ÈmÅTãŒä€<ãÈýzª’Ñ™¡L ÝXRXéæ ›Ç,Oê3”íœòH ëÇëÄ8:aHˆQKžå¥ÄØ”ÝOq[LRÑFûP.ÕÛ“ û)çéöã=8Ó.»‘h›§Ám£¹ªÖWE=K™À+í~ZöÙ· œÆ~’_¾Gÿ/1þ£¯¸ :•ãÎ+°¤³Ýª¥ÈTØ¿LäôÖaš ˜§;K$&Z‡þi0~ý85¡gq@1ÈÙ/’}óÕæEPMÊÀGÀoûus*jŽ Ãñú…@Ò ¸ÇÍÒÉEÑcú¯S1Gdbªõn$…Ní^œ¼ªLÙ xàcÓ«*°Øõ„¬?.ÿ›¨¨h³ê1–#ª-S9C8/Ï·U–ÊÑ¢S2sçÏC•æYIÀÁÈÇ¿ýú^U3£77n?¯CEÚ„®ÙÉÏß«-+%‡‚s÷ê (ºñ•Î02z™¢ã+Û¤<ä·PA@\Q‚IC˜íê@(ó! ]yÜ^¦Q¢¶¹ø²\¯¨w§BX!Qr3Õq““Œu@&赨$à~ã¨æ…YŽ…ü¤uVÙIA2?¶GéÐdVvB°aó0WD”?]Ž2O=T[Eb¢™òyèÅyŽë«c۫Ȧr‚jpúž¦E]-Å[`gþDˆU±EŠ¢|ãúufžáP¨€ÒÈÁXiEÒ^ë Ù¹Hèn‰j§HÀÎÿש”J³RÊ9p’y¤ ta… jb›n–:{岪‚ç5Öž Ðáé*妙N8e’6RyãêTZbÆ;”¨à¹ß܈½yð½Ü(ôö«ª¾÷kµ5Qí‚¢ë%,w«IxUiÔGÔÎd‘UÎù7l\Æq7æ?-Q#bÔiô•ª‘m@F‡³ð­¾æw¾ùcÐ_»ÚOF_îzr¡)jnÔ†ìÊ´Îdþ nÂÃXTfÄ3|ÑïÑîe|X`I»¾ß$¦7¯‘ÆËçgâg¾]Ò®j~î~ù½Ð¨ƒéúºªBi%†<(–”Ì ŠêÀ–ØB‰$%㾡òxÜk‹óvåöû…ÞÁ± @‚U=_ñɯû¡§­šO¼W™5”TH­nºÊÉOS@2¨Áv0>¨x®pxC¸‹« ª:ÚHû­. ÜÙ›b«Šø5ЇPÛ'²Ð\l×›\všó¿¨ L“‰á,FÛò°ð¬´±¡³_~ ï¤×Xª3¼É ²ùYE9J R¬S%+KøŸJ %Œ@ìdã$‚H ÍÖjù‰!×CL4 ­H¿Ý)"ªÿöˈP«ùR|c9ûŸÐômJ¹o¢g®–åKG$Tp;–;™É$¿<1ž2yç¢qæ‰Îµ´L <í$fGm¸ÎJ·×o×íôê³Zë#_¸¼ù¤ñ\ªZu¢@CFw«  ç9ÇöûôM!hc„\ÁHæºÝ&—ÕYêÙ•¿ nY¿×žˆÝ2dêž­F¯‚B›Ø·8Èþ¹ýzKŒÉ èpä‘D³WDå…P“"²G’@ëÕ–À’¬²Dè›æ3M'mÈ!AÁ—íÑÆÁY|غßnšº:‚H¨—q’X‡@mp©ŒµŠ³¬ZU¬Çr¹ÏlªYB©IÃ/¤„ÿ.0sÆõëzùú‚Å]Ï̧W©!ŠªJøª!p¬Â©å\‘ôç?¨ëŸžX+&×QE±AQ[ëÅ##³+ˆÏ8'æûuÔf/«EÒ@„š½¢³UøªŠ—Ý’ÙÚÉÇý¾Ý*5æáP¨Í÷OËu¦©JdvÈä%‰Àäî xÇߤ»%H!GWu *µ²¶wç>ÿ®Gß=-‚B°ø¥ßêjPJ-´Îß•·ª)ÈóÆ:#†væ8/Ò"$ ‡‘3í»#ýºûdÔ^5¹œ1SM”‡ÑföàóÖw½Ã]…6“ÕFµ#  }²1Ð £dfŒjw£@ «¡é®wK4F«ÑÁD?:6¾xê:¥M•¶›’‚R׆ËíÇÐõ3UB[I%–:¡âÉ÷xéÌuMÂKÚÈR]ª£ÏMÕ&6C9'«BL!¾2·QÝŠähP†Þ;A㪠‰ºÈ vçêMá\Ñ…ŒÉêeþ¿ÿN &èL#öDIœ}ýú’wPBTÝï׫3ª»£ÓòN=¡$£êÊ>0›±ê¢lç¡tî04S¤lY×N³ôÛ9?¢äˆ!“åmÿ÷é€É”“!eX –\7éÇP¶C›’5H8Î:6W™r 9?n(j¨Oæ?Û¨\eIä½'¦FÒÍý¼uM„ÓQL61»–'Ñ2IRB*(ëw„—cGÀ? è`ê¦f§˜–%ô•Ðt$B€£dpP~½P*AÙžsãª.ÙX±Î>Iä„¶W•å\0ñž¡>h ê¼WÜ?§P*4A1±î ~^aº@)‚>l¸êÉPÊÆ1Ï u'e&,ìA|}:‚J¡u쀿óGœõ2¨W‰ p¸'ß 6 ìlD26vDÇúuyítPUIÝêžái/yÕú&†Û[)tÖÇ€³V,yfMã%Q“p%Au 0 ‚½g©_(Ìb62âtUž¨6®övªë®ûE¯µÕ–é@òÕŠ9<Í-E\oj'…¸üè6€ m¹„kU!™ØþÞhË_LÜ~»íñVÿƒU|=üCÃnìµâã ”Z"þ’ÃWf¸Z …ÆŸcºÃ3¡Xç„Eè³Ê‡kD@äVÅ7ÒÆÃ_´Ü+Çjé3@§sËð}‚¹Óføüïÿg&¾öæã­¯º†ÃÖZ*-׉žoN‹ðòB'?Äv‚¬K‘p=úã;WÊXþ°¸¾¾Çjê; N«sEϚ瞤î=Êá¶ÐÞ.1i Ìa¤üSË@Ÿ˜*œ»/“ŒÇ9É'– ‹`6;-‚ЫvºKO"Æb‚‹—>I}³ãéïЖNªÉ)}-dw੯­q,A£Ê“Ï ø^1‚y=%ì,ùtR‚‚\(®W *ª ¸ÃUO¸‡õãÁ$ÿ6ÑàçŒû{ôsÍ „¨>êZ ïvŽWÆYNÐl`ry㧃¸@ØÉM/sޱàjz‰iß{¨ûœóýF3Õ8ø%’^áël´Ô$µ4×Jpðf‹Ÿ•ãqÒÚ3ê€åiæ™’‚?N8ßÐ@X•A;½¶ÀêÉ•S`Ý;IpmŠHV ‚•9,9Ü>Ÿõè§}“EŬG¨§˜½4õk;O|ÉíŸ|ç?N…®&úrDÖæÔÙ:È(­ðÐ%ªãÅa6V¡9%J°á½Áãýú+÷&ô¦t°úòŠH¤ ^G¨äœõçÀ÷é¹Hë 4¿ÉO¨ôýÞÙ$Ež)Yx1#n(r?“??ôé¨";0R9Ve4æºzŸœ–oË´ùàlu>Or\€ìÇßá*„Õš¥J5zŠhSk³à+d øý1“ÐçaûæÔK-ñÍëËVÓ¼ÕjT)e!sïý¼gìz:oÊ™šÐ5NÒÚ)nU/SU]S¾=@È]î2O·ëôéMº…E‘`STvX£¨˜T~"x ¨ÿ0“¸‘íŸäôò\FʃLY>ÓÕPÃG$i+@ŸS/Ë.xûgìÓ¤Û¢|Á.€i‰¢OÄP˜ÝÀ>×ïçëÑó¢®î¿GG§’š¾1︂:ûk^w!x³L A1z¬~ëÑg i‰%j’$_Ôž¤´ª·A;¹ÝÏ×=X@A›¬¨÷*ØÆ?)芣t=ØÆ^„«Áý³Ç×ëÕ¢„hdàçýz‰`ò^$àðHê—!‡öÚ|}z¢ˆ:ÆGÌüz¬§U¡9ÉÀû¯¿Cuy€Bܼ€ÑÿéÔÈP’€ì`¬»ñ€Àg뎬 +ÏE¯¨n`G‚q€O×ÝXC‘ùSôÏC\„#ç9çíÑ(¸àlã¨aPqKᨈad†I{=f©MÛLm@5 Ú9¬daĈ~èzÎYYkkèod¬Eap㶯Ðñî:êÃda˜s£’…¥³*®+£>ùÞ½guZ§dÆQ£22x¨DL#ŸÕo`n£ònÔ¦À"S ŽBJ2*{†ç޶‡ÏsLÝa(Õóºª%ÿêÈÿ^‰ÕÈÙP£ÿ’=-Žÿ’¾™›Û8éNÄrÔÆa‰ùJWûš¤dþ&?ìzWÆ64M8 пsÌy³}ÔñÒÆ)³¢‡möšœdI óõ=Å·u_ ýP ®«Èx¾¾z¿‰jŸ ýŠ)­µ å?æÇPbXP;SeŸÝÕùˆƒõç©ÓÓPaêMÖM²¸ñ¾Ôõ_ÈÑÃ=¥øõeSÏòÿëÐ;6Gðgr6¨±üßþ]ŵym‘/>”-=Äj†JóÒÅÈX•1ç¨Ú¤ê¡`²IëSƒÿÌB?Bzd8ÞË›Í`Ëç׈ÿ÷xêy"ÌêÜM)W­tåm®Ë©k´f§P^Õ}£PÓÚj| ‘7(c ÆÙFÏ Ž:Ó$@·½ûô®Ë—®âwoà‹ºúG¼ôr蛽‚©¬QÁI$¶Ê:³+Ênn“H¸X[jÍ e· #Ëòª¼êUú'gÃÞÁl ìì ˆ‚¹MûA}JóÜ»ª­z&Žñ~µÃUrŠÇp2ˆj%Q,u1’Åf†Y&2.L›dŽM†<Ÿ5ÄXÖÕpùO}ÂëaÕ"5d®´ßÅ]n¡T]å‡ðóÌNä•óýÛûà=yšÎ¬ Ø™]Æê«Ùt­²&ÌNÕăr,¬X!€ä1ÇçÛ§ŒCµrVQÊ€jé-ÓHaQ½c!Þ8ðKœžWÙ³œ‘Ç?~º4‰7*³Jw¶V×ËMN°ÛÒ´lQ ;9›–öäùþÚÝ\lš–è.5²¼mKRÒF‰Ĩ±’ØÎ1ÏœñÒΠj‹£Î?Ó¢@ÔÙ )Yã‹Ôy‡ò²¶ò8ÎH9À禂%2è•Û R5%L’K ÐHβ ϦÜs‘ÐסsÄØjÕȶž U#ê¸EW9§šCA±·ãíŒd7öãÿg¤rLÖÔ¥"÷éBˆµtc)E\l`ûp?ôèú7NÊQׄÕC-Mi2U]”IJYfqöaà}=¹é¤ö*kÎú%·ª jusI ‘Ù"–ÇHiõç§7ýÊÎþÍ‚lšÑ0J Ï$Hûõ©˜ÐurÉSviHŲ¨¾Ž© =;âÛ„†x7e­òª ”õlOÿëÆzƒ ¹ ; @°>H ÉÃA8ýTôÒöÒ²HI¤!”Œÿ”õñÍSXá²8C(8ô_>)êƒÇ5ä,¬n917×òž¯0Ø¡•‘œå7~£¡é²ÙÙ1ž+ϲŽ”ú…XÝѦš|µP3—iýº±TòFh$!Š4_™k) F ô&¾ÐUô$8'HO ÒDÃÿäÁeªûÙk£L‘3>)ÕiãÀ+5F?þ\޲¹çpµtCŸª¦Þ=Y¿8ç /µ‚2Ò‹(9œûùêgB[ Jÿ;óõ9êçtÈ 8l`3g랬8L”D6c< ýAÎz™Ð† ¬cÏ1÷=X|Üžà£2 ×ß ‚ˆC “ýz¨Ý6A^.½T¦`‹bžì|õê„›$¦8‡Ìdr>çíÓs»D³M« #çäL}£©ªêòU—tû»ÛÌi·Õ}ÔÖºwBiîBÔ×α™HÆDH~iXdeP1çÇ@L\›+m<ÆÁq¿ö‹kÞÏüFöãCÖö¯¿ú2–÷j¸É]GæîÔqM!¥šJw‚ŽeDž9„2nBŒ3‘„ë™Ä)¹Ã; ‘<iú-¸v>›îÙ çøÌ»÷®íAv¹SÔÑ\â Ž–‘"þ5]ҰعyÊ&œ…_x>#X¹Ùù.í*-‚#r°U;MKèÐE[NÛá(Æ4æ*F6àû|ç#¬”±R3en¶Êº«¹]í.µqlpÌÅ¥q'èØþ^O+Ž:êSc$*iT×Z(ënôb•cŸY[ÿ ϺŒd®xÿ1ÏŽ‰¯ å(¿b÷â(bŽ¢Y× â9! I´œsœ«~œyèÉq +ÒÒ³p¾O-çÏ¿Q¶Ý‘t÷Gµ3©ž›Ó§ƒì9#Ûïã¨Ö‘©MJu¦šŽVžhòZ6‡!³ò¨ÝùAä“öÆr ºlˆ’—ÖBµ-O~¢oOåØÙ%¼œ}º&EL$ûDbG©–V'g#~}½¸Æ:$ T¦ 2Ʊé2$G1sù½”ðGÐç¥4´XN&Êo,¶(˜JÄ‘“µœãŸÓ¬¤“ºCÚÒoõ_¤DEÔ|ÒI û××u\¶´óJ„rsÏÛª!0;šÈ—>Äß¡ƒ*,ú€ ““Õ¨f¶ñ¹ˆ>ü­´‰Ñ «4"ÍÖ‹, ÃpöÆ:/…$³‹`Ô¢e»Á´ˆ¦‡>-Ñü#¸K~0GT¦ö«Þr×'Ø+œÓÛGÿõ‹£­ê“×ÝòN£ç§ pIugMªUuZa’­ÛŽrëþÇ¥¾‹H¸DÜEApe:C} }Øûÿn³; Sqñ¯¿$½otxÌ’¢§?öéí‚sqÌÜ£…æ€ùêÍôÂ?•‘|e.hFù@¼4‡ûgþ½O‚¨vWñ´ù ˆm°^èáõy þ£KšÇø†ÚÞÕéòëÑTÄ'ˆÒö~ÐmZ‚ú?õê MJ§q)·æó ô?÷ëC0;••ü@Å‘K|Ÿ?4tçÛÁÿ¿GðmæRÆ<’¨eã4ñÿsÏBp#IVÞ"y#†£lÿòë÷ùºÃûQŽ%Ø]C–¦r=À;š!ÄužPŸüÇž¨áÍYÇSä²/vâX´R/·ä ÁÔØ«ê\½æ×ÎVO®=1Õ· Wu>.Ž‘èŠkµ´ƒü[ôAÇPáªs@ìU.H¶ºP2š•ç#?éÑ ;û8Ê}¡"–¶r“·ò­ž˜Ú.Ò]]³Õ?Tê‹™Ÿnx9=7£–GU:J,Uʧ¼£éóˆÓo%}1QÂçTÿ0Oôè>¼‘ K§Ta¹Ô=PO?ËÐ|(ä‹âÞ„·9ÇAÿ~¡Ã7e%ÈBäØ:£‡DÜA¯>0>¡Sâ )ªÜð\ÿ~ˆS…B±XüSqüFþ‡ª,]PÆ«?ŒqŸœž>Q¥Ø™Ó /ñ²û«¬Ñòƒ¥rÖôM¤»wã¹·ÎÝi¾äš­”õ\)㩸ÁHC0¡iVBcTß!§PªbÉ#&#HjÜ}?dm¯Ø¾R>5*»ª;µª%íVZß]VXé#¥’€Yd–·§àØà!dfU\&× ™àxn‚£›NíÚçß‚ô¸6¸°I¸öŽRÇøy*©©ežŠäK«S˜C­V<ÉùœþQã®6"©wY× £ ƒŠ-Y<•¥»S¥\ãÒËÈ íñ“îIùçŒý3Öz´3;;uS=¯¢®õ-BÅE²VŽ(SµŠ ãh'Î8úû§Z°®‡exÕ¥(±ÍûÊ%dHä»M ™aˆÆsãÀüŸœ|õ1m ?øŽÙV[:¦JÝ:ͺd[#¨/V탱Ê]ùÏ Žx'ç*Ð †8>}¼ã¬ôÈy. ¦tUÍžãjyÅeTRoh‰hʱel ‚>€õ¢F…*£Ä@Õ5à c dM©€1»æcŸýz0Í‘5†-ª&?Pz‹2È~pÉ· ô8㉌9T²–%Cë`ð%?oqЗ!*€.R;¤”5AQQ1e`=8ÆAãïÁþgcC´Ñ% NŠC`ÒöÅDžŠ¨­LeÕÚÏŒñœtÊ„êãdØÿ%§¨2ÍiS˜öÛêƒü¿6GX4 îdéºÚÍPîÊ0 žK`œ xÎ|ïôé `¬.¤fÂ@Kíºfú°Ë?á:DœOçéÏõê<€$îšÆvJÍ=Ds´!¢!H˜Ÿ ÁÀϸÏ=S)ÈÕ¥:”é(ä©hƒ*r«ËpÙÉÎ|i‰ä£§¢zq=A$Ç ì ù‰ÇÀ_~“U„ÂYkb‘P­UUž g I¸ ‚?ÍžñŽO׬ídÙÊÅ#Rú[…Y¦S~”±™1;Wù¶çßÈúsÒr€d¡&Уu±Ï1xš¤&È'æûôa‡T"ú˜_¤šb ‰*qì7ž¾è;ŒuWÊ9j*q=b ÿœã¨æ0ì¥k«šªœ{ç è™:#éÞ?Éd\kTóLß®ý:ŸÎJ¾&¦²WuKü­S3aŸ=FÑhÙ ¯R"QE·’H'žŽ·’²=°Hè¡LÁx¸\dÔr£Í,‰éÊâHªŸaÇútšvÅ33wgt8 OÓ ud$4¬Žv±$uz!‹Ùp®OéÕºuRÛ%Ši},êLŸSŒt‡Ω€242Š íP?§LíI1²ò©#¿÷êÉQ­ºs§¤YÑ ò62@þ§=e©Xã½h§@:OÑiÙ\ÿÃÕ±^z`©i”§S+p2 yÉéŒ6Õ%ÂKp0ûôÜ¥++åeÞz§¨BSS}Vž09Wwýz[³F]Ê4% 4€c ú~¦z^4€aÔú,zp¯ågDZ+Œÿ¯WœU9­¢ ªø Ìz($].V ‘ÎÓŽqÔ-*‰ÝÔ pAÏÜŽ l¡ã0óƒŸ×ªu=”ÏÕç9ýz¬» Ê÷¨¼’êà °õƒ2qòg©’TéòʤÁ³Ð–•3V}D8$1ÿ¯Ceyïe^3ËÛ«,ÝLá Ö‡ŽI÷êƒJáÏÎH겕aádËúƒÐ†¹^f? »ˆþ½^G*ÌÙF‰ÐO>ç 4Œ£Ì ñ¨ˆq»ƒ÷ a•y¦{ÁÜZþÙiº­iQ¡ß[iJy®âŽEjÊ:@2òŦc*¼Â㑇[¢ng‰ö'RÊNRWÈOí»Qê^î½×MêWî>Œ¹Æ× ¢Û,õeGü R–lþ·¢›•dUÀ;Ô©ëÃãëù±æ=Ûeéð [qÝÖ„Ùez¸¤ fg« ¤ÌygTî!‡·¼Œc®&!°slº@À0\%’ßW4±$&–@QÀ —ç$çÆGp8ém¦ Ù Å슢¾Ò¸‚·R0ÖŸbƒés“Éð G¶qÐ? àsg)[Üæ­¨J …M5$ Œ¢áŠ—‘à1Ï01ž–ü6^³U=³Õ䈸=«‚–Gšê!tŒ‘F=àÈçé´"t¨\(œ—Ii-Õ4PµÂ¦xái6É ÜÒ0Ù€ÇçíÖÊn`LkÍ uUXŽ«'IÒFV…?å±bWöö9<ççK¡‰dó)®®s%=JÎ2ÅÌ»cž[Îîw|‰„¨ÔCRÚ:5¹O©PD}òº¢næV'‘ÁßÏ@ê¹nQ´N‰l¶´zi•¨wÅÃ28ç7±<çcç¨×é(é ,±Ä«"C$ÀDBîÊžqÀŸ:k¢'š< ‹ê˜—MŠº©dY#+ŒäH6§̓Àçß« Ke4²—X«)-fT¦G•Wk;èÿO`rx䎖ö&ÉÍÒ!@ÅBÏ©+idv(+"b­Œ‚ù>¼ž:q=YîD]#¹[¶´ ²ÍS;COMxþbÿ1ï·#9ãïÖgçËar‡åSjŠúï᧨‘¥¬Èq“¸ß9ÔõŠ7ÍЇÞêe ‰d™)Yê¥z—C•%]¤;wç «íœqÇ[;9BlVÿ9¤/¾2T(9'ß$cÇQµ•lŒk¤ÕTTôuQú°ÀH©ÌJWqòΪæ< Ÿm¿§MtNeMçD ê"X#’A] Šæ6G$Èú}=¸ÿn±V'44$<’D$sÕUÕH#‚IM±FÄQË»Î?Aíçß§t3rQŠRd©5 ®*Hê¾m¿;“€è>ÃŽ°ÖsóuEµÜŠý!ÆÙø^I 1Œg?íר²>`. ŽÄqކE›Ó<ðOÓ«ž v†è" 7ÎÙ¦{ç=X®îH¼×¿ Fæ¹#¢éÝ:!VsL†xöo6–$ŽOý8ëNgn³-›í5,°EPµ1 Héð`„φ+ÑѬ»½)`/œõg IE´=5g€dàsÈ?§VÚò&è~Û”{S,i¾JŠU㌸èF"M‚³†1rᦖa˜ŠÈžz#]£T,Ã8è8hdOŽOEMàèRœÂ 8Uæ%c¹ä=G<%Si¸¥"Ž£Ï Hó‘íÐüC7(ºΉ#1B›ÑÃ…ÈüÝ^ ±KèÏ$0à/̬¿LŒu&ò€µ“ØG‘ÈÁê6Q¶2ž¾[y ãŽzÑ(^îh~¬Žff>¯VEÕf2²­ÀöèŒ+pÝ®|¿×¨@(D…çù£A$†4YäD$±Þ i#Œ)’¿2ý:‚€‰T*”¿4Íœ‰£ÏÐÿ^ª’ÓÍ&eñµŸúŽˆ;šSÀˆ²Ài?æÇDÛ%A••A'’¬3íÕS2lQëG,˜ôá,>ÍÒ`>db‰7 © çÔˆ¯·9ê Àè¬Ñ:‰hÑFçL/×?ôêÅIÝQ¤9 …”m AñÇž‹1Ýh˜„o £ÄmýýúVwL÷„SD8‰ñãŽqÕµ×ÕGÈ"ž!¥JŸpz°ô³MèþRz"ä"šÃBy'éà{õÕäºÇ¦98ñ{«ÉÉ¢O9óÕ’fÊta!àufªX§Ú‰‘©b+ëU"àcœõF£ˆ²gGÚµ³ârà7mµ}°¿Úmz”SËÝ* ކH÷1Ì*?„ä€IS»ƒ×#‰T­’/Û¢Õ‡k\/ÿˆxõ5&µ»'pí íN笷5´°ÈÀ|µI¾HÙdÈ!¡r «1ƒó. Â*Le<¯÷ý״½¥ƒ-ÂÕÙàzMp¤jZªQ'§Vœàó󑓃Î9ð1ÖvœÄµÂå8Ê2êöËÝ¿ñ6¸Ô™ ´ÿ(FxÜrx'ýpߤ1¦îº F@Ò¾žÕVõºÉ#ˆÅê*îo®NyãßÏ=lè‹„nT¶©]²ó2I—*D¬ºÔó$`79;X×>ÇÆzUZë`4“`¥¡íóe\Á ±¨õ§ï v!÷ÇýOž±CÚo1ÉYr¯u…Þ¢©éi)–¶ž’ž3 DÁ¶«À(ÛÇ;O<œñŒgmÀÒhvcrRóÚ(Y²QEJÕ¦®¨f#)HÓ [Ë3rIÁþäyÜj¸»,+B®³²É-Baê€J´€ý½óõ?OÝ6œïd—:ܽú¥iu¨ü$I$¢¦LÄüù-ãÆ1‚s·Ÿ¿P²LhSCŽ^²p´ÖÀõ@f»rQþ\}Êþ§ýyè*Sp±UÒÛb¦µ±Ù§‚@Ôä£ìv UüóÏœ1úô¶¹Ó}SmS¬6ûJÒ¾H+d(T>s€ÇëЕ&U‰´*úºšz:ÄZ7ß@ÞÙÆ×9Á<ø9ë_i²2 ÎÅ@º_ñMº–"j’™A;˜+NÑ퀼ミhkZ è˜Ûi¸­°ÕVIpªŽžÞ»Â’Û RãíÆO9÷럈&:·)uˆ›÷¥7¸’;…u5;ÇJ!%c•‘ª}ÆBl}øÉ&›óu ÊmÔAmÿ»#$„ÌØÇÊ e'“œù<úc­f˜(€1.ˆ"hÚ©£ŒX¯ ð0ÓÎ:Êê/n—HË'¹ÕÁbž:„¨d“${ù?^³Ò¯§Ñ%Á7—žYi÷Ûî3S A]øùŽ2÷çÏ[©UÊA;¦² ˜ _@!Zi+6²‚¥˜d´IÏœsã¤U{É0¡sˆ•®Õ‘ÐÌ´Ñ´1 \€Í´ÿcž“ðÎuÖw¾÷_¢ \j7JAåAÛŸë×ß2r^K9Щ6©pv%$‹ÜgÉ'Û¤? mSÛŠØ E{’6g•¥8ŒøY  Ö’—BcÁ†)[ Ü÷é7Eñ HZºZ»©’Y äŒ`gíÓ²†›%:¤ìw\jý:i(*„Ø +öΓÒ%­ hÚçàçô=9)&§Àò ½³ž²š€™S›NÚ(lìô2•¸;°‰ã®ˆ¸¶‹#ÆS=Ó^h Ãüd ¹'?©ë9¢ãº0æhBZ—Šf“©Ø8Ü­Áý:‹ªÌÐmªg©¯«šwÅk¼xãŽÛ5Œd¨âbRûMÖ²”KRA"±òüìôªØv”Êu‹f Ò»ž¦®Ž3L² ÆöQo3u!ñGEê¯P;í‘È÷íÖŽ©-ªBTu t±DƒÈõB€ª¡¬H‚Œ¢»)Ú³îÛõçÿ^‰Ì;%‚7OKs—‘J&9 ÍŒÿ~“”ƒtygD\u“A:¼³³.IÃxÿN¨U–Êßh_;i¥eÇ»c¥:“†¥08è§¾Ûä}“DÐs¸Ÿíõöê7 ª&á'šïL¯ON=$#å$äôM¦@’P8l¨¶”Fzy “›‘ïӘ㺧4ˆª«Ó 4Nx÷Éý~[ؤh¤tu-0üK¨pO™öë#³e¥­l]&’õlÉG‚w€ÄÔ‹#ù¥’Á¨NZŠÐ!!KS€v€ÃÛëÇYêPzÔÊ´ÀJÍî&å¨Âäàãn•ÐhFk6,›¥ÔTÕUšHçûô߇ JS«‚cE™g· Š÷,°nrAǿө•Ü”%ƒtަõº0RIjX´(ÿ^” :³F—LÒjwœFÀ8ÇN8ëK(†Ø¬®¨âuC§½,ß*îW<x=o4²Û%¡f*Ò ˜t¼ÍQ­t& Š«¤Ué>ÕÏŒg¯Ll%\!O¨Œqÿ ¡ã« ¡ )<·ùM:nÂ9üÃ9Çê WtÓú®ŸÔRZ\•VZå”Ç}Öµ–ªCY:N)ƒeä óãß=HhE’vU^ ïfµSêê˜®ê ­€ ‹Å²’¡¶*n I Yþ+ea!Û;@ÜȈ 춙ЪS¾_¬©í¦¦Ô–žã[¨D¶f¼Ù/vêˆf©H#P â©–5/¸ PùL.;L4½§Ç_M}–᪵á¾I;Á­.ºÛ¸5÷[…}%Âå"…•Ìҭß™–$™CåJªÎæPÙëçXÊy=ûî^¾…"ÆÁTuL¶ç5ª»ðrdÔÔnçÏlsÒ@ÔÝQÖ KÖžÜï1:Q3¨VF!ÉÏ8QÏùèM'ôN£0V»\§‘Íýc¬æI ‡e1­·[a¤¤ˆo¬,L?Ü0ÙÇæc>0=úæôï$ç3ªC[3›œp-4Tôˆ¦EbÜT>|m`ãÏ?NÖ2æ©ÇÉW Hjª#Ye’dm`VTÉÁ*ÿiú‘¹ëkœù¥f×E0ŸA\#¦Ž’%¦ŠÓ¥—cÀÃ8'#Ç·Ó߬íİ™hè̺ošÁ ªÙ-\´0ÖOÐ_×h„냂3Àã¡éAp H¨Á·L1Üè¤)ø/ÂÒnP0 ËàŽ?_<ôlBtPT'”»UGE)¬tÇn÷Aó¼áHçû}þÝ`&]ªu"à±Es–¶ºù꤆­[ÕõD`m]¸ÉL{2F<ôòÑMFg&[L”7.æSÖÔ¤ñà Ý!8Õ.æÆ Îæ“ŸqfrÝU€ åÝZÖûmUÄ›ˆ©ÃÔ¼’¼^“IùrFÈÇ ø ë‹2•AˆCÖÌié­4ô†”K$è­‡)ë å›qÉUûŸ sÒè˜Óî¡€£7KŒÐÖÓKPe¨¤e 2¶°Îp888ç[˜D*.€›¨T´Ôt"s[W#:†u݃œ)?EÎp8ÀèÝNnš[ k<ñM3Ï,uq(>‹§‚|øÈ8ÇÓ“Õ¹§P¨ƒ©„ùk©¹ÔIèS${XmWŒ’6yÆÚH?QŸÓ¥ÖÈÖPé%:Uµ¶Ž–©&Ü*Bï ÍÇ ý>¹ë˜ó7`û,¯…AÞe®š¾RD rܾ:×L0+;²»P¿Lh(-5U¦éIdÉiü uõÃYàI+‚)·4¥%½EdÁ®N>¹ë0}G­6‹¦ª­˜Ã m ȃýzÔ Åœ³Â†jh¤QJ¶ú`|eG8ú}ú 9¥@‚ÒÃH"–XD-¸9äéÒºB~R¯£TåY©ê`§Z: ¤›tœûôäôºXV¸æpFüAjU]n3+¤õ3Tîó–#­L¦ÁpgTq) "†HÕjRMÀãå玘âfBXh…‰aµ©ùVX±Èäuçꮣ[ª!#Žè÷ ÷ä’zatj•®ˆÈi;pÇ?AÕ—µ„'H)éÂeѾý%Õ 3².Œn%(zZYz”ûsŽƒ¤;}Ü$ÿ»­nå0+ƒÉêtîPQj%"¦ ´«:ãñÏFçB^@ %‘Z!•K¬©}¸éf¹AGµb;~ÉvGT¹X íýz·TP¬ÓÙHâÓvÙ¡ŽZAIë>6¢œéŸý:Íñ.Äÿ‡I7O´š[L¬/ûÃRµ+($ÚÀbG? ë;ñugªÙ”c Ág:1œ£¬ J“])†G¨Î? Çëž›KeÖ(KiZèUi§¤«ôìÐÆ¼Ò6Ð}‚¯ê<õjý©Á“ H*£¬‚C,Ò¶æ9 (lóã£aiRÞÒ™MI¦£Ò”:sA$¾ ë&y? ECH˜üØ÷é­ÌG$“–QëMA"‰²CöUÿCÐftÄ#±ª ”‘²,”¸Ûœ–`Û«kˆ0PºÖ›xPìdaÁ>Ý)Dò^JHÕ÷ï‘랣‰Ù@JsYFÉ7Ô¨ç¥+ÍtP;Ü¡?B:©F‘UÑÃ&X”VO§D *¡%Ž€’®²B0ð}ú…Äs²}yY¢ôã(ŒyÇIöW"#ÑÂF®Vòr=0ÒUœ€O§ñÕ:±•y‹–Õ¿5:ŸJk9ÚmÛ]gܺ„–¿O\ä·ÔÚ)ᶪJÓ ¬>œÕ°†#F1Ì…œ`¨BƒŠ%½X$-èMÓøüÐ:µ6ÊS-~ºÖÙbŽº¾{<Ôô)rÂ8‰4ÆU”`Ä—zî*¬GckŸ~HèàKŸ ‡½Çø²×š£¹š{º6ÚÛ•¯ðùlñÐUUÒª‘à aŸiV–VF1ÊøR6©_9W]PÔç²îRÁ†´´ÝjvµšÙ_i±ºÚçj¨˜î<Ç,eœ¯¶á -·~pWŒddùN™¤–î;—Qàˆ%RW ¸à©Š’yåXvaŠEÝé ·ûŸ<}:¦™5I/Uék©§¥HÖµ„û’_Éà‘È8Áo¿Kù‰æ¡’!@î5…*Y)}!J·ÊªG'lÿ~¶0YH;#¨àŠã[m\rí.ÏåAX ŸýxéU*d2â„Âv’ײŠç2Ãqr¹.ÕÆ9(Äœ –ÜFwCQ€êT¾ß8¶ÓéêD¹þ#•%¸ú¯Óý‡Yj‚ó >f SUe¨E´é³’‡úÿOûž€ÐÊÞ²Ò’\ìw I(f‚–Ñ;¿ªd€û¹㓟íÀÏDÚÅ®VZ!7ɧíHg4w9-×F=ëµ_ÖÊ»æägœ¨9äþSqq’,­–±ÝDëkeÊ5,ë-Ô#H AÏ óŒÇÛÇ[iµ è¬¸ZkR_ªª©k%¤ËLj¤1€ñ¦ïÊÇ>ÄîÁúñÇEN›A–HsIÛÍVô–š—4†­e§•˜îÝ…$gã¯úu´½`ÄÔ.9vUQÐMxÕ"¢¢ssBçR0ÀƒÇ<9çQÓ¨0Þ³71ÓEzŸY½H ©• ƹíÖ¾€ídÖáœn¿M(çU!SqSänóúõõhæ¼Ál Í)]ÄŒcAÐ n˜æ‚‰.‡åˆ08ö㎚ ‹¥¸E’ÊØcynÉäùéu6éµ[U$Ó—ˆÏކœ©Plz{Ûø³>~¿^¤;%ÉF¹1nQ&ÿn}ú2£›”Y—-¼Ž|ôëh‡(ÙáHßê1?~€9L³p½ ¨1,¤6õ Ëy(áRáË«6>Ÿ|uYl7ñZÛ@f+ú Ðd$ $舒¨“ˆòÃé­dD•èæaùùǰòzŽ<´’j©©cÐ4^J2J<\gÛé*cŒg:¡LL©œè“¤Žs{ñžzq ¥‹%´µf–QRôÂSÿœŸõÙ˜e•mqS¬µï_*ŠŸš%üŠFSª|l¥RÍQôÕ”ŸN‘Õ%0¶ÉªZ‚ìÞ¸/ö8mdrÜÖ5ÃòãëÔs't=ˆÏÞ©. 3c’O·@i‘©T/¢9ëŒl¢…6“ž£Y"AVlšîº¦Óg¢šãq¬‚ÛAÇÔLábÕ3,èÌ:¾ŒîU è«Îî÷›CöcE]µïr.³Ù4\ÃKS2Ó5K4‚ ¾’ä°Ë|Ü1<‚©c54EMŽ{²µ|¦üYw‡Kjÿˆ}ExÒ³iÚo¬¦ƒNO§DÐÛ$‰!‚uܼ3|б¢Ë¾EPÞ¼æ"¨/‘§bôx9c2¸AZ»­»&£·Ñ$–êš()ÒZxêîõ‘úÍ$fw8W)½•Jª ãž¹®{‰Ê6[i°%TÖè Õå˜O&`åøÙôÜ>„ñœdgÏYqµº1qªc ”²¶oÁ6ä¸z— þcËí8løã¯2ü\ˆÅi¨n©{å¶¢öï5ʼÎÄjØ1FdÚ[b¨RÞyÁ8'ý:–*4 e¹¡Eµ*›aŽj(Þ–Ž$–Xúädòyúõ§ ù¡ßú%éªù#…™á€èð´O´•!†Ióýº[±@+"„ë_]]e¦Š;d–ø#”«<æEà6ž0}óÁ9étéµÇ¯ueñºWe¨‚áS ¨›rϦÞÌ;TcŒqþýJ˜pÑ + ›¤ÒUGA©‘j©)ÁÝK ¤d`{ŽGM ´QxÒÙWè+•«§¨Qƒ¿iUlûoËýúˈ¤NÖFè7X[„Ó8dŠ¥¡ü»¥_é ÷#íÒz0ÑÖ²èQM]¨­‚šŠ¥¯œ"îãiª6þ€ûóÓh5ÀÎÉ/©USÝ.ò\"G—jHH™“ üã’IgÇzÐÊ0l†ûëER·ZOè© Q—Œ”‚Óã•À8óútUÕSÕ-ßÅLmÏtpŠš:Sêì“Ôˆív#nÜ{ðIã¯Iqëo Ì®«Õ:fãÃR^ ­ ØH}6qçô Ó ˜] ï^:z }==-Zoœ!dÉ/üîIoÓÛ£‰»ƒGZZŽ…®rÔ^ªHÃ>Òv¨Çå‚8ö÷÷ÏI¯$È êá;Ú¯’4’ÐSPB£ÔÞNIÁÆsãϱÇý:K©ŒÒí»v'¨jéª ¨¡•¤ªÜ±aÈ@nFïóÐ’áœQ—+U¦º*êb†ié"$~fÝ“·qÑrVêHÁ »~AWhTEì±µóœä|ôd…,†€òßn‡8„… Ó`8ãïÔήŽèôIXp#Ž:ÔªÈQ‰¼…Pº/(‹Q©4‹7úõpªH )3)À,O<ñÔ:)ÚQÆmäaŠŽ=è2ÙÒËÅã#Écõ=˜’\/rwWîqчÂý:Œ¤+ŸrI$õM/ܪp"fLácÁéÄÚéFÈ$އÕ6(]Ü€ÌÄ /ÉöA©d^_B’3íÇRÚª„|.ñçwúý:Aº&˜A’VƲ¶9ê5»¨ãQÜ_ˆÚöfß%^¸Ö›4¸VŽ—2Ëžpª3ómÀn qÒkäÔ”Ú,yÐ-vªý¤ŸãTÙlñq©¶ÕÊÐMsH§DÀ•V‘1Ê1îX7±ŸñL ZÆå²§‘ünü,OU ß ©%SÒ(ÊŒq–*P̸“òœðx8Ûñ4´Ì³ü%Sp¯h6…Ò½ÔµéÛ¶Ýs ’†á=Ituã ¨Dup:°Ã/– ¹eu1™]¤…¢Ÿ%½kÓ~ý¨½¥Óšs\\¬–;ž©®‚/ÿ·š!²žçPñ)‰' ‡…Uý_PŒáb rÃñæ]ܽQÒÀ¸j¸ý¡>?þ"tŽ£¿ÜøƒUWAYK]D ¢EŠi€aÝg-ÇDò˨{Âic‚ v!”á°¹-ÁásíƒÏMsÉ2tQÄdñ?…¤£H¢ 4‘À†B¼rÌHŒ`øÇӬϬ ˆØ%‘°Y¥à/~ñh \¶Iãëöÿ§@÷`h¤N©æ’ª*Fs<KŒ~DÉs–8ž8ñÇCF¹ú\€ˆNϨ©©¢Hè£4Õ²¸2¢€Wɱ¸ø8ÉÇ?×£y.ŒÚNª]¢kšå(ü@–y"“§#Œ{yQã§2œDl©¢.¢7ª—œÍë"Å3ò@Í÷ÿ~z{)ò2¨2*ò¦:#<3 €²ãÁO¹ûðxÿ^¶´•mË2a6¿$]Tc%ŽO뎨Ó'bœÒ_¥¸¥lyúûuôVÔ †(¬ ã=¨§Cd1NþËœøèKÆèzYô‰R88ûuIB(Ê4Ayò1㢠*è–˜†%Ž>ÝN—uF‘‘²c±ÔéU k!I÷?öê³^T4ÖJŸéã¨"È2B÷¦Û³ôêQ¥Ø„!ÉxÇ׫éUt+&9ÏôêÅMdÝÑ9çœu}8ÝFI°E:“à|½1®„6EˆÁä(ÏÓêÅMŠEgåÆ1èsrV)Ù%mÃ+’z!St—4ì€7gƒÔéF¨Cn°X9=I”Ni…§ß6ÊöóSPÛµ†NáTÐÏMKC\m[E,±â¿ A ›Ì© C€À`¦«òèn™BŒ¸Jù¤ÔгT\k®·›•Âëpgĵ.d’uÿ39'-÷þÝpÈ|Þä®å<£E®¼AX(£†)™éÝÏùŽ}øÇÛ=-´ný¼Ôfßt²¬¬ë('‡9Ý÷ôãþ:¥Vn•Ó4”U/]r™y ¾¦ÿEFÕÏ 8ÃÇH¹±É8Xßu©¾T´5±Hâ àeëïÿôéF•Á)`™… ­¸Í¹dI$bTG!ù€Ç·öëH¤"ç(ê{šGOPò–Î݇œqÇÔì^±ÔeáX|\×ÁZа“瓉0®¿ïœþÝ+ -Õ]d:¹¢¸R-(0¹ÚÒ1@HËÏמ²ÃƒçèˆI7«m#QM]$ꬬ ˆÎÀoæ×<ö=eÅbR­¼¦ g{„%.êªX6 \,xbÞH-ïþy%ÅÀ´9ÇeSÕ^%­õiç¹Eo‚Œî\ù>G¿¿ëÓÃDƒ—öJÝ¢¶;}2Jꪡ#ÎŒç‘àûŸ×¤Öfc}µóÞœëª*«äzÙ^±•ÕI„ÎçÁÜIÀ{§õÉJ`P<“ª&ÁCãH¥Ž¢NãºvÉp6ô#ôÏZ11$¨[h :®ÇÊ)$KmÙÿúõÝ$l´úÖR¸ÒvË ’*ER¥IŒyðHúž”A™(`J}¦‚$§¥z¹Óš¦Ä„Èó/ëã#éŒô‡’l6 Ëy$“_"¥JÔŽžy¢+&å˜òí#<ý=¿¯D(ÌI…2%UWýK]v©ÙYIÆ ©ÀÆóǸÜç­ ¦€’H“ùVŠÖ¶û4 ôõ2L¢VãoŸ¡÷ð:çUt¾û+x: Z‚ÛGTõk‰9TÜ(É1ãíÕËWo ûšR#3Óåò0Àç äñÿ_§VqM•Ò”ŸÀÁº¦;PàÌ <œðƒçªs‹›2šÒ"Áantl"ØWÅåOÔ`d~¸ëE?Ê&T&  ¸S/®muÐSÉüx¥0•Wˆ¶ƒŒÊ‘‘ÆTŒçެ¦V™(IkL'+4WúÊikhè…u:™&y%#n’J‚Àç܃ö÷¬Xº¸zNÞ@.@÷b‘RiÏâ©êc”•A›àýŽqžžê7ûi¤‹%ÕB¦šŽY¤oÄÑ&åSã'ÇÔg߬Áê”öòLõpÞc¨’”Zn4U0D&©ãetG«à•;”ÿQž´°Ö‘¬nƒZ+„p˜L›YðH >ì|c½-ÜZRQ³):JÍs’xè+èâ™@%èvdñ•-œtöñAÍ%ø0 €…WÛg£¥–²ª½hbV ™"$}ý~ýXâ€éu_š¿À’¬b¤^,ÒÀy^\ãéŒíÔ«l{d~SúôÕpQ|vÿáUÊ4S!@À àȃì çªX*vlŠ—·B…fºÐDïó/ñËä×¢RÓ ª Rþ*Šùªvça¤`§Üá¾Ùý~Ýxœ|Ñæ…ؚƎ¸0&Zù”rvÀv€<’O뎘x€JiÀhzäj)k V©dQƒôå¼þ™è¨ò…uGÍ «"ÛárA;yÈÇôÇöÏV8ˆÝÀy&š½!y§…ºi@òTp¿®|tcˆ³r§ÀòMbÓ<Žié¡5ÁÌf%þV÷òǾ:êWÀ#ÉV7ÇSI4 4eKp|p§=_õ&î§ÀM‘uPÃõ’,0¶@gR í Od~¬q6›¨à›Ö¦ÚÎñ,¬ œ3;GÿwŒóã«à–pDY)jmÊJð0X–ð:¿ Æ xDP‚F¼‘õÿ¯öêÿ¨ Цà%6À§eMIš†¢‘¥gÖ`v¯ÓúcÛëÔøá0Í#zØ#Ù½Bæ?Ÿó¯×ÇGñèO…’õDuKEZ]ÛsI&6.ј^NA$Œ– ÆIÉÆHè]ŒcÝšòáÛ…¦ß}ñ¶,µ³é =.ÂÅê ™ þP¾™ÜùÀpsç©Wˆ0ˆ ¦³èÔ*®õðõÜûuÝj´õ]$LÍéFC¼ŽŸ” êçç«æ…øW´J«Æ˜½ÓÖ JË]Õ*“…F§“~y ¦¹í‹ ù¤ÓéašºŸLêzŠ »‰íA#ÏŽ³9íÑÄJc);P, ²iÍMWTÃd¼MNK:´tÌVL  žG'èºZm0J&ÐyUÙª)ecYL !ŠPÈCx ä‘Ïn©õfCP.&Y¨ª`š5ib1çs–ý9èzfU¶“ˆÑé5E2µ Gâ÷íܱsä’ÀöÏ×=dq ÜÙFÓq6'©¶\&žHcªÌÿ ÂÌþ8\dsõö笟ØÌà˜Ú.:‚™¯×›u‚¦Jש\zÜ©öÆúçúuãñxçÕqÊÐÓ÷O-Ê}¹K]ZÒÕŸ@6öÛ–Èðý3Ÿ~´Ð-#6å@ûJ ¶¦IDµ¿+£æO`Ón¯3 “3OÌ¥qÝ 2zAasPŽFA>ü{yuž­1ºŽ™€ž#šíU&éaVŠ^"-°ýOY:&Y‚¬›vœÖì)MœÔñºáeZP¸ä q’8ù|§X±˜õ6Î!sØ+,FIóÏÔ´QÇÓ"ο-Ò²“¦ªÀ©í†§zѨéª#¹¤”ùª¢Ž5Yi2Ò IR‘3|ÛYsùpFyU8Õ˜Ó6/6”@rÂ:§²]Ñ–Þ'm7Sj¦,ê$¬‡*….á› ° ç#“àôMãx`d<F‰’å.Ó݉‚„R.´ÔU•QÏ”ÔòBó€WjÃV9á…Vò‚3€»ˆ=a­ú•¤ÓÛ]{E¿}‚ê~ÿ…>&ÃuÓ1i›,ÕvÚz¨ý1H#$IzB£nDž› v±.pIbÝr¨b«Šæ¶`fúŽøŸÅ¥˜Gº$G…‘ë¢4ÊVÔ[h)Þ+%J¼’Ú©Q#¤3FËÉ aê©d]ÅU‰Oæä'V¡—]Àêo°ûZÉ¿æ™Q*9ÛÙé­¢’(žOYãcêT Ã+nÎÒ¼ªò¾ìHË×Tq PpÊdÙª*A®0Ò}û„)íV8íºÖÝ%ªÛÔÖ@ªåwTÈRøùš5]¸oårrM%Õ3T2èŽá>Â#HmE”TÙ¬´¦X,RÍQBœÅSQL«Y'æ@…—‘µN0œµ`œÇ€Ggò±Ó¦Ý"ꦪ’ªgBÐÅ?§ˆÖ\2©AžI1Æ:ôTpà Mm;¨¸Ó·=K^ôT0T×\‰XöÇ÷™‰ÆG9ç9ÁëSê5¢\`E€ÝÞûSEž¦x餂¹$YÌ{$8#1…ÇæñÇOëÑ?Ðs6æ˜ZÒ¦«Û÷€bºãAnbÌ©ERBä!1’Tò>dqúƒÖñÚm1>@Ÿ¢æ‹¿E‰5&·k…=Iü:ÛkfuX¨Öûs†¢7c€­Ì|· mÆ9ëÔº–A%ÇÉh’võJî׈˜®TÒ¾”±Ó,k GIªMDS¸';DÁ¶°Ç8aÀ÷#¢§MÛ ˜è˜›V÷Í¥©×Ö«D~¯ÍºÅ&ìû°± 䞦zè²z(ù¥vÞñ÷nÝS#_õ/lª¨Õ7*-EcÔÎ3A*ä{möóÏXz L˜î²s¬5 GQ{ƒW×Û+.¶þÊÝ*$‹å{†¥¹STª3´N@Éc€Øè:]Í$à¯$ܧ4««¥–®¦ÇÚzj™éÁFJ SQ#ÈÀ*¡À+ƒî>ž:sŸH¶_Pù$ÃónôuÐeŸ@QÙkHXêµI…ãðÎŒÅHúŸFš&áÇþªúû抓Y\tí%ÚíU§m²C4ï³Wðúq)ÜþxT^GQ´i9ñ'þª¾.š†Mñt5- ÑÐÂêÐ–Ž¶f^|d7ØýF3Ó΀»³OrHªó¤y¨ôýû¼Êfk}âÍl¨õ5TÙKãÉVWŒŒŒ{d€a°²I=èÍZ¢ÂfŸ»T–šŠëô:–ÍÄ.É'ŠŽ§×`X„e—ÿ.=ø:'QӦ݊‹ªÄ©õ¿¸ÕCKp:—Fj(÷‘ªªÞ&ˆà¶B<ÑœãFzÓ¤FŽòP=ûždîÕòªV³Ü-fJ*h·ÒÉ-qž ™åPºÔHêÄgæaƒ’22®†“NPR™ÒaGnZ³ZUE Ê¢¦×e fX’É[Gp§ÿq*®IãqѲ« Еà& ¤7::_Þ4:ªíª¤ŒmÐR4 Pîl¨‘ÕXg>FsŒg­§J"ªXsÎáHíi}ºZ¤wÛ(¦”bZ:ú8P¢cs!€Éär1ôëiዤ±ÓãùN5*FC¨k­´Õ³·RѤG¥U¶5Æ~NHß­Í¡@4„ƒUúG¹³µ2ŠnäÜ-n1ˆ+¥öúGp9N1ÒÝJŒ˜ižåaïÜ…ênâÈÒIW?sêT…C¶hf…À ñ´Ç'|8+ ¥¬¢>‘ÑhN•ýà³þ.†šÝ¯**« ’™ÈRyò¬<|£q9çíʺ CŸš!QÑ·’~èºÄó½ö¦)e`BšyË·iØï>\ç'§3þW=å ÄEñ>°ÏÐÝj®'p®µF_ ‹‚FïëuFž‰b»JsNàê“ko§¹Ó 9Ý<5,í½3Ÿ8àxñÕ >Ä£u]ÂÚ³RV9š?J&yVc¾ŽrÏúœóΩÐ8 ”=3R1pÖŽß‹cn¨ª?œŠZ˜bö%VSqŽO¿=pÎ)OÄ´¢¦=m%d­¯EË• Éø™ Uòq*ÿ¹¡…~Àù(q”æäy¬=«Q•4Öƒª‚DÙ%+^¦AÉ9Ú}S´änz`ÁU›ƒä–qÔ¶#Í1=+S™#©Ðš"€£‰ä+ç'#;±Ã}¾øéÿ^;<¾>€?Ê‹Üjlô5U:ö®&]¹h „–csýOn­¸£Ÿ¢§c©=Um©;ÁÚ›9Z[–¹øv°Ô«Û&Õ@c1•ùòàóÏWѽ—váv¿O¡U…ėîzJI{«Ú›Æf`ðÁÕ†ìîy‚ ¼`œ‚äô?Æë÷üªvîÒꢹ|SüêI¬ºöê÷V¹VŽHÕJ9ù˜ÀÇéÆ1Ÿ§Iw¡þGê¬`+,E¯:ǹ¿w*™kè;eÚíYW%p§¬™oÀðÅ“êHVIÔ8.yþ\c Ÿ¢×½¨ÐZN‚çW<¸^ݱO*=W˜;ÀõUw8ÏÌ ^A Où4Á1Èþ駉i‡8IìRhûyQKE_¡­Z.‚Ѧ‰æ´ÒÑÖÀæ<#Å(Ü ù0¤ç Ž0O2·ƒk¼ÿu¹œ7çJ_¥®«…¹ôïn{9 ½a.jëíI ÐIÏ1Ç ¼åFgz’XçFìOâøg[#¼çîšx”†Ïb¨ªq ¢iMM ‰G/RPÕHÁ·úHbŽ3€¨?K¯ŒÂÒ=Æùü£NÒ÷‘–µ˜‘æ§jJŠJ9 ºRX-V˜ý† ‰0C» lpòF¡‰]˜üç8;ºá½ôê8»1¿/ ?º§4ž º¯«+ìhóšy"ž”aǨ¥f ¾$–Œ†^HV;N}†ZB«œHeon±á­Ðt/|† zù©úâÙKR—+€¦š_œÄÒT‰Ô´­g}Ÿ.ùaqΠðçôe£Ç_@ì+¦ÂÃÐþé¦çO­o+r†zŠÞÔ,‘¥ÓÕF•*'Ü a”ÊÂ9v)@T£‚KiÃÐÑœßß3ªÍUŽq™÷ïØMVhgºÇgÖÚªºž©&z:¸#ž½Œ¨¥£‰!ä•÷gb.ö˜dõ¬bÚÆŒƒM¶õÔwø&1ð%¢LyûòìJošŽé5mÎmꢶ(Á–)ž‹ÿ×É]òUC¹IÆß~—Fµ+Ž“µH\à-Ú›¯½³×Véï%…ôýÎè¤2Õa«ê%•Š7¸ˆp06 Œ²êl,5­~fºÖõžîŶ¡-Dsì(„=“ïú–[ÙÒU4H§a,•) „%¦IÁ!†…ÁÞ:곎ðÊ$Œò|Èì<§eF…Vœ®:{ç þ›»½k¦‘µ ³iZiäAIQh'|Ó0#qŒáC-œc¬•?X`ChÉ:hb9ÕU¨9„X•ri^ÁO UŠ¢çi¶]iSeÒ§y3o#&8øô\2~lœ‘€\¬Gêâ[H{cÌïºaÁÔ4ÁiúªîËéêåé/FÁ~¼Åf†Âµ«¥×–ë¤/°Q´rM·¿¦U˜¨l®ÒîJ‚ñïGõ«ý²l$ ¯ä´PàµKI}$çqíÕ‚Šâ×­WEaÓ7*FCWD’ÔиÀ-³'äY%Ûqä¶:ÌúXïøiÍÈ’/Í,ðwµÙv÷º» ø}ÑñQÐQêeb–éOCóZ½UDåTfLàgÿ0sÁ'®U|n"‹Í6ºäÒGtéÚm±7_FÔÚÃâNŽœ´ýÈ¡«¥Â¡gÓT(goæ’pOý²?VŒþ^…p¾"¤ü¾¡9%oÄ ލÖ6¶ŒHc·RHä´Û˜à~qçzsp”Ž‡Ð¡5°õ îž³âfª‰6kÝg¢.úÙÿµ –ù‰Z’XŒIöñК ú’‹¤'[x„ß>ªø›¤Å¸êí%"ífF¦ªoNOüÈËQ‚ëŸïŽœÜ#MÃGý\—Óí>¡"‡U|D$‚Z«ý¾´mõ$X«æ &}ÿùœdcÇŸuOÂÚÌõ*Æ$·¨KÖ¿»³F’ISOè6JÅ-Ý¿†~…ð<·=A?ûÿ%A]»»Ô ÏM­®2ÅQ|´i žÈÙwOwfp pø¯?—’uB )ú'Kwú¡RÚ\¼1ÔhÝ+éíåo­“”µÓzc°µtÉè¨b©ºv¡³Û¢‘ÍF€Ò›•—eÒVÁÚÀí$ãq>ØÉÇž©Ü>©l!8êBù”„K$‘F°i­0’,éQ ÕOU/£ È€œ> ã8dt—pj„üªÇ¥þÉ-bji* â>ßÎèÖš“»''oå$sÎì{ûtæðºÖóK"©>Ÿ² ÓÐ@ÊÏSc—Œ˜`}ˆ<ëÔ-§¯ÝKZÑ>üZ[¨­%~š. `ßåÎ@[K˜VMsÏËöJ"ºØ#YT]tí*à«ZEùIöÉð~½©CRGŸî¬7Èù-r§¶Ú.ýÈ~à[þ1»Å6›·ß*c¨ÑKr²Å`Y NZ#O%NiÃm„¤¶¬¬ãšµ\9už¡ V\V\¹}çY÷³µôÖÚÛ|=аiJòQV¶Ý3I AxŽpPîSŒœ`€Fzœ_Ár<Çå2—Å»Ÿ¯á6Ð|SvÏÑŽÛA©ŸUTÀc¤©¨)@iqÒ˜±d¡É"’`©ú‡Û'Ãò¶3cŽç×ð“V|Zhúx)룴jJ¥œâ6‰šO<•@X¹s÷c«úÃÝù&³ôÎ0ÿŸÕ$—âÆ1$ôèÍ`ô¨Ñ¬³5ÆŒÝʦâò+óí_— óÖGþ¸Á!ŸDÑúS§IõU.»øþÐZ;÷}%ýh,U²Æ’ó¨é)%†<?£,Ñ–CŒ»>3‚A7õ³]ÿ +ûäŸÒ†b¥_~jIûD»WqE¨¦Õ§¹ÕE‘6‚I_ÄpI™µ÷G¸}¡i«R¦[е&«–÷AueŽ&,¬HÃaO G\æþ£®hŸ?ÊÔxE=—1»“ð‘ð-£;…r¶wËâ³¾ÿãû†.ÕQÊÔ²×Ö,†@&;iX9wLd° pr]|7ÇVÌ`ËÏÙXjpÊ 9\ã>û—·ßűÖ^´Ïq{÷©½ Qd†Z¸iªâ2dÆd§jbJmÃnV!¶±Èð3b8þ.™‡´ú*ƒÄ‚J¶`ý›¿ °ÔÐÒ\ øŒº[[ÕLº¢™bˆgE°£æÛãŒø«úƒ»AìùNo¦íóZ§ÞAû0û5«/zWèˆÞâÕZeh«#¤ÕÁ㡬 Ò`4d¤d®@#“‘×K î!U‚ ph:H¿Ýsq ÂÓvS$ŽÕЯ‡Núü.|QéÛ5hô=Žù_¦è¡¥kV¬¡JËͦUEuy–@ðì‰wO0~|3渆½ÍI¿#eÚÁÕ£]²È¶ÇU³tºnûG_| ¶émAoùPȶè©Ñ(Ý9ì¡&àää.IXu€×9¾R|JÖÈë><‡÷KáÏNè‹ü4VnÌ_-ôô¦)&²ÓÚjèí•q0aêÁ,3ãõ>P¬G .8c×F†/ëÍÄR¢ÇmÆÛBÑNæw;BPkKµF‡ÐUöAø:»5¢ºœ[ß0VªYR¡]ʉ *2a†áÈwð´ëd—|3Ч2ÙDñ¥,}¿Ô6êXhî6­¥%…ÿo¦¬µÂPYfô£XÿÉ·9.3ä8µzáÙØb9ë(žKÚ:ÙYØ$¨…UN†³Ì-}¾ÒÔw4ÕI_*¦*™†Ô_M”:2Ì8Ëf‹É㦜eVÿp±1ú-£ ò¢¿ôfìòÃø­W_ áÀH(ž3ÕŒíþÊÅÛ™‡ @ :äÕâ™zõår|´ £G LÁªoËU_wnº.Øh­Cy·è•Ö‰Ú†š–Ÿç¢Þ]WÕ¨2À9· ß"¥ˆéœ2\v P´k:[hí=·H¬Û‘¾Úy+J|Fv a¸];Cukœi5ÞŽyç’žßV­¶@ðá{[`Q"´€Tà1õ®àdŒçÿ"Løhµ½´£û‚ݪA]ñIÜïˆ*Y´ßoô×JhANÔ«S E,(Á=B.#9Äù%°Ý€®)ÒFI£oÇ$}ª h×»ì«y.n¹’ŠåM&¬™b’¾šDS_YG†Š9©a€ª¥œùBî [jõæëð²æVÍoýF÷Žk=|#2x+dµÅ¦æi®×ZúØ6¬pDÉ´¡ í„Ù€3ŒuÌF [Ù"uï×DuiSm)N}Å”’¹nQIm¶UÝŒ3ªHè«g¤ÜtXÉF$\¶71$~PO]Z\>£€1-ço^J€% Ò¿eAF¯žßBÑC%ª÷y¯SW%•7ÆUZnL¢ªÊÇ1ã'kgrã­ ÜHðþ!r¨PÅIÎéš›JUW¥5:êA墌±†4`ë’â&Âó BÇÆ\fê7Šá鸆·7çíÞŽ•FS|4ùþ?‰V>ŠÓ×M¬ fÔ+©5“\U]ETò+l‰—B†Þçc.3"¶æBñ1œ[âìþ£@óçßïT5˜j»(&@ßÊ™Óhú¨ï6z½I¶M[?¨ê‘KRjåTm°£b‡s0%•†X$ÖƲŸADˈ;ùŸµ–ч,›·Øoü+¬½º¶ÚiîÚr×l¬¹ÖÏb‘’ÃqÀ*Ø ZaÂ;§Æ4×k„÷aË›uÆþþš*ú‹³úNÑK|¥íÜÚ¿¸i*ÓS½ÇÕŠ±¡—hØUŸøyvÜ2ÌNí½e§ÅŸŠh¥DÜ“ÖË ’t‘ࡦÐÜÔîOb±;MOª­ZÙj×—ªJ» «7+„µ5Ò9]Ë~ öSÄ¥cP¸c±soWX5âlý¾)ÔŽV‚럪j×¶>ÙÚ/×Ä0iÛjÔ¼“ȧӂw}¸3&ó˜À<ä°÷óËø Îm6ѹÐåßOÙe¨àR½-­~ìJHµ$V¸T–«T–Ž®µã‰3¡ÂŸ”„’3Ÿ| Ux‡ž€ÕìÍê³Uâ4¨Ç‹êºŽ;ÃÚXïñÖ㬌Oú õú<ñ¬6†¨ó_6o ®4aôH*~ ;9B ³jûF¿)u¦™€çŽvô÷é.ãøP#¤Í0pzîÿ¢mýž,Í¢–n|Ãl•³ýºAýG…Ký k8%sþ>¡'©øšíb+4uZޤrVÖÊ<³•ÿäØ_ö>E1¿§ëÏÊn¿â‡K4Nö»N¬”'2*©öÉÇõéýW†}÷§·ôý]á@ë¾)ïE¤Û< 3»ÿkõÏ«ú­³, s?O·u¨øš×-XÞÍLI<30_9~1Î>ÜûsÖ'~ª«2Ð=VÀinmà¢WN÷wé‘õUÊÞãðÕ2C÷òŸõë-_Ô˜—Zc¹j§Â([> )/qµ»»$šÛZ;3xýå6[û¿ÜyûuÎÄ|ÊÒÜ,Áä‰}s« ƒQªµivm¿Æ¹JàyÆücÛ¤|}s«Ï™Eðt¶hò ¦~ãUÇX(ëµ…þ ‚»ÉšºaÉ%½²£úŽ…øÚÛ¼ù¢Vh<‚—¸´sÖÚ¤¼j‹ÕÆÆk¢Zä ¼ˆ¥’Ÿ-È]C&J’®™>pkã*ÍÜ|Ê·PdX$ëu§–¢z}-u¯¶S´Ò”ýâíS:A“±_kFž R¹lm$p8WdäqŽÓ'Ñ1´o—è£I©5Í J· Öž¸ÔdD’fʤí 8`ÃæüÄ©?Ê}‘Ó¸s§ÏòšÚ  'Ks뺪U–JK¦¦…å8{u,(¨£“»øíž3ƒÆN9ë/Æk.õ(þö G²ñ_u¨š›·Ú²Ï@ñ*«äŽRÒ ´°Î$à`Œ}z¼E¿â}…tðnD¥ Û½n‘£½ÚÓ` }[h-<|Ì>±`øãxäžNrGY_ÅsùNn@2µ÷¸…]#YwïOwþ&õm“AYî&óø+“Q 9$(ŠÈÒ€ÙHÕS2ØPKÖÊ\R­aÐR¦$ÚÓ=ë\Aéâ Q9ñ‘ð—|™­Žäj}OR³z{ÍŽ ¼Ä+ꢻ/ßhút/ḰaãÕ2ž6¹ñWN‘îOi;¥~—Eè]WK¬6+COvÓÒ£¢`»dÚ0B°ÉÎ2<Ò\Þ„‡×™¸[§ ç#!®î•tTöÃ÷uM]Ê êÛš<µZY!Vã.ªcI—` ¶’ æ? ‘aÞV€Ó7R4eXÁG~£Ó“1'ÑDüC‘€ü²*Œb2<-ÎÉ¿Š(:'JMy†žJ*‹¥U°¹ÿˆÜð´Äû´ad FHÀ' g\ön­8Õh;Ÿ¬*a­½¡XÊútñåÆÖaÈÁ Ÿ¯Üõm-Òf3+ç‡ö–öú¶Š=DSÖ×Õé«TÁ? …R9PØXaDg’rq×Ðxp0‚ D¯-Åé“ïÃ'a®ô _~©§¸þzÉM23Õó´®ãå?Ôñ×3‰cº­+W¢X3G‚ú³é‹6‹Íb²ÕÕ-:e–®(@Âß³w¨ðÇ^K3fWm¦Â•ZgNÃÍK”´R%$•.Ó¬“$(˜g–Ix‚¨d¹Æw`€hƪ·î}·RWvÏW_»YtÐ:“¸×JæÓ²U[b[mLÆXðí3M,^‘„:«ò €Nã‘ÖŠM㥘hê®4w·àk⯻÷Éõ~¯“±³k©)b£üUÙ©dŽ™ ›"T‚”Eÿˆà¿œ0ˆñé°\N6åfœ—'€}C˜Ä¤]©øuïWÃÖ»jO©4þ™Ó–JV¼G-ºñQzc4còSѬx>©lÀ@IܤgŒeWC@$ø*ÂÑ4ÛÖ´x­ƒ¼ö·â3¼Ý•Ñý×ìxÇ~©n41VÝ´µ%öÝi¬¶T…Hâ}(÷#ž”ó&xm"lð”/ƺ-uÏkWÁÄgtuõ,:›áG¸ÚjºkÄÿ¼ozÆ®­mc|£|žœ éìÒÇ,ÁÁÈ#ƒ×mØ–1¹¶´Êm¼—Fë©}—ýŽ}®Ó—PjâjAu’ì$‚—NÐ\=8-…ù³Vci+öácdjã/)ÉŒF1ù`ŽK}50I©–ì¯ÁÆ­Õ´¾ÑWÍ?­®öj*Á>˜Ž÷W[kQGRž¼5—yii¥E–̰گò‘ÃuÅÄÑÆ'{®ƒ:?”-/ø¡Ô:ƒ²º®K…¿EéënŠ­²\¬š[LÉ{šûa³NÇ-sHò$“ƒ-CB² ù`6¢çv‡¶wõ\ž!QÔÍ÷K•”1ǨïñŠ )k d=>õH"9/…LnÈãêpÓ®ÅZ¹[Ë“‡¼4]l&­ºQZ-öŠºáoy%g¨ôÈ>®Ä<3 ‚ Æ<ùóÇ™«„ÍP¾%t³;6R¦Ý¸«¶Ý„”Û…= ÙAž)…”Eq`¹%Ž3Ÿ#9øƒÖô”¶C‡"Vï[{¦Í‰f¢’‚ŠYá‘Å,’<•õcA”#í‰ÝØg9Ú›ž¼UwÕ5&zÚˆ¯?ÉÑt]ÃÆòvºsæ¤õq[ÒT²þô²PÛcc’ž‘‚FHƒÚT9Âdœ1äœø¬w»5_gßðœæ‚ìµž7æ™´½5>¥–¶éAo«¹Ò,» Ó© Çü% èSrawÁÇ<7ÈOVÌ=9ï>û ®·Y+×X'àèI¸Y÷K_#¬§‰v R»X„@Iï8×E±D1ÎÌýH]Vf†7ªË»Ckx£%³^5–¤µÉz…ã ¸¤Vúz9¦]œ# ´Á3r 9$ã$åÇTh4Ün ÏogŠË‹`s¦¡Žãî•ÒðUëþš®V”ÀÉ]¼±`U%}RÑäAPî6ðsÏâz†¨¦Ø¨5°´ýgĦ×ÊÆ€o;v÷¨Ýem±½ÕÒWYí³ØáµÆ´â®I PÎU•Þ!˜ïT!‡Í `Š0z<5 ´ÃÃYwÚ#»³—áSú¹³4Aç·r•ÓéÊØÖ]-¦¢Žíe©XfŽ6hVJTbª¢6Ê´ópŒä6I'ùž»ÇY¢Üˆå½¼!m£H0d<¼–¨­j+^»Úe¬™ZŽo‘ÿ³&á‡yK0;TFŽ~½.!•˜ [–MÆÝÇóªuåöa¿rÓGÝ:;%n³Ko«X!«›PÛYfTùÁUv’­$cAyù›•Ñ“=ç@yê´SÎ$~UŸoÓw½Kg·ÜjVßqßMü·¢Ð»AŒ.ڀᙥRS”QžryÕÖuj;1Þý±n|“8‰2wE=²‡nÜëYªû—§,šj‚gNàEemlH}E¦c£:³˜Äj|ƒ»Éë[8xÄÚ"$èA“ïDk6“%Úyµ²Ãñ ÝŸ‰ý>ÚJj.ÞvJèh®šŠi&V¨”¬fZßùQ'çbç«1Æ¢† ¿ú‡?`tµôÝf¢ú•a× ·ç]þ‹š½Åø‰ïƦøƒ[Άշ½-k¶Ý#£Ó/d3%®×Iø·ŒÑVÁÿ&iœ)w$1¹ ¨_E©Ðn=FÜÌ‚í2;9/<Ìus_3lgNc’î.œîõ1³Ûuî¶Ó Ü( &Ššb±Š³Ê’Nà ¬¸Ü>`˜ :ðxî9NGR`cí×v®:™%“oÙk4TrϨï—Ê‹6¦®«X–¢{ÌxyÄ&CêÓ‚O ¡$,«Ä9Á¨n¼Ö#Z±ËPÓ; Äïã Œ*ìÚ}Ï/¢>¦õ¢#ªª—Wµh¾Ë#K!ZÈ©ò¤ü¿Âۄ€ 8ã$ôMÁ4ÑhYŸT0–¶ +wªEº=J»­¶ت«‰ IÇÜ㯲‡r]ÂÉ0›/”ô+cºUÜ-R^í BõSÓÉIëzÉfFÑ~ Ÿ.F3ƒôé´^öµ§SjÅ2WÍÆ¨øçï…ËW¶«Ñ’Úû[b›å†Õ` §†ž r2fïñùØ|Ób8u!4ÆÛï®ÿ²óƼm½ú®±|&w~W$1]+Jê CGø¯À” '¯Ž%l¬›ó½×–Rç'‰.có;M'éîë½F¨¨Ðéºßêx«é’¥¿ÃÓTnF{°ÚH²à¬yóž8è¤nžäˆ6ÚØêd™4œ¹”‘+.ëŸcç¥È…`rMWÛÍ“ «ÔšŠž“NXi‰ê®wiiãÁU I…×;½Pm'ÀÏÍcç>±‚ÔâÊ_ì£íd‰êÍQ®®£áhˆöŽ °©Á9óŒyñˆ°DÖÓ•¯?üøy­î&–íV‡Ôšß»ZÞóz¤°Ó¥;„µÓÕK(\K[PL`G’ï鬘òj?“¤y «-|}X\­Ä—·6ë Eê¡×Aj-;K#SEPûÿpp-<»6±c°¨'kqÕWáÝS•æwåܘÌWXYK,ôèáAn·RRÓýó[¶¨½ëVi*Ib·\»¥¥4å\u)K,-}¤Žv¨` ÂA;½R9ôÏÌG±óÓ…3°V×IOºoUé½I$ðéÝc§µ,± ’ZzJ…i“çh܆X`†o8R§8ÄÖÓ7.>KY¨ãò… ?_?Úôö¨¨¾Û;©e5ëK[hÒö‹…$0Ù:ÃUƒ/(ÊÎ+òœÙ|/ ÓõIÒu+—Še{;UMü7÷â;¶}ÀÒšS³p­†ív½GEuµÍj‚˜êZÍÿ¸UÔı#’% B®P—bOj¾*˜¦ArÄÜ;ŸV@²ì•Ϲߺ&¶íp¤ÑÓS骪U­¡»T\«®±UAOéSà:J‚õ2.Ð’%u.û@BÇÌÑ­P‰vBáU—oŒ_ÚEi«µ[»yðÝ%l³ÕÔRÑÝÚ‘L—5m©´3Ë @ÈÃ×D—•R¤ ÍÄ POrÍRÿÛ½hv£í?ÇgÄÝëR÷cWé=týçK-–ã|ŠógŽŠ½éÊзîÙ$IY% Ü®wùNÝ­ÆPa©¿qú,GQÿñéÞCAðáÞÍcž¯]ä$zІߪZ¶ž°|²Fµ+N EU1“²Ipÿ*œ±—Œ®Ú®'é÷]='1‡<È*í÷Â…ú·@\;¬5.ÓWrðµ&‚Ñ%œÜ™äå–vŽe¦ã#Å!ˆí©$mëQ¨ÇÓ9a¤XI“ߤy´Ë@uF›ëõ¬]Ѧ¹\»‰û¦ÇMO°G m(KSQƒ’@ |Ã`q¸)ÈNÍ/yÕ/Lº¬0xEÒ+MΧO\"[Õ¶*6Eªª(c”´a@m¹ k62æéõp­x–ßE(Ssj¾ìýÕ×z·IÑM}Ô7{m*¤ª†Ï VNѬJ:»}%ÇóP ;Iëƒ[Ü=ri 'Ð#£R ¤Ç¿¢uÑ÷Jãêª[;SÝ/Áé¢ZÆ3É ˜ …i$‹>@-µ@;GÓ¦| uGõ ˜`ÚŽ3yû+Ó¹wæÓZv«Fié®U—*©Þ:j1oÎ\¤•c#ç ¨s´î®Sðý ù„v›'ñ*Ž-èéÅÔWMhê¹ìâí¡Šïqƒ0IWC4òÓÔ°¤ˆä•‘W`<îÛ´ãÄTÑÍ›Ëp³ÈŠzúýÕ³=ÒЩKM$b¥¢EÔ[çmÄ*á¾V÷ûä€uÎ¥QÏ%¥ÙG¬lt·d­’à&^Ü‹í Ø5zú¾®W’:X) ùVQ %H· ®w—<´ãó4Ò"tùò“Z£Þ@lÙ¯òTf–×lª±­ÎkÕæªÍE=q–åApP×+ŒD³·¡ŸQ‹(OU@P #s«„â*Ò§”X<‰‰íçÍb­‡<¼dþSãMp»½¾?úv’*g¨G¾*8¤7P&iI>YŠnŒb0:ÚÖ¸4¿ñzró[)Q{îëŶþØa’†‰+çôÍʦZºÃK-CFê›Ø"ͱ·ŸÎrpV:<ðë¶I‹i÷ìG f\ÿ)ß—b¯µ=ueå-–}ÔWKŠC$Vù9ÄTÒ>ù*‘p ¯¨K~brܺGkEGÙƒm{£IîHÆaóõÛ[z$ú2®¶Î¬Øl3YcšªégŒÕS$ñiÃ3rÅv° ¹Ü—¤ô/k¦—T™Ð@òìÝdÁP¨\5‹köW>³Õ‘ÓÞ=hrVÂeZšjÃad_„¨Bǹ=‰×JZ%†Ù-¿Y=þ×–ŽZÈRž ´ìàOR2ûðˆFæc± :â³=™©› Æý‚ÇOeL6#‰=…fÐO§ªµ%‘ío´­ ?­¾²h  ­ÚÁÿ,‹Uí'nÏ•D´óƒÝkŸU؇GggaåÍtZ¶ü(ÿrí×Ý/§­6 )a]q]^r©¨¥ÒÐ.wEél’2ÊT”Hý0s–ÁÆêáô‹a±>ãO?Eƒœ³£cdvÓÍZÝžÑ]5j¹j›æ­C­.5rÔTNk=8)_kGLi…}ƒ ‚K ÇoƒŠ¤F¿·}~ßDü˜z<™¾÷Vv Öuk¥¶Çþ£¿VlJJÚJcp’!€~dv\ì“ÀÏ…9=? ‰«XÅ rûú«cÜ÷6)Û·Ù²Ó¿ŠŸH~®zjÒ–ªm¬+Õ­ Žt§”™/aeJ¿æB "€r^§€þøªŽ&Àn9òž]Ë3Šô!ƒ_zªrÏñ1UÝ{î“YvW¹uökÔ”ÑF•2ÝVe Èc‰bã(F7Ÿ8èbx}'-: 8zöwû„ª|Q®|<@ßunÝ{¿_Ü+­û%¦«h4çg*«£†®Ä”•´ñ2Ã(s V;Ô|®UX|åºòÇá—aÏÚL Lí Ž8Úá´]¦ÚýKi쾌Ñ×[ާ¸Z­–‡^V¸Odz±>&Ž4éÇÌhÃfàÛ³ïÂhñLV%îeWÿlØ©ñ»cÅ' HI› ­~Õ)·\®Z²*;V `‰ià˜®&FI(U_•¾@€œ6å_©$Gòsûù÷¢¥ƒ§ñ®3ïØž+¨ì6ú/Ä×Ìö»M-\ŽRj£êJ¤ø$þLï_—qÁ$œõ¨Öé3R¦Ù1ЫU¹KXÞÄØöþÑW%;jþØYõÍ#XÒ¢y.[„>Q6Á$h Œ`ŸrNxÇ¥‰e0Ëh<ÿ‹/.xiªsiµ‚éE-¦š5+Ƥ„ÿšÀõ϶xä}Ç_UuéÈæP(((n4b¶—ñOgEf3¡ ¬Wò¾Óùç#‘rd–¸ƒ²¥–½Tü|*—GðíÚÍó+ÏêÄéò3#ù!›’B`ÿ2õ¢§¬òNrIH8jfùAVV•ìb4ê’û£{_Ûm1zƒzC[CoXå§ …a$•ʳ/ã?QÖgÕs…ÊhÃÓ@SÚº‹]¢Ÿtvùê ,ˆVšœÌË— »$»%‡€ à¨ÁÔœçœ`çÛÆc¡2ç‹ö­ X‹0ù.â~Ë®ÕwGµºs¹:_»ß}àз 她%ÞùD#‚¸ hÄQÂAq*ï•ÙÃW`ÀÀÏ…ãµêÁôÝ#Kmßö^‹„Ð{D8Gjë\Z!ð´¥Dq.D”ª×‘çÏ\RᬕØË*§ïŸb[º¬î>‡±Ù"ÑZÊëdž‚Ûz0¬on™ˆto”n³(WÀÉGr3ÖÞÄ›N³k³A¼|»y «G3KI\wÑ_²âÏGëm1­);¯ÙÈoT®PÌ¢á0†XÜH2žHàq“ôëÓb¿QP©L°ƒuæéðJ­‚\M'ì7Ć¡ ©§½÷·6©9šCM:¼°%=G#,Šß0QÏûõÅþ¦É·èºíÁZIºÚ;FŸÕ¶ëNŸ¡¼OCU5:R‰)î ‹Rë¦é°œnÁkŒpzäL­¡™DíIb¯a%Íé+k¢ø6†@¾ÅA]¤ùäûgLމ‹!sÛ)[ÑÙe1=àPÚHÑ¡­¥1o•Y €ØÚÙ“Á>B¹W7”ªZ=#A=%UÓJE=C8£S±œªzŽTG 7ryúäôla’¨Ô‹)¾£L­uÁ‹ažŽÛ$€ž|2G´ø8çþ-ìËrˆ‚Q7šÊŽ+ÔöÚ§ùÅgZ—u›b~©#†<m?EFÚ­Cø±íV¨ïöŽÑú·}ëÖÔ:nëûß÷Ó¤%{=+öV¦ØS`f“åS‚ÄmSŽº8 M*Y…VÌÇ¢F#÷€Žåͽ1ûõ…öõ%×Uü`hº³^.52QYëä¬z÷Ë ýZ¹P½C}FÎG?^½~6×6Ýyé •ý-ÁÒ÷.žösàoRöÇ^i w©¾&»…¯ª­FB¶Ú{ %ºšºG…¢ÍC,„ÊC#‚–Ï\JÏcÄeõ].¸›E½Ù´ñŸ×›PjJÜŸ•ElqªŽ8]‰§3ž[žzNFL¸+™ºj›Kèšj#s¯£ÕZŽz8JOS]S+uXa”cÇÊ08óÕ2“€!Œ™U„zšÙO4qÕü$÷>KdGÓ§’«÷z´Äç.°KTÌ£,×' x9è åú#<ÕËq¼hlMF²Ô•U^¥4&ŠÍ¤ÍÂá¨BçðÔèí²3°Èñ”.ï˜z{iƒ¥+JKê8k÷J§ÖÈÖ¦ì“Jad¥‘hà†™©¤$¯¹ÁF8å¯Caü¨ ¶Q»­Áêê©hj46¡¿A1q [oMHS¶y ª’€«óxÉóІ4íõM,>Ê5ª»ˆÝ¾·ÖjøcÖ÷{ˆ”Ä‹§ìTUÎûw€±‚[vÒv’qœŽ<R¥'—z[É#敟‹ŸÚ3Ýþï÷£µÝ§¦îOcÉ$ÖK½’*¸ÅMÆäguÄÓűc(#ô‹21i ŽÕ.ÒÐêÍüÍ8Á›/Ô/v{ö|]ööš×bîl´Ïuª¨äü¹^©6]é¾ULËU ŒÌ¨­É1’A9bAÈÕáTîZm²sq'u¼vþó÷bé{¹jˆ4oÀÅ–ûWlŠŠ¶¢ní aJŽEC|ïUI'nÞ¹Æ0r|“dŽ´…Jw ½}èµÛ­Zž]yðKd¤¦¨‰(ÿrÔMèËùÀG¨0²Ä¥7©µ°¤åU°ì¥B“œ{l£ÞàÙ.Z7¢ì߫ܘjô×:Í«E]’רaÕ‹Mk«¦“;SÁVztªUœHô껣ÜxóÒ«Ðe 1›–ÿ•–›+f59yè>‹r¦íW;"÷$î·Ç¯²Ú2ñ †Šã>²·³Þ奅’‚ q, £|Tñ2‚IPÎTÐôÓÝ*:£H.&{e¦zÿ[êÆ°¿o¬½ñªÕ=·õRªw·XÞžšé(w>¤ÓÖ¥7¨ä´›§u$îÜtöpñ©ïû ~ c¹Pqß­rjúýCf ª¶Ë Ù¹ëê*àŠ- … í» Ë– ‚yÁíëiÁ1Í "݉-®æQºø©bWQÝ¿y\nz誔"½ Y‘åÜpQJK±B¨ »‚IU<·"ü *`5®CQîÎà èn»Û Ý%Š‹Xèê{­µí¿‰´Þ,ÓTRÈXGÇKULÎ27ÄJ§Ì¾íåzjDº›Öî÷+¯W왋mÙøT]U÷FèºMS¤4²ûeF¡AY^Ó‘YvªlmE·Œ¢ áU7«èÅgT x0-Ùä©•i´Lvû꣟=¿Ò/¨ë-†K´±þ a·Pþ3ðí@*ކQµÙ—yÿ˜<ðjÎ[þ_…–½F´ƒ˜9ƒöÕÿê!¤³U_4µ{Ç tãÑž®ŠXª * 4e²ú„±!Q†Qwœ|تp÷¶£hÖ¹‹Æž‰8¡ËÑ’\OdåuŠ¢¢áj£¿].ÕâÝ%ÈÛêÒª¥MÀ!—Õ v´…VP=`Eœ.룈Â2i‹h&'ó¾ë-'f"åL+µ}“GÛ£ÿ R ³ÉV•’ËS!SŒ†}›PHÈ k»äÝœõÉ«…ª2æuö÷÷[±m6€Ó¹º¬i;™sÕUÔöÛLvè!yŒ5U¤l ÆFSüBHÆ ç;Njð–QnwIq½ÿ5çÞ²ôî·»÷-‹Ów‹¡ŽÕ<4’Å-;EJâ–š5$^Hö¬¤’­™ ÈÛ´n žK0ØŠ·’cœ ð…·0,“xç§’n®¹éû]}³JQG}U\IYL#Hd” Œʈ¬˜Ýç “ƒž»Xj/ZÇKßßbkê´²Öñ+'BÛ4µÖ(/­s¸~ñZ‡‚0á'88 v*ƒ¼ås¹H˜ºèz:B9ý­Ø²SÁµ­Ë¤±Þ¬±Öi«,v[Õ ,â®ßl‚¢¦xcœ……ž)ÆÉ<«!>§¦êåd5®Ä¸šy»ŒGä…Š›±@Lv€·î}“¬QIŠK¤ð$³µ%JIQ‘“h• S…ús· 9Î3Óé5ͼŽÓÛö]æ`Z ŸäóA­ì¦¡ÕvŠ檽VÇ<±ŠkmDTÉ17“ä]Ì͈À ½"‹XǨõ·ðøYï…1ÐúJç¥k}[šRSS„x¤ª§ªÚÒÆÃ™ÔgnõÉ;r~ü_éìi»éôOÂàà†èÏSÙìæ8å©¡jêXý(°†¦9 GŒÈÀ–| ŸrzçãpÎ ކè`iÊ 6Xø…vRxkÝ~ASZù6M +ÓÍWr«´©^”Ñ”J„q d #}Ϊ>TbŽ—Ã©»£†Ë›÷ç"þ«ŒÞ%»+LO½=STÊÖPG¨«¬VM¥éëmSPY¨§¤•%ªÉ±•B* ;`ÆJ²IÇ[±zìžZã G´¢5j™¹i"×âUM©™ènšNóeÓ÷w3ËXô•4©Y%MVÅ*e–`Î nmÎá‘ïRuVÒkX.-ôÕÓ4äß—>jC¦´çrõ©½Õ]®W8¬óºÅ!yWlÇzÈ 7(İôÃe—à“ÏÆÐhÙœžß-µX›Á‹†f‹{Ó²šÜ4¥—¬ªíרosI,ôt%3¼m"€É*íi)ÆH#·µPÃ<cX‹Æ‹K8Xcú7 ¬|tQKõƶ†y^µj­åŠc4ŠÙZxƒ˜Ì™“x.È;¼à=uÛM´…Æcm9ì- sª r ­´ßè¤Õ°TYD´¶DÙ ) zÑSÅÁ2ÖHJ«;eð«É8àž¼>¥gtµ%¤Hååü.S&¥lîFžr­ûEM¾ŽÃK3Ã¨îµ ÒAm]:úÁ¶•Ûàneo#ߥâé8)˜&/¯šêô-b}TŠÑuºÑÐà ®ž½ŽT‘M%Vg n;ö8ÚvŒþ¥(ɸZ(€Á|ãìºÐš_OoT™®óH2ß5dž|ÿ)P:ïmIœË@ÎIÆ 5§iËž†ªDC鉜2BæF dûøêêã«?æy*éaÃÔl'šj M5-Ù %$—/ê)xãõ½FfŒOZŒs2¹°á¸ m¦Á§ò–ûè_‰^àCj´Ò-úãIUN룩ÛZæ2HA,®I(H ¯@ûõÊu¼­àØTŸy¬z»:š«¹7ïˆèiˆLê³QZn´ÙéÒMHÒÄ«·6KîÙn@<.,Z˜`'Õ&¶|òV‰h­cß½c©îº3Iüeh -q©¯Jz Kޝª¥§¸A¸¢™¤-ü)XðÌàw~‹aÎaÍ—¦sœ[œ{û¯k~ã|JöQZ»Þ §wí—Ù§©†®ŠŠ®ªZkŒ(Pú”ón"q¹•²„Î è°ø*UIu¬Ž½zŒ}…Ûíq¨ô·Yk-§;«fŽéO-e{ÕSÝh^¢%Ä‹)«õ.ÕegfEõ×'>*,ᤎÈ?U³ j’á`îx»É¥ªnÚOâ;¿‘kx«*%’Ø,w ø`îT£ž°˜•Oã§ÍŽ‘›A”Ÿ×hŽôXlÕ[ &{Bg¦«î%Ö¾éAMx;­¨¨«&‚¦Ø–j±;ʧ"¦2¦S‚U•¢.+±ßƒÑŠ 6lÐGbŽyŒ¥Äv!OðçÞîäÁG{º|,|KÏ4õk"˜´•tÃ;KÚte¤bÜÁpO>z×G)·.tŠ´ ݘµ;êÙ}ñ#t²[*ôjív­GRÉSp¦®¾«ì‰†(³~*h¢Mã䌓—#Œt±ú¡Îé4î|d¥Uá}QÑ‹÷­FÕ¿ òöBñ÷þ½°Ð·e–*Í2•²\µ$mÚhèRh¢Vš9&¨‹)ƒÁ%Fê|@Ö¦z6~#Ñd~Ã3¼ªà©¾èÞàU®Ç¦{Ÿ¬4-;=%U5êó%ºŠ¢®ÑÁ+ЩXÕ%‹†vÁçë=,?DsÔ‚{5õû-Æ¡©féÛ¢³õÛÝûíïZÉtÏit&–¡†š†“Lhú·Øèè© Ú€ Iž¢L †Z‰šIÛ,ß.:§b)ê[ö&6•HœÙ‡’¨©S¶ô2Ýêj5U˜‰šªšÕ"DôøS$KP6Ò’>Ò#<6q‘´™R½K¶þû’i²œãeSñR㢬‹IÒÑÒZ*ZdœÐª|䬒Îð0ÙÜp1“Óß„%’ùðYÛ‹ PC£®¼_îwMg(D†«dq¬˜o_Ur6;»hÎÜç˜ (êÔ{É. e~“]jEÒÃT(hÍM¾8ÖlDî1´Š@f,­’¹n0É뫇mBʇ¬vÕ«T"ZtV¼4}ՒѦá¹j;µD¾œÒRï9ÞŠ›A :~dTäýÀ)=qjñL0q 9‘ïÑÛˆ‰y s2d_‡Îïê*:Ûõòójôn2šˆ"ž·`ZYí2ˆ¾Q#•l0’M˜PB–gêª9ÃiÉœû%U %GÁç·ÖÊ‘î]§Hè½M“«¶V×Þ訫„še.Á‚,©(fòJ¯¹wg­¼&¶*£ Ÿ`IŽïên-¨=GÙ-‡FêÚO%§NWß5<ô´ôÖk5²7žFšªŸzHT’ó/¤ÄÆØUüçåð:ô ¹îóõÑgst§O¢CgÖ7K¥óNÛ¨)c޾Ø%޹î-¢LäD؆C´ï § ç€CŽ ,/&9s÷à†›‰—r¯/TÐ]iÞcJ”ñD)e’4gteS*¨ÚNù6œ"œŒcÇB£GHâ\éÔý=„Èpngu¾“õMW‰,t–}%eJ(‰ncŠâìÖ°Òš‚IîpJöúYÿ…4¬ ˜Ò§#ÑõTe}B+3G`nxƒãæ´áj¶f/讫Ç|)¢º4·9––ªâïQ Îd4‘ˆE‰9â ÈÙbwmÙüÜŒVã¨ÍO$߈sŽ@5WWiõVާ’g¾CYø‘R¢6Š¡i릉ÈÜÕ“«Û!)º#;p|‘Ò>Ìonü¿x]L=VQ<ÿ<ÖÏ/Ä~žZÈm6ÑMø¹´´Ä#`Š¥£R7sÂý|õŽQ¹›¢Ä5ÇYPfî§î F¤«¤Ö¶}-¢)Ù‘îa™Ô¡UŽAPZbÇbWÓmøfWÚ9”ðE¸‡V1Ë{þ{×/ ÂìC«“mc©{¤;O46«EãSSêj„õ Êh#–hSqUaú>íHÄð::XC«Dß%t]\Ñ—‘1Ú}옮ں·Qê;u¢®å§ízžhÞžûŠzBˆÔÿ ú)€TOP•% ~ls±G\Ëì™í:}qÕÈ I;ê{Ïwtv¡P-¿AÒZæºÒ_nÕÐú•0Þ'†¸Q<ŒâYAA"Ó!%‚L‡Ü>±¨:ͳy7膣²34[žÿOK¨âÏvÕzšÁz·^j¬WŠ…ꦀ©†F_Z9Z)U¿:úad%€Pvà6Jðͦç‹GÖb}î±+^sž¨ßãoºy‹FX¢ª¤¸I %5uUKÏSUTñ4•‘nWfÝ#Wc¹ÉÚqõç®{ñÝ0Æ™oøën@ÛÍi ÑM½Scôå¢U[«µúºéiÐv˜õ=é9™9vDÄ‚ÄcÔî^Izíá¨?6zކ‘ï’psÜî`ûðUõÖámÓ•RWj½IUpÕÒDµÒ¬&gŽOËHÀ§A õÇ¿ZËÌ t›”M»}ò)5«u.©ô8¹ê(e†¶Š=TLE¶¢¤n/$MÁ;½7Û¸ÈC6ƒÀÉ=V ±4Ȫ%Òn4ü…(Ð&ÏÞSºé}9L­ÖÚh-ž“–ØãdrÀh¸ÜFÌí*p ¶:ÑF³Á!Çq¯¬­8j›@6òNÔ:^ÇKUv¹QÏL•RHf« FfeU-½_ÑL``’ç$ ‚:Ë‹â´éדa°ò¹æ”k²˜ê\ú)4ö˜#ƪO©hcØÓÏó:…$ú¨ ‚@ÆAäõÎn;?Y²A÷É–9Ž ž}‹ctGíì-ëCj=a­ªïZj¢ÔÔÔ[ ’4µ&¥cZ(8y°brì>HÆ 0ë+øN Tèõ=ߺÐ1Ôˆ$ÙU÷¿Ú¡Úê[µ=»Bv[PjZyUÎû »<|ÇÓ>¨ >gÏ<ì¥Á*¹²ç‰Yªñ0Ò JA«ÿk7gô|ª³Ïsœ$f’j µ>&%AeyU’‘•#/ÓÕœ'4xº¼f›{U÷ÚOÚ¡ûÃE*h‹U ®ûU¬¤©¬‰ž) n;Ûê€RÃÜxÛ‚yØžR‰Š§¹nÀâéÖ×–éþ÷ñmIHiV.âè™ê¿Ñ%Ê(–&]ÌÒ2œüŠ Êç>VtæaØÖÞG}Ój8NQÝOêxm@ú"øGÇ̶ôŸ²ó]Ùè¢]Eñg¥$X%pñÛl±©Î2Á$’ Ø?CçöëLu¥¼VªT\ÍÉZQûW»u/bôa;UÛ«ÿWéûà»ÜïwJzsê\j ’⣖WlAG›aË1pO ]oÓ”é»5Y“1ܱqž•Í h1¿î¸ç§ûß][éUX»?ÜŠÊ î†w³Tß>$•dø œ`äõèêã)2Å蠆 g >û×ÕwìØÔ=ÓNËSiŸˆkVê)¾‚w¿ŸJà2pâb’"¥6åÉS•Hù dx)R™¬Z>]bÛó^›‡1í¥0åÐÝApÑ&z '£ÐBÆX­T¦®Šutt` Åé?©>™ ±pùN:ç E6ŒÂr…Ñëºo>iµwomtƒRi«>°¾MêJe{ )zuÁÞìÌa@­ FIbÀmç¥7ˆµí\J£EÂÄæ£Íø£í‡eôæ–¹kkŸsl÷{Å\¦ÓBåä¹UHî‹gÍÀ@gR†6î*»TíÂa«âQ§œÌ$bk2þáÕjÍ7í ì¥<º:ýDÕ_ˆ…j㿚š¸žl,’#|Å$ ƒž6aYF1×dþ®6•ÌþªÍ´Mš^ÍÚi{uÝeð£øšNò]­ÓÁl»j Wt¨­zÿH¬sÕ=h‘=RíòÎãnैØ:ÏS‡b3Ž”n[­ ¤`•ò Ü'Üþ×ë ®îÆ‹ÕškWUO,õÑj g‚®¸–e’`óÎÜûª…”~c“×»¦Ö0À^]Í,0ðdóW/ÃfºÔšUÒIL(nV”’–HªkÞž7]„ƒ+©YIOÌ<‚0pyÜF‹j0™¿r×Ãë9®p¶kXwžDšé]}Ô:tÍ$+§”@âCËÌŸ) Œ~n6øëO B¦.fL¤ý¸ì|{¡Oøë'jµýîÅ$®Zšºy©è‰!ýWQ”àÙ“œqàtx®#J$›4º<:µCnÛ)½¯àÿUWÅc×=Ò×Ö½+¤'§ªkue¼–û“D‚DJz¶P%Œ¡À!yhÛi*7uÏ¡Äñ8Н£‚¢l&]aü¬˜ºb…AÓž©Ð ®vÏàRǦa·ëM= ëEÔдõµÂ¶ŽÚ•–çÚÔ®²M¿.Fñ±±ü6œœùŒ[ñ];‹º¥§žý‹ÑápTLÔ¨éÞ¦—ÞÀöKLGCGf»|"RˆӢߵêz4ãåæj(ÙËîRÅÙy%ñQ‡XhÐĹý!y˜“'ËNqªÑ]”üVó}–SŽÙü3]ï6Énz;¿Ÿ}¹~%$§¤¢¨–h¤8W;d£R0l§vw¯Ás»00{Gä,Â¥@e¤}{û»–Òj†ŠÉtɱ¦¹íªãQM-=wî½(n™•cõc¥P2$"Tù™•È*F0*XÑ~`{ã²ÜìS5k`wïÍssâzÏð§l¥ÍM®û³ßeMn«h+-tô”ëM>ÀÀ}Ì™gÉgf‘þBpûŽ;ü:­GÕþß치œc\ObçŒí7roWw¨´jV¨•¡åã’5h¶¯ð&b¤/Ê„CŽBžåzL{z6¶×X(až—}Gíîêý¶ü:ÔX´î®ke®7ê¦Ww}5i‹š0ŸÂ±&U‰'ž”ì5@àüÝ‘#ߪ6S¦ gÓèª>âi›³$w ÕD´4ôG§GüÀ¬‰¹¤ y Œ‘ÆÜ•Sci’¹)˜º ÃØìTÖ­¼ÍsºKUM'/ bw•*عfÉb<ñ޶PÃæ¸09.Ew‡F]>¿²&ÃTh žó=-²tTh‘§±Yb¬}ºE<1€ãªªu1¢²­š ¬”õ1ÕURθU&edcÁù8ÏcŽ„°é´KUÃ/uêÃG¥t5 ´¯Oø)«¦häÿ™€ ¶Nvç €1œŒOƒ9Í–—ÕqnVÙZ:gVjÍ8#Oœ–’4­515U¨%bŒì90ÛðÇÁ ŽyäâÚpM¶ËS(=®u€s•íø¯ÖzÊ»¸—«}þån·Ä²RÔSÓ -<§lje“ „,N@!B«ùzÃŽ{@œwy+ :Z½0†é°WµUºí5v”­»UÙZJ;•--}u¢z+U±?½Ã#^\õŒø&ktjéè(t…¢šÞôUUµítdOB8Ü„† ŒF§j´6|ø#æ=mv«©pCDOû{îZiÖ{ÛÑE†¦7WÕ¾ØtýLõšºßWlšäí#TF›ŠÆv S hÈ„*öÜzó1$S¢ÀHEâ=¦S›X5°Ø·¯áS×û­-êqS=TqÙÉôR8ª‹š˜ñÿ2ݤËçÜ uÓá8b]ž³Lòå⑆{s{Lª£WüOÃÛ*ª›/nmö«eDKGGM-CK;¼ÎÈ>V‘‰b$)Qœp:õ”xXª3T¿¿Ê ذÂC"J¢)u=N¨¬ª¿êúɵ-Þ`óW<ü´à~ѹ¶m £Bƒ‚ÛëËeʾÅ]b«‚ÄÏUi©ÃQN0]fFù³ùHÊ· ξ«…ª*4Ï1ÌnUcj4±âÅ|ÔkÍ Þo…]izí]_nõV§š9}K6§¶Y*Úší!Òh’2ɾ"C£ „ñ“ïœÚX†Š­t´ùŽÃïKè¼£¨Uø´ {«âåyïr4u²–ÏÙˆ‰&ZxƒSUæ¹Zt`”Æœã*Hç{ò0´iÒX@¶UuZ”þR ©Çbÿh²mŸÿÔš}BˆE.Ä î\Ëé¤Ù'ØduÓ8œ+Èy…Ínïñ0­m)ðñ•c·S5/ÿwnŠÎÆX*a†9£-†Þ“Ë0B£#$ròyçDêàºX ÌOb›]fož¬XBvfÛ¥Q”É$·½an…£üÁ#”•|cÏKþ±A“/ºsøuGêš#·ß±»½—Ích§ÖËì’§¬†K=§jÚÏÁ’Ò$Š‘— 1| †ç§ú–€ik[»ÕfoªMÍ–âjع¥RTê~ß÷]Nµ-5%¾åuk3å!Ú)cšD\…¦ÞG—>zHãáÍËPy~ä­îá4ÃóS™´'쳉nÀ·ÃñGØ{…}|‘—Í?5Ue¹ãpâ4šJ¢H;pÉóÏA[ŽQÎÑÛHFÎzÆuìü«~_ÙoxÔ6s£î7K5†6R(´Þ‹* ƒêõ“T´Ä¯~€ÏBÞ(ÀI óü(p$Œ¹ˆßusÙ¿gvW´%æûß.ãQRÈŠÆ¢¦š™j ]Æw NÂFfóóñœ–àgœúïÿ h¦Ð.g¼«ÒÙߨ„ñIpøuKöøŠ´÷êI.Ã’±¼€g#óÎNppz¦T®ë}p¤uÕ\ݬøKÒÝ³ŽºŸKö»GiJz™!3 o±UÈv®ÜN—åöÇI«EÕ>c'¼¢eF0Kmà­š^ÆÁ<óKW¦ûCM"UÇ5+ÁnšYãU•ññC‚F y9è‹>¿u:Y)Ê>ÊV¤ëS>¡¦…Þ†E£·´i'Ì sêÎêXüÃ$g r ‚®žSô¶ÒÅâŽãQ¢š'M†ªx©<ÅE7d}3ÇžB(0(:GOò—^ûw¥µ -«Ré- ¨-1”ÒÔZ LK.1¿Óa°§0'’r:e6†ü¿E3ÊMgÒ”*hí :+=’œ¿üŠŠ’”1;‰y_<¿199êžÉ¹&|å ºÚ¨­´ð×ÉU}¡ ‘Fˉ¤À)óx3·8ÜÄàí'žzEFµ­.q6í!6›KŒEÑõ:KKß-·x¼IQO%2š ú†­Œp Ï#L cårN ò ð‡Q¤D󟺧5ÇæŸ²Gn°öSO$TvÚ- $5‘–a¿ñ P‡-½ç–F-œðwÞGEK `b;“©ÒªáÔ;¾ŠKi¤í…&¾Õ`ÒT*Òº´‹lŠ•abcÅSw¸]-bmCk†ªGX’®¡aÚ™-ŸM"‘YÜìb£#Iª©ˆq»,{Ja=w}JùÒý¥º–ó'Å4u¦óMt¤k²–×W¬?t¤nVª1»o"q4ŒÁ@c:“{oÓÕÐÊó\j–Z°4ŽP¨«uV«±]-¶(馾VF´ÓÍJ¹«Wmب 0„qž³ÁäŸWMÀ·½qº³fÖ:_NRÁpÓê{Å<»b©™‡â#X)‘Ü2¡U}ƒ“ü t‚ˆ¼ÈV%£»¸ïÍÞã»û¤­——§e”ÑÝÀ¸Ûý'>¤¤vv;¶FrÅdçåæV†™e–Êx‹u®¦ºön|ë«»\ÐØ5†•´Ñ—{•®Í|ü ž­Ka¥ŸÔ03ídL„‡ÀÀŽY IpÍΊ-i2[eÒ^Õh߆þÉ[,ÔzW¶Ýº³U$F•ú[|—É8þ9\Èdd+ç$œõä«Ó‚\ê“ãö]ÚUœà[ Õîw{[ÚÍ0šË¸ê­7irÐÓÕ\dëS.ÆJfõ%៑›#ÆzçšnÍ¥©š¦ã‡mC]¡VÈe© YœMDgæÈfÜÛøÓÂñ¯ÄU.~‡n]Ë7¤Ê4C`ý{JÔM¤5¶¯h©­”rIU;(ôý1’âbL² òr=ˆëÚÔÅR ßïXûõ^[ „«^nOª¼¦ì­ÏLþ#÷Ü”´UôeZ¢‘i•v¾ˆ%V<™ Á‹&1ó×wcþCozþ˺xs©ØØøz"5lë¯oGAbÓ´zi£„M,Ï1Žy¦‘ò¨Êîc£FeåpÌWÆÞ‡ Æÿóô\Úœ?#Îb>Ÿ²×ú^Ñk>¨¨ýí¦¯”PRÄ¥rÃïFPÑâRx0›È#=¡Ä¨šY}ÛšÇK âý,­Í?ÚOÁÅ ßVjû}ŠÒŠ?¤õq’Ø1ÇËoÈ*AWÀ$ŽzN+Š44äåÕÃð·jóê‘éí–û­Æ1b©©¤ƒ òTÈ…?3¼«ÀÈo™Cà•ÆFBŒÏsžÞµ¥nE>´IW-¼]5¥%‡QVÓWÍl|ú´vÇYeX©¡C¸>Ü*’U#E<ŒŽ âÔ¬Ú$´A)Õq.ªÞ” <µìïP›—uí–ª½+n°Tiúzp•ÑÅ3´‹q«ÞúÃÑÁààL‚ nƒÁ;¦žÁ·½{׸òA >¨zzµµ öJíERn’SÃëü©ÿÍ%Ûœ!åÈ›h^2O]Ú”XÀDt©æ9ݶŠâÑôôu)4²^ä© Zö¬™êÆ$‰Á¨›iU¯+‚qüß™\]NÝXëïš:yÞ©¾¾ÿ %®{—.¹¾]4Ý–®Ó´ÿ†õk&Ä¥ˆÆvxDÂûüØ` ùêøom }#…ç½ ê—KFêŠÔø% öz*™c(’Tz_ÄTH¢c‰FŒå²sô=z8Y9È÷Ígø[•º}UIx¹Gs©¥‚©ç¬©Å‘°1ÀÐxÚ3Ï]^ެRð¦VK´ ._ˆ¬”ÂaÀò¡%åÝ$¬eÆAäcÏ\÷ÐaÚ›OyV.˜Ô¦Šj³i“Q[b¨S+•—ñ ±’)ÔAI¼°/zåÕè¦q ì"b|}ÊUG“J¹Vöµîö©µv²çÛŠjÓZ&­£®K]" t›kRyT‘·1;_æ]¼€J޽-_ÔMÄᙄÃ7#b‡Ë^ýO5ÉÁð¨©ñ8ƒ™ÃKiµ½ú­4¥u–°QEHQdg,d>7cÀ9ò:Ah ´Ã™>ÏNây¡†×ˆÈV”'Ê[¤‘3ŒýAèRQ»1Ð}ÖŒZv-É6ñásýGôëÈG%ê€B“ÕY-p£5Mѽ¬]U³…Çw7ØO|uz ÙxÛ,êô‘ŠMpfˆË˜¶ƒ€Ž6·¾ÉN‡KÇ’³¢†Ùi„¬bÆD²=ç§ îVË ¨òþý6•L‡3ltÑUFÈÊëƒïd -ô×5R¶xèid'pšÞZFÁ<üm?Cƒýøé’uF%:[ìÖ{}7¤”ñA9}©@ò@Xð>¾I?~¡º ãª]%ešœf R<± 8$Ž:°ÂlPi¢<_V…‚Q‹” Ïó¬Jé¸ãœa—'Û©”rW˜ì½ûþº®I‘)«]Pá·ÊÞ£xç¿Ô}:®„h«2[¼Lb’* })Î}IÜŸÓœû§ëÑ`*“*Ÿµ|+èš[ôÚŽMWÜÚë“Uþ8­f¸¿UÃëo/“Ö,L€±2»mÆAÓR xë~A±˜WEmlµST–¥šG•eR†äx™•äðIÆxÇJ€/ Ꞇ‡Òq¿Éi„ÅË J!‚2ÆÈ£Úˆ<*úõeãR4‘ºs¦ÑÚgð­M†Ð‰Cú J>û؃ƒíÕÛì‡4Ù/·é}5chj(4¶´I ‰i‚&EöÚÛx¦:ª•#X ³H„à5>ƒ°o’§Rè‹Ï#;1¨¢¦Þþå‰'‚sÔv \šDíõJ.=ÅÐ6Kd·+Þ°²þ%Y %Tr–ü»cK8$Œ`s‘Ôf%†aé£ïõ*?OÞNÒU;KOx½¼êˆ‡L×;Ê»°JÌÃ?N>ãÏPÖh>ŠŽ¡zÊZÞ²Ï]55·¶ýÝ¹Ñ n ¦ NNqµ¥áv#“¸^z‡5‚|¿eƒq³ˆçùLÚ§¼Õke<ú[´]Éוõ–ŠŽSvFžj¤Œd"‚[ê¸'¡À[™£ßª¶á6.¿„®«¹•Ó)X{M«æªd-„Ûe'žw7®Ê˜'I>xèN,“fÝÁÈ’ñ(þäkúº‰én=޼ÚmecZy…pCúÑúJ²ÀÇ&k?R=ù mòôýÒHõ6³‘ª)¡ÒÚr‚Öp)Emúy\±ãæÛ´…`'ýIÆ*ø·ÇT´Ò~É‹ îc M‡¸Sê#Õ½½ÓöÞ6RQÚd”Œg;äšvÞ|a”/õmxÔ(þ„‰=ä}‚uMKY|§¾Üµ^ »Þ ±U üè8Õ Ì¦×¾÷×íâ´ÆÁÝk펚Ïz{m}$3´ð%|ˆÈ’8 Œ7€Ëô;X:õ™ÁŒ¦ëÌ‚@—\º{¹ivªg¸Ê²4¦bU¾IIPHñÎI8'8ÇŽ ª[ea¡ÊÝÓÚ¶’ÉK¨µmEÆjIc£5W Ö—Ô;#bŠ¡|°ÉRBñÀè@D«6îPúŸÚÜÊó± žíÉ¥°ÛaŸÔþlI4ü.é˜QpCŒ)õÅÅàCÝ.[)â ­‰í?z-]½­²ë+Þ]+Kn)"WZêÕÇw–0Z¦šC¹¿.W•#ÆÅ­‡mIar]j8†²rèÿr{ŧ;ýGÝo‡MIkÒ—y—O.£Ñ×û$­===dtí4¶Nc`U «e}£è1Òcé€I²yªL^4×j;ÛÜú¦ƒ¶½·î®±¤ªRjk]–¤þwz‚6ŒH䜀1Ï]J}Ü‘Y•z€Ç¾jý¡ý˜?פ‚®ÏðÕªmBgbËt¬¡¡ü8Îwž¡6«q€Nœt‡ãú¹‚áÃPš˜¯ìšøË–:èoZcµ:jÙ$.­Sw×TþŽïÉ!HLŸ(#Iç-Î@=+⨃˜:ch?²{°Î´¾ÅËŽýv7Pü9wFõÙg~ÑzžóA3EqÓõé_AWÃzxa¸«FÀmÊ|}*ý3:V^ÂóUéߣ™+s¾ ~ 5€?l5uÚt\Ÿ‹¶ÔÎ Im¨U1‡ØAõ`n#•8pJàñ8Ž‚*·Qܻدsz#຋~Ö1ë+•3Zízráq‰u/£ UÁ–2ÎïÊLyeV^ÎzÃSM£3DœÌ%Wq€UÑYÛ.á_4ýÚ¦Ù ®•š˜K ÓѰZD®’.Ã,­|¢ïåÕOׯ1[굘ozôT°¬§Lè\µW¹’¡½j­EØŽüVijêežâ멚º®¤ŽÌаzŠ«Îs²•rjƒè´ÉìÕc«Iâ^ËÏmýù­4ÓZwºÚ‚û¨5Ι†J­JõGþ£®©xHhå‚ux}`¬[…È?:maÇXbA¹Z.{¡gn­`]RÀmº³Œš+Ih[íÏZ_êÓZ«SbÙ•}[œ-"2È&J˜•d ¿t"¹ X’r1 ¨CX x@ó™H}JTN_¿³Þ¬+zèÚ”£µÐEA5M•¯t4ú¢…èijãFÞL:‰„²Ié`–p­Œ:ÇŠÀ€I#xµýûÕkk)µ²XI"}ÂÕKG~jç¸^M¾ß ¶¾®¨MMOL‚OÃ$R. ÛÞM§s”l³ç8ë¿O„S¦Ð·3Ú¸¬Å>X “²ÜÍ9Ú‰îãé8¢²h»Ä?…”й*«hè䨒B³O%A£yxtuEUdÏ® L6µ Ë”rü®ÓêtÃHƒåçÚ«näü÷ÒCm¬ÔZÓDéjÈÑ·\n‚p‘66„x]Ø‚=ÀÚN1‘âŸÆ(Png‰îY«Óy_Ĩà n‹Ôš²ÏiÕ‡[=RÇC3ˆh–%ØèdC!Ü­»ó¨ Á—€sО2Úíl€Ã¯3ï½c§yy¦½Ôúß§åÑÖhï•Fé¦â¦¦Ÿñ5âæ¢0W|4Û>tÜ3ï`rü¬X²æ~,âѲâ|VÚT_—ûg+9ß1ïE§úº+(kíïOµ=Ê­æoÃ2¹JuUÚŠ™L °ä‚ÙÉÉôxrÑÕ°îóñ\ôÌÉlM†¾ EÛºª¯Ü­„®·ÑR¯§rÑ,N0î sÂŒ~[1’à ÏŠÝB‹Ë6S«æ¡jª[U5MÂFÌ IµR8Ÿp9—8ÝÊ3z€”Q¹l“Æ¥J›ê¹±ÖhÔƒ§¿·WÎãL"Þ _ª5"[EÂÅh«-¨3\*N7Èþ`üÀn$e†;T(g‰ÛN^K#ˆ(ö{;~× šªzF5–I-¡ÇòàùúaÏõë¦Ú0CïH-›©Ïã}6¤/éF<+yäŽIϰûôªÍ¦w÷¢&º‚¤öx§+M7ã‹J‘æ¡G :äT‚‡ƒç'¢+© ÚÌÝP%Z–­K=»O_¨)îÖ›DQ¶I[ðóÉ¿z#5 ÊUyˆ'`n¸ïÙÌð ùÿ‹}: È^ám?aÚU1qÕ+»Üà†œ4´Û)Ì™Zm²£v2ØU9$ráºëR±± ;«M„ T~'ðÄmõ™‚°ãPsžOMsÙ$: Ó€ž©¦†˜qµ$,ìäð3Ç·VuYŠúضjŠ*åièiôÍdHÍ’SÕ‰•xeùcòGÔcÇ^(bÇŒ2ýqÕôÄ5¿z¼žiTš·YˆX-º‰#ž(œÓÒ/±–mÓü›w Á¹Ÿ=g*ò „|uÁ¸-ÆÕU‰ÃŠšHè&yá<… µq» ™~`0O€5 Œ§ÓøT"éò¾ÏÝJºzi­KŽ®–MõÔÒÇ4Uc ³ÆQ$\ ²ít`ØÉ`1ÕÓx ‰º—M¥+/0ÑKv’úè±úÙÐVVn]„§Ææ8Ç×$Œ<lv÷FÓUËU!±™Ñ™™¤zY$>üçsŒyñåºIu9ƒ‰‚½Si>©mv“¶ÖRÕGl­4’° º }K»ËGè trŒs¸ˆb˜Ý5÷²s¾oº$XN˜·Iû’ᨪ%3’ª¾åspÄÊÌÅA ò8À<{ô·b‹Ïʉ˜}Šk{&¼¼½=Un£¡ª–¡™$ ¥®4ˆ †*$”oV*[?.#ƒÕ3@îGÑs>pé}Ý5VíAQ-Ú‘šejú‹R-D¤¶T¤r8GÎOŸ›ŽrÒEG€dÛcã·’'Ó¦\eZÖë=Ì4Q¥¶¸Ó#¢4ÔÞˆ' …WTfœF>xëK wÊ-à–æÇzµíöúí£mʸHŒÓåüÁ¶“údO]ÒÌ.±’FK§^LË$‘ÉS¥ž0åÀþP\œgëíÔF¢VÖ)ëEÓÔ[në`¾O§µô’SRÝ? •?‚”ƒ¶F™p¤þF#?^šÊ "ÝèX£t®‘:vÑOIy½6¯»«–½íÐQ´¼ð}E 8óާFÙ¶Š³ŠC[¯­4Õö‹D˪otãÔ–‚Š¢ž0?”Æ£+Ë'æå³Æz¢à {ú"ÊNŸuÓúºÉrÕ·‹õâÎöTð‹kƬ„¥27¨‚ÿÁl–fc2ÌN>£´#ÓŸj€Ì¥Úšõ¢nºgUéû•-®[,ôTuñÁ]HTBÑÊTãr9IŽ2N1ÕSÄdvf‘Ì¯Š®üvC»_ ®ßf½Cfº$W ª-z‚–²+„WÁby¡š‘Ê™dUË3X õì°O¥Šip6ò!yÜ[ŸEÐØÖ}…J?y¯½‘¥ºÖv–ËlÑÐÔWÍj‰‹¸‘¶ó;ï•#ãj²€1õÏZkpöÔxmS#µ!˜ç¶KN°…Wñ)Ü~íöWVÙ{±y—QTEøy-u•¤¬Ð$uHJ‰‹ *ì6¸b1òGXÏeßÚÐêœqî©@²¡˜ÑB)µ5eÎÅi²\`¸_ôªOø‡²*"PT†GU2³þ|¸q€ÃnÜìo˜w=ÿ 'Æ8Œ»rP;å<öª©j,ö¦šORx^¢¢8Süˆ3íÇù˜Ÿ¿[Å34YI&N‰ìkû½f‘Ô¶*ªZú#"R‘$Ý"ä©aùsÎ<ŒtaLä ©¨©_ÂÍñOè/£ [±„$± r0rÌF}ÛÏ@A3)&.Pà¼×M®‰i¨ýf,å €cç MÉÈäy$°‰(µ6…Ú¯Ù¡×?5²VEr¨Òö]>æµ¥_PSGê)X÷àªï‘ðAl±ön¼çÊÊC½wø8—t_Q•i T2ÖÇL§OùI‚NBxçêxó×’uRu]Ç 2V•üDêÞþXìÆËÙ}«¯wZÐìn4¹ŽÜ |Äe›2±ÀuÑáôZ÷qÐÑÜ¿ºÝOô«ˆ’ñ*Êí_ìÎïn•­§¹TQêù 2RÁ¦k+ã‘ÀÎÏNx¢þ&áŽO9\uÌâN•Y iôûÖÞÂêQëÛ+¥½±ì>žÑ7;×t/7{´òGl®…d§ôØ’¢9(ã0RþŠ)xò 'w^kúŸñ4ñ§Ÿ?%ÖgF×ê$îvîÙY}ÅøÑ–K¥²Ñ£Õš ö$–6·OeJ¹*©ÂK)bÑK€v[r“ç#=eÂaŸTKi‘ÞGä$.­M‡®éžQö•QÞ¾5tÕ÷ë½³Fj]GW\«j“ ² §Š""‰ØÍ/¦¼‰ÀbìƒÕ¥Âª“$ˆóú–®*–S}«Qû³ñ©Üçéû®”ŸNRhKUDÐÌÏc¹ÕM*C~›z p’a™é3œü¼uÛÂg¼æƒÜ=ö.MjÝ%œ,V–Á¤{mn¹ *´-<ë‘+IRZI#;pvxPAÁñö'²j¸ˆ•™Œcn¾è¾'»™¥#¤¤Óä½éëLT‘SRSIZÍ#Äñzh¥7cäNO\÷`ÚI1åÚµ;y”£Jk×”®ÔÐh­IZi5][¥’vÈi#–’31u”³ò3òòJƒió'Ñt)ãœ!²<‚ê·q4ÿÃu—Ahi[E>¹´”uQÅQQOo‡ñb,ù¦ j‚Ê’nCÇ\ sº2áN$ÇÒß²mlePs6ño•¨÷î§`;‰h‹Jvþ ­ú*†ª‘dYè¯1§HÕ¢‘#hÈÀ€I¾`F2zÅDbAéßž½êUÅ2«!Æ/©1~©Mf¸ÖjÝ%þ%–÷M_I$Ⲫ(á•å‰K„îÌc‰B”p[p”#å=d®@yÌ÷ÜžÑoD8jT€{CžÛ•XëÚw»ï¼_«¯šnŠŸ|‘úTþœˆÏP%U3çvÕL) Hä’:ï𾨴UT§5iŸ~~‰¶É¯.5T^µªÌ× %="­ÁîUÊ&dg(µ±žh—f‘pX8ܤ筄kL¼ë ü¬#àé÷k@®ï >®¯—@jꄬª¶Á2ÑÜäš TÒCò”ŸÖ%š<á~c¹›!W†Â¾˜é©¶vÛ˚Ї£¯ E¬½È»Q5æjpËB•Š’¬?­;ȨxTnÚ~fPp¹nO^†W{àT¬¸¼A͆\zª‚Jêšø‰Uä¹ 4æIW*.ãã-‚£>þzîQè%f­Ö5LFfÆéQÄ >\ ï#ê×ûuÐ7˜ÕHc©•è«$¥Y$¨T(·v Æ@^2H?Ó=cÄ4dÂ÷mÕÕ£ôÓ^é*4å PJYªf, Øw±ÆG>ÄüÇힸX¼KÚù1—¾çÁn Ç¼ôp#šmî~¤ÓÓKkÑ>e§¶Ä¨Ÿ}E\ì0õ5 ™È9ùG€˜Éë^ ÷“V¥çN@rü£Çâšç2À*šßMH))ö¤ÍT²€û0 ’ ùÉ< ×­î¥.€¹ôÀ'úCXµRÕ­DIUè°W)¿nF0 ñžOõÇ@ö–j˜×H±NTñRà %c-]Ny1Çß«-ÍpajÚa}s&˜´½L³<7ƒ9%O©UPû*Ûì<¼!{¾X^ÑÍ)õìÑmVhܦ>VÁ#¹þ½ ¶ÊX SÒYê–¡!¸XŠÆþœäÕ¡ô}É“”/¾yLuøòÝ LË©´J×%µ5›šµÂKÆÊÛ•ó´«—  çÀÏŒdjXíüU‡½ücÔ~i¤zišãDÁCOl–BòghÃ(bÍŸå qút`‘ª èÑH©Ú¹žHhì:³*7aè4|ñò»x8ÇËÇN¦G+Òõ¶êÙ & DpCšŠ•ˆ§è¸9÷ÿN«¢t\ Ìébi½[, jRÙF•Ô­TÏ´cµà~˜çŽ…ÌtE‘uIOÔzryƒ•l0>p®¬¡XðNdbsœb}±çª` ÌÝ ˆ@‡¶VÉàôh{˜tôk0ôáié¨ z*j™Ü/y>~§=9ùn@î•Y€Ù9ÔöC¶UöªÛæ Šº÷A™ ÆU37°?*ý<G“ÒZyr—Ø¢’t M'o{y%J47Ù20óSú±:Ôã2ù›Ž8 rr¦T§l¯¿‚™Œ]¿UŠmÛ QiÚ›>“X`F”ñDEeŽŠ®3,*F@b$<`Ÿ—œãÛ2\AY£/}ÕU¥J:®º‘R].Õ1 NÞi%k g$?Aþ½l‡a)@?2e©èF’ßOg¶ÔJ’-Uy›ðí…j2cÉÈàŸcÏCR›Ž†ѯ¿TÛ©4¦£ÖZ^ºÁwÔ•öÙç Kûªç=è·~O^$Y69‚Fá碥-0ã)uÚ׋{íB{º””³kÉEJDȾ”~¡vÚb²œ8\dnðN[99¬€naX‚¦nªî¤–ãA©»¯ç³ÔA¶-ö[m"e~yjM<™|å±ãè0p¹ÖÊÛv“û)N™+Fµÿì„íö¾k…Ùûîm:¢ºu–²¶k]‹:ã d¥_¡yÈç®ÖÔežÙ‘ü¬8ŽJ¥äÏz®hb'ií×3xºü@ëêH? ”ÓSÛ´õ2G€±šYÎç –9Á'@ã¦Öã™ÄIïü‚žŒ9÷䶲߳á£áÿR^µm„ëÎåêg¶5,t÷ÏÁIOî˜bZt‰'mŠ›Ù²ªxÚ±ç⸋ªŒ­êÇ"V¬=´’ЮšƒO†ºÛŒ—JÎÄi«ÍÊfižJúmȲoA‹<‘•LqçJ™¬,mâ™R› ’Ñu¨?Ú:£FÚ?ø9Ú­3§¯Q×=¢ÎZ{‰`PDŽÄ“µ$ŸÓ’@ë¥Ãx»óž•ć„°Ð%sCW~ÌŠ}ZµúÁk š€!À¤£Ù÷&¢XýùÁ#ï×¢RŽ^³‚åM¬N–ï ¹µþįŽzúÅߣ´µ°$IqÕ> V‘à‰å û¤äã¬âôu“ï±>ò@.xþý¢?a|êmæ§\Vv"ú̲ëYSN)ýÙ’–8ÚY¿”ou@<‡8ÛÍ­ÆIêÓ³}óÑtépª` î“Üb=Rí½¬øWýš*Ý&ºî“íâÔZ* *j«ïmÄZL¼ÒŒ»ØÕB€FN|}|&'W¬ìÝÃO.ÈÇaè·#©ú¯Úðõ¥Ú»P[4ïÄ~¼¡­‘¼Û訨go§–hó“–}ìÇ8tiðGè5«-Lt ñªo®ý°:ᨓNöBZ–X}XÚ¿Q ‘îÎ!ƒö'#ßzÐÎác Å’$*"ùû]»Yq[­›´½•¶ÁR¢Z[•\ž‘oçv’% ÀÏ ’O[ið@%ßOÊKq®"fÊ1®?jgÄ5ãÒ¶Ùu¶ˆíª›jmVDF³^c;œxèhð–‘*ê×Òê OOŠRªžÑñ©Ràå̵5õ°Ô€¡JÇí€Æ7³úu#Ö ²X‘b©-sñ?ÝmtY{‰Ý}W®`_ŸÒ{Ôï ?ÔÓÈ}&6’9óž´ÑÀÓ6ýÉn®ʪ®=â»Álº\tœ»*OA™ÌÞ´™ C@ª6‡Ï>Âzcp²á(_Š9mbµÞçñ Üê 壽Ztt–ÝÂQÂ!ô›w¬U¨ÌAbs¸óž2zé bAÑsN5âÑdçQÞ¸jÑÛM¨EK,ªa‚aEBƒÃ>AÀ#ÁRxÁIaÑúv§U¥¡ ïE_KHíi»PÔ¤b9!")#¸ùÁØo~€áÈ3²cq–˜OUZúÃWi­X)ç²ÞÉ8X…4q¼¥OÎFÖÇósœÞã9_FgŸŠ:•Á]U‹ðûeÕ=ú­Ñú¯S^*eõê+袘RYbb›êg1¹ÌlÀ9Ú¨AÇEVžJeõ,ßv÷âŸgJøh¶çeô‰¬~£“µ7¶~Õ~ü¡²Yc³Ó^e¿ÍOq_Mr*&†/á3´œå™ö3Æzð˜¬sÝUÕXÐ ÷ªô50T[7[hüŸÂæÎ©í̽¾»ãVèÚmfiEo x¥ ¨i0³í`¢TÜe—{lÁ“²®G”ƒÔùÁer1ù³ŽÍÕ©ØK‡•«Q— Üí~\–›ë]$ë}ÂËø¸,±I+ÕMS4’T†i§êŸK*c;•‘½¼úL,ç-&ëÎU¥œul²£:{¶Z²ÿLšjݱJÑKVõE©à*¥³$Øã ó`ãÁ¢xƒ)õ\Jm>ꀺÐ7ä®ûV‚¦§¥ÓT¶:ÚË•-ÆÍ|Q l‹0Þ§9ùOÌARF˜bsæélߔ߇`ÊÊRIÝKû»SKÙí-K¥i)«f¿\ie¤á\!Är8² W'Tg‡\\(8š¥ñfÂ~=âƒrjã¯zÔ»êKe+Ý*£¡«¹’Xì »¼‡*ÎÌ–#9\ŽH9¯SHAÊÝ ¤'TŠ‹TÒÌôP¨&â¤8ÃÛ’ÎÃã'}úcYyYºR] iXÑR˜}HÖ7\K4a¸ÉÁP=¾ýVIë¥îŽ©M—=Edó¬QBŽC*&Ü Ààî÷÷?sÕ±¢4ŸA„ê û)°Ù¬¶®Ã UÎ+e KLÕµóVT2…àI<Å䕹ÁvcŸÇ_:c`ȵkFP–O§4ÉÚjÛv™¹U«òOH“0sÁ?26ÓçÛߣkÝÉ4:ɶ†Å¥m‹p¡µÖÙ-ô^§©<v¸iQ“` êÂÈä³:²¬{÷Úšã=c(Ú j ¤Š™ª ŠF3g”Å*UôÊ6Üqàô=#š3 ,pïMvÚÍ?_š¥i¯Î‡lŸ„q# ;¼;0*7~‡#¤9¯v¯ô„ÃU§üU£My³Z:;ú¬¶°ÄHV‡i ãƒíïÐ7 fK‰ôû!5|¡+¼Äa¢/ Ë…Y*V¹þ²x““Ž ã9åx§¶ˆÒRÅ@u@îò\jg°Ü)ûy§ê望:TT^*eZ-Ù@côiÎýÙÁ ³œd ’¥»Dö•3Nþ‰[>­ž8ã¬Ñ­¨wMºãv¨‹‚6„QN 2 ÝqŒô µ¼åqçèJ;F²¶ÕVÝkµÿf­ª‰éÌpÉ¥å"CüÑFK&æpv‰$,Aãž‚¦®-nR¯¤ Ø•7ž‚²í =r;l™A¦©–‹MÑÉ-R’qgoáãÙTpsÇŽ©´š:³è? ówó*w`ÒÓNbjÄ­© #-U5¦–iš@íêl•*]xe9*IsžJíôü%½îðí*eþºW´ér¼Ñï^eŠ×wú‚8ÿÙèÙ@Åæ:¤Y/¿Ï¤t&”¬ÕÚÿ]Ri-#EéµmÖï] =9fä`€31 £É$޵R˜h%g}`Ñ.6U¥‹¾ :®¢–‹KüEvÎýQ=@¥†ž‡V¤Ï<¥¶ˆÖ5rwn cÛ' \Q/y„4ñìuØAðVM ]«ªÞtõZ¤"¢Z⥽>Wz©|‘•aíöë!Ãá÷„Ó‹©²QEjí-ØÝÖý[TÊQ$LW#vJ†ú ä{8Éé4TD÷ j›Êk¸Ia¶X&Ôš.›@EJj#Y«Yæ ,1°Riãi$a³nÅÆ@ dã£u16ìGH—$¨ÅϺšVÝT)d½WTÄZZx(+#’2¿ÍSƬ¨ 3pIé¤:$ý Yk…‡ª!ïê¿]©¨ì]´ÑZFÉ$³\¯U#ÄÀdD´ôÄòHÉ}å6ûç }–êŠbò—Vë*KÕMFž—»=²Ó5$&ú5¥Y%àŸø©“teÈ rzMV9Î<ØüH*4´ ¶êÀµÙA £½SRcyj($¥€T£%Ö5|ÏódgótòÐt>üÉ“ÍI –øC.¨‰ åV5[šU‰ãiÎNzIæ_vú×ViíW~ßPUN»iš2µ2’ŽÊd]À çån°;ˆS‘ä yØ©=£ZPPÒ-6¤Ö¶­õã’WqH”²LKòÓ¬Œvªñçi$ùèèãl4Tü9ØÝ$ºsEÅhÍlÕW—Γ-5¦…é¢E-AiZ†% 2’GÊO×­¹ { ÞÞjceÕ¶Kù«üÆÃT"Bô·*Wa$d´l@ÃdûsóÕ6»M§ÕB$Ùzÿ†–ÏZôÏRkdY)è‘b*õ5;ªE»ÉÈ-œ`*³x!õ >•p̹;ûF¾>;³ð¹ÚžÔÁÛ ~žÿj9%£¹j …2¼£4rK­4¥Ü‚áÖ0˜*K)ë_¦12ǘ#×·³µeãDч0uO§bù;ºw#Wê+åOpuµÂã­5M©ênwKcÔÖÖnüË%S1v;v‘ž (uí›A­hc-áeæ:B.Bw›ZÞî’SY­Ú‚± ¥)à¥ñ/âÙ¦—l’í'8Ç¿@ÊlmÈñZ W> Û[çÁg}¡ÑV}E¨ô¾†ÓQUÐ šYnWšjJÙÐãÉî]ÛHb7å†6Ž[xåò3Lvº•8]Lë*:>ÆwkHj;E¥4äUôõðFÔóRêz*Šv‰¿Ë"ÈÑã+È}§ôÈÏM˜ª5Z^ k¯ò¹Ÿö¼6=l¦‘voâ&;µEšÃÚcY3á™VjYç\•FÏ•‹ Ê_ÃdÍQॼ?Ÿ+œ.Ÿ Ö±µ=v ¶éŽÕE$¸ž-ÿŒ¬ÛÇ¨É LÆN9FË® ë%>3‡&ìÅiw­S«Ýr¢÷_‡îúÿ‰nÖ]Û.ä÷‚ÛPŠŠéeÓµÕTµ§ƒ¤Ñ Ï dùë[j‡·4eïþVz˜*Œyhi#cü§Š?ßOW ¼|/÷®†Ï0jY¶ÉLŠX‰]@ÁóÇyêŽ&Ë„ø(ÞYÆ­ ¿e/ÅåMΖ{÷g¦6Q–á N«Š–h—hÁCÈË"†Ü¹ ¿—r‘‘Ò«qfu¸Ÿ°FÎ ëÿHWÎŽýŠ}þ–ú*5 æÏAdaêmä5Nä`z»˜&ß$„%Ž3×:¿yh mû}þæðZ,7¨ ý¹~Åû…ÊÛjýÙKš®%õ+ä«ijf¸O†_Nž¦ž8!ÀR2¬wNëˆb3TŽÈç©'ÉpNb/¦¦Þû¿oý:ZñMo]QÙë]¡¨èĉ¦Ô±Ú⨔§|4±Ô1LÃtÞW ǤÒÅצ Ô;™ò°Z(á°l–c¿òE;Uðݪ;Cf¹Xû[¡t5Y¦ýá²)JU=<"äeôcÂ*‚Às޳ÕkêÝï%nv"œåËöû•8¬ímÖ¶Šž-kÜ+Múí³Ð S A²r¦Y×>Á$Y„§0ç{¥Y.湸¬(­ó¨û¨ gìöÖ¤Ñw*ª¾èè[WKR—‰ª+Ñé$; ©I"Fv;ˆÈ©ÈÈg.¥78:˜&6çÖýËCpá”ÃZ ÈÓµ7k鵕²}5EfÒÕ÷ïMíòÜi­3ÇRPcbË:S#G,ÊÊ#ç`9ì/0çKG#o0O¡+©C+›'S­ ýUqr±[ã­¾Øu…ú‚×ZgõÒµ¨Þ¢ŽzfÜ’fÒU*rT+«1\€ChÖÁh;w=Þ‹8¯B°#8$_oIVN©ø{Ô’Øl:Š’ûg6£ÒÖ +.6ÌUEP²BjñÃ䜱ÁÈÁ)å¦òj¿À°ù¡[ôtÁ'ße½ÝkõÓ²’ž=U]Wª-•ö‰cDŽšžE) F.UÍ$€±lî!°1ÖŠüF€kr·3ïDæp¬Að|£î{l¼ºv´Z}^ËO ëŠÉãNŽQºF\¸Œ•—aÝ¿¡Cà|»¹u1õ+ÔÈçÝôåÜ€à†GfqÒÐvçÑhf¯¬¡¸ÔÛi¦¯ÊÄ¥ŽHQæ"'Ê æO›s©)¸ñógó·^ãHfÙyœePçë1§/ÙQwè-u•2\oS%hmªÏ ÊÈÄœ´çùëÀå½— d¯N¹©šš™HÞZYBóï‚Oûg«É©›dH•nž²[¤ž¸ÆT:Å!8 Î<œ{~®5'µ6Å¥gªuOf§¤Œ£%MDxÏÕ7b|ñЊbQ¶¡æª;þ…ÕÖËÆŸ¹è«ý«µº~šy&¾ÑÁcüdú‚6+ˆ¤‹ø`¨ü,ÍÀ\mZöàsld[õ ›?)…uAÑbßq)•þ\±ìõ23ŽF~\ŒãŒô@¸õB Rv–ÅnºVÜm-!A[o©ž®:‡¬¬$6D¬åÜŒîÛ“€zލdôû«2­{5’Ën¤·‰d©’Q*å’$÷8£8ùˆúŸ~© j£¦.¦Ôvš9÷Ë5 Wª–1Ï O1¨Î|Ÿ'¦6™7ÑuÓÕ5LÐÇ_ºtý [”ª¬a2ØÈà‘ž3þ½r„JW’Ô¦J;5 rç!#§ŒÄñÏ€¯L"ê8UT3š‰(P«°•%Ç-¸)'w/¶:8” ^e[íK$‹¡·Í+§> TeQ#DÂʈÉäûu¬¥Éžð.™uƒMÖÅmÓº3¸ú”¾j*§ŽðÐ@ÛÂohã‰mÊ„†0f]Ý;¢luG¿~*‰y*cIÜ[í] mÂåÛ }N 1xÃ=,óV6à$QHÇX1Éä`ùêÁu [½(°æÕ.§¸×Û¢7pl×z¨ê¤¤¶RÍ'âb‹œ2¶ 9;ÔHSžPi È~UpÒ½ÍÔë^«Ò½Ø‚‚ycôËAK1‘¸¤UF]̬0Jg~QÕ:™­ö*I@•UUöVÉ¢¦šù­-•öºy+ †žHëé—*#‘šÌñFIJ"° Ø9ùH ˆÕS³'efÑ÷·©CG^·z=º£h†ZÅü/¨ÇH†%°“ã=g¡º¢ÃÞT÷¦™Ý#ÇUnš@gŠxY˜ž6ìóÇœg=.®6‹lç0˜ÊO:#¦»[iÌ0¬ôôÑ <,ø#*9^€ã)èÒ<Â1Aæð¤¹RU—4ñÖ×.Àw*¬=È>HñÕ3xh'ÁC†1Ö w•‰ª¿LsÛ%¬¶Ë§ –TXÂ0ÁVÝÉ`Œ~§Åbݰº‘õM0¶“c³UÛ´Ítâi t+J‹àF¤¨>ý:Së±ÒÒ@á5”òõ¢|v}-n«$¯Ö?Œ€#„§ŽTƒy Flùç';¾`Ø\R}œAî2£*<øû '¨;}Û[•_âKàõí ÜäéhäýÜU z›^”¨TаPăœô-ÃQmäøüOªkq•bͧï)]¿DöòÝR.q×;ÖÏnÕ¢ßGòF€moÄ, (lƒ»‚ÇèC©=ÑމΪî°`¿å/ýϧm±¬1j;Ǩ$@ÑÜÓ ÙÎÕ}¬Û›òä{pzk&œÿØå¢5]gäµwVöS¸ê½ê}?Þ6Ó—¯‡ššu’ÓežZ»|5ˆÁc’T‘cðbUŒ‰Ê V …w0S.Íã÷úÂE³.Žè Ïí·Ãb{B× îÏöOöúé=8†JŠ54"h‘;À¦á펆¥ZÕ`–Gû!œÁ ¸öGáYëj×7+Œ—å›RÃ9ؤåâ,$ÈC„$¨Üåpß*ªõ³÷H=€%ç¤Z`÷ŸáPýçøj=ç±TéW¥ûC«4›G#K§¯¢jšI¥<£$ª‘ÔSl; ´R©Â¥Ô(º“ÅJO‡{ÙGbXZYTHóûÂç¬ý‚ýƒÔ×úë݇¸zãµ¶éäõ¢ÓY%| “±$«Ý _`w£Æî½=>;T´ghŸ~ö\ œ.2&<ÙÞÑþÊ®Áö&Hj;sßâLi^ýw¦Žç\™a¹ã2"•“zo„Gù9Ú®V2½ZÖ{ˆiØ[ò¶áEC¨Û÷Ý_úïàw²=ÐÓVÝ-¯âÕÚJ)=ZJºZãCUHJ…u†X@1Ç"¨VNTŒýsÖlQ~vÍùÁû'Ö®* ¥¿UÑÿ³—àçEÒÁo¶v†*æŠaQEmuTµ> M›šP3eHgiÆqž·?Põ‰ü$5Á£ª»ágøtìfŸ£?º{ogª…&iæ.¡¿+,²6ážpsœ·YÆ@ò„á‹©¤§Z~ÝÛm÷;\~ÛöÞͦb©õå¨E1×UÊúQÇÐKàÒ´ ` Ø é‰7q”ø”:öaõ+íšž¡ª‹îX Ttž=YØã>¡Ï¿Ëàtiÿgr–QÓL´ÑØiMfܺÀNÅop…€$}ÈÓ¦ ywJš5PNw·=BîÕ7º)ž4åæŒqŽH $Xæ™e¤±”ª@ÉNó+¤²Á+$»É ¤0nxaÈö#¡ÊÎJúGLÞþ>õ:0ÔÚÛ©á#þåu¯©Ïœ=AãÎ3÷é”\ò<Ü&„ê­új }¾×h¶Óѵ®™#bwf) .'œ‘„1ñžsäFXÒÈMg;¬MÒ*塯h(Þà‘º±2ECS‰]§'ì¹<ù¬ý3>PG¨ý“›EÀf"}QB˾9R[ލ¤‹s$d×H›Ç2¸bJý 9>:vBGò’׉Óè£ýgFŠ¢T¹Üd*ášáY4ídevÊÍÎAä:E:'ŸeÖ‡W"Ä{ðIä²EBߺg¡®¼Ú+fô^WvÝNJ–.çx_Gå!™”À9`xf…º£Ã‘Îtí*ªã²·9€G¯šÑ?‹nùØû bÑ—[]5%gï*šª[U®º¦¢žªy =K¤sÇ4„†t¿–R äuxŸ¥M­¥Nî‹ïnÞS·bâ·ˆT}C:¹a¢¾'{£h©§£Ñ¼íÅÚ‚KhZÊ«êÊ\ÝfbjfZ¹ži^%gVrÄ7ª ,@`©ÃéôG¤6=€mÈ# Hb4„åÛ«eßW[j*hí}½»Õ³Ü}y4ï©Dµk#²SB®¿ÁGWæRŒå˜gpÁì[ *d°H±Ç½‚èaÀkÚê^?z«>ªÙÜ«=]òáq«KƬª¬4ë2À%ü#P¢&e2ŒùW r0£¬,ÆÒs@¦ì§S0»½w4º/6åÈÍS©M$rÓê Šz]L´UU\«èŠÉË&À$DVXcø[HfÚ >È}È.Ý-ºssÓfW|kù‹*ÛVhÛ¾µ¬Ó×MeÜ7MMP†²yë©eQISë²¼r,h­4¨†2q´m ñŒõ¯‡cèÒªæÓa.;s:.:G¸²±ŽG]u÷梩ðã ê¨ç©¹w/TÌÓ2 é!ŠT’ ÍB<í$üÁ?1Æ:ëUýM^“E&ÒÚOªÁ‡áTKËd’p½Eè»ÛjC§·Ø¬UšZŒÈ’Ý®î¨gŒñM!T#X„˜dôœWêLQt½ð9F½¤ënAl~ါMɹ$É=÷#ÃU8¯Òýºô¡ƒ1ÐVD„úé¤tfPÊóÉ"°<¿6ô\ñí™O[V Ö[›R“Ú ¹ì6ü&-iUCþ" Óÿ¼(oóék*mt±¦“Ò.%|þžñe¶1’3Öü œÖØn{{Ò*b›T–‰†óïíA¨Ò×ËÛG]k²Ù ´••VµÐ,~@Á#ÆHãŒ{tç>˜0ó'¹f8ë—GŠúF·Õé ©YmÕözéOϲ)wœà`þcí鎱;«ª`nbšu€¢SÕi;uMŠªo⯩ž(¶rgÉ##¦5®?-‡qFz¢Jo–Ápº¤¨ý¿¾AGꀱե¾œ§#if–@ –ãÇIèê‚tóMh?U"ƒ¶Ö”u¬“Bé®Hß$K:góýÁ#>ý9¡Ö”§8nž#Ñòúc}ƒMNà¢)X¶lF<ûã¾Ý-ì3!¾ªT‚kM\ Ð[-š~#ïxå® d ñ!Äw’9ä}³ïмºvóRÛ•‡·×ÔCSIAs²ŸE0ò8y=7ÍTcŒzJ¡Œ¤y+ÌÛ’Bç¤/0TÙi5^ÓWK„@·* l`|ÀÁ#n”û`€2p<õ)ájX—Û¸-1,cƒòFθñæ£úçL÷~¦m=CÛ=_cÓ×Jià–áU[‘k¨È)54‘˜¤Ë¸Œ©VQùƒ2õªZ^|¿–ú´ÜnÐ<>ŠmW¤ ­’ž’é=š¡n4òϼ4õ)¸îFfV%F@À “âÆ@Ÿ?ÊP«b`"_Mé‹üõÔ/{–î)œSÜ)ÒY2W;&õ!ùpF8þøè—$MļDY7Õv¿EÄ)ªÖšž H‰fGRN8*1ÎqÀÏK8:@Ý4cjýÔ>«·]—±PUË4–x¨é‘ž¦Z‰¢V•3™*%XÇ~ÛÀ=m)Ë©ñS¥¨.›t­óµºŠfµèÝAq¼[VšFf³Ý‰@§¢ÄÎ7e¾Þ:¾ˆê‘æ”÷¼Üú‚¬ ¬z"óà.v*éìrGèí¨ªÆ¤ª¡\côöÛ¤†Ð@ú¦Rû+µè3Ðí¸Éñõé]gÓðœ+E¥TÆÞœFмMHÀú»|cÛ£k\¨3´ë)SSU•@· lòV w®Æ·ç?n-ä¥ç“¥’…*ŸRiälä9 ú:…§IC"b)"Ÿž¤“œƒ+`ýxöþNŒ)œéÉÕÆ•¤i6mXÃd“ŸÌIþ€ôBFÊ:•ÀQR<Õt´QÔÏÿˆ±…3° “íÏPéu ‰Ñ¦pG¨¾˜' "îÚ>žÙú u/)aÄ(EœÇ*«´„–öÚ@Oõ=@Ý‘EÑ0;a,!´.$o˜qï÷è‡%n‘…‚ ™÷ñÇôÇB¨´”\FŽÉ²¸…EË6=€Î3ú‘úõO%Bk·\jjÄòÕXn–UP¬Iˆ´§àFï‚=óõã=PyåÜÈ ^³Fè4fŽT‚~‡ª™(H1tBÔ£I$JC²cvûûxç£Ì …æŽbCò’‘€s¿_ë㪠(ëD{ÛñÏbíV¾›·VÍ÷gºWza WUÙ-3TÒC»;¢âýY“i>^JŒóžœÌ&£sÒe»J*N—CŽåJ|h|vw²v¾Ö'k{iOKþ*¶ËY-ÓTÛj©%³ÌjU¥•9*–)}BŽä.ÒëWÀÏ-¨rÇŸñÛbâ/©Là U¨v¯ÚwßÙêãcî-‚ÕÜ*3N•TÁèã ž´°WW@¬¨»ˆ¶¾àG] Ü Cý§;åc¡ÄžÉ{VàhOÚ? õ-º«ßl5u’–«×õªª®MQI(ˆ ñB]2ó ]c1P•‰ÆX+p'SXù•Ðo{ˆ´z-’íÏŧgû£]WhÒ—yh5 dÕ[.O §Ua–i$•ùÐñž¹Õ…Jp\#É=ŽkåÁ×ñü*k⯲ßÖßnÔ? ºÛSö¸(ÿƒ¬š¢ÿàk¨YÄŒv”Ã0a$`’˜ŒíAÅàiÒš­{ ŸM•bçˆkíÝû*´Ýˆý¨º 9íéñ?Ú ½lõU›íEEò'BŸÄIc0ïFc*èàrꗭsÀmûú«nœ›ã±ñ[аîÝu%ÝàÕP÷P¤˜Y4Ýum¢@¯üIãqRí.1ŒŒäuËÄW-þÝ0K{f|Å–ª8jS¿0ÜÇçì¹'ñ®;¡ðÏÜèu ËJèžÛÐ]è)¢ÔÕâåê&géHòV?(Þ¡[ ‚ êa¸k«µÔÜâdþßE+b›IÖØ*Vßñmq½ÿ‡«¯Äí=Ú¢h¤¦¬¦EV\$@N8äJ_M½MĨÃÛ>ýj<Á¥°ÿ§Õ.žÃƒóù‚6çÇž«)Ü«±Gu޶çI,tº‡S飂 öèã.„ÿ02Fã#>ùö=˜bO…•´´lŠ¡°ÓÚÌæK†¦¾6<ÕÔ >¡(>Hz@6à›öþUš“°ðD GªYæ‚xnx"6%S ° mæn8ÏŒg«! ÝLåU²Ñ_©ên¶¹ÇÖ4,‚½¯©RQrO©·%¢Î^FbÀQ„HÏ#Nôð`M•9WYÚ­9u²[5/yµ´:ž©'ž’×wÕ´”RTFÌ0ÂLe£Ý+ç‚qœž…ÆŒf-ú”¹¼÷S•†çh†œii/ð«#i«³A>AÌhX<’¨ÿÊ6®à7GPUþßʳLê eG©u|õõtQ\©ÂÑ41Èÿ»¤»±;›M]@Æ“$ûŒÚ%º›EÕ«O*Ôü¿½§ÿ˜Æ+ûäyÿ^œÑÈ h¼SÓ(]ÏXÁ½Ë±çõöê@T9Ûv½Ù¬TSWÔCp­T84ô4’UÏ!Æp±F Á?îGR`Ø!–*5–‚Ÿñõ× ]ø•à O xߟaý:[“¢`aŸezïz–ÛG%tvº››,FXá¦%å¨Â+˾Â8öè³yA¼dUõ•öè«-tp!–ñ­X’2­Œ€ÑsÁÎ#ª.~Á[šÝ Lvk®¨¬­»G~Ó“éz:9}(*e©¦’+šg™b 3¼kÂü³o˜qœôY\.J¼°©Yå*a$èsŒ@çú}­Ê©)¢M"V××ÓÐÓC<èM!iU¤Xó´lÎ݃%¨*õtvŠ+] y¤JIj¡HR¦V $‘U……FqàW@8 ÃuÂ/ð•ÙnûÛ¯ºž†Ñªm•ëY<‚BŠ¥‹TÈr*=¼eGæ玲bbB߆‚VäÑÕiÚ–¸Ñ_b´Zä£hÕoQÅM,eÔ(ÌÍ€à•î#9uˆÖFkú§܉Il½øìlY{}®u-}¦žTcMê~:Ð$ïÅ™DÀΤ6rvõc™ã]9«ez´ÿã•ØýñÙÛ]^ݵ´Ph=Qrî}Þ€<4Öú3-2Ԙǭ =Brß ˆqŒò:ãVÂTkÎFÌwhºtª—œ ëut6¾ï]âG­µvzÛeô²òIz¸úF©Ê²+n8`3“ŽzÁ™úÈ X¡M K”éê4^¤£¶ÚÙ®w“wj{~媂^0'çÊ~lôÑPÇVG¢Ç5Æ4ZïÝåÓÚÞǨtƦÕ÷Ý+¦Ú›lT«‚Ò†DC2³­. Ù´FJîÀ\¨lõŒbHy¨aÝòcÀG¯šk²–ëÞ}û+’ýËÐö>ÚEu×6È÷#K±…e¸Ö­\þ©SS-KàâEmÞŽÀsœç;¸LELŽ-Ö·œ+n´–#N_[÷­4½w[ÐWV]n±j¸#–º–‚OFx²¤bš .6ù^qÈÀÏ[hpŠfÆ µÖ|ýê°c1U)¸çðñÉY¬£¹êŠj¾Þê¼B,UusÈW­³qh¾”íq±²¿)åë=jN¢Épx’àÌÔÈcI×»ÅÜþþë‹…Ô´µZžY®·:YXg£©®È RåUFcelnão§€I,züôû«U{z¢÷ó>i˜œWAC¡'A÷\蛼}ʹ]*jï£ZTÔ¸–¡®UÑFp0Ù|íÉøëèÍÀaÚ!¬hÁp]‰y2\J™vÿ¹z«E\©îÖKÕ|0³3ŸD«BÇÏñ"'œàrr<Ž—ŒáÔ+´²£AúŽã¨MÃbêRvzf º(;ãªï÷*ë½ú’†ïM*ƱSŸ†Š$ÀE*±Æ7y>NzâÒt©‰¤è>¾auYÇ^IsÄΪݵ÷«¶®Í]t€KµRžŽž–)`¢ @TD’]»@2yÉÈ#9Çéʭפú´ñú.±iÑnä¾ýÞÕV\櫳h.ê^(¤T>»TÒC¹‚… F`“hÀÏM£Á1-zž¥%ÜV‘=PïûBú磪¤®©xéïVÚ¹â#zÇW¿Ó?B£ÇõëÉý×yÎÊœ µd4ÑÔ×ÔºFžš†IQ<íLŒž´’ò ”¦†¥ÓY@jWÓÛ†,„N1ÿ¯Ž ¢îäaÃd¾-;4ÓI*Æ0¡=X§y• ­t¹4õ¶6ÓË!ð3&×¢ÊI)Gî+n6VÆrq!ý=º¶·U%,ŽÝB™ EÉÿ.z¼¨I„ÑqÕZË!Šíª´UžOòÔÝ©`aýÁÿ¯JéÑ$D£Fêž´üXü3Ý­uÓÞ®ÜXí Y5 u‹”VÑXñÞÔëRÈòÆ7/ñvœñõëŸÃ¸Î^0îÌ`˜1ᤳIÓDÉtøÝø6²SÍ=Ïâ“°´ôëyäòc-Ÿ~:êOaò?„gÈ™[".–£IK[Ë-ȲÄñ«‘w8äAþ½.@@-NoZá2¶¾ÒIIz]LÔÒ(txàw,Œ {ûtصüš.:—½z5YVÔµ  ;Ai•‚ð1ÉÆ|ûg¥üsvÉ1¸WÇî¿{-UQNö]+¬®2Dü¬´†ŸxÎÍÞOØã¥üc¦JŸ w(Õîu}H)´ê6Ü2jæŠ5^|ç9?Ûž ÅÕÔ1FÒÄ¥)­5#ú«QCd·´€®òìÈòwmÎAÔ§^¡vÈÖëª)õ-ûÒï4§nãéÀÿíÈlïÔÌè×ÑF%zMEuoÔùÀHdcÈð6€?¯×BdÜ¡ “l÷«Œ†–ùS¼†1´»¿†}òìØéÐøP• ¸ÝôÅEÎÝUÝ.G5pÍf”1Ûêƒ-¸ãhà‚rsyۤϾå"m Ú]c§… D¦éš™I'v”*“„Bd@Xd‘Ï“ÉÁ= êê,´Ý1 Y{ŸßÝ?¢žÑn²ßôúäjÚ†¾–$õST¥)¢žJ´Xea¹ˆ— ´gðQP´4Ú;bÿR©Õr_eqØõN¦‰+fÔݶ®Òôð"nô²Ä`Û–Ë„f~bxÇŽrqÑ‘™i‘§+Šè’Þªt¦„¨¦’%¦x–®iã_wrƒÁ_<ãÞ:ÖÇ»¼( G©M\UêR–+‘’¢4T‰€ r?6[žqŽ:¢ûÊAp%UVRi1Ôû…6²ÓÓjîQý4cz€’Ã8+Ÿ›Ñåy°÷䈛ÂE {“gîM%§F×Ü+ié$HåzÛ5mK ‚¿‰†/S ¹2#>zަñg;Ôx-ù‚±Òšä@2M@HðJÔÈfÈ2„d4u)TŽ9å“sœàxAútA‡š½,œTíÝÕÙH ‘§ê>ÿ~‡)”9¤ê“<Ï=%ÊH¹g$‰Šæ yùHçÏVLØ«)yR5 ÒFŠH“ŒŸn§aTI¦c!Ã($!X7ßÛ«j®wMò\¡Ž­èçÑ•B³T¦pd-«ƒŸ'>Nç k‚\ب¢µk­LÕVøõ-ž°LÄÓKfµcÂ@ÃI#Fʰۅ9$pTúyŒæ·`ûÊs·>ŸTÍ¢»-¦45Éo6ËÏsu ØÄa5íQ[^»Ÿà4‚|cñ펴M›4}ê8ÌØ{íVãSÊõJÒ8Ž0~@HÜN< 23È>:„¢ä¤#œa°1à~­F˜ ÛHæBHyÇT@…m„SF’Ä#ƒã8!½ú‘²´TTH8†xÜ¥‡ý:„s*ôÕyéFf¥F df>ÓÏT“ª%hbU JßsÉ?©=^@B…憘«9…AHÏØãª E0ˆ¨I))jª!Š1£12ÉéÆÈrŸo¡êÈkB©UóÙût>-WFè½ðǤnzmMt0j Jhª¶@m…dHi×—ß(õ Ÿ—j!>p:\#ÓTÎï•¿UÂâxÁ=WÉëÔµÚãÜ+Z™±™&Ø\ÆÝTy<9>àuîÙM­ Œ]y)LšÂéI0ŠÑø¸hÁUžRï9\äÈË'’ à}ñ’ÁÓ:„ÁUÀD­Éíop¯·+-¾š;šÐjA†Zj Ò´¥A`S8À$cnp|{uÁÄ27–îºtžç€lØ-«ELôÑÅSûºVcéË*>p6€Cm`6 g¬¸‡ZanÃŒÆÇÑ\Tڞϥµ -Šž[Œ”Ò…ªªšwhæ™)ˆÔã†'óx*=dcª¹¥îÛ÷O{iµÝí¯ì¶;´ß´váÚÎæ»—~ÂíÍ=жží£Ã}¦žd•—‚#Úà dd«‚{éô´›~J¨bÙN¡§QÐ9þÛ­³ïÿíøV´vºÙv©×ýÙ×’êæ©°ÐÙ*UTª>%i•àP~Pò+%¶îÛ‘‡Ãëb^i:œêI¹CŒâì¢î¨™î¿ãÉiŽŸý«=¦†Á¶·±ºŠû[´ÃýÿøU çs´1fG8bXœ’ÇÆO]öþ’'üÄǪ]OÕ/wÊÐÜ_ÚÙkÍ$pÚ>ûuA,/˜?xÜ®5’îÜX“‰¢ ¥Ž[Ž~üuÑÃðßòòhûÊÀÿÔ5É´x-DÖúSÝ¿}Z´ÆƒÒLÄ)k³¬ n\¡gy2Üd–9Ç9ëc8 f»ã鹨ŒuZ¦^}^êÖqÍXh¯•Ti'Ï*QFøwÈ£''=u‡h /¬ó©0¡­gÕ5õU×*ºÛ„ïï1;ˆüùýGZPÅ–bÇ:qI%J!pÓ0cv8ÈÎpN|dt$Â0Ôó T4Ò5]T0:pCEù¾ÊG¿úsÐtÓòÝ…ÐÉKÑTC[PË–Á*Àòxý3Ÿ§E&ò¤M‚> ,æŠÙ§¢xãRd§foTòB#9÷>øè A0hÔ¦6j™ cRÄxyÚ>œ=F7(Gjûõ}+w”Ã-=¶Z†ZRF׉$|c¢ºóœ7’~‡åŤ›Ý}Ïi:ûú¥u¹5‹èÓêÛ¾ž`%ÓÀyùwÇ! ÷>Ǧ?e`öS´Ô]àЖ4{“w÷˜ÔSTˆœ° ÌÔÐ3ð lí>1ä‚6ß7ÝX¦ÝŠ™éy{¼Cè)b^Çf‘!šu·rå­ýÿ ¶š†ÔV­q|íõM+z‘IY¨|TÌ ˆÌjíŽ<ÈO<“×V·eZn§ˆc:ØßßrÉW‡Ñ|ËOª·,?³ßáãPöCvc¹ú;Ǻ¢áx¡ºÁI%A–y ßбÄq¡ ™ƒžNvª/ÂhQÀÖ}L²±ÿãߌ m"ɧš«âý”ß¶[­áén=ë¸ÓÑÒµMu©'@$,†SyãäFÜAöóר{ÀimÌÁ¸×ÆÑØ4dx®šÛ5†å §¼TÑÓ H›ðõ€@`BL¯ù |ÃÜuÁef€›BA¤{£qÔ0^æ“Gß´u¢’w‚’zÄ¥"ã ¢x–&b¨ØU¾bå šÎ¦Ð¨´Ì%týÈqÑ×ÛSTUQÎŽ#µT£Bò}6XŸ”}qÀÀ'£sßŦt„ã=Þ¾HmD¸¡Ú›Qâ‘¥%ÄaŒŒý9Î0OS1ïU‰Î;‹™ãi„±“2†'ês€?N…°u*¬4 “µ¶ÛïWÓzoOšÙÍUcÒÆ…ªç,I–f9gnO“ïíÓ Œ’ xR¤*ÐQY휓·–ÿLuBj äÒÓlµDËÏßíÕvUžSd½¿µÕHò×iÚö$±j•I·1û1?o°èƒ#K"57”šå¥)é§µÒS[,M,™ufô˜Àƒ,ªñ.ä®2ËÆpsÔ¨ÒárµNºª²¯áòÛ¨®´-O«n×{…!vÒºTFL„œKòÌJ•W(6íÎ2I=,P¦m«‚4OVÞÐY´Ð¸SÒÕC}¦©O 5–:Bp~fõK`…A´` ¹9bI<¡·ißoUN­2~–Rûu•,Þ•efžÓW‹‰eE–‚Ñ ãåùååsÏéÁè38Eü’œòt*c5meR3~à¸zm„ON(‹ñ¸åŽqŽrCNµW\°þJ[IÔ…“M\ià’k^ž¡žõ±Ìu— jj¹QËg,çan82¯>Zœ64÷Ü›š, –ŠnüA©*«Ûn¸ÑAˆ"¨®Ô4íQ*…È&(©BR¿Íœ!|©¶@è]IÃåw×ß’¢ZÞjqs®îý²;ÅÖ:XõtŠˆÔôúi3°Æè’Y¦X—Ïçlx'Q´Ý¤Ç¾ä°Æ#ߢÙû¥Ü{–ùjûAª­±+”}Õ÷XNZIjHl)jöÉ㦚oƒãû*èÉNµá†ËkKö¦Òú†Ùc’x₯դ)P®2®«ê FßÌû.\?Ua‡QïÑI î¶†’‚µÎÿeÓp6Qbª­†I$ gòÂòÇ n=Çõÿ~  \¨ZS•=íÐJ«Y*6’|Á?Ó©sxA”Ä%_¦¦§dŠi\í8bžê3ýº•5VFË0êºèÄ*û*£Ä‘³g(ßýCÛíã©‘\ÅÔ†~åTTINòRSRËlùéìãÎ}úN/ª)r]KÜ)$ycŠh×%p„ Ÿ9ãœóý:ÂARNêGkîš–±­´UÍTô¢:)  ú»€oħzܼAÁéu)Ô6j#TEÅÔyûI6Òô&Ÿ(VÈÏ“ïÏö¦s¨Ÿ·›áÛîÏVëå{”~Kä²V5·ORÐ]ib]Œ°Üå‘Õ9þg“–)öÉÉ㪔D,¤tº`ÖTZ«®t””òÒª59‚IÐÄÞWyRA÷`|øã«.–Á€é k4÷¬”µ«üÒGŸ—Ôýú†b0{QÚ½+sÕö-u}kÍëQZ樞€Ëp™!£y”+•‰Q¸QÃnƒV–<¿A"Ú)MÓLÙîÈñH6DîÒH"/QŠ•9Ú<ø¡,Db£¥"¢í抣«YÒªHÔ*IPí;¨¹“wÊSÆ›)Ò;E3¶¥5¢’ LBŠ–$ôã"Æ£Ø*€0>˜ã¦;%‡˜‚çrdUZÙòAÛÇE<åMrÛâ­žikiéffusêLŸ9;ˆ`äçœsôêÁ“ ú£³¢¡ê­h3ù½PH8äð3ôê¼ 3ª=(iêó¸QÈdžÌÜõ2ƒº€¥"Õ*¯ÍBü`+aíÑdªÔJNnÐDžßeRc…rN?(σúõRT R4Ôw ¤Ì”5´,þy$g†V+Æp~㥸ʑ¢ST82¥Dn¬T†|e¿¿=X ³Ü’O[A‘j'¤Õ z°·éü£8äŸíÑHª 3¢>‚¾ ²éÁ½=ò‡A#c?(#$yÏ>GVØ'EÓº“SPÄfwc-TLUa†| íçÏý:cX'ª8^B{4´¬É5ž?E–e‰˜Ÿƒîx?×éÑɉ 4ºl‡%¢ÅZ‰KU§éê©dEÓ–—1…NWr0À=¹8ÝG*ϼ¥µ%Xèí>œäÙM…Pþ:0Õ 0“ÜÖ¢¾›ðTÄï9Ëþ)qâ˕þoÍÈùqïÕµÅSZœÕÐQš¢çt6¬B(âŽSãåQ•OíàAÕfæ¬Gr¯®6zI5U5ÄSÞíw3‘SS¥ö8âr^\@s¼Œ©/Ég9è €'Çì¬S:ª‡¹Ú~él§¶i[ž©š®J©¥ü=|óSD6ziÓJꟛ &Öbs€€Œ©fäá:›šfžùJ6Öé¾êþí¾Ü*û‡Û‹®ž¾Š“R%E=L‹ÉEõwn@F}H•D™÷ŠnXÊæ‹ ÏMÊ»ì]ª°[kà¹ÐGT] oÅÔÊr¾0LÌ¿^ÁÉã§7PB_îžÔTë3»B?[põ,œònü£ÀêgƒªAíMU—ëM·&稴õ»nÙê⌃ôù˜sÈ㥺»w*ÃQ+§y;_h¦¬ªº÷3GSSÀ¢J‰^à…!_výOKn2ŸûJ0׈ô*«»üc|5ÙçzYû¹c«©P JX¦›íÁTÁþýGb™É4aªŸñ*pøýøv¦w47s|‰ £TRX¦ü:J€Ò¾rèF/úºc0U¢KTCP~ÑØZªšÛ¢5½åÕZ‚'§ˆR…8;°\“Ž~\ñúuMÅH“²­®[&ËWíÒ—„jÊÕk ‹@™a3Gt¥WÜq#}¥ˆÈàqÖJÜHÓt><ÿeª©U¹šU…nøîø|–­­×ªÝE£* –Qq¤@³Á nÙ#þ£ëәĚnc²èjpœ@7 æÑ½øí/p ‚k>¦zeuH`ºÓKA31R‹0ƒìÀãïÓ¢NR`öû„¸uzb\à ÚÄêeµY<±V$°ÉéÖ “ , Fˆôõcƒ8?n¬¨In¡ ÕEL®ÕÏÇFa †¶3ào[üAXtï~{#¦.ºÇºv:t³]¬4 K=æÓ¹š9ibþi¡y$Üž] e9íðlPeLŽ€ åq, ZܾXâŽý¤5]ÞɪlWÍ)¬í ÑV[.55]äs$Nˆqã<޽5 Ò%«‘†•r¼]]4š¦ªÕi¶MIvžr²“Y‘9b20ëËüËÈqã{޼¨¦^â õí¦‚yÒ}Ó¦[üI$ÕØåvÊ´ëê(8 ¨_ü,ŒyÝã¡©„=x|{KúÁlNÊko¨ñ¼™ª·M^®<˜yMÀŽ~¾O\êÓÕ²éÕcj –=ÇÔ-K­ï¶ý0´ôíÓG$¢H$° <¨Á|½þŽjaïÝx ÎÈ)•«¾ãER[¨.:nÚ²’*˜d™‚Š˜¤$ÑäÎ ðAF:ÍŒá-ÄX:.µáqÏ n&BÙ-; {ïßíÙ4寗OÎv-ªÞÏQrycº³ ¼œ§¶G€G\¶a¨ajCåǶÃÉtªRÄâX îü¢[öu÷êótª°éþÎ÷>†¡ÂºTÖÑÊ´°àxip}ýÉúuÕþ¯L7;ˆ_%‡ú=r`7ÍZWo߈ÎÊÐIª;—ØmÐ6ÿRëx§¤Š¢:8_ ëI˜ÝäV)òysÖ*xúU]ÕªD› Ÿ‚«HKÀ!k­×°šJþM6—·WÐ^d˜²ê0!¢™Hÿ)È#Ç¿]ÖUpù´ ›Ñƒ º¬j>uDqO=%ËÓEføÊoMU‡;+0ägäšúÍ bƒˆ£ôƒï…ݪšËÛizŠÙ,´MU67š ÞG<óŽz±VœH!05v´”&ìÄ {JöcºNvXjH\ï³£Ìy…>¿ú”Ñ'f{ÙNò$½«î\!@%šÉT×`þÝ÷p…ØÚ–$ß]Ú>èRQQW©ª½rÀÃOl©2@AÇñD<ã‚ 3Ž©¤“+8JŸê|Š™Ÿ‡íuM¦©õU},Ò¬QÖ=pó’ш¶€0<¶Nxž“R¹Èâ{­ç(߃plïÊ †ŽÖwDÉ*Á£umbÿ›¢§kàùÉŒqÏÛ¦ôf$´$¡…©þ‡È¥Qö£»ÄA¢õl[©ºjŸŸÔ!3£q¾YòD0UÎŒ>E}ðØko–›5º•š8¡s Î Ô èͳï€OõÏ_#mA:¯j×7e<—Jiš¶J‹¥/ïY«S+É$¦ŒØOûôi$è¤%ºŽ™`¥D¤¤QÂCEÀAúcªÍ:¡ ÌÊ]E»qÜk€çl`¿THVÙ !hId÷P_d“M@ÿ( Áúýz§Í—(ßZ™ÜÆ-­Ç“9$§þ½;‹*2úw ÜŒÖÂÁ~aº¨yú¿ýú’!BHOñKnÚ6ÙíçÜ/ªXÿltÀAÐ t§ãYÉa£Œ{s÷Ö æ…ø'-e·²…*qŒÔã=Ct#µ –„°i­ÔÀùÆ@«š°ãÍ/[m¶œ™…- (‰p|øÎ}¿éÕ•Vb” zuÄÇNr›Íôäøç«í(KŠ=ÝcPJ€»y€¹óÏþóÑE¤"éªã-²1u•a7OŽœx9'Ó¥½ö”C2O ¶Ãˆ‚ †tõbõ79ÃŒgýº°"à{ñBç%í L²Š³ƒˆT|q€Ç¿¿Té:ªQ$DÑÆÉc•˜ŸÓÿn¤ÅÔ^–ì¢-ѧâH dx¿·T %A;# s‡•bÆÿ3`çõdqÕ º¢á©Hê5 ¾•åõîvšdVijá\®ÈùøFO¸út³Q£q怖óQSݾ×2ÊS¹:Y ­9i €wc ûœŽ…؆ ÕµÒP÷›³—û•6Ÿ·w'CÝ.óJ«,W8ÚIœ°Uù‰bQÉ<z†»&çêˆóVEL”ÔÛÿÔô‹üÆiB`\c£uFÂ8¡÷äööÕ•7Ms£(éí,÷I Ëßӥ|C¥X3`ª«ßÅc-ÔW'p4õÖ4`²5!yX¯‡ËÏ#‘‚G=¨6È£èÌrU-7Æ÷oÞñr®£¥Õw‘ˆéä§ÓÕ>°œJ5AoD)ù˜yÀ‘Î)Ω"Óò­”\D¨…Ïöè™–‘£íå|14Æ0÷żsôP®Øb3ö é}5C`™ü"è?Ø…ºütêzÙE&ˆíÞ–jf¦þ×úÕ2—Û¹$>šãAÀ#ô—cƒA/t{í[)ðºÎùgÉ4Ïñeß{$1ß/öÃJQ:mGýË4’²ðÞšÆj6œŒùðHÁëœî* ŠrO„}MŸ§_–jº<¥Võòî6´ÍÄ÷‚-3]fDµØ)iå*ÇCG7<w|£ÎFOK¬…†9Ï9#ž°šï.ËÉtG ¡—3aQ—ýgSo¹þ" êC(_ÄÝÅN‚2OŽ”þQömC=°£8Àhë€G`RšèRÜ,UMaÐúOOY'Gþ='â+dÈe/)p9üޏ^@qÖ,O ª*QÄ»Ëì´Pâ¬,!ºwÂf³C`–í]¶Íc½BižIžŽÛNÈóÁ1D íP SœûsÖŠ­¯’ >% ,VºgUq[5ÕNŽÓQ^j´õuE:G;R«TKM\€c冔s·‚ª˜>p3×4`Ü÷Ç¿4êÜA­ ù˜f朗´B,}¯ÑÓ3@cs[r(då¾Y Êà·9ëu>ÌÄÇp•‚¿«–-ã e¾>+õÖ»ƒ[vwYÝït²ÆÕ–ú`Òþ:Œ²Ò£Ü«d(\îE%yÚ^’¶´Ø I#è¼Aœ—èåÊK•m"ÃCYp°ÜX/Cø†Sïó?‡×Éê©R¦ ¹¥"¦x‰µ[»”_¶«]wmû½Üíó~Ô¡¥³Ñ"žG–rÆO¹ñ×c O šÂ<Ås14j8K]ôü.mwFùûF4õ4׋·r;“f³<×€!ˆ·8ñúuߡø}C_År*ÓÄ´I3ëö\ÔîÏj¾#»ãs®¹êj®âw+P3G$µ1Yd¬ªÜ€Ì¢"ü” ãcwiàéRfFÙ½öõY»Žh'À­]Õ½‚ïïoõ=ºÏ«{9Üø*&Ž:Ø ’ÁSêUS¶WvØÔãày`ã¬#‡µàš$âÆáñå,>û“Zv½™ ÔwNÒ÷'÷D ´Ü*lu4pFIY¥™5çoŽsÇž³U¢æ²Tv˜Ü%a-Wž”‹PY*ôä5z£JÜ¥,«Yh·ÆkjgÚ„[o€ ±ëŽpĆ‚{tõ0»-{™î[;aø6øSަå©u¯j~9ô¥ž =Tõ÷k2þä3‰ݽ¸ÁÀ?lôÌGâ h¬=Ó÷EO…a‡ÌÃçø]:íZüvþÏ¥h,T½½† }¥¶K–ŸJšÚK+¾ K!Ë»3m*76@\ž¼µ~#Œ5\ç ÔvÓ±uƒ˜Ñ•€[)Añwð¥AøK]³»ÑJþ¡ðÔ¢5R8åb‹¼ùûsÒº*ï’)½ÇÅ!ŽÎtú•~vŸ¹6žç_î¶»>—Õ‹b¦§õÖ¶çK,>¹$|±ʸ!‹€W|ÜãªÃ‡‰éš”u©†¶N¾J%ñ£Ø+¯~þû…Ú½|µém[^”ÓPËS!Š ÞDž„Ž9dÛ·~S´‘Œõ¾†&:¨íeqñ´Veñwq¹kË&¤ŸOêV¯¥»[ke¤©¥õÝ%§–)6:;•Ã#·ý^Ö¦0½¹©‘yÜ5‚ ®­‹y¬ßŠÔV+•M\Õ“C C+Õ ˆâ“p8ç¢l¨›À)"ÓÚÙê))ªè¦ž›xa`ÏN­’ "ò àžG<ôY÷sÚež–š¡É2GBB˜jr…jÿ(pþ tRB¬ÄJ4SÒË4ËD‘N˜Æ}Ç`ûøûô’'t],j,• yO—sÿù•ÿN¦SÍp7UKRhí n†¢Z+M-;fiÕ2åýùQ¸ãŽOÐ}:`¨JcœW ¹[4ýTÉsÔwÝ_;$0Ò0ZTQÉy˜’·˜œ€¶n2LNë4zãN=TSÁQ®ëíë”Zˆ\©šgÀeÈ$xÆ<­Àk–Ýéft*è£Ôv(a€MeiþPþ¬úóŒ}~jˆ°CP—6ª°1ض2ÒqÇìVçî!:¤Òê:OTz:^ˆ¸‚êIQ>:¨bÁXêQâ ÆQ46«Lj$­;6GÓÏK*ö£Vùª¥ ©8`±Ó*qôçÁèÚ_²§7t´Ë¬fŒæžë2°ñêÇ?è:0„°ªQo¤Õ³#šªHÕ·a=zòÅ?üxÿ~€0û• îµRıàÿºpc¢ÅPšRR VjÛ[çXéÏR2Ã#©•×$¡$v¥°A_½ëä”nü¢›íŽz<¤êU–Ý/¥®*·»š2ÊÇ ¾ÙöêÈÙLÛ£"®ŽYgH‹Dv¾3íïÑH¡,C‘ç}Û#Ç ÎF|ãëÒܬ5ªNèÖ–FÉ+ý1ÏŽ„ŒÀ¸ñ!ûZu—k~!{ÁÙªF‚ÐÔzVölÐË_OøÚ«’¢‚Óœ°UÜ[ˆÐd ¹$箽^Tå,$æØ.{±Ík7ê©JÚAñ!ÝúJTÑWmUgˆ‡ZªÛU®T;çJ…”¨ÿÎ1œùë\£lAÓŸà-TCë£LvÙ@õ—r;騭PÓ]µõ«M[fŸÕ¬½Ou«€­T€„'.xÉY×/õ=U,I°1Þª¦<å‚ï~áÓMqT¶¢«¤‹äß$í[¼C {pF~Ýt¨ð³e̫Ā0ÑöMuÚ’÷EY-TZª+kJ¨Ž• ^¸`ãÔSàü¿^zÁ2"/ܳ?ífÝê=sÖÇôÂzyÞP$J¹I%WÝÎT¶Óœœ1ƒöê‡3˜iÉõ[ebCÞ¤[nUÖïRáQ Ü`œŒ…ãk`Œð~Ù«uHk…Ó¯S)-3èŸè5¼“[ËGP©ešgHþ¦ÐS²H1ú9Ïëп L8.¸ú™zÆÊoÖv¦£[5>¡¡˜Ç0¨1ÕTˆ˜Û™wøÉUëÁñžŒàÚÞ¶XðKn9ÄÄèš*ïVyjU’ ÉdŽEZxUc5(ÒnÁQìžAýOMk>h*bÛ î—.¤£¶IR-ôT:}£Mäì?)Qä’Y[ï·Ÿb:¯‡$f7Añm‹Y:Z.Ú£¹Öÿ„¬”š‚ímhÌòÒÒú±RÂŶoq³ÓÞA7e°p8[©Ò§ÿ+ v>¨¨Ö«T»o·ª2TÖ¶;piô¸KÔnVYàžHË‹ó…ÀÆwd`g=,Ô óg‚;ÓA¬,êgÖýÚü[oku½ý•Ð$of¨©%r3Æ ÀÂŒ#Ïž—ð yŠdxš*bcå%$¹è¿‰þàÐ‹Žœømï KE#´UY.)NÄ GOOé" ÈsÈÀÜN:éáp,÷â$÷É+zx‡Œ{í+nfïe;³¢{ïq×}áÒº¯·– KE{Â+(Z9ˆÂÃùXi>JøÇCı”ZÈc¥‚<6³ZK€Ûp»ÓS¨-¯ƒIN# %Ô1ÎKEþ¤õÆø×nßÊDr’-] q•#¹i«}G,¢Zèfd\ Ûw¹'úÊçÝÙ¶$^;Ñ §d¸«þùËæmí PÅŒþUœÀ }Á'­YÚý>#ö@Zþ1ï¹=ªhÄrÐU×HD±îë‘.~fAµö6?˜lcžØcSêµX~`¤ö&«²ÞkgŠÿ[-¢¥ýy"©¬ Èü°„ ä±ÉÏýÃÁù»Æ³êƒ¦anXºvÔzkOê:XâÖû6£¡‰Ä±ÇuHçŠo .FîqŸíÖ¶½¬9ƒ ¬íqiµ”fÝvÁ}9bÒ]ºŽeY!¶R)Aö`¹ǧq" Ìw¦S·Õ:K¥´‡áÜ [tTùtyúcÿ|ôœí7 ZðtôQY{MÙªšç¸¿jô\ÕÒüòÕ 7 4ãÿ;˜òãìsíôé¿R#­ÓÕ]PZcÅ?&Ñ4”“Ú­º:Ûh¥”)al·­!û0’ŒÇAU\CÞ:ÒP8’nmÞ«*ÿ†^Éͨ§ÖóvÖŽë¬æ ¯q’²£ñ,7Uæà@þÝ/¦-fF€pdY›1o!øUާø-ø~× Ïª;K¶ ¤A¨€È¾Äú2~¾:e n!–aà~ÉOÂÐv­Ü«¹?f?Á´òšÙ¾ômÖ¹™Y§¯ºTLòãÝØ‚ÇÇמ´ÿSÅæ}ÁXmX4Ô~U¥þ ~´cEŸøvì] 2°3P™X0<~hÿ®sÐ?Š&MKøþS¾ 4@·p c¡§¹\&]k¯ìÔU,dö:ømðÑ(?$QúP«Ç ,sïÖL üã1í'ò—ÒÈçÞOØ©•¿´zf™.VÜ»¨éjéÍ,ð]õ-e\/mÇÒ§#Êàã6Xáü¤¼°òNZSµÚDVV\t®›‚Ñ_Q‚yEUDÌñ†Ý³ø²0  1Õ¾©- GpDMqˆwïqÑzVçøoÆZ)ÞX¦Z…xó;K2\r~VÈ>àô bê…G'!m £Š?ÃÆb%§ ¤åcÏcÆ3ÁöêEïªÞj/¨´Í ­·Ç=n²§Há• Ûëÿ+ ¹˜¯ÎÇÎÒ<Ož€ÓcÎg‹÷•Mn=ö'‹-žÙ§mÛ ´W›}"S%L»òÈYÜÿæbIúõ¡î“0I¨úû]T”H7 Q£ô„ \Ôp #.Üp>¾ý%¢Jtw(‰íì7Á í°ÅLÞª=Uþ§ÓòX²XŒŸ~>þÌi‰l ö(#O²q¥°éÛ½-¿MR×T¨1™ÅHŽ<ð]dŸ`—$àœù÷ÏAm£Ð~è‹ EþÊOœjyÍ’Ým‚­J4ž½›ZÊ剌çéäàõyNÿdLýïIê Šø›æ¦«†2-°¼s9bFСX~¤Œws› 'o}ÈÚЙ´¦”fz–Ù2Éê {ôõ’Êy-&HÆO@ñÀŽ©ôš]$Ç€AÑ·¸«ÒË~ª¢¥Ž-C§,Ï} ¾h(î G†Ç†*W<ŸvÏJ 0â>ŸUmÂÑêI3Åd£·FT°f•GŽ@'ñÖ–f X!pQß≂É!žÑMòò™U•H7/ÌìÞHùñž~ÞGVº‹…î²íÞ„ÑwýoܽIcÒz…ï•ÂWŠšݵw0ù‰$íU\–8 œõ."åSž—/™Žø~ÔM;_©uOÂïÃŽŠÒ´³O?ÿÜÕjŽ[½ÁYv­X˜FÒÃ)üãæg^$øï7 ŠxÊú¤4lî°Tâdí‰ñè¹Ã¢iþ »é®jn":¨;”eå¸T²–B=IJ9ùÈ;`9''ªÇ7 †dÔ×ß»¡Â³‰|è%uzÁð¡¨i´ü· þ¶Ò6èéâ2UÍS¼c{i8TGrÂqa²Éä¢Vžá^-vš¨¯z^–ñyWÄ—IÖM¨€áFÄ8-Èá€ÿ~¶; ꘷XMw°IÚœq.Ž)玲žäc˜©êfŽ0W†ùÛn< c#¥;Á¨}Å1˜‚L´Ý;WêJšû}JÜ.7*ªy0’*ÅH#ˆ!He;½ýñÆ:ÅO ÜÝP'ºëCñ 7=éÂϨ4|Mošªáx´Ô"IM%-57â„ѰÆàK…'ŽT×£~ó O¢¦ñ sa yî¤}ö»ÕE4jÞœ3z pI FCp8óÓGi»‡ßè–x™: Šƒ_XD‰tŠ× Ó.+ñ+¾A ¡€'û“ç«vã儯qhä–íªl‚¨£±H´ewHÕ5~©U$_$‚8ûÓ¨Ü+šeЕW"F‰–ïÜk¢ž–®„ɰåÙÕäü¿2œyÈÎ<¶R¦ó¨²ç×İ\kæ Õ½õ¶ËéÔéªP¸Yâw1´ƒ P<®=ºp @µ–CŒ›TRýßè ˆúR@r=wF÷ã¯ØgªrM’Î4 Õ_pïÅòã.(orW9ùZ8JLïö˜Î=±íí×BŸ¨n|á+ãOT¥:Jýñuiáќœ3Riªú€ç1œOÓÏN«Àª:ïâ*Uq ¦c¹]VÇü}늘!±|&÷¢¨D¢(Þ¦„R…\p¬Ó•äyçýúCøm6޵VþÃí)n*~Hï }ÕÙaýšŸ´Ó[ÈÍU£tkh¤`Þ½çQÒ4‘¯ÿLBfÇÛu2`™óÖŸþ!ÇñõL8\CŒ¹Á¾dýÓöóö#wzsf»÷câ>ÍKsPíS’Üõ*§?/¦ólŽNWÏŽ:æã1ÔÁ&ƒg´Ûò¶að¬j½Äö¬ýáhÙÚ«LqÉ®»™ÜeR9t¦–d9ã"(Ëã¬'óò´O‰[N‹L†“Þ bkÿg‡ÃåEƒJé˜;{m¸YmsŠ¨è®—jé©%¨?š¢x‚ÔÔ7ʦIw£` ¼u„tòHpÒbã»aàµôôÉÛvYþ»]m§ü4}¼í<@ŸiJ$UåW1–è3ŽYŽ úšŽOq÷E3 íu&«§ªÒµ•ºHÃ¥èÛU`…ù ãSóxÇ>ÙéáŒÝçÍ,ñ9³šš›Ò[ÚžøÚú›ÅÃÉ–¢p¬ßcƒãߦңA‚ ¼¬O®N‚: Eº?áòx*Õ€LnéÍmd€Õ½ÓeÆÓ¢\*\©tô±œ¦gôŠÆ0NóãƒÇ¿¿Dú”€’G¢¶½âÂV¸÷#½ =„¬¶YµmN—Óµõð¨œ5Hñ«l,ÏLªAãçØën>¼ô]`;’«âê¶Ä8÷UÜOO‡{žJNÓ÷_¶õúßÖŒŠ[®ž¹Õ*Ä2 %=,þ§<ý¾w¯2æ¸ÀÔ¤RÅ=ä5ßõ+ntþ³ÓUš~ÂÕ–:ä©­Š9b‰)$Œ±Øƒ)Ç˱ˆÛžAë‘ñ« #]O…©µ»ìž¦ÔzJ)£ž³JÜ¢«;Ø@•N0A>?§BÜX7èÏ’/†© #Í;Òêzºª§Ñ÷£‘þA< ¼|Êî?ß§ [„e¦|’êa_þÃÍ,ýírX`ü>—i¢”²°…mLppÛsŸ ÷è.¶ÌŸEMÃÞ<ÑÂí¨XÛ§‚Å]MUBôÛ¤<¥HŒpNNšÌMgjÂ2Ñ’~¬X×?RØñ*SBAÚߊ®ÇwŠç«lG¸ Ge¯j…LÁ©äRW†uTq•a”,2<óÓC*Šaè`ÂM6gnfIÊÖ›\hÊy£Iu¦’i9Œqˆ´Ž<€ŠÅ²<‘Î@63à¦I1ºq ¿P]˜~ê’Z¨Éf2µ4ªŠŸ™‚ÿO='•‘e rNm&L­n9óœný?ïÕe²¢?è¬Ì&q€#©úýú¨0¨‰!*”›|‰2l+.Ð ·ÔŸ ãØô.ìPÛÅ“Å0uGyÈ;9Áç õ´Î‰@(ÙÚñÇÇV{SÀæ¨[ž¹[®·j«E*ni%>šÄ~ìã<à·C.&­=ɱÞ#¥’‚ëLUÙ•%|“2€YCrÀÊýÿ§B×T7‹sº¢²ÔêZEœF/M=[! u+ïüÁpysŽÏÀˆ´k rê›M ÔÞ%®H@å…rá°|ç É>àc<}º³Y­Ô¨[7B¯Ôt¶êj–¢-Ys™·{ãb‚r9ã=_J m ƒdÂAWÜÔ™kBR鈡FE¨¨ž|úŽË’ ‡,3ÃéÕ u8ƒ¯r€8¦ê{¯kÚ²š9{{Ir»‰¥1ÛSÐ,IeôÛ~0Xœ>Ýñ#R'ÁCIäN©ÏX|@Y´²KGnÑ— úˆ r­K ; FØwΫxýOMm{[î„Ò©0T«OëËÝ*á¤õ- *áÑe)¾Or[ÇÏÓ¡ÉîïS!ÝHf¼j+S¥]¶¶’Û4CÐ¥aâw«†iKn9 6mû’rcj˜¶èM1Í7ÞªµÒ™ÚÙm¿V"F@ôªéD³ò$ȨŠá÷yão6Ö“¬4N©ëKM«?qÑ­}±­•£rŸÞ5‚®©pøFŒ”rG<7Ð}€5±iû¡‰T‚ ¯ŽŠµt”¥ÄŒY£$™ùHÈã’=¾ýi˜”E±º4ÇQ/ªZ…£YOñ‹8Ý*à€3öÏý:’tR%)‘£¨,ÊÓPòãØ äŒn«B,¯p˜gÓ2M -¿TêM7 DŽÖðÄÌ^7%F(¯ô@vûSjýKÙ=)•),‚ EvqüjŒŠéQ¶Þ‘‡çÏ^³…áZÆ8u‰ ÄÞç:,¹­£þ $Ò”–Ý?y±Zk(BÊ¡Dþ«*ªŸQp‡Œ¯Ÿ˜Ÿ¯G_òs0ÝgÃq.ŒAýhîóöšÕÚyô5çLjºKø¥­ »ÒUEOSHØNð­$Špr €s·^n¦»«È-" ¯IGŠSe.ˆ¶gNj îÍv 4ÔW$¼Ümч1=Y^cŽ$1HNHàs㞟K…6•ØûÝ%ÜD»«²­%ÖW¹ …¨ˆ¦ª‰÷#=Ý]<;ìÑMÇÃî´Cªõ™ïÉ>[{_ñwªV³vo½W¸d1´ É‹yÆ óýç{}µìIí•DôõUõîFÜÐPRư» #3xã‘ïÒÿ­ °)øÉú!w ]=o5½zà‹ágOTµ² ÛC{Çj\­Ò¼Ä _RQ.É ƒÏß߯8úŽÏòIºì® r†%;Ñßÿ:*I®Ÿ†~ÄEp‘Œ<ö_Æ»19ÜZ¤¿?¦Û®Ãxö4´U v@ú,­i°ÈÚ´&–ÓqE‘ÒºKÅÀÿ³SÁé°YªVÄÔ»ê“ê‹§¾jZ?x¬›šï?£å‘h'ëÁë?“«ŠT–*e.’” s ôrd?rO>?Ó£w’„Õ¢ôñߨ« T~HÔÏœý~ÝC#R |ý&X¶ŸÄHÞíÁ=W@® ª9,±wü½NAàd.OPaƧ꠪A”žJs:mzYUHö© ¯õÏFhÝ:©æýÖ“dµ+JÄcG9þ€ô-Ã0hØUÓ©U×põÿm»UeJuz¢³Pd¤t¢)j§“ë¶ž0Ò=ÛFFHÏ@öRˆ‰îº”«=Ç«$ö*çµýúøpï æ¿KhÛý–}WÖ‹á+%¾(¤ÿš¼ùBqƒ:&SafGxö:¥VØ’¶tý€g{n ÿü œþ¸ë@¤Áh~!üÊ:K=‘Ä~¥²†@¤ß §ÛÛ« !éÍ ðÕC[¨DGʈWúc«—“ºk·é«ª®jëm-C’ÌËMbI';ÂîÏ'ßèólIóFj¸ˆ?Tý— €O#ž¨²ëÂgÜ?ŠÁ}‡Ž~½LƘ@g|œ9ó‘ÿn¡tp°¬ÌYœÍógpúýº²ë¡pXfç ïÕ‡ZêÀ ¢Ø“Á!Gþýú‡°¢ );ЦÿbGލÂ%x¢ £ €F?^¦dDí+Å€,±¬±’nPyŒ{g¡“¥ê§+$jAóÆGõÏWª£m.›èmv›bΖÛM¦Ø²6çÔ±Ãê«lQ¸óïѾ«ÈŠ':Ú¬Go¶Ç9¨†ÙmŠ`wz«M¾qŒî9Ç ¨è‰Tï±É,å˜cëœô·9 yNãµ>D-ÈÀ<~¦dqÚ‘Ìj_h‰áˆgæ$g#§× 2£`n™.2Ò¨Hé)朖ðPÏ·=-Îr"P«uú’– ´Áj;«Š9„brFFX6[Œø>ÞÝg‚þ¨V6DIo¯W+-ï^@ãùZ½×ýé„b=ù¦6˜ÙC«ôþŸ’©&½À—“B$•×Õ'ÕFOŸþjeRÐ![gd¥«%´[¢¦£´ÃIo§ib•â§ŠÞä±p¤œØyÏI5Üu´öþeĦ5Oti-UÑ[i¯}¤°ÞjKYzˆf0¥˜FR1½³1 çô·¹Ä|ÞRUº£õZÍ«¾ »aO|š×©»£¢¯s, =Ö½Lná·Z%“;sÆ~„ýú#ê8fÏqWñŒ(û¥–¿ŠŸ‡M5Ej¹ëåÖÕÛ«d—ðéEn¸T­(Úò3Æ€ä²Kcåû]>ù.cdø©K`¸D‹Gül|0j_aÒZ#ZÒX/7;Œvèæ§íÐŽ ½G%^ªIdÜÒ–!Ifžµbpµi°¾£ ÖÈòÕfocŒ >!t*§G÷ºã~°ˆu-†Ãmû 4ú;Õ:Ÿ••Œ ÀqŒ‚¬ì͠ÒÝO˜Ó´BØâ5è’jÍÜeºÚnÚtÑ]ij%ŽGŽŠ¢@Ë1ÈüKË·c®ç'’8¹¸V=„O߯P±àõ÷æ­*-&m””TW uïP;–3É ‰´’só¡l ÀÁ'зÐ,¡¨ãÉK-­Cšyá¿ÙòƒpôâRù>cÇöé·Š Û):Úæ =,ÑM xh¥[ú¯Ÿ~¬0(RÕZhömŸ¡?ûÏF)tY¤#ŒÀ(À#ìz·6A‘OXæ;Gܶ:©æˆ5$ýï&ÜpÃס.æ«£*”ï­ÂMIÛ}c¢?Á½ÅԔ׫mEªvÒ÷z*úx¥M¬ñO+¯¦Ø<0ϸÆZpUxtí¡òDZG#â¾\Óã[üKt³éžÐj;þ'†ÝSv¸ÐRÊð6bvIð@þL©ûg¯RjacÚÞ˘ô\·àªf$DvŸÂ€iÿØËñ½Sz¢»ÔéÞÖéçŽt˜5f¨‰Šrx‰_žWŠa Hé'¹§öXépJÒ^= jidÅ-{Û¦«×=±OÚWUÌà áqþ# f'Ã÷]SÄAw¡ü«fÁû;®B=Ïâ;NZd`}ACk«³Ó#ÏJv;»IòÛ€¦?È©½›ö!éùjéj5‡Äv»ºB„4±PYbÝ}ÂË$’lývœ}:Žâ”@!´Ï‰üºðêS¿§ámž›ý“_zn8Omnz¶ló^ïo3g%œnUÉû(d¬~X°Ýin“t`úýUï§~þ´|±Ïdøxí$é²½š œ÷H¬zQâxŸÿc¼ãé ÀÀêäÈY4ö—Òk é­9dÓÂ1„ýßG6ÏÓÓUÇXjÕ{ïP“ÞIú¨^M‰RI/ó‚Á¦¨™IÝ)!xöÏI°@d”D—“ƒ<1ƒÁÞ3Žˆ€¤B%az‰™ÿ~Rzdœ*¾8ÎOû€°rUÌj“›•®íkqB,!†5ÏЃǿßއá›þÅF¸ “­»HÃIK--N¥¾ÝZE òO2o>6çtCͧÍ}¤õKa¶Ñ)%TÒ/$›™ÔŸs÷ûtbƒtP<ì4ë ˆÑ‘‚ ¿FÚMÁLäꪕúte¡Y!áNz²ÞÅFB­¬s19ä&@ý~VXšÑ–ʺŒ7 gª²…©$µ“éÑÏ(ÆxeóýOU~JàªÔ߉ñmd¦Ó–¯†ÇX5­Î¢G’ér½ÝéééíÑ(cHÙ”¼ŽI%¼(_·(azOž ¦y'Ñ-ìy=WZç¤ôÏíM×Õˆ{—Ü.Ôv+N,ŠÎ4ý,KŒês‘À‰1Ç,[9ñÇDü‚MGT=Qç Û‡?æëv~HVÍÛàêïª4ô–­cñ ñQ¨.rõ+é5q¡ÃŸæZxcX‚ÿå ûu˜62)4ŽÙ>¤¦–R6Ë+_ë?fp·VÙôçÅôÝsã?^ìY³'ªåQÝ[é»Èç#޶‡áÉëÑ#ìRÜÆÄ;Ôƒ·ß³Ki{­¢ï«;ÛÝíjôÇQÁ¶—t ŒÉLÛ†óŸlàã¢}J&Âæ ͨØ×¿ø tõ×tµÖŸ¯ª¡Óž×:²(bõ^©)O§1Î6D å›Éò00}úáWøÂà)0k©#ØO£„¦n÷€¦ºX^5mží µ–‹©T̉t¦ôƒ?Ò>Iaï’Zh6¸µv€{AˆÃ±°XéS¶œŸË¿Z"÷Yˆ ú·pzŽhC`ˆ±“$9ÀÎ}¹ê²î¨"ª¹ÉÊñÈõpAõ© ’1ލ5A;¯fŒgª[B “à‡xãÇBÂ6W” Ç'i }¼}ÏUÒ6bU†¢%¥‚1ƒÇ-äõf¨…]$(È=SÂprì?ßëŸëÐg™T[Í0RŠ*jºúêj;ºK)Wc!ŒþUfÚ£`: |¬¿‚ÕWQôÃ4°üJ<\ËJ¡)*ÉcçäÓº¹|ü¾¡g4„H(B®¥”ÂçœÊœ}ýóÔëòü hÝç¨Æñ÷_P–ûcŒ~ m^Å@7™I¤®«Ž”Ó£H$PÔÙ…È’ÇÈäýñއ£û ê’¯QÆrñ¾<:‡ý•õM€I¤šIÉŒT1GØäÿ~« ’|Õ‡~Ë߉¨”)Ž6>ÐvU~çíú}º¾…£T¸ÝFÂúYnu7Ø´ÆœŠý&7Ö~ %žf3(Žd¦'`€|¼}‡V táÎÚžß*ût³P¢МébÞÎ ³`£Oסé #x‚RY5ÒS«4h¬ <©ÏLi$L«kBq[<s5IrpyþØé³d¼÷ÑdÓA†þ8ÏöèÀ€ l ŠJS€`ŒçíÕ<ÝcªQÒ¢îZx³ç•zVmQS7LµÈ”ѳƈ[ê@édÝ*_[<’¿+8ù: Æ &0R©œ8þ+“úô¬åè/·7eŠHØç@|ôÑ12‰Í Î=Iyp «ÆN0qþÝ \U=¡Lí7 ŠŽ%(Ü}1Ó[PêJŽW ~Ýq ÍŒ =T¨@] ·éÐ¥ÖâWù¾¿)Ïõè‰3òˆE¬’’ßÅaïÀöêFè]k£ ¸,Xàr@ç£$2­¢J.J§Ž¡#T‹AåzA¨SKBÀ™Ü¡8íÔk®Zb}NO·D\ˆ4(Š™ÞYpIúÿïíÒŸˆpBÖh½  õI<jmBEÐÕ±„hP2^‹9”3%`´ý:I¨a5¬ ÕÎÊ©•\޲»ùDÖH2£1_k¥ˆÜ…ú~½AUï$f!8ÐhNT••NÀË9”qÁEÿ`[ÍÛæ)$ !-£ªšjHæ£9È? Á#>:P§}JXr2ggFÜÌø vçõÇž¯(1"eS^A²\("§¶ÔIny-ugiõáÃ8ùÁ?œ09ärŒ”LFˆúGsÑ=zpÄÎÑAlN ªcD ¨ã$J8 y^ @Eáb¥T’'S³#Á*~¾G>ÝÐp“Ôà \MTÔGÛ].G àñz¬Òåc[!>8óYÓ2§ $çy¨|í'G@çd¾Äå”🠪a[Œ’ £.N=º·8‚¡±²Hõ–#·aWÈýP»uCTLò˜¢šDU  ãÏK›«sŠÅDqÈÐ3Æ¬Ê \ûtÆ™FuI!·SƒT²ú• ,‚FW<P1€1íýsÕ¨J/:'Éfb|~©Î0ˆ”’TŠB­$ò61–Œ1þätÆ™G™ÿÙdemokritos-0.3.7/test/data/category.atom0000644000076500007650000000266110316664521021425 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello what goes here? demokritos-0.3.7/test/data/category2.atom0000644000076500007650000000033710366723567021520 0ustar jtauberjtauber00000000000000 Titles/categories bogus bogus Test of titles and categories. demokritos-0.3.7/test/data/content1.atom0000644000076500007650000000517110316664521021342 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 some icon some logo Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z <b>hello</b> Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z
hello
Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z [hello] Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z
demokritos-0.3.7/test/data/entry1.atom0000644000076500007650000000211510316664521021024 0ustar jtauberjtauber00000000000000 Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z 2003-12-13T08:29:29-04:00 Mark Pilgrim http://example.org/ f8dy@example.com Sam Ruby Joe Gregorio do whatever you want

[Update: The Atom draft is finished.]

demokritos-0.3.7/test/data/example1.atom0000644000076500007650000000115110316664521021315 0ustar jtauberjtauber00000000000000 Example Feed 2003-12-13T18:30:02Z John Doe urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 Atom-Powered Robots Run Amok urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 2003-12-13T18:30:02Z Some text. demokritos-0.3.7/test/data/example2.atom0000644000076500007650000000321410316664521021320 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z 2003-12-13T08:29:29-04:00 Mark Pilgrim http://example.org/ f8dy@example.com Sam Ruby Joe Gregorio

[Update: The Atom draft is finished.]

demokritos-0.3.7/test/data/link.atom0000644000076500007650000000206110316664521020537 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello demokritos-0.3.7/test/data/nochildren.atom0000644000076500007650000000033410316664521021730 0ustar jtauberjtauber00000000000000 test hello hello ooops demokritos-0.3.7/test/data/source.atom0000644000076500007650000000311510316664521021103 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello original id Foo Bar... something http://whatever logo attribution subtitle title 2005-07-31T12:29:29Z demokritos-0.3.7/test/data/summary.atom0000644000076500007650000000331610316664521021303 0ustar jtauberjtauber00000000000000 dive into mark A <em>lot</em> of effort went into making this effortless Mark Pilgrim http://example.org/ f8dy@example.com 2005-07-31T12:29:29Z tag:example.org,2003:3 Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z hello Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z <b>hello</b> Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z
hello
demokritos-0.3.7/test/data/test31a.atom0000644000076500007650000000030510316664521021065 0ustar jtauberjtauber00000000000000 Example Feed some-id 2005-07-31T12:29:29Z demokritos-0.3.7/test/data/test31b.atom0000644000076500007650000000031410316664521021066 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.3.7/test/data/test31c.atom0000644000076500007650000000033010316664521021065 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.3.7/test/data/test31d.atom0000644000076500007650000000033010316664521021066 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.3.7/test/data/test31e.atom0000644000076500007650000000032710316664521021075 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.3.7/test/data/test31f.atom0000644000076500007650000000033210316664521021072 0ustar jtauberjtauber00000000000000 <b>Example</b> Feed some-id 2005-07-31T12:29:29Z demokritos-0.3.7/test/data/test31g.atom0000644000076500007650000000051410316664521021075 0ustar jtauberjtauber00000000000000 <xhtml:div> Less: <xhtml:em> < </xhtml:em> </xhtml:div> some-id 2005-07-31T12:29:29Z demokritos-0.3.7/test/data/test31i.atom0000644000076500007650000000103310316664521021074 0ustar jtauberjtauber00000000000000 Title some-id 2005-07-31T12:29:29Z me some-id some-title 2005-07-31T12:29:29Z This is XHTML content. demokritos-0.3.7/test/README0000644000076500007650000000154210373067672016701 0ustar jtauberjtauber00000000000000DEMOKRITOS TESTS Standalone tests can just be run on their own from the command line. Client/Server tests involve starting up the server from one command line and then the client from another. Standalone and client tests will raise an assertion error if something goes wrong. Client/Server tests require a subversion 1.3 repository called REPOS to be set up in the test directory. To do this just run 'svnadmin create REPOS'. Standalone Tests ./test_format_parse.py ./test_format_roundtrip.py ./test_protocol_parse.py Client/Server Tests ./test_general_server.py ./test_general_client.py ./test_collection_paging_server.py ./test_collection_paging_client.py ./test_bad_index_client.py (use with ./test_collection_paging_server.py) ./test_bad_content_client.py (use with ./test_general_server.py) Samples ./svn-browse.py (web-based subversion browser) demokritos-0.3.7/test/svn_browse.py0000755000076500007650000000417210374615047020563 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") from pyworks.web import Server, RequestHandler, Resource, HTTPError from pyworks.svn import SvnRepository, SvnNode PORT = 8001 if len(sys.argv) != 2: print "svn-browse.py " sys.exit(1) repos = SvnRepository(sys.argv[1]) class SvnRequestHandler(RequestHandler): server_version = "SVN Demo" def get_resource(self, request): path = request.full_path node = repos.get_node(request.full_path) kind = node.node_kind() if kind == SvnNode.NONE: raise HTTPError(404, "Resource Not Found") elif kind == SvnNode.FILE: return SvnFileNodeResource(node) elif kind == SvnNode.DIR: return SvnDirNodeResource(node) else: raise HTTPError(500, "Unknown node type") DIR_TEMPLATE = """ Revision %(rev)s: %(path)s

Revision %(rev)s: %(path)s

    %(entry_list)s

This is a sample of pyworks.web and pyworks.svn """ LI_TEMPLATE = """
  • %(text)s
  • """ class SvnDirNodeResource(Resource): def __init__(self, node): self.node = node def content(self): rev = repos.latest_rev() path = self.node.path entry_list = LI_TEMPLATE % {"href": "../", "text": ".."} for entry in self.node.get_entries(): node = repos.get_node(path + "/" + entry) if node.node_kind() == SvnNode.DIR: href = entry + "/" else: href = entry text = href entry_list += LI_TEMPLATE % locals() return "text/html", DIR_TEMPLATE % locals() class SvnFileNodeResource(Resource): def __init__(self, node): self.node = node def content(self): file_content = self.node.get_stream().read() # @@@ should look up mime type property return "text/plain", file_content print "Listening on port %s..." % PORT Server("", PORT, SvnRequestHandler).serve_forever() demokritos-0.3.7/test/test_bad_content.py0000755000076500007650000000404010366742564021713 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import httplib, socket, urlparse import sys sys.path.append("../lib") from app import parse_introspection from atom import parse_atom SERVER = "localhost:8000" def request(server, method, path, body=None, headers=None): conn = httplib.HTTPConnection(server) try: print "trying %s %s to %s" % (method, path, server) if body is not None and headers is not None: conn.request(method, path, body, headers) else: conn.request(method, path) resp = conn.getresponse() conn.close() print "got %s %s" % (resp.status, resp.reason) return resp except socket.error: print "got socket error" return None ## collection_href = None response = request(SERVER, "GET", "/introspection") if response: assert response.status == 200 doc = response.read() service = parse_introspection(doc) collection_href = service.workspaces[0].collections[1].href if collection_href: print "getting second collection in first workspace..." server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src raw_input("press return") ## location = None if collection_href: print "POSTing atom to entry collection..." server, path = urlparse.urlparse(collection_href)[1:3] body = file("data/bad-content1.atom").read() headers = {"Content-type": "application/atom+xml"} response = request(server, "POST", path, body, headers) if response: assert response.status == 400 raw_input("press return") demokritos-0.3.7/test/test_bad_index_client.py0000755000076500007650000001016310364544614022703 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import httplib, socket, urllib import sys sys.path.append("../lib") from app import parse_introspection from atom import parse_atom SERVER = "localhost:8000" def urlsplit(url): return urllib.splithost(urllib.splittype(url)[1]) def request(server, method, path, body=None, headers=None): conn = httplib.HTTPConnection(server) try: print "trying %s %s to %s" % (method, path, server) if body is not None and headers is not None: conn.request(method, path, body, headers) else: conn.request(method, path) resp = conn.getresponse() conn.close() print "got %s %s" % (resp.status, resp.reason) return resp except socket.error: print "got socket error" return None ## Retrieving an Introspection Document (APP-07 5.1) print "getting introspection document..." collection_href = None response = request(SERVER, "GET", "/introspection") if response: assert response.status == 200 doc = response.read() service = parse_introspection(doc) print "service:" for workspace in service.workspaces: print "\tworkspace:", workspace.title for collection in workspace.collections: print "\t\tcollection:", collection.title print "\t\t\thref =", collection.href print "\t\t\tmember-type =", collection.member_type collection1_href = service.workspaces[0].collections[0].href collection2_href = service.workspaces[0].collections[1].href collection3_href = service.workspaces[0].collections[2].href raw_input("press return") ## List third collection if collection3_href: print "getting third collection..." server, path = urlsplit(collection3_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src next_href = collection.links[0].href raw_input("press return") if collection3_href: print "getting third collection with wrong param..." server, path = urlsplit(collection3_href) path += "?foo=bar" response = request(server, "GET", path) if response: assert response.status == 400 raw_input("press return") if collection3_href: print "getting third collection with extra param..." server, path = urlsplit(collection3_href) path += "?index=5&foo=bar" response = request(server, "GET", path) if response: assert response.status == 400 raw_input("press return") if collection3_href: print "getting third collection with two indexes..." server, path = urlsplit(collection3_href) path += "?index=5&index=3r" response = request(server, "GET", path) if response: assert response.status == 400 raw_input("press return") if collection3_href: print "getting third collection with negative index..." server, path = urlsplit(collection3_href) path += "?index=-5" response = request(server, "GET", path) if response: assert response.status == 400 raw_input("press return") if collection3_href: print "getting third collection with index too high..." server, path = urlsplit(collection3_href) path += "?index=1000" response = request(server, "GET", path) if response: assert response.status == 400 raw_input("press return") if collection3_href: print "getting third collection with string index..." server, path = urlsplit(collection3_href) path += "?index=foo" response = request(server, "GET", path) if response: assert response.status == 400 raw_input("press return")demokritos-0.3.7/test/test_collection_paging_client.py0000755000076500007650000001753710374763343024465 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import httplib, socket, urllib import sys sys.path.append("../lib") from app import parse_introspection from atom import parse_atom SERVER = "localhost:8000" def urlsplit(url): return urllib.splithost(urllib.splittype(url)[1]) def request(server, method, path, body=None, headers=None): conn = httplib.HTTPConnection(server) try: print "trying %s %s to %s" % (method, path, server) if body is not None and headers is not None: conn.request(method, path, body, headers) else: conn.request(method, path) resp = conn.getresponse() conn.close() print "got %s %s" % (resp.status, resp.reason) return resp except socket.error: print "got socket error" return None ## Retrieving an Introspection Document (APP-07 5.1) print "getting introspection document..." collection_href = None response = request(SERVER, "GET", "/introspection") if response: assert response.status == 200 doc = response.read() service = parse_introspection(doc) print "service:" for workspace in service.workspaces: print "\tworkspace:", workspace.title for collection in workspace.collections: print "\t\tcollection:", collection.title print "\t\t\thref =", collection.href print "\t\t\tmember-type =", collection.member_type collection1_href = service.workspaces[0].collections[0].href collection2_href = service.workspaces[0].collections[1].href collection3_href = service.workspaces[0].collections[2].href raw_input("press return") ## List First Collection if collection1_href: print "getting first collection..." server, path = urlsplit(collection1_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src assert collection.links[0].rel == "last" assert collection.links[0].href == collection1_href, "got %s" % collection.links[0].href raw_input("press return") ## List Second Collection if collection2_href: print "getting second collection..." server, path = urlsplit(collection2_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src assert len(collection.links) == 1 assert collection.links[0].rel == "last" assert collection.links[0].href == collection2_href, "got %s" % collection.links[0].href raw_input("press return") ## Creating a Resource location = None if collection2_href: print "POSTing to collection..." server, path = urlsplit(collection2_href) body = "Hello world!" headers = {"Content-type": "text/plain", "Title": "My Title"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location raw_input("press return") ## List Second Collection Again next_href = None if collection2_href: print "getting second collection again..." server, path = urlsplit(collection2_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src assert len(collection.links) == 2 assert collection.links[0].rel == "next" assert collection.links[1].rel == "last" assert collection.links[0].href == "%s?index=10" % collection2_href, "got %s" % collection.links[0].href assert collection.links[1].href == "%s?index=10" % collection2_href, "got %s" % collection.links[0].href next_href = collection.links[0].href raw_input("press return") ## Get second page if next_href: print "getting second page of second collection..." server, path = urlsplit(next_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src assert len(collection.links) == 3 assert collection.links[0].rel == "first" assert collection.links[1].rel == "previous" assert collection.links[2].rel == "last" assert collection.links[0].href == collection2_href assert collection.links[1].href == collection2_href assert collection.links[2].href == "%s?index=10" % collection2_href raw_input("press return") ## List third collection next_href = None if collection1_href: print "getting third collection..." server, path = urlsplit(collection3_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src next_href = collection.links[0].href raw_input("press return") ## Get next page of third collection if next_href: print "getting next page of third collection..." server, path = urlsplit(next_href) response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for link in collection.links: print "\t\tlink %s: %s" % (link.rel, link.href) for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src raw_input("press return") demokritos-0.3.7/test/test_collection_paging_server.py0000755000076500007650000000271210374615761024502 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") from store import AtomStore import server from atom.model import PlainText, Date, Person atom_store = AtomStore("http://localhost:8000", "REPOS") workspace = atom_store.create_workspace("Colleciton Paging Test Workspace") collection1 = atom_store.create_collection("Collection 1", "/collection/1", "media") collection2 = atom_store.create_collection("Collection 2", "/collection/2", "media") collection3 = atom_store.create_collection("Collection 3", "/collection/3", "media") workspace.add_collection(collection1) workspace.add_collection(collection2) workspace.add_collection(collection3) atom_store.save_collection(collection1) atom_store.save_collection(collection2) atom_store.save_collection(collection3) def add_entries(collection, count): for i in range(count): path = "/%d" % i member = atom_store.create_media_member(path) member.media_type = "text/plain" member.raw_content = "Hello from %d" % i member.title = PlainText("Sample %d" % i) member.updated = Date("20%02d-%02d-13T00:00:00Z" % (i / 10, i % 10)) member.authors = [Person(name="Sam Pell")] collection.add_member(member) atom_store.save_member(member, collection) add_entries(collection1, 5) add_entries(collection2, 10) add_entries(collection3, 95) server.atom_store = atom_store # @@@ hack for now server.AtomServer().serve_forever()demokritos-0.3.7/test/test_format_parse.py0000755000076500007650000001251610366120014022100 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") from atom import parse_atom, AtomException print "example1" ex1 = file("data/example1.atom").read() feed = parse_atom(ex1) assert feed.title.value == "Example Feed" assert len(feed.links) == 1 assert feed.links[0].href == "http://example.org/" assert feed.updated.value == "2003-12-13T18:30:02Z" assert feed.authors[0].name == "John Doe" assert feed.id == "urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6" assert len(feed.entries) == 1 entry = feed.entries[0] assert entry.title.value == "Atom-Powered Robots Run Amok" assert len(entry.links) == 1 assert entry.links[0].href == "http://example.org/2003/12/13/atom03" assert entry.id == "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a" assert entry.updated.value == "2003-12-13T18:30:02Z" assert entry.summary.value == "Some text." ex2 = file("data/example2.atom").read() print "example2" feed = parse_atom(ex2) assert feed.title.value == "dive into mark" assert feed.title.type == "text" assert feed.subtitle.type == "html" assert feed.subtitle.value == """A lot of effort went into making this effortless""" assert feed.updated.value == "2005-07-31T12:29:29Z" assert feed.id == "tag:example.org,2003:3" assert len(feed.links) == 2 assert feed.links[0].href == "http://example.org/" assert feed.links[0].type == "text/html" assert feed.links[0].rel == "alternate" assert feed.links[0].hreflang == "en" assert feed.links[1].href == "http://example.org/feed.atom" assert feed.links[1].type == "application/atom+xml" assert feed.links[1].rel == "self" assert feed.rights == "Copyright (c) 2003, Mark Pilgrim" assert feed.generator.uri == "http://www.example.com/" assert feed.generator.version == "1.0" assert feed.generator.value == """Example Toolkit""" assert len(feed.entries) == 1 entry = feed.entries[0] assert entry.title.value == "Atom draft-07 snapshot" assert len(entry.links) == 2 assert entry.links[0].rel == "alternate" assert entry.links[0].type == "text/html" assert entry.links[0].href == "http://example.org/2005/04/02/atom" assert entry.links[1].rel == "enclosure" assert entry.links[1].type == "audio/mpeg" assert entry.links[1].length == "1337" assert entry.links[1].href == "http://example.org/audio/ph34r_my_podcast.mp3" assert entry.id == "tag:example.org,2003:3.2397" assert entry.updated.value == "2005-07-31T12:29:29Z" assert entry.published.value == "2003-12-13T08:29:29-04:00" assert entry.authors[0].name =="Mark Pilgrim" assert entry.authors[0].uri =="http://example.org/" assert entry.authors[0].email =="f8dy@example.com" assert len(entry.contributors) == 2 assert entry.contributors[0].name == "Sam Ruby" assert entry.contributors[1].name == "Joe Gregorio" assert entry.content.type == "xhtml" assert entry.content.children[0].name == "http://www.w3.org/1999/xhtml^div" assert entry.content.children[0].attributes == {} assert entry.content.children[0].children[2].name == "http://www.w3.org/1999/xhtml^p" assert entry.content.children[0].children[2].children[0].name == "http://www.w3.org/1999/xhtml^i" assert entry.content.children[0].children[2].children[0].children[0].chardata == "[Update: The Atom draft is finished.]" print "3.1 test a" feed = parse_atom(file("data/test31a.atom").read()) assert feed.title.type == "text" assert feed.title.value == "Example Feed" print "3.1 test b" success = False try: feed = parse_atom(file("data/test31b.atom").read()) except AtomException: success = True assert success print "3.1 test c" success = False try: feed = parse_atom(file("data/test31c.atom").read()) except AtomException: success = True assert success print "3.1 test d" success = False try: feed = parse_atom(file("data/test31d.atom").read()) except AtomException: success = True assert success print "3.1 test e" success = False try: feed = parse_atom(file("data/test31e.atom").read()) except AtomException: success = True assert success print "3.1 test f" success = False try: feed = parse_atom(file("data/test31f.atom").read()) except AtomException: success = True assert success print "3.1 test g" feed = parse_atom(file("data/test31g.atom").read()) assert feed.title.children[0].name == "http://www.w3.org/1999/xhtml^div" print "3.1 test i" feed = parse_atom(file("data/test31i.atom").read()) assert feed.entries[0].summary.children[0].name == "http://www.w3.org/1999/xhtml^div" print "no children" success = False try: feed = parse_atom(file("data/nochildren.atom").read()) except AtomException: success = True assert success print "link" link = file("data/link.atom").read() feed = parse_atom(link) assert feed.entries[0].links[0].rel == "alternate" print "summary" summary = file("data/summary.atom").read() feed = parse_atom(summary) assert feed.entries[0].summary.type == "text" assert feed.entries[1].summary.type == "text" assert feed.entries[2].summary.type == "html" assert feed.entries[3].summary.type == "xhtml" print "content1" content1 = file("data/content1.atom").read() feed = parse_atom(content1) print "entry" e = file("data/entry1.atom").read() entry = parse_atom(e) print "category" cat = file("data/category.atom").read() feed = parse_atom(cat) print "non-Atom" success = False try: parse_atom("") except AtomException: success = True assert success print "source" feed = parse_atom(file("data/source.atom").read()) demokritos-0.3.7/test/test_format_roundtrip.py0000755000076500007650000000560610366723567023044 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") import atom from cStringIO import StringIO inp = file("data/example1.atom").read() feed = atom.parse_atom(inp) out = StringIO() atom.generate_atom(out, feed) assert out.getvalue() == """ Example Feed urn:uuid:60a76c80-d399-11d9-b93C-0003939e0af6 2003-12-13T18:30:02Z John Doe Atom-Powered Robots Run Amok urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 2003-12-13T18:30:02Z Some text. """ # make sure we can re-parse atom.parse_atom(out.getvalue()) inp = file("data/example2.atom").read() feed = atom.parse_atom(inp) out = StringIO() atom.generate_atom(out, feed) assert out.getvalue() == """ dive into mark A <em>lot</em> of effort went into making this effortless tag:example.org,2003:3 2005-07-31T12:29:29Z Copyright (c) 2003, Mark Pilgrim Example Toolkit Atom draft-07 snapshot tag:example.org,2003:3.2397 2005-07-31T12:29:29Z 2003-12-13T08:29:29-04:00 Mark Pilgrim http://example.org/ f8dy@example.com Sam Ruby Joe Gregorio [Update: The Atom draft is finished.] """ # make sure we can re-parse atom.parse_atom(out.getvalue()) ## inp = file("data/category2.atom").read() feed = atom.parse_atom(inp) out = StringIO() atom.generate_atom(out, feed) assert out.getvalue() == """ Titles/categories bogus bogus Test of titles and categories. """ # make sure we can re-parse atom.parse_atom(out.getvalue()) ### print "All tests passed." demokritos-0.3.7/test/test_general_client.py0000755000076500007650000003101310401246734022372 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import httplib, socket, urlparse import sys sys.path.append("../lib") from app import parse_introspection from atom import parse_atom SERVER = "localhost:8000" def request(server, method, path, body=None, headers=None): conn = httplib.HTTPConnection(server) try: print "trying %s %s to %s" % (method, path, server) if body is not None and headers is not None: conn.request(method, path, body, headers) else: conn.request(method, path) resp = conn.getresponse() conn.close() print "got %s %s" % (resp.status, resp.reason) return resp except socket.error: print "got socket error" return None ## Retrieving an Introspection Document (APP-07 5.1) print "getting introspection document..." collection_href = None response = request(SERVER, "GET", "/introspection") if response: assert response.status == 200 doc = response.read() service = parse_introspection(doc) print "service:" for workspace in service.workspaces: print "\tworkspace:", workspace.title for collection in workspace.collections: print "\t\tcollection:", collection.title print "\t\t\thref =", collection.href print "\t\t\tmember-type =", collection.member_type collection_href = service.workspaces[0].collections[0].href raw_input("press return") ## Listing Collection Memembers (APP-07 5.4) member_href = None if collection_href: print "getting first collection in first workspace..." server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src member_href = collection.entries[0].links[0].href raw_input("press return") ## if member_href: print "getting first member in that collection..." server, path = urlparse.urlparse(member_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc raw_input("press return") ## Creating a Resource (APP-07 5.2) location = None if collection_href: print "POSTing to collection..." server, path = urlparse.urlparse(collection_href)[1:3] body = "Hello world!" headers = {"Content-type": "text/plain", "Title": "My Title"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location raw_input("press return") ## Listing Collection Memembers (APP-07 5.4) if collection_href: print "getting collection we just posted to..." server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src member_href = collection.entries[0].links[0].href raw_input("press return") ## Retrieving a Resource (APP-07 5.3.1) if location: print "GETting new resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc assert doc == "Hello world!" raw_input("press return") ## Updating a Resource (APP-07 5.3.2) if collection_href: print "PUTing new resource..." server, path = urlparse.urlparse(location)[1:3] body = "Goodbye world!" headers = {"Content-type": "text/plain"} response = request(server, "PUT", path, body, headers) if response: assert response.status == 200 doc = response.read() print doc assert doc == "Goodbye world!" raw_input("press return") ## if location: print "GETting newer resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc assert doc == "Goodbye world!" raw_input("press return") ## Deleting a Resource (APP-07 5.3.3) if location: print "DELETEing resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "DELETE", path) if response: assert response.status == 204 raw_input("press return") ## if location: print "GETting deleted resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 404 raw_input("press return") ## Listing Collection Memembers (APP-07 5.4) if collection_href: print "getting collection we just deleted from..." server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src raw_input("press return") ### atom collection tests ## collection_href = None response = request(SERVER, "GET", "/introspection") if response: assert response.status == 200 doc = response.read() service = parse_introspection(doc) collection_href = service.workspaces[0].collections[1].href if collection_href: print "getting second collection in first workspace..." server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) print "\t\tcontent src = %s" % entry.content.src raw_input("press return") ## if collection_href: print "POSTing non-atom to entry collection..." server, path = urlparse.urlparse(collection_href)[1:3] body = "Hello world!" headers = {"Content-type": "text/plain", "Title": "My Title"} response = request(server, "POST", path, body, headers) if response: assert response.status == 415 raw_input("press return") ## Creating resources with POST (APP-07 8.1) location = None if collection_href: print "POSTing atom to entry collection..." server, path = urlparse.urlparse(collection_href)[1:3] # from APP-07 8.1.1 (@@@ externalise) body = """ Atom-Powered Robots Run Amok urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a 2003-12-13T18:30:02Z Some text. """ headers = {"Content-type": "application/atom+xml"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location raw_input("press return") ## if location: print "GETting new resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc raw_input("press return") ## Listing Collection Memembers (APP-07 5.4) if collection_href: print "getting collection again" server, path = urlparse.urlparse(collection_href)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() collection = parse_atom(doc) print "collection:" for entry in collection.entries: print "\tmember:", entry.title.value print "\t\tupdated:", entry.updated.value for link in entry.links: print "\t\tlink %s: %s" % (link.rel, link.href) assert collection.entries[0].title.value == "Atom-Powered Robots Run Amok" assert collection.entries[0].links[0].rel == "edit" ### Additional tests after interop testing with whateley print "*** Additional tests after interop testing with whateley ***" location = None if collection_href: print "POSTing atom to entry collection..." server, path = urlparse.urlparse(collection_href)[1:3] # @@@ externalise body = """ Titles/categories Test of titles and categories. """ headers = {"Content-type": "application/atom+xml"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location assert location.endswith("titles_categories") raw_input("press return") ## if location: print "GETting new resource..." server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc entry = parse_atom(doc) assert len(entry.categories) == 2 raw_input("press return") ### Additional tests after interop testing with David Johnson print "*** Additional tests after interop testing with David Johnson ***" location = None if collection_href: print "POSTing atom to entry collection..." server, path = urlparse.urlparse(collection_href)[1:3] # @@@ externalise body = """ Text Test This is a Text Test! """ headers = {"Content-type": "application/atom+xml; charset=utf8"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location print response.read() raw_input("press return") ## server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc entry = parse_atom(doc) print entry.content.content raw_input("press return") ## location = None if collection_href: print "POSTing atom to entry collection..." server, path = urlparse.urlparse(collection_href)[1:3] # @@@ externalise body = """ HTML Test This]]> is an HTML Test! """ headers = {"Content-type": "application/atom+xml; charset=utf8"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location print response.read() raw_input("press return") ## server, path = urlparse.urlparse(location)[1:3] response = request(server, "GET", path) if response: assert response.status == 200 doc = response.read() print doc entry = parse_atom(doc) print entry.content.content demokritos-0.3.7/test/test_general_server.py0000755000076500007650000000222710374615761022440 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") from store import AtomStore import server from atom.model import PlainText, Date, Person, InlineTextContent atom_store = AtomStore("http://localhost:8000", "REPOS") workspace = atom_store.create_workspace("My Workspace") collection1 = atom_store.create_collection("Collection 1", "/collection/1", "media") collection2 = atom_store.create_collection("Collection 2", "/collection/2", "entry") workspace.add_collection(collection1) workspace.add_collection(collection2) atom_store.save_collection(collection1) atom_store.save_collection(collection2) for i in range(10): path = "/%d" % i member = atom_store.create_media_member(path) member.media_type = "text/plain" member.raw_content = "Hello from %d" % i member.title = PlainText("Sample %d" % i) member.updated = Date("20%02d-%02d-13T00:00:00Z" % (i / 10, i % 10)) member.authors = [Person(name="Sam Pell")] collection1.add_member(member) atom_store.save_member(member, collection1) atom_store.save_collection(collection1) server.atom_store = atom_store # @@@ hack for now server.AtomServer().serve_forever() demokritos-0.3.7/test/test_protocol_parse.py0000755000076500007650000000313710362321362022455 0ustar jtauberjtauber00000000000000#!/usr/bin/env python from cStringIO import StringIO import sys sys.path.append("../lib") from app import parse_introspection, generate_introspection, AtomProtocolException empty_service = """""" try: service = parse_introspection(empty_service) assert False except AtomProtocolException: pass f = file("data/07-introspection.xml").read() service = parse_introspection(f) assert len(service.workspaces) == 2 assert service.workspaces[0].title == "Main Site" assert service.workspaces[1].title == "Side Bar Blog" assert len(service.workspaces[0].collections) == 2 assert len(service.workspaces[1].collections) == 1 assert service.workspaces[0].collections[0].title == "My Blog Entries" assert service.workspaces[0].collections[0].href == "http://example.org/reilly/main" assert service.workspaces[0].collections[0].member_type == "entry" assert service.workspaces[0].collections[1].title == "Pictures" assert service.workspaces[0].collections[1].href == "http://example.org/reilly/pic" assert service.workspaces[0].collections[1].member_type == "media" assert service.workspaces[1].collections[0].title == "Remaindered Links" assert service.workspaces[1].collections[0].href == "http://example.org/reilly/list" assert service.workspaces[1].collections[0].member_type == "entry" out = StringIO() generate_introspection(out, service) def compare(a, b): print len(a), len(b) for i in range(min(len(a), len(b))): print ord(a[i]), ord(b[i]) if a[i] != b[i]: break assert out.getvalue() == file("data/07-introspection-out.xml").read() demokritos-0.3.7/test/test_spec_example_client.py0000755000076500007650000000343310401127126023420 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import httplib, socket, urllib import sys sys.path.append("../lib") from app import parse_introspection from atom import parse_atom SERVER = "localhost:8000" def urlsplit(url): return urllib.splithost(urllib.splittype(url)[1]) def request(server, method, path, body=None, headers=None): conn = httplib.HTTPConnection(server) try: print "trying %s %s to %s" % (method, path, server) if body is not None and headers is not None: conn.request(method, path, body, headers) else: conn.request(method, path) resp = conn.getresponse() conn.close() print "got %s %s" % (resp.status, resp.reason) return resp except socket.error: print "got socket error" return None ## print "POSTing image to media collection..." server, path = urlsplit("http://localhost:8000/binary/edit") body = file("data/beach.jpg").read() headers = {"Content-type": "image/jpeg", "Title": "A picture of the beach"} response = request(server, "POST", path, body, headers) if response: assert response.status == 201 location = response.getheader("Location") print "got location:", location doc = response.read() print doc entry = parse_atom(doc) assert entry.links[0].rel == "edit" metadata_uri = entry.links[0].href content_uri = entry.content.src print "got metadata uri:", metadata_uri print "got content uri:", content_uri assert content_uri == location raw_input("press return") server, path = urlsplit(metadata_uri) response = request(server, "GET", path) if response: assert response.status == 200 print response.getheader("Content-type") assert response.getheader("Content-type") == "application/atom+xml" print response.read() demokritos-0.3.7/test/test_spec_example_server.py0000755000076500007650000000132510401127126023446 0ustar jtauberjtauber00000000000000#!/usr/bin/env python import sys sys.path.append("../lib") from store import AtomStore import server from atom.model import PlainText, Date, Person, InlineTextContent atom_store = AtomStore("http://localhost:8000", "REPOS") workspace = atom_store.create_workspace("Atom Publishing Protocol Example") collection1 = atom_store.create_collection("Entry Collection", "/blog/edit", "entry") collection2 = atom_store.create_collection("Media Collection", "/binary/edit", "media") workspace.add_collection(collection1) workspace.add_collection(collection2) atom_store.save_collection(collection1) atom_store.save_collection(collection2) server.atom_store = atom_store # @@@ hack for now server.AtomServer().serve_forever()