Initial Review: JSON contrib modul was: Re: Another swing at JSON
--On 18. Juni 2011 12:29:38 +0200 Bernd Helmle <mailings@oopsware.de> wrote:
Similar problems occur with a couple other modules I tried (hstore,
intarray).Hmm, works for me. Seems you have messed up your installation in some way
(build against current -HEAD but running against a 9.1?).I'm going to review in the next couple of days again.
A bit later than expected, but here is my summary on the patch:
-- Patch Status
The patch applies cleanly. Since it's primarily targeted as an addition to
contrib/, it doesn't touch the backend source tree (besides documentation TOC
entries), but adds a new subdirectory in contrib/json. The patch is in context
diff as requested.
-- Functionality
The patch as is provides a basic implementation for a JSON datatype. It
includes normalization and validation of JSON strings. It adds the datatype
implementation as an extension.
The implementation focus on the datatype functionality only, there is no
additional support for special operators. Thus, i think the comments in the
control file (and docs) promises actually more than the contrib module will
deliver:
+comment = 'data type for storing and manipulating JSON content'
I'm not sure, if "manipulating" is a correct description. Maybe i missed it,
but i didn't see functions to manipulate JSON strings directly, for example,
adding an object to a JSON string, ...
-- Coding
The JSON datatype is implemented as a variable length character type, thus
allows very large JSON strings to be stored. It transparently decides when to
escape unicode code points depending on the selected client- and
server-encoding.
Validation is done through its own JSON parser. The parser itself is a
recursive descent parser. It is noticable that the parser seems to define its
own is_space() and is_digit() functions, e.g.:
+#define is_space(c) ((c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == ' ')
Wouldn't it be more clean to use isspace() instead?
This code block in function json_escape_unicode() looks suspicious:
+ /* Convert to UTF-8, if necessary. */
+ {
+ const char *orig = json;
+ json = (const char *)
+ pg_do_encoding_conversion((unsigned char *) json, length,
+ GetDatabaseEncoding(), PG_UTF8);
+ if (json != orig)
+ length = strlen(json);
+ }
Seems it *always* wants to convert the string. Isn't there a "if" condition
missing?
There's a commented code fragment missing, which should be removed (see last
two lines of the snippet below):
+static unsigned int
+read_hex16(const char *in)
+{
+ unsigned int i;
+ unsigned int tmp;
+ unsigned int ret = 0;
+
+ for (i = 0; i < 4; i++)
+ {
+ char c = *in++;
+
+ Assert(is_hex_digit(c));
+
+ if (c >= '0' && c <= '9')
+ tmp = c - '0';
+ else if (c >= 'A' && c <= 'F')
+ tmp = c - 'A' + 10;
+ else /* if (c >= 'a' && c <= 'f') */
+ tmp = c - 'a' + 10;
json.c introduces new appendStringInfo* functions, e.g.
appendStringInfoEscape() and appendStringInfoUtf8(). Maybe it's better
to name them to something different, since it may occur someday that the backend
will introduce the same notion with a different meaning. They are static,
but why not naming them into something like jsonAppendStringInfoUtf8() or
similar?
-- Regression Tests
The patch includes a fairly complete regression test suite, which covers much
of the formatting, validating and input functionality of the JSON datatype. It
also tests UNICODE sequences and input encoding with other server encoding than
UTF-8.
--
Thanks
Bernd
Thanks for reviewing my patch!
On Mon, Jul 4, 2011 at 7:10 AM, Bernd Helmle <mailings@oopsware.de> wrote:
+comment = 'data type for storing and manipulating JSON content'
I'm not sure, if "manipulating" is a correct description. Maybe i missed it,
but i didn't see functions to manipulate JSON strings directly, for example,
adding an object to a JSON string, ...
Indeed. I intend to add manipulation functions in the future. The
words "and manipulating" shouldn't be there yet.
-- Coding
...
... It is noticable that the parser seems to define
its own is_space() and is_digit() functions, e.g.:+#define is_space(c) ((c) == '\t' || (c) == '\n' || (c) == '\r' || (c) == '
')Wouldn't it be more clean to use isspace() instead?
isspace() accepts '\f', '\v', and sometimes other characters as well,
depending on locale. Likewise, isdigit() accepts some non-ASCII
characters in addition to [0-9], depending on the locale and platform.
Thus, to avoid parsing a superset of the JSON spec, I can't use the
<ctype.h> functions. I should add a comment with this explanation.
This code block in function json_escape_unicode() looks suspicious:
+ /* Convert to UTF-8, if necessary. */ + { + const char *orig = json; + json = (const char *) + pg_do_encoding_conversion((unsigned char *) json, length, + GetDatabaseEncoding(), PG_UTF8); + if (json != orig) + length = strlen(json); + }Seems it *always* wants to convert the string. Isn't there a "if" condition
missing?
pg_do_encoding_conversion does this check already. Perhaps I should
rephrase the comment slightly:
+ /* Convert to UTF-8 (only if necessary). */
There's a commented code fragment missing, which should be removed (see last
two lines of the snippet below):+static unsigned int +read_hex16(const char *in) ... + Assert(is_hex_digit(c)); + + if (c >= '0' && c <= '9') + tmp = c - '0'; + else if (c >= 'A' && c <= 'F') + tmp = c - 'A' + 10; + else /* if (c >= 'a' && c <= 'f') */ + tmp = c - 'a' + 10;
That comment is there for documentation purposes, to indicate the
range check that's skipped because we know c is a hex digit, and [a-f]
is the only thing it could be (and must be) at that line. Should it
still be removed?
json.c introduces new appendStringInfo* functions, e.g.
appendStringInfoEscape() and appendStringInfoUtf8(). Maybe it's better
to name them to something different, since it may occur someday that the
backend
will introduce the same notion with a different meaning. They are static,
but why not naming them into something like jsonAppendStringInfoUtf8() or
similar?
I agree.
-- Regression Tests
...
It also tests UNICODE sequences and input encoding with other server
encoding than UTF-8.
It tests with other *client* encodings than UTF-8, but not other
server encodings than UTF-8. Is it possible to write tests for
different server encodings?
-- Self-review
There's a minor bug in the normalization code:
select $$ "\u0009" $$::json;
json
----------
"\u0009"
(1 row)
This should produce "\t" instead.
I'm thinking that my expect_*/next_*pop_char/... parsing framework is
overkill, and that, for a syntax as simple as JSON's, visibly messy
parsing code like this:
if (s < e && (*s == 'E' || *s == 'e'))
{
s++;
if (s < e && (*s == '+' || *s == '-'))
s++;
if (!(s < e && is_digit(*s)))
return false;
do
s++;
while (s < e && is_digit(*s));
}
would be easier to maintain than the deceptively elegant-looking code
currently in place:
if (optional_char_cond(s, e, *s == 'E' || *s == 'e'))
{
optional_char_cond(s, e, *s == '+' || *s == '-');
skip1_pred(s, e, is_digit);
}
I don't plan on gutting the current macro-driven parser just yet.
When I do, the new parser will support building a parse tree (only
when needed), and the parsing functions will have signatures like
this:
static bool parse_value(const char **sp, const char *e, JsonNode **out);
static bool parse_string(const char **sp, const char *e, StringInfo *out);
...
The current patch doesn't provide manipulation features where a parse
tree would come in handy. I'd rather hold off on rewriting the parser
until such features are added.
I'll try to submit a revised patch within the next couple days.
Thanks!
- Joey
On 7/4/11 7:22 PM, Joseph Adams wrote:
I'll try to submit a revised patch within the next couple days.
So? New patch?
--
Josh Berkus
PostgreSQL Experts Inc.
http://pgexperts.com
On Mon, Jul 4, 2011 at 10:22 PM, Joseph Adams
<joeyadams3.14159@gmail.com> wrote:
I'll try to submit a revised patch within the next couple days.
Sorry this is later than I said.
I addressed the issues covered in the review. I also fixed a bug
where "\u0022" would become """, which is invalid JSON, causing an
assertion failure.
However, I want to put this back into WIP for a number of reasons:
* The current code accepts invalid surrogate pairs (e.g.
"\uD800\uD800"). The problem with accepting them is that it would be
inconsistent with PostgreSQL's Unicode support, and with the Unicode
standard itself. In my opinion: as long as the server encoding is
universal (i.e. UTF-8), decoding a JSON-encoded string should not fail
(barring data corruption and resource limitations).
* I'd like to go ahead with the parser rewrite I mentioned earlier.
The new parser will be able to construct a parse tree when needed, and
it won't use those overkill parsing macros.
* I recently learned that not all supported server encodings can be
converted to Unicode losslessly. The current code, on output,
converts non-ASCII characters to Unicode escapes under some
circumstances (see the comment above json_need_to_escape_unicode).
I'm having a really hard time figuring out how the JSON module should
handle non-Unicode character sets. \uXXXX escapes in JSON literals
can be used to encode characters not available in the server encoding.
On the other hand, the server encoding can encode characters not
present in Unicode (see the third bullet point above). This means
JSON normalization and comparison (along with member lookup) are not
possible in general.
Even if I assume server -> UTF-8 -> server transcoding is lossless,
the situation is still ugly. Normalization could be implemented a few
ways:
* Convert from server encoding to UTF-8, and convert \uXXXX escapes
to UTF-8 characters. This is space-efficient, but the resulting text
would not be compatible with the server encoding (which may or may not
matter).
* Convert from server encoding to UTF-8, and convert all non-ASCII
characters to \uXXXX escapes, resulting in pure ASCII. This bloats
the text by a factor of three, in the worst case.
* Convert \uXXXX escapes to characters in the server encoding, but
only where possible. This would be extremely inefficient.
The parse tree (for functions that need it) will need to store JSON
member names and strings either in UTF-8 or in normalized JSON (which
could be the same thing).
Help and advice would be appreciated. Thanks!
- Joey
Attachments:
json-contrib-rev1-20110714.patch.gzapplication/x-gzip; name=json-contrib-rev1-20110714.patch.gzDownload
� ��N �]�r���>�O���0%��'�b���-Q���%��<��dIt� [�U+b>����=��=�e�{]���"�mO�G��* ����pX�&���/�����~ ���������[�k����e������1a����Lv�`j
T���z=g�����T��k5���+��3`�w��d�+
���~��Z�BE����os�/@NY���]��o������_���=�,~�5,����,�k�k�uO����V��=��R���0����L��i#6r��~�iwI�ab�-��&�@si<`���X�7G����k3v;7
�*�fK�\�/�cl������������������W(d6�E��1��������t�l2�k@��m9}p���'s�A��w1�M�����F�p��
�5�����@d�J;�R#�����z�����k�81�pR_�w-
���*d�an�h�����.���Y��n�p�D�l��ac��6�"B�Z��qK��O��=���y���R�[/r��+�?L�Hr��
����@��/���������'��/ �Q��_~���/���gP~ C�����5%A&9� �������<�DO�����Q>A�
J��)M(���+ ��H4J�k����'�}������V��r�D�%�g)��2|j1r,���pE�C�Vg����7��I,R�re)aH-_���*��ry�����Q���l����CC;-!�V�T����d>5���U�9�1�������� �:���������TOs=?j�v����� �@���?���dC����J�"9���Mo�0*p��|2�>wn�l�!�k��A����fk8��^����5V�F��RLyR+��K��O�,7�0�O�\�s�dLm �|2�` �~2����f�x+��F]���b�zM����DP(k�� ���`�{�L���s��7�~;�V�
����v,�u���&������Qo?9��;=89C��Z���.�v��I�w�x�sw��uy�r���A�g�q���L���aH�����Ns�s1��U��=4��;�5%���>�R�������=<9�5��8��F]&=��y����������YE�������[�}%A^R`'b��E�����_�B<B���Ym8��L��V�R�Q� �������+��g ^]�#�7&.��k��a.�_����,K��[J}�le�V���e��3{������M����]6�]��L,�`iw:��&`���������j`h�<t�NO��Pi�!6��MH��Z�'_3����F0���H<V\ �9�P���aI�#�
�R9��pp�n�D�����=g)&F�i|�`��=�&������SHy�_�h,����R���a��KNW�Uul���Jq�w���4��sV�0L�� U�1��=m
)&6.U�le7��v��so���$��a��%L������Y��[t� �g�[���b������{F�%��L )�����V:e��yb>p�����|�R���k�Wk�K�-��Y�������)J��l?�la��t���0�Lv$L���"0��B��6�FG=��&�j%�����<<����
�FNr�|���\f��)�F����N�<�U�w1mX�FI3���^+��������u #���`nb�����Mp-��3��V
��v���1�y�;�L��wd��/O��9���^kIS0��q�-/��+�#����Qb�P�H� 1�� � BQV`?M��n2@TV` T2���P*�V3�IF{�3@(���(+� ��0#*5���_�E7�4Si����34�A�
�J��� ���{��)�u(����
���u�P���x�P�'zr�J�h�$�(��F�AQT0Dr|��������~�x�
�e����^�;m��y��e���N�e����_{�/������������":�'4��K9?(��]5�Q�ji[
G,��Q}P�A�SnoV���>�5-�������4�"�T��c�:��������T��#m\�ko��a;"�f��R��A����x7���M�A��?��K jC���!��?�I�+��fq+��� ��$���v����*u��\���,4*�6�7��c���8gH�S��e�]X&C�� v�6�
y�j3D�^��Em=�S�?=F��j���J$���}� ���U��
�@?��G�����]��?~@�������������a�����]��j�&D��j}�$���n�gD�k��+��k������5�G���65���v�M�����k���7"�U�Y$*y��W�D�Q�l��1���mbAO_���e�8&C�:�����2|UV�W��_\J��������}6��
���]{n�Ds\,�Q��"�����a���.���wr������f�k��
G�������*�{��S)�q1'���6WH��m��LR�������7-���M�3�t�"a-�;��Nd���1q;6��;���!�ug���i84Fs��������M5@< �
G��7L
���t
�����|�A�9�b#�����pl�qT�����x���t �
`F9�|n]9�p��z�}f�/�9���%<�X���\���31���1�N_�<�����J&�"�$a9'LTZ�*���S6|k�.ml�-�WZ��v'�i��s��yb�/�������|���:._��[�>�k����"
O�(������a�l~�ZC��5�����~~� ����\�jryJ4L�
���Us$�o�M�;�7�a�#H�@��H���Y��,�FJ�(I|P��R~�� ����<�/m,���iV:��h�UM��
���@@����>���' <@������<(�0��#3����*�#i*5i"N��R��U]��5� $����r������
8�J�f"USP�%^)��X���<�$Fel�SEV�b���p���e����,����Q���Q����2<��)#8>>N��,�J%��JV%�j/�0���i��*2�`]V�UM�Y���O�4��7���U���bBz�yHk�c\J/$ �V�����5�%�D����]9�����,�*3�j=��rn�2'J+��:���^���BMf��������������|��H��f�B���0��c=��r����yr1!� K���0�,����yivZ5��1��&�*0���k��P�}��<5y�������D�nL&B�?�����Y�T�wq>�O%RowGWK �K��J'�D,�Q�C����~��1�Z�H3�pH$��W��B�U�b�*!���u��UI����jRV��4za�����K���p���S0f��������qZ`x�f`�mR:T`�R�O0���hJ�����j~�q}�5���
d�4�N�$����|GT�wJ���Y������,��)_�Db�6���+��]��@>�H�/�@r�Q��u�YI9���e��p#� -+-'�A���3�`�]�S��o1]:�\������z�
Gh3���2�H^xB����!�d��<:y��+�%d�I�2D�\#K� $� �x���nM��t�wB'uW�&�)���J���@��l'+Yf����H��d��/��4�:���_<�J;%�K��=������O�K������S�����)z�z��'�f���\%�55W�r�\�,M��H�K�28O���+��T���jpC ��HRi6���OxJs��Z�,�%�4��3�w���b�t�� �V������[o���L;y
m�7�W��E�C;x���+��__���q���t����6_�����[����M>�4]��s��NM����>�w���>k�i�����^]�^������aN������ZsWl�.���6��$���\a������}�I��:����P��������i��U�5f���9!h����;$�k`\��6PI���T{����F�9g62�f�2�hk�ov��j]s����U��
�Z��b�76Q�W��_{��5��y�}�i�;������ ����"�?16�����0�D�tx���Sd��w�;�)|����`_�P����S�i/ ^�b���}v�oD�B���Q�;�R���u�;����d���
��#r2�P����3�f��M�R�;h�����,��>����KU#��z���5���&Vi���J>�)���/ofq��z}�`x������-����LU��H8�O'
��������37�%P���������/�����z��2z�����7J��g����F-�1!f�r*n;;��~�e�8���2J��
�C�f�
Y�Lc6�h��l��d�h@�^�`{i�P��lf�n) =�f��1���oA�\��� _�o��{�&�����
e�8�w������X�Zm��Wo�^�[:WW�=�v4��I ���r��vv�#~��V��4V(�;����f��Z��t��������8����h8�L��tm����8b���O8?�1@.��o>T�x�+��1�:������M3�i�/���l�:p=������N ���S������
��� sT ���D���lF��W������>%����B����v�i ��&�;�'�{�C �R|����^�
D�9���)s�"1�r��M�i3^�1������������N�;������-�Sm4���NP}'C�c���Z�!��x[.R�4������6�{B_�R��k�5q��|4�����J5� ��#�5�
�kz��|?����T���g���e�O;rV�u}�+�����)��K_g��zf�_��L�p��K=r�~���������<��G;���o� ��(��g?��_��A�~�>�y�H�u.������<^�S�p�[��'��k�07���a�T��zeo�^kv�Q���G���e��w���O��hZ�����n�����5�u��m�(p�i�����1��.���� ������l/�nK���f3��t��yb�Kw��\<�$G����Xm���ZD3]L"<�E{�������o�����
��s��,�!���!�������v���;���r4�����/���OW+�4���`H��l��kh"�?�t�r��3)��(Ck�~����G{�=��lB�7#�C���lp@>�����s�}st�g�:9��{���=2���2��� �8�|+&��[C��e�����'�Op5^�R��N��;^)��k0��
�sxO��-������m^K{!]���8Cg
�Io������3[L��F�o���>/�3�FR���;���b��f&����A���Izc�A�(�� Z��X�v��`�6�-%if��=�������Q[D�����f�b������i�-���h���.���h�������$o��{>2�P���nV��j�6����l/�}
0�&�g#��BU6�\ME��g�5.��M�*��+;{�o���d�1(p��{� �)*,��Np�i*�M6Q�Z�X����.����������9W�v�#�P-�W�F�Q��?�Q)�U�Z���1T������x���
�N�]��?�0g���Xo0^� L��p�&R����������$,l���5��z1�x)�l��]����%05]C��p����������wS?��mGV�}!O�����j�;���_�0�����" Z��[`YV/�(b2n�<����F,@�x[#��]�����E�Gg�z�u�x�Z3���L�^)
4��Vy�M\�����&�~M.:�K�=��g&�5!�4��F�3�CmB����;�q���������;�CU�� ���;�=��������B�A�G�D#bY�m^Dp��6���o�jcI}�_�[�c$�;���� ��c/����X�T�K*ZU��f�!����KN\�Z�
�5g������2"*/�������6�G������ZDw�u���NSw������G�����^��������������B%�(���vI�&r��0����h��\�$W���u��Y���2���?�W��Wsj�����1����[�:�]�Z�<-��}2��:�t�G~>��3Ha���<#���h.
�Qb,h�v+�R�>��J�8�4�A��� ��8KS��>I� �:)qy��)��Yr���bz�Qg��] *[I}k
�?����OLp�Oa�
�g���+Z.�l�
�)��0�l�%c6Y��
���N�����_�u� ���^�,J�����%UCZ7�h���W��R��~���9�����~8�m��>T�B�����8�D3�����M��%Z��D
UO�O�TZ����%-8���n�� Fz�{d��jD� ��k�hX$0���DpH.���?k�fX���y��� ��-����s���������$�4}��e������&������~#�����o��4 ��`����;� ������qd0t8&�f[�[G�;:@iA*e���
7�WgT�����b>�M2���= ���uk���5��M.���2������<���dF�[W���l�@��b�=9�Y,b�
[r�"l�����
�D8%u ��/p8�~�8���6���R����*�d:gJ.����q��("=�.������iG���}8�Z�`pIK���q ����r-"���0_����[��� XW��_"������ b����36|&'d���8w5J��#r�e��\�'�I�Tt*&����c'�[��Gmn���!� v&75nG".��Q%���,k~�q�/3��/gb[�[������2�O�v
!�8��Fv�
�j����D��E��|��S���%��~�!��ut���]�h�e�i��}K��h�SL���?^��6����l�M���d�FBf�@S��=�!C~�;����K�9o���]�s �R����l��^mK�d$n@�c��~��`�o�2���a ��x��k�D����\��]�vVI>�Vj�;���$�e���I�[��F��R����jo�� ��O,���+ u�� }rd������D.��?Wr�vP<��<�u�1}qN���v1��Z�$@b7��|����P�����9E�/��/uE����^=KNK�4!���&��w�L���a$��hNb$�!�G�^����� C�{j�E(L59�� �{����U�l����qq���Y�K��x�13F��>���S���0���WB�`6����)�,�<\R@�/;l�B���q;�r.(
�Lb��������u5�[���$�T��K#
#���������7 ���o���*�T_>\�����n8����i�bP��� 65���j���*���N��Cz#]K���!(�7�P�&��X"����)vY, ����z�c����q�U��k�%�_��+��u8
������V�d����/P{��L��1��A�g����?!&�C[�V4����Q��7��^��
��q'�r��d[J48�b���l��%I��;�B�EI� ~��-���
����LD"NO2&�%�T��V��%B��5�����a��n��5h,-�XK#]g�"�b���d0���=�.^R$ oI0�l�E��D>�l�J���s�|�����������%�8�,1�E1u~��c�t[`�^��hKt�������(������g��P��!h��c�{��Rs�xn����J5.{��
BY���f/����#��{�=I�`��TN����=�o��a|�#�dp����X<2����U;�������m�c���Zj���Du���{d �
q���(�-�tg�|q��m�E�����7P��(W�0�I��"�Ic�c���.���q.���.|[k�'&���l��&���8O���� �
TbBZU��>r^J��b`�H���
x]�.D5����7�-I {V����+xyx����J�F(����lm���^)FQ���e�1�j�dt�������0��v�.J�����#�dO�{���������3�rVp������y��H�
�n'��&��� �@k�i��D� ������gw&���sX(��4���t6��v�h��P���]�G��Pk?�.����$V�N����[��^'���K�������f��E��� ;� ��0R�{<��1�J�H����*7��*3�_����M&�lK��r�>�XKV0����f3{������W[��&n�Y1|F�d�_�n���2,U��@;R�V�� k�6BU����z��$%�#�0F,�>R�F�FB��u�#;�[JU�����!����X�s�MK��!�� ��Mx@LiY��C��k�r�2/����x��9��GM����|���Y����K�) �,S�����%D&[$�m>Y�$\�&�&��6��Mx��RF�}i� ����
t#���YV����}&>�z�{�������#������y���K��*�Ik1)���<����Q���uS$L����T�3�$}�zS�����"���5��M0��3z��6����0r@��D���ik��>}�<w�\��
S;k���0da�?�)v(���9!��1_��/FQq��nB��dG��}�&1���>�����U48�� �����������;��� <i�(�*���I�D�3"��:��J�TM���Z6H��t�_��?PI����� ��&�$�7|��0�(��
���e�,�`
�Q�+��6�p\K�$-Wru�)�[��1��Vo�cR���a��
KD� �?x�.�F�5��n����:����u��f��z���-���U�[�����E7���@�"��|��q��t�b]SA���-�)���SW�#AJ��"��`\���}rR}Y[���.��7n��x�l��(�y��r-��`o�a>����d��~���
�Nz� K���3h+A(�\�\���u�g��he_6�y*�5�,��(�=u�l���I�&���Ky9������T���"�����gk�:2� �_/f��c���t���jy���C�E
�7G
������qst}��� �� 3Tg�lem�DL�z��d���� �E�.����wy����s*P��)"Au���M�R�N�o)?��2"����0j� a��v��6���J9�_EzA`G���s&�'��R�+g��Jb��
�_�Xv���[[�{�0%f�02 F�Z�ivX�
d�n��*�_T�������=��6r��s��n��^j�,P��;Yv�~����s�_(�:��I�W�E��a>[<�l���DP��PP-)�8�z��L�������B#�)��_��������������1csK"2���u�Jo�h0�X�!�����^p�h.��$`���++4�s����Z�����jX.;u������f2#c����Y�'��N$&1j�1w+�<������}u�7y�Zz��~?��@�i���A�O)�����Sp/����X������yKn�� ������I�����$�<�!��xtd�!������N-��������o�&�����R dV9ER���-5�� ��`�F�bM���mwGk�7���Qx�O��5vJ��&4�����������aH�������r��ox����1DFf�+��A�C�
'��&�[��w�?r�7cr��y��FAP_�K���l+j��w�M�����������a�B�����A����HP2�m0�(�[�V�2��2�-TO����m�]�RO` ��r���OB����0Eg�q�2�����L-���a����<.��%�Q#�w�����7^�y��~o�wP�#�y�:�U�����nM�D[�b+��B��\�E��^8���sj��s&dn�6��R��+O�h��'�x�����P��j�������a
�a�000`M�!�#���t�����zA�S^���i^�Aiz��4*����X������� �V�f����^��k rK��T��o�f���^m�q���������Z���;�\� �Cqcg�}�W$�����^���B��<�7���P�����$�h����r���BAo�|J�{
�"Y�:��������[,��3�����������������\Xaa��3*���+B��%{h���p�[�x��_�<J�8u(I�1���)�����9����L�����tf����'��*��<+S���E�����E�p;MbWS������X�G�������t'�gl������[�&���$��w�"��O^��G�f�p����
N�8����Ut�K����1/��d���=c�/�<�?f.���"y]�AE&'�}��aJ����k���Q�K�����:��,�}��{��Q�����=�j=qk�8LQ���l~n��W���}���S���<+��]:���uk;����7��u��2�F���R���I��dy!�_������aYd�(�I�Y6s�5�-��K&H[N���d�.�-��p����g��}&L��q�Fl(�1�F�������d�L}V���f�K��i_�����r�s[�F�b�?E2�B4�d�Wxp��>�n������Hn0KR������,�2kQv)c3��sY�|�o�^{��j�|������oY��v�� eD�W`�eaR�cw`"g��m���)W.ep���(���F��o��,��(�S��2z 1�vL���Q0�����l�M�����7�n��q��y���${���������r��c#�v�@'�����Tl
�W-�1��8 �U��Q)Je�=����{�l����R_�T�V[�Vm`���|�������^���~�t�ew����������H����^���z���.�?�����10�Y��9�.I���&��_��vx+���2&���>�NW��8�~�\�9��\�����Q}��,3�FH]=��G���N�~����{��v#��Rs�w��3^�c��������
a���qF]�v�V(��O�������H�q[$>N��>������s�o���s&|�m��h^��? ����V�L���R�D��uP�!������f�#��������2����bW�t��k�d2���r���S/����"_��OPf�#�U�'��QV��l� Ig
�+���KF�K����en��E����Z"A����>�~8�����B��p����;>>:�R��&3��;�������������~M��8��V$e�
�o���
���L
D����o����ah���P�������D���,���D%�0��/7U�s$� �6�)IB:�B��.'L��tNcFO��Z�U����������w��|��m��n:>�pV�|?���z��xA��2�����$Q��x�l����.��S�B`��%j�R����*� ��wV-b�L�L�8��*�jOF'%������({�e.��,�x��)�jU��4�u�����8�0�����j
q���60� �#/�\l�����.�;R&