/*
* libpinyin
* Library to deal with pinyin.
*
* Copyright (C) 2006-2007 Peng Wu
*
* 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 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#ifndef PHRASE_INDEX_H
#define PHRASE_INDEX_H
#include
#include
#include "novel_types.h"
#include "chewing_key.h"
#include "pinyin_parser2.h"
#include "zhuyin_parser2.h"
#include "pinyin_phrase3.h"
#include "memory_chunk.h"
#include "phrase_index_logger.h"
#include "table_info.h"
/**
* Phrase Index File Format
*
* Indirect Index: Index by Token
* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* + Phrase Offset + Phrase Offset + Phrase Offset + ...... +
* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* Phrase Content:
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* + Phrase Length + number of Pronunciations + Uni-gram Frequency+
* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
* + Phrase String(UCS4) + n Pronunciations with Frequency +
* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
*/
namespace pinyin{
/* Store delta info by phrase index logger in user home directory.
*/
const size_t phrase_item_header = sizeof(guint8) + sizeof(guint8) + sizeof(guint32);
/**
* PhraseItem:
*
* The PhraseItem to access the items in phrase index.
*
*/
class PhraseItem{
friend class SubPhraseIndex;
friend bool _compute_new_header(PhraseIndexLogger * logger,
phrase_token_t mask,
phrase_token_t value,
guint32 & new_total_freq);
private:
MemoryChunk m_chunk;
bool set_n_pronunciation(guint8 n_prouns);
public:
/**
* PhraseItem::PhraseItem:
*
* The constructor of the PhraseItem.
*
*/
PhraseItem(){
m_chunk.set_size(phrase_item_header);
memset(m_chunk.begin(), 0, m_chunk.size());
}
#if 0
PhraseItem(MemoryChunk & chunk){
m_chunk.set_content(0, chunk->begin(), chunk->size());
assert ( m_chunk.size() >= phrase_item_header);
}
#endif
/**
* PhraseItem::get_phrase_length:
* @returns: the length of this phrase item.
*
* Get the length of this phrase item.
*
*/
guint8 get_phrase_length(){
char * buf_begin = (char *)m_chunk.begin();
return (*(guint8 *)buf_begin);
}
/**
* PhraseItem::get_n_pronunciation:
* @returns: the number of the pronunciations.
*
* Get the number of the pronunciations.
*
*/
guint8 get_n_pronunciation(){
char * buf_begin = ( char *) m_chunk.begin();
return (*(guint8 *)(buf_begin + sizeof(guint8)));
}
/**
* PhraseItem::get_unigram_frequency:
* @returns: the uni-gram frequency of this phrase item.
*
* Get the uni-gram frequency of this phrase item.
*
*/
guint32 get_unigram_frequency(){
char * buf_begin = (char *)m_chunk.begin();
return (*(guint32 *)(buf_begin + sizeof(guint8) + sizeof(guint8)));
}
/**
* PhraseItem::get_pronunciation_possibility:
* @keys: the pronunciation keys.
* @returns: the possibility of this phrase item pronounces the pinyin.
*
* Get the possibility of this phrase item pronounces the pinyin.
*
*/
gfloat get_pronunciation_possibility(ChewingKey * keys){
guint8 phrase_length = get_phrase_length();
guint8 npron = get_n_pronunciation();
size_t offset = phrase_item_header + phrase_length * sizeof (ucs4_t);
char * buf_begin = (char *)m_chunk.begin();
guint32 matched = 0, total_freq =0;
for ( int i = 0 ; i < npron ; ++i){
char * chewing_begin = buf_begin + offset +
i * (phrase_length * sizeof(ChewingKey) + sizeof(guint32));
guint32 * freq = (guint32 *)(chewing_begin +
phrase_length * sizeof(ChewingKey));
total_freq += *freq;
if ( 0 == pinyin_compare_with_tones(keys, (ChewingKey *)chewing_begin,
phrase_length) ){
matched += *freq;
}
}
#if 1
/* an additional safe guard for chewing. */
if ( 0 == total_freq )
return 0;
#endif
/* used preprocessor to avoid zero freq, in gen_pinyin_table. */
gfloat retval = matched / (gfloat) total_freq;
return retval;
}
/**
* PhraseItem::increase_pronunciation_possibility:
* @keys: the pronunciation keys.
* @delta: the delta to be added to the pronunciation keys.
*
* Add the delta to the pronunciation of the pronunciation keys.
*
*/
void increase_pronunciation_possibility(ChewingKey * keys,
gint32 delta);
/**
* PhraseItem::get_phrase_string:
* @phrase: the ucs4 character buffer.
* @returns: whether the get operation is successful.
*
* Get the ucs4 characters of this phrase item.
*
*/
bool get_phrase_string(ucs4_t * phrase);
/**
* PhraseItem::set_phrase_string:
* @phrase_length: the ucs4 character length of this phrase item.
* @phrase: the ucs4 character buffer.
* @returns: whether the set operation is successful.
*
* Set the length and ucs4 characters of this phrase item.
*
*/
bool set_phrase_string(guint8 phrase_length, ucs4_t * phrase);
/**
* PhraseItem::get_nth_pronunciation:
* @index: the pronunciation index.
* @keys: the pronunciation keys.
* @freq: the frequency of the pronunciation.
* @returns: whether the get operation is successful.
*
* Get the nth pronunciation of this phrase item.
*
*/
bool get_nth_pronunciation(size_t index,
/* out */ ChewingKey * keys,
/* out */ guint32 & freq);
/**
* PhraseItem::add_pronunciation:
* @keys: the pronunciation keys.
* @delta: the delta of the frequency of the pronunciation.
* @returns: whether the add operation is successful.
*
* Add one pronunciation.
*
*/
bool add_pronunciation(ChewingKey * keys, guint32 delta);
/**
* PhraseItem::remove_nth_pronunciation:
* @index: the pronunciation index.
*
* Remove the nth pronunciation.
*
* Note: Normally don't change the first pronunciation,
* which decides the token number.
*
*/
void remove_nth_pronunciation(size_t index);
bool operator == (const PhraseItem & rhs) const{
if (m_chunk.size() != rhs.m_chunk.size())
return false;
return memcmp(m_chunk.begin(), rhs.m_chunk.begin(),
m_chunk.size()) == 0;
}
bool operator != (const PhraseItem & rhs) const{
return ! (*this == rhs);
}
};
/*
* In Sub Phrase Index, token == (token & PHRASE_MASK).
*/
/**
* SubPhraseIndex:
*
* The SubPhraseIndex class for internal usage.
*
*/
class SubPhraseIndex{
private:
guint32 m_total_freq;
MemoryChunk m_phrase_index;
MemoryChunk m_phrase_content;
MemoryChunk * m_chunk;
void reset(){
m_total_freq = 0;
m_phrase_index.set_size(0);
m_phrase_content.set_size(0);
if ( m_chunk ){
delete m_chunk;
m_chunk = NULL;
}
}
public:
/**
* SubPhraseIndex::SubPhraseIndex:
*
* The constructor of the SubPhraseIndex.
*
*/
SubPhraseIndex():m_total_freq(0){
m_chunk = NULL;
}
/**
* SubPhraseIndex::~SubPhraseIndex:
*
* The destructor of the SubPhraseIndex.
*
*/
~SubPhraseIndex(){
reset();
}
/**
* SubPhraseIndex::load:
* @chunk: the memory chunk of the binary sub phrase index.
* @offset: the begin of binary data in the memory chunk.
* @end: the end of binary data in the memory chunk.
* @returns: whether the load operation is successful.
*
* Load the sub phrase index from the memory chunk.
*
*/
bool load(MemoryChunk * chunk,
table_offset_t offset, table_offset_t end);
/**
* SubPhraseIndex::store:
* @new_chunk: the new memory chunk to store this sub phrase index.
* @offset: the begin of binary data in the memory chunk.
* @end: the end of stored binary data in the memory chunk.
* @returns: whether the store operation is successful.
*
* Store the sub phrase index to the new memory chunk.
*
*/
bool store(MemoryChunk * new_chunk,
table_offset_t offset, table_offset_t & end);
/**
* SubPhraseIndex::diff:
* @oldone: the original content of sub phrase index.
* @logger: the delta information of user self-learning data.
* @returns: whether the diff operation is successful.
*
* Compare this sub phrase index with the original content of the system
* sub phrase index to generate the logger of difference.
*
* Note: Switch to logger format to reduce user space storage.
*
*/
bool diff(SubPhraseIndex * oldone, PhraseIndexLogger * logger);
/**
* SubPhraseIndex::merge:
* @logger: the logger of difference in user home directory.
* @returns: whether the merge operation is successful.
*
* Merge the user logger of difference with this sub phrase index.
*
*/
bool merge(PhraseIndexLogger * logger);
/**
* SubPhraseIndex::get_range:
* @range: the token range.
* @returns: whether the get operation is successful.
*
* Get the token range in this sub phrase index.
*
*/
int get_range(/* out */ PhraseIndexRange & range);
/**
* SubPhraseIndex::get_phrase_index_total_freq:
* @returns: the total frequency of this sub phrase index.
*
* Get the total frequency of this sub phrase index.
*
* Note: maybe call it "Zero-gram".
*
*/
guint32 get_phrase_index_total_freq();
/**
* SubPhraseIndex::add_unigram_frequency:
* @token: the phrase token.
* @delta: the delta value of the phrase token.
* @returns: the status of the add operation.
*
* Add delta value to the phrase of the token.
*
* Note: this method is a fast path to add delta value.
* Maybe use the get_phrase_item method instead in future.
*
*/
int add_unigram_frequency(phrase_token_t token, guint32 delta);
/**
* SubPhraseIndex::get_phrase_item:
* @token: the phrase token.
* @item: the phrase item of the token.
* @returns: the status of the get operation.
*
* Get the phrase item from this sub phrase index.
*
* Note:get_phrase_item function can't modify the phrase item size,
* but can increment the freq of the special pronunciation,
* or change the content without size increasing.
*
*/
int get_phrase_item(phrase_token_t token, PhraseItem & item);
/**
* SubPhraseIndex::add_phrase_item:
* @token: the phrase token.
* @item: the phrase item of the token.
* @returns: the status of the add operation.
*
* Add the phrase item to this sub phrase index.
*
*/
int add_phrase_item(phrase_token_t token, PhraseItem * item);
/**
* SubPhraseIndex::remove_phrase_item:
* @token: the phrase token.
* @item: the removed phrase item of the token.
* @returns: the status of the remove operation.
*
* Remove the phrase item of the token.
*
* Note: this remove_phrase_item method will substract the unigram
* frequency of the removed item from m_total_freq.
*
*/
int remove_phrase_item(phrase_token_t token, /* out */ PhraseItem * & item);
/**
* SubPhraseIndex::mask_out:
* @mask: the mask.
* @value: the value.
* @returns: whether the mask out operation is successful.
*
* Mask out the matched phrase items.
*
*/
bool mask_out(phrase_token_t mask, phrase_token_t value);
};
/**
* FacadePhraseIndex:
*
* The facade class of phrase index.
*
*/
class FacadePhraseIndex{
private:
guint32 m_total_freq;
SubPhraseIndex * m_sub_phrase_indices[PHRASE_INDEX_LIBRARY_COUNT];
public:
/**
* FacadePhraseIndex::FacadePhraseIndex:
*
* The constructor of the FacadePhraseIndex.
*
*/
FacadePhraseIndex(){
m_total_freq = 0;
memset(m_sub_phrase_indices, 0, sizeof(m_sub_phrase_indices));
}
/**
* FacadePhraseIndex::~FacadePhraseIndex:
*
* The destructor of the FacadePhraseIndex.
*
*/
~FacadePhraseIndex(){
for ( size_t i = 0; i < PHRASE_INDEX_LIBRARY_COUNT; ++i){
if ( m_sub_phrase_indices[i] ){
delete m_sub_phrase_indices[i];
m_sub_phrase_indices[i] = NULL;
}
}
}
/**
* FacadePhraseIndex::load_text:
* @phrase_index: the index of sub phrase index to be loaded.
* @infile: the textual format file of the phrase table.
* @type: the type of phonetic table.
* @returns: whether the load operation is successful.
*
* Load one sub phrase index from the textual format file.
* Note: load sub phrase index according to the config in future.
*
*/
bool load_text(guint8 phrase_index, FILE * infile,
TABLE_PHONETIC_TYPE type);
/**
* FacadePhraseIndex::load:
* @phrase_index: the index of sub phrase index to be loaded.
* @chunk: the memory chunk of sub phrase index to be loaded.
* @returns: whether the load operation is successful.
*
* Load one sub phrase index from the memory chunk.
*
*/
bool load(guint8 phrase_index, MemoryChunk * chunk);
/**
* FacadePhraseIndex::store:
* @phrase_index: the index of sub phrase index to be stored.
* @new_chunk: the memory chunk of sub phrase index to be stored.
* @returns: whether the store operation is successful.
*
* Store one sub phrase index to the memory chunk.
*
*/
bool store(guint8 phrase_index, MemoryChunk * new_chunk);
/**
* FacadePhraseIndex::unload:
* @phrase_index: the index of sub phrase index to be unloaded.
* @returns: whether the unload operation is successful.
*
* Unload one sub phrase index.
*
*/
bool unload(guint8 phrase_index);
/**
* FacadePhraseIndex::diff:
* @phrase_index: the index of sub phrase index to be differed.
* @oldchunk: the original content of sub phrase index.
* @newlog: the delta information of user self-learning data.
* @returns: whether the diff operation is successful.
*
* Store user delta information in the logger format.
*
* Note: the ownership of oldchunk is transfered here.
*
*/
bool diff(guint8 phrase_index, MemoryChunk * oldchunk,
MemoryChunk * newlog);
/**
* FacadePhraseIndex::merge:
* @phrase_index: the index of sub phrase index to be merged.
* @log: the logger of difference in user home directory.
* @returns: whether the merge operation is successful.
*
* Merge the user logger of difference with the sub phrase index.
*
* Note: the ownership of log is transfered here.
*
*/
bool merge(guint8 phrase_index, MemoryChunk * log);
/**
* FacadePhraseIndex::merge_with_mask:
* @phrase_index: the index of sub phrase index to be merged.
* @log: the logger of difference in user home directory.
* @mask: the mask.
* @value: the value.
* @returns: whether the merge operation is successful.
*
* Merge the user logger of difference with mask operation.
*
* Note: the ownership of log is transfered here.
*
*/
bool merge_with_mask(guint8 phrase_index, MemoryChunk * log,
phrase_token_t mask, phrase_token_t value);
/**
* FacadePhraseIndex::compact:
* @returns: whether the compact operation is successful.
*
* Compat all sub phrase index memory usage.
*
*/
bool compact();
/**
* FacadePhraseIndex::mask_out:
* @phrase_index: the index of sub phrase index.
* @mask: the mask.
* @value: the value.
* @returns: whether the mask out operation is successful.
*
* Mask out the matched phrase items.
*
* Note: should call compact() after the mask out operation.
*
*/
bool mask_out(guint8 phrase_index,
phrase_token_t mask, phrase_token_t value);
/**
* FacadePhraseIndex::get_sub_phrase_range:
* @min_index: the minimal sub phrase index.
* @max_index: the maximal sub phrase index.
* @returns: the status of the get operation.
*
* Get the minimum and maximum of the sub phrase index.
*
*/
int get_sub_phrase_range(guint8 & min_index, guint8 & max_index);
/**
* FacadePhraseIndex::get_range:
* @phrase_index: the index of sub phrase index.
* @range: the token range of the sub phrase index.
* @returns: the status of the get operation.
*
* Get the token range of the sub phrase index.
*
*/
int get_range(guint8 phrase_index, /* out */ PhraseIndexRange & range);
/**
* FacadePhraseIndex::get_phrase_index_total_freq:
* @returns: the total freq of the facade phrase index.
*
* Get the total freq of the facade phrase index.
*
* Note: maybe call it "Zero-gram".
*
*/
guint32 get_phrase_index_total_freq(){
return m_total_freq;
}
/**
* FacadePhraseIndex::add_unigram_frequency:
* @token: the phrase token.
* @delta: the delta value of the phrase token.
* @returns: the status of the add operation.
*
* Add delta value to the phrase of the token.
*
*/
int add_unigram_frequency(phrase_token_t token, guint32 delta){
guint8 index = PHRASE_INDEX_LIBRARY_INDEX(token);
SubPhraseIndex * sub_phrase = m_sub_phrase_indices[index];
if ( !sub_phrase )
return ERROR_NO_SUB_PHRASE_INDEX;
m_total_freq += delta;
return sub_phrase->add_unigram_frequency(token, delta);
}
/**
* FacadePhraseIndex::get_phrase_item:
* @token: the phrase token.
* @item: the phrase item of the token.
* @returns: the status of the get operation.
*
* Get the phrase item from the facade phrase index.
*
*/
int get_phrase_item(phrase_token_t token, PhraseItem & item){
guint8 index = PHRASE_INDEX_LIBRARY_INDEX(token);
SubPhraseIndex * sub_phrase = m_sub_phrase_indices[index];
if ( !sub_phrase )
return ERROR_NO_SUB_PHRASE_INDEX;
return sub_phrase->get_phrase_item(token, item);
}
/**
* FacadePhraseIndex::add_phrase_item:
* @token: the phrase token.
* @item: the phrase item of the token.
* @returns: the status of the add operation.
*
* Add the phrase item to the facade phrase index.
*
*/
int add_phrase_item(phrase_token_t token, PhraseItem * item){
guint8 index = PHRASE_INDEX_LIBRARY_INDEX(token);
SubPhraseIndex * & sub_phrase = m_sub_phrase_indices[index];
if ( !sub_phrase ){
sub_phrase = new SubPhraseIndex;
}
m_total_freq += item->get_unigram_frequency();
return sub_phrase->add_phrase_item(token, item);
}
/**
* FacadePhraseIndex::remove_phrase_item:
* @token: the phrase token.
* @item: the removed phrase item of the token.
* @returns: the status of the remove operation.
*
* Remove the phrase item of the token.
*
*/
int remove_phrase_item(phrase_token_t token, PhraseItem * & item){
guint8 index = PHRASE_INDEX_LIBRARY_INDEX(token);
SubPhraseIndex * & sub_phrase = m_sub_phrase_indices[index];
if ( !sub_phrase ){
return ERROR_NO_SUB_PHRASE_INDEX;
}
int result = sub_phrase->remove_phrase_item(token, item);
if ( result )
return result;
m_total_freq -= item->get_unigram_frequency();
return result;
}
/**
* FacadePhraseIndex::prepare_ranges:
* @ranges: the ranges to be prepared.
* @returns: whether the prepare operation is successful.
*
* Prepare the ranges.
*
*/
bool prepare_ranges(PhraseIndexRanges ranges) {
/* assume memset(ranges, 0, sizeof(ranges)); */
for (size_t i = 0; i < PHRASE_INDEX_LIBRARY_COUNT; ++i) {
GArray * & range = ranges[i];
assert(NULL == range);
SubPhraseIndex * sub_phrase = m_sub_phrase_indices[i];
if (sub_phrase) {
range = g_array_new(FALSE, FALSE, sizeof(PhraseIndexRange));
}
}
return true;
}
/**
* FacadePhraseIndex::clear_ranges:
* @ranges: the ranges to be cleared.
* @returns: whether the clear operation is successful.
*
* Clear the ranges.
*
*/
bool clear_ranges(PhraseIndexRanges ranges) {
for (size_t i = 0; i < PHRASE_INDEX_LIBRARY_COUNT; ++i) {
GArray * range = ranges[i];
if (range) {
g_array_set_size(range, 0);
}
}
return true;
}
/**
* FacadePhraseIndex::destroy_ranges:
* @ranges: the ranges to be destroyed.
* @returns: whether the destroy operation is successful.
*
* Destroy the ranges.
*
*/
bool destroy_ranges(PhraseIndexRanges ranges) {
for (size_t i = 0; i < PHRASE_INDEX_LIBRARY_COUNT; ++i) {
GArray * & range = ranges[i];
if (range) {
g_array_free(range, TRUE);
range = NULL;
}
}
return true;
}
/**
* FacadePhraseIndex::prepare_tokens:
* @tokens: the tokens to be prepared.
* @returns: whether the prepare operation is successful.
*
* Prepare the tokens.
*
*/
bool prepare_tokens(PhraseTokens tokens) {
/* assume memset(tokens, 0, sizeof(tokens)); */
for (size_t i = 0; i < PHRASE_INDEX_LIBRARY_COUNT; ++i) {
GArray * & token = tokens[i];
assert(NULL == token);
SubPhraseIndex * sub_phrase = m_sub_phrase_indices[i];
if (sub_phrase) {
token = g_array_new(FALSE, FALSE, sizeof(phrase_token_t));
}
}
return true;
}
/**
* FacadePhraseIndex::clear_tokens:
* @tokens: the tokens to be cleared.
* @return: whether the clear operation is successful.
*
* Clear the tokens.
*
*/
bool clear_tokens(PhraseTokens tokens) {
for (size_t i = 0; i < PHRASE_INDEX_LIBRARY_COUNT; ++i) {
GArray * token = tokens[i];
if (token) {
g_array_set_size(token, 0);
}
}
return true;
}
/**
* FacadePhraseIndex::destroy_tokens:
* @tokens: the tokens to be destroyed.
* @returns: whether the destroy operation is successful.
*
* Destroy the tokens.
*
*/
bool destroy_tokens(PhraseTokens tokens) {
for (size_t i = 0; i < PHRASE_INDEX_LIBRARY_COUNT; ++i) {
GArray * & token = tokens[i];
if (token) {
g_array_free(token, TRUE);
token = NULL;
}
}
return true;
}
/**
* FacadePhraseIndex::create_sub_phrase:
* @index: the phrase index to be created.
* @returns: the result of the create operation.
*
* Create the sub phrase index.
*
*/
int create_sub_phrase(guint8 index) {
SubPhraseIndex * & sub_phrase = m_sub_phrase_indices[index];
if (sub_phrase) {
return ERROR_ALREADY_EXISTS;
}
sub_phrase = new SubPhraseIndex;
return ERROR_OK;
}
};
PhraseIndexLogger * mask_out_phrase_index_logger
(PhraseIndexLogger * oldlogger, phrase_token_t mask, phrase_token_t value);
};
#endif
un-&@X:ټՌ';1i.ғ˖bxM.
h$wh?_u9Ĵmh
I-Ej]ӽعv$4WN|` 50
Mq KR4ÞRa6w"Ӧz9ԭYgįe_]ayG\NJ5|P7b#>= [px{G:zҽ=:SBy#n0S<{B$]KNXĚ
V_5aP{cDvZaP0"a;%.@aƺQxu3pRDہ̠z;_~@x1mkytt9GTSwo8",jnٕ}Iތ:EڬD]BV6rbMa$UJ]@J 2?Hg1 o+@aoIunn?'EYZm Ss)l0| ^ l(2zS@dmq+|O`dYZm; V`Է~f7i\.Y7Оs|=^J$
a%Ci76VI@|1>4}ػU!W#Њ?*{EIͻ`E'S<UεܕTuś7ma@߉61K|O'/0q?8:n0rfkſB%mʍ2pi#C_%4[̦ Is*j<JKqG3#BJW$׆}k_WٖE̒7,)t,k&>
?ݤܨQKXBчkЉ>ʲ/|_wJ>YO"YPal(X{s*e
ZpzQg
)7*c?*"}Qrlsbo'倯al 1V$
l/'Lm|Plk,0CW֦lD4ZKbI(ax@QʺLpեje
S(B~KƳK<bn*RwGFdx, I,vMDd.!le\-/< z+ɾіbt 0BovZ@/ W_7*m`]7AnU@j{/+
^
w*~&?1?l%K_-TTO0ѓ
5yWlc|Fqhd?Ԭ0oރMgF)
eW&`+ p8(u%A
Ğ= ^f1Gi$|:Q4uO0yD<E/f7UgQsj䶱SojǾ䄞*U)'e
2 Oh6BPD=7HHjh#,#mJ:A;g;1h.Aȴ\0Sqͬϖ|M"fNl(2>D)J|Ba*\c8W+F+JWi`\8z26u5sQb{\E۠ȝKFZn7g@R|fcE
W
̘"Kȍ3`_Y "?gi#
Jd
ڪ֎*g
÷;)E-Y7@I4y3H@qj"6t6h|l&Y&. H>dNOϑ@n-5ngZ$\$=&|+ _9'k*IVF8#.
yL71
+iYױ'|{dz`Dt\)KȎ `LzLuxBC}7+"IF(8lhcV>uU$Xvss`U8&NB$nWmqmr&MmUs?fNݹ j2
vGni}
n~B6fMp φh_BR}buxx1YA4
S@7*) lZGl
RMy!-U~HN'kGuEmsl`phJ8Sǁx*hÅ잗qPXD{'_߉﹞Q.i2 h5 sE#ܓ}ܒbZl_4fy<8CΘgeş}2_c^nG"fhe6Rͺ":7B
fŗ[CfBI9+|n
ݜCmJxBY#_6
zKrFa^5qͭg
3IMmDQ5* Ί#p#4廉鵵'Y$nXl
P»T:v_ ]-M1iG+u1K(AR{.C'2/,G9x\q
jqsuδlЙejdJB|='P+{7Pߛ\Z)Noq,
exk#U 5ezH6a`O")ZTRj-l+ >ĿL<Nڶ|N 6[CN4.%!hY<_ev[=30oKQt"#o5Xyfne(0axAUb-[ KKp} P>?[ʛ'V }o?
yef߹_iFU^!=MJ{CJ`¥1g:/Xe GJ{)/xP0ԬbP9H>|Τ' /nq`_
ek>+A.QMJfhVL2ML\۩`FրV}JU'Y=$@ù|z|ώ)m0ۭ{ Ԫ]#>P9g&SX}UVDR{wfmU(Zx`mM'ҏ5E[[W{W Ԧ=DٍN7%,ٳD_d~0I͞"|W-6&sPt"K~nw3K+&_zYpaE)ArôHC?5DXPȩTmc{jJw\4tz6Zi\f~\]
yQ :|t˫46->ӣoXzpHAFF?9o֫dcbvsr^'
n_Ѹy= NW'\GΞvd-;j֗wy_!af
7.Vj|]h
2B$9>N@Єc*9GH՞d` !0-m~akUں-ځJU3NG7<[:Pr
AE3. <%ON(4DV(Q!瞒Jn0a_:DUqjBpP&'OS3f\c##t9TSu f
f
i`^oTr([߶|[WȢ\)T[bKU+Ӥ[RecT z{2D$\Gg%Ӽnſ:ϲ@-m8}*I\/-8E.2aY
^ )ySn쑻WtrɴU'%AcJ\Kl r+2t5ޅw_)ؙ,l9akaS=Պg>K9vR'44^@DJٮv+ץŝ|c@Qi;p͊q`eu
UȲ~\cdu/QD[e̚Qnoy.18>o-NrV, K||^Nv I#"%$a|zQ1R@`HY?bhB= ..4h-
xK̓
O??9`{l">ai3mmg3qRh|P]-
65t.ȯ
4yN/\szIV\طKǍb1O'EQvM(
R
xOeH][fܗߵХ~(=mOyt!(T.svWJ{DBC^ O:%lAM!+uC1)&̈=Q/i+z^KmA`wAn*M6F@(';-cOaؠu)Pq#~Nq}ОR-~k=e?:ͮ' 蛀?$?
PF`|;Y*xyКЍigUUyo|uRacb5~ܐW,0b̼MbP
|/(D9r#404*眹m7Y M_7Ր".:yrLd;3:i"1,jh5M1AZf`(T 6K8 0/#2}CzEIK]66
R`P̹P='iلus؞&4``p'Cr6^vx7muIuE-st)ꊺR)TΎP؈X',4ܒyhd=>LvۉZMg2.'0ch&
R;įtID^C]yxSiΗUMq2^+~9
G0KBZ4Oc锌$"{C%Syؔ3d~[WYU><:P=3V^|";;&k5Y]KE^;Iث6Cc*5)uAS/:*c3&o;$Art=mI