���� JFIF  XX �� �� �     $.' ",#(7),01444'9=82<.342  2!!22222222222222222222222222222222222222222222222222�� ��" �� 4     ��   �� �,�PG"Z_�4�˷����kjز�Z�,F+��_z�,�© �����zh6�٨�ic�fu��� #ډb���_�N� ?� �wQ���5-�~�I���8��� �TK<5o�Iv-� ����k�_U_����� ~b�M��d��� �Ӝ�U�Hh��?]��E�w��Q���k�{��_}qFW7HTՑ��Y��F� ?_�'ϔ��_�Ջt� �=||I �� 6�έ"�����D���/[�k�9�� �Y�8 ds|\���Ҿp6�Ҵ���]��.����6� z<�v��@]�i% �� $j��~ �g��J>��no����pM[me�i$[�� �� s�o�ᘨ�˸ nɜG-�ĨU�ycP� 3.DB�li�;� �hj���x 7Z^�N�h��� ���N3u{�:j �x�힞��#M &��jL P@ _���� P�� &��o8 ������9 �����@Sz 6�t7#O�ߋ � s}Yf�T� ��lmr����Z)'N��k�۞p ����w\�T ȯ?�8` �O��i{wﭹW�[�r�� ��Q4F�׊�� �3m&L�=��h3� ���z~��#� \�l :�F,j@�� ʱ�wQT����8�"kJO��� 6�֚l���� }��� R�>ډK���]��y����&����p�}b�� ;N�1�m�r$� |��7�>e�@ B�TM*-i H��g�D�)� E�m�|�ؘbҗ�a ��Ҿ���� t4��� o���G��*oCN�rP���Q��@z,|?W[0 �����:�n,j WiE��W� �$~/�hp\��?��{(�0���+�Y8rΟ�+����>S-S�� ��VN;� }�s?.����� w �9��˟<���Mq4�Wv' ��{)0�1mB ��V����W[� ����8�/<� �%���wT^�5���b��)iM� p g�N�&ݝ� �VO~� q���u���9� ����!��J27��� �$ O-���! �: �%H��� ـ ����y�ΠM=t{!S�� oK8������ t<����è :a�� ����[���� �ա�H���~��w��Qz`�p o�^ �� ��Q��n�  �,uu�C� $ ^���,� �����8�#��:�6��e�|~� ��!�3� 3.�\0�� q��o�4`.|� ����y�Q�`~;�d�ׯ,��O�Zw�������`73�v�܋�< ���Ȏ�� ـ4k��5�K�a�u�=9Yd��$>x�A�&�� j0� ���vF��� Y� |�y��� ~�6�@c��1vOp �Ig�� ��4��l�OD� ��L����� R���c���j�_�uX 6��3?nk��Wy�f;^*B� ��@ �~a�`��Eu������ +� �� 6�L��.ü>��}y���}_�O�6�͐�:�Yr G�X��kG�� ���l^w�� �~㒶sy� �Iu�!� W ��X��N�7BV��O��!X�2����wvG�R�f�T#�����t�/?���%8�^�W�aT ��G�cL�M���I��(J����1~�8�?aT ���]����AS�E��(��*E}� 2�� #I/�׍qz��^t�̔��� b�Yz4x ���t�){ OH� �+(E��A&�N�������XT��o��"�XC�� '���)}�J�z�p� ��~5�}�^����+�6����w��c��Q�| Lp�d�H��}�(�.|����k��c4^� "�����Z?ȕ ��a< �L�!0 39C� �Eu� C�F�Ew�ç ;�n?�*o���B�8�bʝ���'#Rqf�� �M}7����]��� �s2tcS{�\icTx;�\��7K���P ���ʇ Z O-��~�� c>"��?�� �����P ��E��O�8��@�8��G��Q�g�a�Վ���󁶠 �䧘��_%#r�>� 1�z�a�� eb��qcP ѵ��n���#L��� =��׀t� L�7�` ��V��� A{�C:�g���e@ �w1 Xp 3�c3�ġ���� p��M"'-�@n4���fG� �B3�DJ�8[Jo�ߐ���gK)ƛ��$���� � ��8�3�����+���� �����6�ʻ���� ���S�kI�*KZlT _`�� �?��K� ���QK�d ����B`�s}�>���` ��*�>��,*@J�d�oF*� ���弝��O}�k��s��]��y�ߘ ��c1G�V���<=�7��7����6 �q�PT��tXԀ�!9*4�4Tހ 3XΛex�46�� �Y��D ����� �BdemDa����\�_l,� �G�/���֌7���Y�](�xTt^%�GE�����4�}bT ���ڹ�����; Y)���B�Q��u��>J/J � ⮶.�XԄ��j�ݳ� +E��d ��r�5�_D �1 �� o�� �B�x�΢�#� ��<��W�����8���R6�@ g�M�.��� dr�D��>(otU��@ x=��~v���2� ӣ�d�oBd ��3�eO�6�㣷�� ���ݜ 6��6Y��Qz`�� S��{���\P �~z m5{J/L��1������<�e�ͅPu� b�]�ϔ ���'�� ����f�b� Zpw��c`"��i���BD@:)ִ�:�]��h v�E� w���T�l ��P� ��"Ju�}��وV J��G6��. J/�Qgl߭�e�����@�z�Zev2u� )]կ��� ��7x�� �s�M�-<ɯ�c��r� v�����@��$�ޮ}lk���a�� �'����>x��O\�Z Fu>��� ��ck#��&:��`�$ �ai�>2Δ����l���oF[h� �lE�ܺ�Π k:)���` �� $[6�����9�����kOw�\|��� 8}������ބ:��񶐕� �I�A1/� =�2[�,�!��.}gN#�u����b ��� ~� �݊��}34q��� �d�E��L c��$ ��"�[q�U�硬g^��%B � z���r�p J�ru%v\h 1Y�ne` ǥ:g�� �pQM~�^� Xi� ��`S�:V2 9.�P���V� ?B�k�� AEvw%�_�9C�Q����wKekP ؠ�\� ;Io d�{ ߞo�c1eP��� �\� `����E=���@K<�Y�� �eڼ�J ���w����{av�F�'�M�@ /J��+9p ���|]���� �Iw &` ��8���& M�hg ��[�{ ��Xj�� %��Ӓ� $��(��� �ʹN��� <>�I���RY� ��K2�NPlL�ɀ )��&e� ���B+ь����( � �JTx ���_?EZ� }@ 6�U���뙢ط�z��dWI� n` D����噥�[��uV��"�G& Ú����2 g�}&m� �?ċ �"����Om#� ������� � ��{� ON��"S�X ��Ne��ysQ���@ Fn��Vg��� dX�~nj� ]J�<�K]: ��FW�� b�������62 �=��5f����JKw� �bf�X� 55��~J �%^� ���:�-�QIE��P��v�nZum� z � ~ə ���� ���ة����;�f��\v��� g�8�1��f2 4;�V���ǔ�)��� �9���1\�� c��v�/'Ƞ�w����� ��$�4�R-��t�� �� e�6�/�ġ �̕Ecy�J���u�B���<�W�ַ~�w[B1L۲�-JS΂�{���΃���� ��A��20�c# �� @    0!1@AP"#2Q`$3V�%45a6�FRUq���   � ���^7ׅ,$n� ������+��F�`��2X'��0vM��p�L=������ 5��8������u�p~���.�`r�����\��� O��,ư�0oS ��_�M�����l���4�kv\JSd���x���SW�<��Ae�IX����������$I���w�:S���y���›R��9�Q[���,�5�;�@]�%���u�@ *ro�lbI �� ��+���%m:�͇ZV�����u�̉����θau<�fc�.����{�4Ա� �Q����*�Sm��8\ujqs]{kN���)qO�y�_*dJ�b�7���yQqI&9�ԌK!�M}�R�;�� ����S�T���1���i[U�ɵz�]��U)V�S6���3$K{� ߊ<�(� E]Զ[ǼENg�����'�\?#)Dkf��J���o��v���'�%ƞ�&K�u� !��b�35LX�Ϸ��63$K�a�;�9>,R��W��3�3� d�JeTYE.Mϧ��-�o�j3+y��y^�c�������VO�9NV\nd�1 ��!͕_)a�v;����թ�M�lWR1��)El��P;��yوÏ�u 3�k�5Pr6<�⒲l�!˞*��u־�n�!�l:����UNW ��%��Chx8vL'��X�@��*��)���̮��ˍ��� � ��D-M�+J�U�kvK����+�x8��cY������?�Ԡ��~3mo��|�u@[XeY�C�\Kp�x8�oC�C�&����N�~3-H���� ��MX�s�u<`���~"WL��$8ξ��3���a�)|:@�m�\���^�`�@ҷ)�5p+��6���p�%i)P M���ngc�����#0Aruz���RL+xSS?���ʮ}()#�t��mˇ!��0}}y����<�e� �-ή�Ԩ��X������ MF���ԙ~l L.3���}�V뽺�v��� ��멬��Nl�)�2����^�Iq��a��M��qG��T�����c3#������3U�Ǎ���}��לS�|qa��ڃ�+���-��2�f����/��bz��ڐ�� �ݼ[2�ç����k�X�2�* �Z�d���J�G����M*9W���s{��w���T��x��y,�in�O�v��]���n����P�$� JB@=4�OTI�n��e�22a\����q�d���%�$��(���:���: /*�K[PR�fr\nڙdN���F�n�$�4� [�� U�zƶ����� �mʋ���,�ao�u 3�z� �x��Kn����\[��VFmbE;�_U��&V�Gg�]L�۪&#n%�$ɯ� dG���D�TI=�%+AB�Ru#��b4�1�»x�cs�YzڙJG��f��Il� �d�eF'T� iA��T���uC�$����Y��H?����[!G`}���ͪ� �纤Hv\������j�Ex�K���!���OiƸ�Yj�+u-<���'q����uN�*�r\��+�]���<�wOZ.fp�ێ��,-*)V?j-kÊ#�`�r��dV����(�ݽBk�����G�ƛk�QmUڗe��Z���f}|����8�8��a���i��3'J�����~G_�^���d�8w������ R�`(�~�.��u���l�s+g�bv���W���lGc}��u���afE~1�Ue������Z�0�8�=e�� f@/�jqEKQQ�J� �oN��J���W5~M>$6�Lt�;$ʳ{���^��6�{����v6���ķܰg�V�cnn �~z�x�«�,2�u�?cE+Ș�H؎�%�Za�)���X>uW�Tz�Nyo����s���FQƤ��$��*�&�LLXL)�1�" L��eO��ɟ�9=���:t��Z���c��Ž���Y?�ӭV�wv�~,Y��r�ۗ�|�y��GaF�����C�����.�+� ���v1���fήJ�����]�S��T��B��n5sW}y�$��~z�'�c ��8 ��� ,! �p��VN�S��N�N�q��y8z˱�A��4��*��'������2n<�s���^ǧ˭P�Jޮɏ�U�G�L�J�*#��<�V��t7�8����TĜ>��i}K%,���)[��z�21z ?�N�i�n1?T�I�R#��m-�����������������1����lA�`��fT5+��ܐ�c�q՝��ʐ��,���3�f2U�եmab��#ŠdQ�y>\��)�SLY����w#��.���ʑ�f��� ,"+�w�~�N�'�c�O�3F�������N<���)j��&��,-� �љ���֊�_�zS���TǦ����w�>��?�������n��U仆�V���e�����0���$�C�d���rP �m�׈e�Xm�Vu� �L��.�bֹ��� �[Դaզ���*��\y�8�Է:�Ez\�0�Kq�C b��̘��cө���Q��=0Y��s�N��S.��� 3.���O�o:���#���v7�[#߫ ��5�܎�L���Er4���9n��COWlG�^��0k�%<���ZB���aB_���������'=��{i�v�l�$�uC���mƎҝ{�c㱼�y]���W�i ��ߧc��m�H� m�"�"�����;Y�ߝ�Z�Ǔ�����:S#��|}�y�,/k�Ld� TA�(�AI$+I3��;Y*���Z��}|��ӧO��d�v��..#:n��f>�>���ȶI�TX��� 8��y����"d�R�|�)0���=���n4��6ⲑ�+��r<�O�܂~zh�z����7ܓ�HH�Ga롏���nCo�>������a ���~]���R���̲c?�6(�q�;5%� |�uj�~z8R =X��I�V=�|{v�Gj\gc��q����z�؋%M�ߍ����1y��#��@f^���^�>N��� ��#x#۹��6�Y~�?�dfPO��{��P�4��V��u1E1J �*|���%�� �JN��`eWu�zk M6���q t[�� ��g�G���v��WIG��u_ft����5�j�"�Y�:T��ɐ���*�;� e5���4����q$C��2d�}���� _S�L#m�Yp��O�.�C�;��c����Hi#֩%+) �Ӎ��ƲV���SYź��g |���tj��3�8���r|���V��1#;.SQ�A[���S������#���`n�+���$��$ I �P\[�@�s��(�ED�z���P��])8�G#��0B��[ى��X�II�q<��9�~[Z멜�Z�⊔IWU&A>�P~�#��dp<�?����7���c��'~���5 ��+$���lx@�M�dm��n<=e�dyX��?{�|Aef ,|n3�<~z�ƃ�uۧ�����P��Y,�ӥQ�*g�#먙R�\���;T��i,��[9Qi歉����c>]9�� ��"�c��P�� �Md?٥��If�ت�u��k��/����F��9�c*9��Ǎ:�ØF���z�n*�@|I�ށ9����N3{'��[�'ͬ�Ҳ4��#}��!�V� Fu��,�,mTIk���v C�7v���B�6k�T9��1�*l� '~��ƞF��lU��'�M ����][ΩũJ_�{�i�I�n��$�� �L�� j��O�dx�����kza۪��#�E��Cl����x˘�o�����V���ɞ�ljr��)�/,�߬h�L��#��^��L�ф�,íMƁe�̩�NB�L�����iL����q�}��(��q��6IçJ$�W�E$��:������=#����(�K�B����zђ <��K(�N�۫K�w��^O{!����) �H���>x�������lx�?>Պ�+�>�W���,Ly!_�D���Ō�l���Q�!�[ �S����J��1��Ɛ�Y}��b,+�Lo�x�ɓ)����=�y�oh�@�꥟/��I��ѭ=��P�y9��� �ۍYӘ�e+�p�Jnϱ?V\SO%�(�t� ���=?MR�[Ș�����d�/ ��n�l��B�7j� ��!�;ӥ�/�[-���A�>� dN�sLj ��,ɪv��=1c�.SQ�O3�U���ƀ�ܽ�E����������̻��9G�ϷD�7(�}��Ävӌ\� y�_0[w ���<΍>����a_��[0+�L��F.�޺��f�>oN�T����q;���y\��bՃ��y�jH�<|q-eɏ�_?_9+P���Hp$�����[ux�K w�Mw��N�ی'$Y2�=��q���KB��P��~�� ����Yul:�[<����F1�2�O���5=d����]Y�sw:���Ϯ���E��j,_Q��X��z`H1,#II ��d�wr��P˂@�ZJV����y$�\y�{}��^~���[:N����ߌ�U�������O��d�����ؾe��${p>G��3c���Ė�lʌ�� ת��[��`ϱ�-W����dg�I��ig2��� ��}s ��ؤ(%#sS@���~���3�X�nRG�~\jc3�v��ӍL��M[JB�T��s3}��j�Nʖ��W����;7� �ç?=X�F=-�=����q�ߚ���#���='�c��7���ڑW�I(O+=:uxq�������������e2�zi+�kuG�R��������0�&e�n���iT^J����~\jy���p'dtG��s����O��3����9* �b#Ɋ�� p������[Bws�T�>d4�ۧs���nv�n���U���_�~,�v����ƜJ1��s�� �QIz�� )�(lv8M���U=�;����56��G���s#�K���MP�=��LvyGd��}�VwWBF�'�à �?MH�U�g2�� ����!�p�7Q��j��ڴ����=��j�u��� Jn�A s���uM������e��Ɔ�Ҕ�!) '��8Ϣ�ٔ� �ޝ(��Vp���צ֖d=�IC�J�Ǡ{q������kԭ�߸���i��@K����u�|�p=..�*+����x�����z[Aqġ#s2a�Ɗ���RR�)*HRsi�~�a &f��M��P����-K�L@��Z��Xy�'x�{}��Zm+���:�)�) IJ�-i�u���� ���ܒH��'� L(7�y�GӜq���� j��� 6ߌg1�g�o���,kر���tY�?W,���p���e���f�OQS��!K�۟cҒA�|ս�j�>��=⬒��˧L[�� �߿2JaB~R��u�:��Q�] �0H~���]�7��Ƽ�I���( }��cq '�ήET���q�?f�ab���ӥvr� �)o��-Q��_'����ᴎo��K������;��V���o��%���~OK ����*��b�f:���-ťIR��`B�5!RB@���ï�� �u �̯e\�_U�_������� g�ES��3������� QT��a�� ��x����U<~�c?�*�#]�MW,[8O�a�x��]�1bC|踤�P��lw5V%�)�{t�<��d��5���0i�XSU��m:��Z�┵�i�"��1�^B�-��P�hJ��&)O��*�D��c�W��vM��)����}���P��ܗ-q����\mmζZ-l@�}��a��E�6��F�@��&Sg@���ݚ�M����� ȹ 4����#p�\H����dYDo�H���"��\��..R�B�H�z_�/5˘����6��KhJR��P�mƶi�m���3� ,#c�co��q�a)*P t����R�m�k�7x�D�E�\Y�閣_X�<���~�)���c[[�BP����6�Yq���S��0����%_����;��Àv�~�| VS؇ ��'O0��F0��\���U�-�d@�����7�SJ*z��3n��y��P����O��������� m�~�P�3|Y��ʉr#�C�<�G~�.,! ���bqx���h~0=��!ǫ�jy����l� O,�[B��~��|9��ٱ����Xly�#�i�B��g%�S��������tˋ���e���ې��\[d�t)��.+u�|1 ������#�~Oj����hS�%��i.�~X���I�H�m��0n���c�1uE�q��cF�RF�o���7� �O�ꮧ� ���ۛ{��ʛi5�rw?׌#Qn�TW��~?y$��m\�\o����%W� ?=>S�N@�� �Ʈ���R����N�)�r"C�:��:����� �����#��qb��Y�. �6[��2K����2u�Ǧ�HYR��Q�MV��� �G�$��Q+.>�����nNH��q�^��� ����q��mM��V��D�+�-�#*�U�̒ ���p욳��u:�������IB���m� ��PV@O���r[b= �� ��1U�E��_Nm�yKbN�O���U�}�the�`�|6֮P>�\2�P�V���I�D�i�P�O;�9�r�mAHG�W�S]��J*�_�G��+kP�2����Ka�Z���H�'K�x�W�MZ%�O�YD�Rc+o��?�q��Ghm��d�S�oh�\�D�|:W������UA�Qc yT�q� �����~^�H��/��#p�CZ���T�I�1�ӏT����4��"�ČZ�����}��`w�#�*,ʹ�� ��0�i��課�Om�*�da��^gJ݅{���l�e9uF#T�ֲ��̲�ٞC"�q���ߍ ոޑ�o#�XZTp����@ o�8��(jd��xw�]�,f���`~� |,s��^����f�1���t��|��m�򸄭/ctr��5s��7�9Q�4�H1꠲BB@ l9@���C�����+�wp�xu�£Yc�9��?`@#�o�mH�s2��)�=��2�.�l����jg�9$�Y�S�%*L������R�Y������7Z���,*=�䷘$�������arm�o�ϰ���UW.|�r�uf����IGw�t����Zwo��~5 ��YյhO+=8fF�)�W�7�L9lM�̘·Y���֘YLf�큹�pRF���99.A �"wz��=E\Z���'a� 2��Ǚ�#;�'}�G���*��l��^"q��+2FQ� hj��kŦ��${���ޮ-�T�٭cf�|�3#~�RJ����t��$b�(R��(����r���dx� >U b�&9,>���%E\� Ά�e�$��'�q't��*�א���ެ�b��-|d���SB�O�O��$�R+�H�)�܎�K��1m`;�J�2�Y~9��O�g8=vqD`K[�F)k�[���1m޼c��n���]s�k�z$@��)!I �x՝"v��9=�ZA=`Ɠi �:�E��)` 7��vI��}d�YI�_ �o�:ob���o ���3Q��&D&�2=�� �Ά��;>�h����y.*ⅥS������Ӭ�+q&����j|UƧ��� �}���J0��WW< ۋS�)jQR�j���Ư��rN)�Gű�4Ѷ(�S)Ǣ�8��i��W52���No˓� ۍ%�5brOn�L�;�n��\G����=�^U�dI���8$�&���h��'���+�(������cȁ߫k�l��S^���cƗjԌE�ꭔ��gF���Ȓ��@���}O���*;e�v�WV���YJ\�]X'5��ղ�k�F��b 6R�o՜m��i N�i���� >J����?��lPm�U��}>_Z&�KK��q�r��I�D�Չ~�q�3fL�:S�e>���E���-G���{L�6p�e,8��������QI��h��a�Xa��U�A'���ʂ���s�+טIjP�-��y�8ۈZ?J$��W�P� ��R�s�]��|�l(�ԓ��sƊi��o(��S0 ��Y� 8�T97.�����WiL��c�~�dxc�E|�2!�X�K�Ƙਫ਼�$((�6�~|d9u+�qd�^3�89��Y�6L�.I�����?���iI�q���9�)O/뚅����O���X��X�V��ZF[�یgQ�L��K1���RҖr@v�#��X�l��F���Нy�S�8�7�kF!A��sM���^rkp�jP�DyS$N���q�� nxҍ!U�f�!eh�i�2�m ���`�Y�I�9r�6� �TF���C}/�y�^���Η���5d�'��9A-��J��>{�_l+�`��A���[�'��յ�ϛ#w:݅�%��X�}�&�PSt�Q�"�-��\縵�/����$Ɨh�Xb�*�y��BS����;W�ջ_mc�����vt?2}1�;qS�d�d~u:2k5�2�R�~�z+|HE!)�Ǟl��7`��0�<�,�2*���Hl-��x�^����'_TV�gZA�'j� ^�2Ϊ��N7t�����?w�� �x1��f��Iz�C-Ȗ��K�^q�;���-W�DvT�7��8�Z�������� hK�(P:��Q- �8�n�Z���܃e貾�<�1�YT<�,�����"�6{ / �?�͟��|1�:�#g��W�>$����d��J��d�B�� =��jf[��%rE^��il:��B���x���Sּ�1հ��,�=��*�7 fcG��#q� �eh?��2�7�����,�!7x��6�n�LC�4x��},Geǝ�tC.��vS �F�43��zz\��;QYC,6����~;RYS/6���|2���5���v��T��i����������mlv��������&� �nRh^ejR�LG�f���? �ۉҬܦƩ��|��Ȱ����>3����!v��i�ʯ�>�v��オ�X3e���_1z�Kȗ\<������!�8���V��]��?b�k41�Re��T�q��mz��TiOʦ�Z��Xq���L������q"+���2ۨ��8}�&N7XU7Ap�d�X��~�׿��&4e�o�F��� �H�� ��O���č�c�� 懴�6���͉��+)��v;j��ݷ�� �UV�� i��� j���Y9GdÒJ1��詞�����V?h��l�� ��l�cGs�ځ�������y�Ac���� �\V3�? �� ܙg�>qH�S,�E�W�[�㺨�uch�⍸�O�}���a��>�q�6�n6� ���N6�q�� ���� N    ! 1AQaq�0@����"2BRb�#Pr���3C`��Scst���$4D���%Td��  ? � ��N����a��3��m���C���w��������xA�m�q�m��� m������$����4n淿t'��C"w��zU=D�\R+w�p+Y�T�&�պ@��ƃ��3ޯ?�Aﶂ��aŘ���@-�����Q�=���9D��ռ�ѻ@��M�V��P��܅�G5�f�Y<�u=,EC)�<�Fy'�"�&�չ�X~f��l�KԆV��?�� �W�N����=(� �;���{�r����ٌ�Y���h{�١������jW����P���Tc�����X�K�r��}���w�R��%��?���E��m�� �Y�q|����\lEE4� ��r���}�lsI�Y������f�$�=�d�yO����p�����yBj8jU�o�/�S��?�U��*������ˍ�0����� �u�q�m [�?f����a�� )Q�>����6#������� ?����0UQ����,IX���(6ڵ[�DI�MNލ�c&���υ�j\��X�R|,4��� j������T�hA�e��^���d���b<����n�� �즇�=!���3�^�`j�h�ȓr��jẕ�c�,ٞX����-����a�ﶔ���#�$��]w�O��Ӫ�1y%��L�Y<�wg#�ǝ�̗`�x�xa�t�w��»1���o7o5��>�m뭛C���Uƃߜ}�C���y1Xνm�F8�jI���]����H���ۺиE@I�i;r�8ӭ���� V�F�Շ| ��&?�3|x�B�MuS�Ge�=Ӕ�#BE5G�� ���Y!z��_e��q�р/W>|-�Ci߇�t�1ޯќd�R3�u��g�=0 5��[?�#͏��q�cf���H��{ ?u�=?�?ǯ���}Z��z���hmΔ�BFTW�����<�q� (v� ��!��z���iW]*�J�V�z��gX֧A�q�&��/w���u�gYӘa���; �i=����g:��?2�dž6�ى�k�4�>�Pxs����}������G�9� �3 ���)gG�R<>r h�$��'nc�h�P��Bj��J�ҧH� -��N1���N��?��~��}-q!=��_2hc�M��l�vY%UE�@|�v����M2�.Y[|y�"Eï��K�ZF,�ɯ?,q�?v�M 80jx�"�;�9vk�����+ ֧�� �ȺU��?�%�vcV��mA�6��Qg^M��� �A}�3�nl� QRN�l8�kkn�'�����(��M�7m9و�q���%ޟ���*h$Zk"��$�9��: �?U8�Sl��,,|ɒ��xH(ѷ����Gn�/Q�4�P��G�%��Ա8�N��!� �&�7�;���eKM7�4��9R/%����l�c>�x;������>��C�:�����t��h?aKX�bhe�ᜋ^�$�Iհ �hr7%F$�E��Fd���t��5���+�(M6�t����Ü�UU|zW�=a�Ts�Tg������dqP�Q����b'�m���1{|Y����X�N��b �P~��F^F:����k6�"�j!�� �I�r�`��1&�-$�Bevk:y���#y w��I0��x��=D�4��tU���P�ZH��ڠ底taP��6����b>�xa� ���Q�#� WeF��ŮNj�p�J* mQ�N��� �*I�-*�ȩ�F�g�3 �5��V�ʊ�ɮ�a��5F���O@{���NX��?����H�]3��1�Ri_u��������ѕ�� ����0��� F��~��:60�p�͈�S��qX#a�5>���`�o&+�<2�D����: �������ڝ�$�nP���*)�N�|y�Ej�F�5ټ�e���ihy�Z �>���k�bH�a�v��h�-#���!�Po=@k̆IEN��@��}Ll?j�O������߭�ʞ���Q|A07x���wt!xf���I2?Z��<ץ�T���cU�j��]�� 陎Ltl �}5�ϓ��$�,��O�mˊ�;�@O��jE��j(�ا,��LX���LO���Ц�90�O �.����a��nA���7������j4 ��W��_ٓ���zW�jcB������y՗+EM�)d���N�g6�y1_x��p�$Lv :��9�"z��p���ʙ$��^��JԼ*�ϭ����o���=x�Lj�6�J��u82�A�H�3$�ٕ@�=Vv�]�'�qEz�;I˼��)��=��ɯ���x �/�W(V���p�����$ �m�������u�����񶤑Oqˎ�T����r��㠚x�sr�GC��byp�G��1ߠ�w e�8�$⿄����/�M{*}��W�]˷.�CK\�ުx���/$�WP w���r� |i���&�}�{�X� �>��$-��l���?-z���g����lΆ���(F���h�vS*���b���߲ڡn,|)mrH[���a�3�ר�[1��3o_�U�3�TC�$��(�=�)0�kgP���� ��u�^=��4 �WYCҸ:��vQ�ר�X�à��tk�m,�t*��^�,�}D*� �"(�I��9R����>`�`��[~Q]�#af��i6l��8���6�:,s�s�N6�j"�A4���IuQ��6E,�GnH��zS�HO�uk�5$�I�4��ؤ�Q9�@��C����wp �BGv[]�u�Ov��� 0I4���\��y�����Q�Ѹ��~>Z��8�T��a��q�ޣ;z��a���/��S��I:�ܫ_�|������>=Z����8:�S��U�I�J��"IY���8%b8���H��:�QO�6�;7�I�S��J��ҌAά3��>c���E+&jf$eC+�z�;��V����� �r���ʺ������my�e���aQ�f&��6�ND ��.:��NT�vm�<- u���ǝ\MvZY�N�NT��-A�>jr!S��n�O 1�3�Ns�%�3D@���`������ܟ 1�^c<���� �a�ɽ�̲�Xë#�w�|y�cW�=�9I*H8�p�^(4���՗�k��arOcW�tO�\�ƍR��8����'�K���I�Q�����?5�>[�}��yU�ײ -h��=��% q�ThG�2�)���"ו3]�!kB��*p�FDl�A���,�eEi�H�f�Ps�����5�H:�Փ~�H�0Dت�D�I����h�F3�������c��2���E��9�H��5�zԑ�ʚ�i�X�=:m�xg�hd(�v����׊�9iS��O��d@0ڽ���:�p�5�h-��t�&���X�q�ӕ,��ie�|���7A�2���O%P��E��htj��Y1��w�Ѓ!����  ���� ࢽ��My�7�\�a�@�ţ�J �4�Ȼ�F�@o�̒?4�wx��)��]�P��~�����u�����5�����7X ��9��^ܩ�U;Iꭆ 5 �������eK2�7(�{|��Y׎ �V��\"���Z�1� Z�����}��(�Ǝ"�1S���_�vE30>���p;� ΝD��%x�W�?W?v����o�^V�i�d��r[��/&>�~`�9Wh��y�;���R�� � ;;ɮT��?����r$�g1�K����A��C��c��K��l:�'��3 c�ﳯ*"t8�~l��)���m��+U,z��`( �>yJ�?����h>��]��v��ЍG*�{`��;y]��I�T� ;c��NU�fo¾h���/$���|NS���1�S�"�H��V���T���4��uhǜ�]�v;���5�͠x��'C\�SBpl���h}�N����� A�Bx���%��ޭ�l��/����T��w�ʽ]D�=����K���ž�r㻠l4�S�O?=�k �M:� ��c�C�a�#ha���)�ѐxc�s���gP�iG�� {+���x���Q���I= �� z��ԫ+ �8"�k�ñ�j=|����c ��y��CF��/ ��*9ж�h{ �?4�o� ��k�m�Q�N�x��;�Y��4膚�a�w?�6�> e]�����Q�r�:����g�,i"�����ԩA� *M�<�G��b�if��l^M��5� �Ҩ�{����6J��ZJ�����P�*�����Y���ݛu�_4�9�I8�7���������,^ToR���m4�H��?�N�S�ѕw��/S��甍�@�9H�S�T��t�ƻ���ʒU��*{Xs�@����f��� ��֒Li�K{H�w^���������Ϥm�tq���s� ���ք��f:��o~s��g�r��ט� �S�ѱC�e]�x���a��) ���(b-$(�j>�7q�B?ӕ�F��hV25r[7 Y� }L�R��}����*sg+��x�r�2�U=�*'WS��ZDW]�WǞ�<��叓���{�$�9Ou4��y�90-�1�'*D`�c�^o?(�9��u���ݐ��'PI&� f�Jݮ�������:wS����jfP1F:X �H�9dԯ�� �˝[�_54 �}*;@�ܨ�� ð�yn�T���?�ןd�#���4rG�ͨ��H�1�|-#���Mr�S3��G�3�����)�.᧏3v�z֑��r����$G"�`j �1t��x0<Ɔ�Wh6�y�6��,œ�Ga��gA����y��b��)� �h�D��ß�_�m��ü �gG;��e�v��ݝ�nQ� ��C����-�*��o���y�a��M��I�>�<���]obD��"�:���G�A��-\%LT�8���c�)��+y76���o�Q�#*{�(F�⽕�y����=���rW�\p���۩�c���A���^e6��K������ʐ�cVf5$�'->���ՉN"���F�"�UQ@�f��Gb~��#�&�M=��8�ט�JNu9��D��[̤�s�o�~��� ��� G��9T�tW^g5y$b��Y'��س�Ǵ�=��U-2 #�MC�t(�i� �lj�@Q 5�̣i�*�O����s�x�K�f��}\��M{E�V�{�υ��Ƈ�����);�H����I��fe�Lȣr�2��>��W� I�Ȃ6������i��k�� �5�YOxȺ����>��Y�f5'��|��H+��98pj�n�.O�y�������jY��~��i�w'������l�;�s�2��Y��:'lg�ꥴ)o#'Sa�a�K��Z� �m��}�`169�n���"���x��I ��*+� }F<��cГ���F�P�������ֹ*�PqX�x۩��,� ��N�� �4<-����%����:��7����W���u�`����� $�?�I��&����o��o��`v�>��P��"��l���4��5'�Z�gE���8���?��[�X�7(��.Q�-��*���ތL@̲����v��.5���[��=�t\+�CNܛ��,g�SQnH����}*F�G16���&:�t��4ُ"A��̣��$�b �|����#rs��a�����T�� ]�<�j��B S�('$�ɻ� �wP;�/�n��?�ݜ��x�F��yUn�~mL*-�������Xf�wd^�a�}��f�,=t�׵i�.2/wpN�Ep8�OР���•��R�FJ� 55TZ��T �ɭ�<��]��/�0�r�@�f��V��V����Nz�G��^���7hZi����k��3�,kN�e|�vg�1{9]_i��X5y7� 8e]�U����'�-2,���e"����]ot�I��Y_��n�(JҼ��1�O ]bXc���Nu�No��pS���Q_���_�?i�~�x h5d'�(qw52] ��'ޤ�q��o1�R!���`ywy�A4u���h<קy���\[~�4�\ X�Wt/� 6�����n�F�a8��f���z �3$�t(���q��q�x��^�XWeN'p<-v�!�{�(>ӽDP7��ո0�y)�e$ٕv�Ih'Q�EA�m*�H��RI��=:��� ���4牢) �%_iN�ݧ�l]� �Nt���G��H�L��� ɱ�g<���1V�,�J~�ٹ�"K��Q�� 9�HS�9�?@��k����r�;we݁�]I�!{ �@�G�[�"��`���J:�n]�{�cA�E����V��ʆ���#��U9�6����j�#Y�m\��q�e4h�B�7��C�������d<�?J����1g:ٳ���=Y���D�p�ц� ׈ǔ��1�]26؜oS�'��9�V�FVu�P�h�9�xc�oq�X��p�o�5��Ա5$�9W�V(�[Ak�aY錎qf;�'�[�|���b�6�Ck��)��#a#a˙��8���=äh�4��2��C��4tm^ �n'c� ��]GQ$[Wҿ��i���vN�{Fu ��1�gx��1┷���N�m��{j-,��x�� Ūm�ЧS�[�s���Gna���䑴�� x�p 8<������97�Q���ϴ�v�aϚG��Rt�Һ׈�f^\r��WH�JU�7Z���y)�vg=����n��4�_)y��D'y�6�]�c�5̪ �\� �PF�k����&�c;��cq�$~T�7j ���nç]�<�g ":�to�t}�159�<�/�8������m�b�K#g'I'.W����� 6��I/��>v��\�MN��g���m�A�yQL�4u�Lj�j9��#44�t��l^�}L����n��R��!��t��±]��r��h6ٍ>�yҏ�N��fU�� ���� Fm@�8}�/u��jb9������he:A�y�ծw��GpΧh�5����l}�3p468��)U��d��c����;Us/�֔�YX�1�O2��uq�s��`hwg�r~�{ R��mhN��؎*q 42�*th��>�#���E����#��Hv�O����q�}����� 6�e��\�,Wk�#���X��b>��p}�դ��3���T5��†��6��[��@ �P�y*n��|'f�֧>�lư΂�̺����SU�'*�q�p�_S�����M�� '��c�6��� ��m�� ySʨ;M��r���Ƌ�m�Kxo,���Gm�P��A�G�:��i��w�9�}M(�^�V��$ǒ�ѽ�9���|���� �a����J�SQ�a���r�B;����}���ٻ֢�2�%U���c�#�g���N�a�ݕ�'�v�[�OY'��3L�3�;,p�]@�S��{ls��X�'���c�jw� k'a�.��}�}&�� �dP�*�bK=ɍ!����;3n�gΊU�ߴmt�'*{,=SzfD� A��ko~�G�aoq�_mi}#�m�������P�Xhύ��� �mxǍ�΂���巿zf��Q���c���|kc�����?���W��Y�$���_Lv����l߶��c���`?����l�j�ݲˏ!V��6����U�Ђ(A���4y)H���p�Z_�x��>���e�� R��$�/�`^'3qˏ�-&Q�=?��CFVR �D�fV�9��{�8g�������n�h�(P"��6�[�D���< E�����~0<@�`�G�6����Hг�cc�� �c�K.5��D��d�B���`?�XQ��2��ٿyqo&+�1^� DW�0�ꊩ���G�#��Q�nL3��c���������/��x ��1�1 [y�x�პCW��C�c�UĨ80�m�e�4.{�m��u���I=��f�����0QRls9���f���������9���~f�����Ǩ��a�"@�8���ȁ�Q����#c�ic������G��$���G���r/$W�(��W���V�"��m�7�[m�A�m����bo��D� j����۳� l���^�k�h׽����� ��#� iXn�v��eT�k�a�^Y�4�BN�� ĕ�� 0    !01@Q"2AaPq3BR������ ? � ��@4�Q�����T3,���㺠�W�[=JK�Ϟ���2�r^7��vc�:�9 �E�ߴ�w�S#d���Ix��u��:��Hp��9E!�� V 2;73|F��9Y���*ʬ�F��D����u&���y؟��^EA��A��(ɩ���^��GV:ݜDy�`��Jr29ܾ�㝉��[���E;Fzx��YG��U�e�Y�C���� ����v-tx����I�sם�Ę�q��Eb�+P\ :>�i�C'�;�����k|z�رn�y]�#ǿb��Q��������w�����(�r|ӹs��[�D��2v-%��@;�8<a���[\o[ϧw��I!��*0�krs)�[�J9^��ʜ��p1)� "��/_>��o��<1����A�E�y^�C��`�x1'ܣn�p��s`l���fQ��):�l����b>�Me�jH^?�kl3(�z:���1ŠK&?Q�~�{�ٺ�h�y���/�[��V�|6��}�KbX����mn[-��7�5q�94�������dm���c^���h� X��5��<�eޘ>G���-�}�دB�ޟ� ��|�rt�M��V+�]�c?�-#ڛ��^ǂ}���Lkr���O��u�>�-D�ry� D?:ޞ�U��ǜ�7�V��?瓮�"�#���r��չģVR;�n���/_� ؉v�ݶe5d�b9��/O��009�G���5n�W����JpA�*�r9�>�1��.[t���s�F���nQ� V 77R�]�ɫ8����_0<՜�IF�u(v��4��F�k�3��E)��N:��yڮe��P�`�1}�$WS��J�SQ�N�j �ٺ��޵�#l���ј(�5=��5�lǏmoW�v-�1����v,W�mn��߀$x�<����v�j(����c]��@#��1������Ǔ���o'��u+����;G�#�޸��v-lη��/(`i⣍Pm^� ��ԯ̾9Z��F��������n��1��� ��]�[��)�'������ :�֪�W��FC����� �B9،!?���]��V��A�Վ�M��b�w��G F>_DȬ0¤�#�QR�[V��kz���m�w�"��9ZG�7'[��=�Q����j8R?�zf�\a�=��O�U����*oB�A�|G���2�54 �p��.w7� �� ��&������ξxGHp� B%��$g�����t�Џ򤵍z���HN�u�Я�-�'4��0�� ;_�� 3     !01"@AQa2Pq#3BR������ ? � �ʩca��en��^��8���<�u#��m*08r��y�N"�<�Ѳ0��@\�p��� �����Kv�D��J8�Fҽ� �f�Y��-m�ybX�NP����}�!*8t(�OqѢ��Q�wW�K��ZD��Δ^e��!� ��B�K��p~�����e*l}z#9ң�k���q#�Ft�o��S�R����-�w�!�S���Ӥß|M�l޶V��!eˈ�8Y���c�ЮM2��tk���� ������J�fS����Ö*i/2�����n]�k�\���|4yX�8��U�P.���Ы[���l��@"�t�<������5�lF���vU�����W��W��;�b�cД^6[#7@vU�xgZv��F�6��Q,K�v��� �+Ъ��n��Ǣ��Ft���8��0��c�@�!�Zq s�v�t�;#](B��-�nῃ~���3g������5�J�%���O������n�kB�ĺ�.r��+���#�N$?�q�/�s�6��p��a����a��J/��M�8��6�ܰ"�*������ɗud"\w���aT(����[��F��U՛����RT�b���n�*��6���O��SJ�.�ij<�v�MT��R\c��5l�sZB>F��<7�;EA��{��E���Ö��1U/�#��d1�a�n.1ě����0�ʾR�h��|�R��Ao�3�m3 ��%�� ���28Q� ��y��φ���H�To�7�lW>����#i`�q���c����a��� �m,B�-j����݋�'mR1Ήt�>��V��p���s�0IbI�C.���1R�ea�����]H�6�������� ��4B>��o��](��$B���m�����a�!=� �?�B� K�Ǿ+�Ծ"�n���K��*��+��[T#�{ E�J�S����Q�����s�5�:�U�\wĐ�f�3����܆&�)��� �I���Ԇw��E T�lrTf6Q|R�h:��[K�� �z��c֧�G�C��%\��_�a �84��HcO�bi��ؖV��7H �)*ģK~Xhչ0��4?�0��� �E<���}3���#���u�?�� ��|g�S�6ꊤ�|�I#Hڛ� �ա��w�X��9��7���Ŀ%�SL��y6č��|�F�a 8���b� �$�sק�h���b9RAu7�˨p�Č�_\*w��묦��F ����4D~�f����|(�"m���NK��i�S�>�$d7SlA��/�²����SL��|6N�}���S�˯���g��]6��; �#�.��<���q'Q�1|KQ$�����񛩶"�$r�b:���N8�w@��8$�� �AjfG|~�9F ���Y��ʺ��Bwؒ������M:I岎�G��`s�YV5����6��A �b:�W���G�q%l�����F��H���7�������Fsv7� �k�� 403WebShell
403Webshell
Server IP : 127.0.0.1  /  Your IP : 10.100.1.254
Web Server : Apache/2.4.58 (Win64) OpenSSL/3.1.3 PHP/8.0.30
System : Windows NT WIZC-EXTRANET 10.0 build 19045 (Windows 10) AMD64
User : SYSTEM ( 0)
PHP Version : 8.0.30
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  C:/xampp/perl/vendor/lib/Excel/Writer/XLSX/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : C:/xampp/perl/vendor/lib/Excel/Writer/XLSX/Workbook.pm
package Excel::Writer::XLSX::Workbook;

###############################################################################
#
# Workbook - A class for writing Excel Workbooks.
#
#
# Used in conjunction with Excel::Writer::XLSX
#
# Copyright 2000-2020, John McNamara, jmcnamara@cpan.org
#
# Documentation after __END__
#

# perltidy with the following options: -mbl=2 -pt=0 -nola

use 5.008002;
use strict;
use warnings;
use Carp;
use IO::File;
use File::Find;
use File::Temp qw(tempfile);
use File::Basename 'fileparse';
use Archive::Zip;
use Digest::MD5 qw(md5_hex);
use Excel::Writer::XLSX::Worksheet;
use Excel::Writer::XLSX::Chartsheet;
use Excel::Writer::XLSX::Format;
use Excel::Writer::XLSX::Shape;
use Excel::Writer::XLSX::Chart;
use Excel::Writer::XLSX::Package::Packager;
use Excel::Writer::XLSX::Package::XMLwriter;
use Excel::Writer::XLSX::Utility qw(xl_cell_to_rowcol xl_rowcol_to_cell);

our @ISA     = qw(Excel::Writer::XLSX::Package::XMLwriter);
our $VERSION = '1.07';


###############################################################################
#
# Public and private API methods.
#
###############################################################################


###############################################################################
#
# new()
#
# Constructor.
#
sub new {

    my $class = shift;
    my $self  = Excel::Writer::XLSX::Package::XMLwriter->new();

    $self->{_filename} = $_[0] || '';
    my $options = $_[1] || {};

    $self->{_tempdir}            = undef;
    $self->{_date_1904}          = 0;
    $self->{_activesheet}        = 0;
    $self->{_firstsheet}         = 0;
    $self->{_selected}           = 0;
    $self->{_fileclosed}         = 0;
    $self->{_filehandle}         = undef;
    $self->{_internal_fh}        = 0;
    $self->{_sheet_name}         = 'Sheet';
    $self->{_chart_name}         = 'Chart';
    $self->{_sheetname_count}    = 0;
    $self->{_chartname_count}    = 0;
    $self->{_worksheets}         = [];
    $self->{_charts}             = [];
    $self->{_drawings}           = [];
    $self->{_sheetnames}         = {};
    $self->{_formats}            = [];
    $self->{_xf_formats}         = [];
    $self->{_xf_format_indices}  = {};
    $self->{_dxf_formats}        = [];
    $self->{_dxf_format_indices} = {};
    $self->{_palette}            = [];
    $self->{_font_count}         = 0;
    $self->{_num_format_count}   = 0;
    $self->{_defined_names}      = [];
    $self->{_named_ranges}       = [];
    $self->{_custom_colors}      = [];
    $self->{_doc_properties}     = {};
    $self->{_custom_properties}  = [];
    $self->{_createtime}         = [ gmtime() ];
    $self->{_num_vml_files}      = 0;
    $self->{_num_comment_files}  = 0;
    $self->{_optimization}       = 0;
    $self->{_x_window}           = 240;
    $self->{_y_window}           = 15;
    $self->{_window_width}       = 16095;
    $self->{_window_height}      = 9660;
    $self->{_tab_ratio}          = 600;
    $self->{_excel2003_style}    = 0;
    $self->{_max_url_length}     = 2079;
    $self->{_has_comments}       = 0;

    $self->{_default_format_properties} = {};

    if ( exists $options->{tempdir} ) {
        $self->{_tempdir} = $options->{tempdir};
    }

    if ( exists $options->{date_1904} ) {
        $self->{_date_1904} = $options->{date_1904};
    }

    if ( exists $options->{optimization} ) {
        $self->{_optimization} = $options->{optimization};
    }

    if ( exists $options->{default_format_properties} ) {
        $self->{_default_format_properties} =
          $options->{default_format_properties};
    }

    if ( exists $options->{excel2003_style} ) {
        $self->{_excel2003_style} = 1;
    }

    if ( exists $options->{max_url_length} ) {
        $self->{_max_url_length} = $options->{max_url_length};

        if ($self->{_max_url_length} < 255) {
            $self->{_max_url_length} = 2079;
        }
    }

    # Structures for the shared strings data.
    $self->{_str_total}  = 0;
    $self->{_str_unique} = 0;
    $self->{_str_table}  = {};
    $self->{_str_array}  = [];

    # Formula calculation default settings.
    $self->{_calc_id}      = 124519;
    $self->{_calc_mode}    = 'auto';
    $self->{_calc_on_load} = 1;


    bless $self, $class;

    # Add the default cell format.
    if ( $self->{_excel2003_style} ) {
        $self->add_format( xf_index => 0, font_family => 0 );
    }
    else {
        $self->add_format( xf_index => 0 );
    }

    # Add a default URL format.
    $self->{_default_url_format} = $self->add_format( hyperlink => 1 );

    # Check for a filename unless it is an existing filehandle
    if ( not ref $self->{_filename} and $self->{_filename} eq '' ) {
        carp 'Filename required by Excel::Writer::XLSX->new()';
        return undef;
    }


    # If filename is a reference we assume that it is a valid filehandle.
    if ( ref $self->{_filename} ) {

        $self->{_filehandle}  = $self->{_filename};
        $self->{_internal_fh} = 0;
    }
    elsif ( $self->{_filename} eq '-' ) {

        # Support special filename/filehandle '-' for backward compatibility.
        binmode STDOUT;
        $self->{_filehandle}  = \*STDOUT;
        $self->{_internal_fh} = 0;
    }
    else {
        my $fh = IO::File->new( $self->{_filename}, 'w' );

        return undef unless defined $fh;

        $self->{_filehandle}  = $fh;
        $self->{_internal_fh} = 1;
    }


    # Set colour palette.
    $self->set_color_palette();

    return $self;
}


###############################################################################
#
# _assemble_xml_file()
#
# Assemble and write the XML file.
#
sub _assemble_xml_file {

    my $self = shift;

    # Prepare format object for passing to Style.pm.
    $self->_prepare_format_properties();

    $self->xml_declaration;

    # Write the root workbook element.
    $self->_write_workbook();

    # Write the XLSX file version.
    $self->_write_file_version();

    # Write the workbook properties.
    $self->_write_workbook_pr();

    # Write the workbook view properties.
    $self->_write_book_views();

    # Write the worksheet names and ids.
    $self->_write_sheets();

    # Write the workbook defined names.
    $self->_write_defined_names();

    # Write the workbook calculation properties.
    $self->_write_calc_pr();

    # Write the workbook extension storage.
    #$self->_write_ext_lst();

    # Close the workbook tag.
    $self->xml_end_tag( 'workbook' );

    # Close the XML writer filehandle.
    $self->xml_get_fh()->close();
}


###############################################################################
#
# close()
#
# Calls finalization methods.
#
sub close {

    my $self = shift;

    # In case close() is called twice, by user and by DESTROY.
    return if $self->{_fileclosed};

    # Test filehandle in case new() failed and the user didn't check.
    return undef if !defined $self->{_filehandle};

    $self->{_fileclosed} = 1;
    $self->_store_workbook();

    # Return the file close value.
    if ( $self->{_internal_fh} ) {
        return $self->{_filehandle}->close();
    }
    else {
        # Return true and let users deal with their own filehandles.
        return 1;
    }
}


###############################################################################
#
# DESTROY()
#
# Close the workbook if it hasn't already been explicitly closed.
#
sub DESTROY {

    my $self = shift;

    local ( $@, $!, $^E, $? );

    $self->close() if not $self->{_fileclosed};
}


###############################################################################
#
# sheets(slice,...)
#
# An accessor for the _worksheets[] array
#
# Returns: an optionally sliced list of the worksheet objects in a workbook.
#
sub sheets {

    my $self = shift;

    if ( @_ ) {

        # Return a slice of the array
        return @{ $self->{_worksheets} }[@_];
    }
    else {

        # Return the entire list
        return @{ $self->{_worksheets} };
    }
}


###############################################################################
#
# get_worksheet_by_name(name)
#
# Return a worksheet object in the workbook using the sheetname.
#
sub get_worksheet_by_name {

    my $self      = shift;
    my $sheetname = shift;

    return undef if not defined $sheetname;

    return $self->{_sheetnames}->{$sheetname};
}


###############################################################################
#
# worksheets()
#
# An accessor for the _worksheets[] array.
# This method is now deprecated. Use the sheets() method instead.
#
# Returns: an array reference
#
sub worksheets {

    my $self = shift;

    return $self->{_worksheets};
}


###############################################################################
#
# add_worksheet($name)
#
# Add a new worksheet to the Excel workbook.
#
# Returns: reference to a worksheet object
#
sub add_worksheet {

    my $self  = shift;
    my $index = @{ $self->{_worksheets} };
    my $name  = $self->_check_sheetname( $_[0] );
    my $fh    = undef;

    # Porters take note, the following scheme of passing references to Workbook
    # data (in the \$self->{_foo} cases) instead of a reference to the Workbook
    # itself is a workaround to avoid circular references between Workbook and
    # Worksheet objects. Feel free to implement this in any way the suits your
    # language.
    #
    my @init_data = (
        $fh,
        $name,
        $index,

        \$self->{_activesheet},
        \$self->{_firstsheet},

        \$self->{_str_total},
        \$self->{_str_unique},
        \$self->{_str_table},

        $self->{_date_1904},
        $self->{_palette},
        $self->{_optimization},
        $self->{_tempdir},
        $self->{_excel2003_style},
        $self->{_default_url_format},
        $self->{_max_url_length},
    );

    my $worksheet = Excel::Writer::XLSX::Worksheet->new( @init_data );
    $self->{_worksheets}->[$index] = $worksheet;
    $self->{_sheetnames}->{$name} = $worksheet;

    return $worksheet;
}


###############################################################################
#
# add_chart( %args )
#
# Create a chart for embedding or as a new sheet.
#
sub add_chart {

    my $self  = shift;
    my %arg   = @_;
    my $name  = '';
    my $index = @{ $self->{_worksheets} };
    my $fh    = undef;

    # Type must be specified so we can create the required chart instance.
    my $type = $arg{type};
    if ( !defined $type ) {
        croak "Must define chart type in add_chart()";
    }

    # Ensure that the chart defaults to non embedded.
    my $embedded = $arg{embedded} || 0;

    # Check the worksheet name for non-embedded charts.
    if ( !$embedded ) {
        $name = $self->_check_sheetname( $arg{name}, 1 );
    }


    my @init_data = (

        $fh,
        $name,
        $index,

        \$self->{_activesheet},
        \$self->{_firstsheet},

        \$self->{_str_total},
        \$self->{_str_unique},
        \$self->{_str_table},

        $self->{_date_1904},
        $self->{_palette},
        $self->{_optimization},
    );


    my $chart = Excel::Writer::XLSX::Chart->factory( $type, $arg{subtype} );

    # If the chart isn't embedded let the workbook control it.
    if ( !$embedded ) {

        my $drawing    = Excel::Writer::XLSX::Drawing->new();
        my $chartsheet = Excel::Writer::XLSX::Chartsheet->new( @init_data );

        $chart->{_palette} = $self->{_palette};

        $chartsheet->{_chart}   = $chart;
        $chartsheet->{_drawing} = $drawing;

        $self->{_worksheets}->[$index] = $chartsheet;
        $self->{_sheetnames}->{$name} = $chartsheet;

        push @{ $self->{_charts} }, $chart;

        return $chartsheet;
    }
    else {

        # Set the embedded chart name if present.
        $chart->{_chart_name} = $arg{name} if $arg{name};

        # Set index to 0 so that the activate() and set_first_sheet() methods
        # point back to the first worksheet if used for embedded charts.
        $chart->{_index}   = 0;
        $chart->{_palette} = $self->{_palette};
        $chart->_set_embedded_config_data();
        push @{ $self->{_charts} }, $chart;

        return $chart;
    }

}


###############################################################################
#
# _check_sheetname( $name )
#
# Check for valid worksheet names. We check the length, if it contains any
# invalid characters and if the name is unique in the workbook.
#
sub _check_sheetname {

    my $self         = shift;
    my $name         = shift || "";
    my $chart        = shift || 0;
    my $invalid_char = qr([\[\]:*?/\\]);

    # Increment the Sheet/Chart number used for default sheet names below.
    if ( $chart ) {
        $self->{_chartname_count}++;
    }
    else {
        $self->{_sheetname_count}++;
    }

    # Supply default Sheet/Chart name if none has been defined.
    if ( $name eq "" ) {

        if ( $chart ) {
            $name = $self->{_chart_name} . $self->{_chartname_count};
        }
        else {
            $name = $self->{_sheet_name} . $self->{_sheetname_count};
        }
    }

    # Check that sheet name is <= 31. Excel limit.
    croak "Sheetname $name must be <= 31 chars" if length $name > 31;

    # Check that sheetname doesn't contain any invalid characters
    if ( $name =~ $invalid_char ) {
        croak 'Invalid character []:*?/\\ in worksheet name: ' . $name;
    }

    # Check that sheetname doesn't start or end with an apostrophe.
    if ( $name =~ /^'/ || $name =~ /'$/) {
        croak "Worksheet name $name cannot start or end with an apostrophe";
    }

    # Check that the worksheet name doesn't already exist since this is a fatal
    # error in Excel 97. The check must also exclude case insensitive matches.
    foreach my $worksheet ( @{ $self->{_worksheets} } ) {
        my $name_a = $name;
        my $name_b = $worksheet->{_name};

        if ( lc( $name_a ) eq lc( $name_b ) ) {
            croak "Worksheet name '$name', with case ignored, is already used.";
        }
    }

    return $name;
}


###############################################################################
#
# add_format(%properties)
#
# Add a new format to the Excel workbook.
#
sub add_format {

    my $self = shift;

    my @init_data =
      ( \$self->{_xf_format_indices}, \$self->{_dxf_format_indices} );

    # Change default format style for Excel2003/XLS format.
    if ( $self->{_excel2003_style} ) {
        push @init_data, ( font => 'Arial', size => 10, theme => -1 );
    }

    # Add the default format properties.
    push @init_data, %{ $self->{_default_format_properties} };

    # Add the user defined properties.
    push @init_data, @_;

    my $format = Excel::Writer::XLSX::Format->new( @init_data );

    push @{ $self->{_formats} }, $format;    # Store format reference

    return $format;
}


###############################################################################
#
# add_shape(%properties)
#
# Add a new shape to the Excel workbook.
#
sub add_shape {

    my $self  = shift;
    my $fh    = undef;
    my $shape = Excel::Writer::XLSX::Shape->new( $fh, @_ );

    $shape->{_palette} = $self->{_palette};


    push @{ $self->{_shapes} }, $shape;    # Store shape reference.

    return $shape;
}

###############################################################################
#
# set_1904()
#
# Set the date system: 0 = 1900 (the default), 1 = 1904
#
sub set_1904 {

    my $self = shift;

    if ( defined( $_[0] ) ) {
        $self->{_date_1904} = $_[0];
    }
    else {
        $self->{_date_1904} = 1;
    }
}


###############################################################################
#
# get_1904()
#
# Return the date system: 0 = 1900, 1 = 1904
#
sub get_1904 {

    my $self = shift;

    return $self->{_date_1904};
}


###############################################################################
#
# set_custom_color()
#
# Change the RGB components of the elements in the colour palette.
#
sub set_custom_color {

    my $self = shift;


    # Match a HTML #xxyyzz style parameter
    if ( defined $_[1] and $_[1] =~ /^#(\w\w)(\w\w)(\w\w)/ ) {
        @_ = ( $_[0], hex $1, hex $2, hex $3 );
    }


    my $index = $_[0] || 0;
    my $red   = $_[1] || 0;
    my $green = $_[2] || 0;
    my $blue  = $_[3] || 0;

    my $aref = $self->{_palette};

    # Check that the colour index is the right range
    if ( $index < 8 or $index > 64 ) {
        carp "Color index $index outside range: 8 <= index <= 64";
        return 0;
    }

    # Check that the colour components are in the right range
    if (   ( $red < 0 or $red > 255 )
        || ( $green < 0 or $green > 255 )
        || ( $blue < 0  or $blue > 255 ) )
    {
        carp "Color component outside range: 0 <= color <= 255";
        return 0;
    }

    $index -= 8;    # Adjust colour index (wingless dragonfly)

    # Set the RGB value.
    my @rgb = ( $red, $green, $blue );
    $aref->[$index] = [@rgb];

    # Store the custom colors for the style.xml file.
    push @{ $self->{_custom_colors} }, sprintf "FF%02X%02X%02X", @rgb;

    return $index + 8;
}


###############################################################################
#
# set_color_palette()
#
# Sets the colour palette to the Excel defaults.
#
sub set_color_palette {

    my $self = shift;

    $self->{_palette} = [
        [ 0x00, 0x00, 0x00, 0x00 ],    # 8
        [ 0xff, 0xff, 0xff, 0x00 ],    # 9
        [ 0xff, 0x00, 0x00, 0x00 ],    # 10
        [ 0x00, 0xff, 0x00, 0x00 ],    # 11
        [ 0x00, 0x00, 0xff, 0x00 ],    # 12
        [ 0xff, 0xff, 0x00, 0x00 ],    # 13
        [ 0xff, 0x00, 0xff, 0x00 ],    # 14
        [ 0x00, 0xff, 0xff, 0x00 ],    # 15
        [ 0x80, 0x00, 0x00, 0x00 ],    # 16
        [ 0x00, 0x80, 0x00, 0x00 ],    # 17
        [ 0x00, 0x00, 0x80, 0x00 ],    # 18
        [ 0x80, 0x80, 0x00, 0x00 ],    # 19
        [ 0x80, 0x00, 0x80, 0x00 ],    # 20
        [ 0x00, 0x80, 0x80, 0x00 ],    # 21
        [ 0xc0, 0xc0, 0xc0, 0x00 ],    # 22
        [ 0x80, 0x80, 0x80, 0x00 ],    # 23
        [ 0x99, 0x99, 0xff, 0x00 ],    # 24
        [ 0x99, 0x33, 0x66, 0x00 ],    # 25
        [ 0xff, 0xff, 0xcc, 0x00 ],    # 26
        [ 0xcc, 0xff, 0xff, 0x00 ],    # 27
        [ 0x66, 0x00, 0x66, 0x00 ],    # 28
        [ 0xff, 0x80, 0x80, 0x00 ],    # 29
        [ 0x00, 0x66, 0xcc, 0x00 ],    # 30
        [ 0xcc, 0xcc, 0xff, 0x00 ],    # 31
        [ 0x00, 0x00, 0x80, 0x00 ],    # 32
        [ 0xff, 0x00, 0xff, 0x00 ],    # 33
        [ 0xff, 0xff, 0x00, 0x00 ],    # 34
        [ 0x00, 0xff, 0xff, 0x00 ],    # 35
        [ 0x80, 0x00, 0x80, 0x00 ],    # 36
        [ 0x80, 0x00, 0x00, 0x00 ],    # 37
        [ 0x00, 0x80, 0x80, 0x00 ],    # 38
        [ 0x00, 0x00, 0xff, 0x00 ],    # 39
        [ 0x00, 0xcc, 0xff, 0x00 ],    # 40
        [ 0xcc, 0xff, 0xff, 0x00 ],    # 41
        [ 0xcc, 0xff, 0xcc, 0x00 ],    # 42
        [ 0xff, 0xff, 0x99, 0x00 ],    # 43
        [ 0x99, 0xcc, 0xff, 0x00 ],    # 44
        [ 0xff, 0x99, 0xcc, 0x00 ],    # 45
        [ 0xcc, 0x99, 0xff, 0x00 ],    # 46
        [ 0xff, 0xcc, 0x99, 0x00 ],    # 47
        [ 0x33, 0x66, 0xff, 0x00 ],    # 48
        [ 0x33, 0xcc, 0xcc, 0x00 ],    # 49
        [ 0x99, 0xcc, 0x00, 0x00 ],    # 50
        [ 0xff, 0xcc, 0x00, 0x00 ],    # 51
        [ 0xff, 0x99, 0x00, 0x00 ],    # 52
        [ 0xff, 0x66, 0x00, 0x00 ],    # 53
        [ 0x66, 0x66, 0x99, 0x00 ],    # 54
        [ 0x96, 0x96, 0x96, 0x00 ],    # 55
        [ 0x00, 0x33, 0x66, 0x00 ],    # 56
        [ 0x33, 0x99, 0x66, 0x00 ],    # 57
        [ 0x00, 0x33, 0x00, 0x00 ],    # 58
        [ 0x33, 0x33, 0x00, 0x00 ],    # 59
        [ 0x99, 0x33, 0x00, 0x00 ],    # 60
        [ 0x99, 0x33, 0x66, 0x00 ],    # 61
        [ 0x33, 0x33, 0x99, 0x00 ],    # 62
        [ 0x33, 0x33, 0x33, 0x00 ],    # 63
    ];

    return 0;
}


###############################################################################
#
# set_tempdir()
#
# Change the default temp directory.
#
sub set_tempdir {

    my $self = shift;
    my $dir  = shift;

    croak "$dir is not a valid directory" if defined $dir and not -d $dir;

    $self->{_tempdir} = $dir;

}


###############################################################################
#
# define_name()
#
# Create a defined name in Excel. We handle global/workbook level names and
# local/worksheet names.
#
sub define_name {

    my $self        = shift;
    my $name        = shift;
    my $formula     = shift;
    my $sheet_index = undef;
    my $sheetname   = '';
    my $full_name   = $name;

    # Remove the = sign from the formula if it exists.
    $formula =~ s/^=//;

    # Local defined names are formatted like "Sheet1!name".
    if ( $name =~ /^(.*)!(.*)$/ ) {
        $sheetname   = $1;
        $name        = $2;
        $sheet_index = $self->_get_sheet_index( $sheetname );
    }
    else {
        $sheet_index = -1;    # Use -1 to indicate global names.
    }

    # Warn if the sheet index wasn't found.
    if ( !defined $sheet_index ) {
        carp "Unknown sheet name $sheetname in defined_name()";
        return -1;
    }

    # Warn if the name contains invalid chars as defined by Excel help.
    if ( $name !~ m/^[\w\\][\w\\.]*$/ || $name =~ m/^\d/ ) {
        carp "Invalid character in name '$name' used in defined_name()";
        return -1;
    }

    # Warn if the name looks like a cell name.
    if ( $name =~ m/^[a-zA-Z][a-zA-Z]?[a-dA-D]?[0-9]+$/ ) {
        carp "Invalid name '$name' looks like a cell name in defined_name()";
        return -1;
    }

    # Warn if the name looks like a R1C1.
    if ( $name =~ m/^[rcRC]$/ || $name =~ m/^[rcRC]\d+[rcRC]\d+$/ ) {
        carp "Invalid name '$name' like a RC cell ref in defined_name()";
        return -1;
    }

    push @{ $self->{_defined_names} }, [ $name, $sheet_index, $formula ];
}


###############################################################################
#
# set_size()
#
# Set the workbook size.
#
sub set_size {

    my $self   = shift;
    my $width  = shift;
    my $height = shift;

    if ( !$width ) {
        $self->{_window_width} = 16095;
    }
    else {
        # Convert to twips at 96 dpi.
        $self->{_window_width} = int( $width * 1440 / 96 );
    }

    if ( !$height ) {
        $self->{_window_height} = 9660;
    }
    else {
        # Convert to twips at 96 dpi.
        $self->{_window_height} = int( $height * 1440 / 96 );
    }
}


###############################################################################
#
# set_tab_ratio()
#
# Set the ratio of space for worksheet tabs.
#
sub set_tab_ratio {

    my $self  = shift;
    my $tab_ratio = shift;

    if (!defined $tab_ratio) {
        return;
    }

    if ( $tab_ratio < 0 or $tab_ratio > 100 ) {
        carp "Tab ratio outside range: 0 <= zoom <= 100";
    }
    else {
        $self->{_tab_ratio} = int( $tab_ratio * 10 );
    }
}


###############################################################################
#
# set_properties()
#
# Set the document properties such as Title, Author etc. These are written to
# property sets in the OLE container.
#
sub set_properties {

    my $self  = shift;
    my %param = @_;

    # Ignore if no args were passed.
    return -1 unless @_;

    # List of valid input parameters.
    my %valid = (
        title          => 1,
        subject        => 1,
        author         => 1,
        keywords       => 1,
        comments       => 1,
        last_author    => 1,
        created        => 1,
        category       => 1,
        manager        => 1,
        company        => 1,
        status         => 1,
        hyperlink_base => 1,
    );

    # Check for valid input parameters.
    for my $parameter ( keys %param ) {
        if ( not exists $valid{$parameter} ) {
            carp "Unknown parameter '$parameter' in set_properties()";
            return -1;
        }
    }

    # Set the creation time unless specified by the user.
    if ( !exists $param{created} ) {
        $param{created} = $self->{_createtime};
    }


    $self->{_doc_properties} = \%param;
}


###############################################################################
#
# set_custom_property()
#
# Set a user defined custom document property.
#
sub set_custom_property {

    my $self  = shift;
    my $name  = shift;
    my $value = shift;
    my $type  = shift;


    # Valid types.
    my %valid_type = (
        'text'       => 1,
        'date'       => 1,
        'number'     => 1,
        'number_int' => 1,
        'bool'       => 1,
    );

    if ( !defined $name || !defined $value ) {
        carp "The name and value parameters must be defined "
          . "in set_custom_property()";

        return -1;
    }

    # Determine the type for strings and numbers if it hasn't been specified.
    if ( !$type ) {
        if ( $value =~ /^\d+$/ ) {
            $type = 'number_int';
        }
        elsif ( $value =~
            /^([+-]?)(?=[0-9]|\.[0-9])[0-9]*(\.[0-9]*)?([Ee]([+-]?[0-9]+))?$/ )
        {
            $type = 'number';
        }
        else {
            $type = 'text';
        }
    }

    # Check for valid validation types.
    if ( !exists $valid_type{$type} ) {
        carp "Unknown custom type '$type' in set_custom_property()";
        return -1;
    }

    #  Check for strings longer than Excel's limit of 255 chars.
    if ( $type eq 'text' and length $value > 255 ) {
        carp "Length of text custom value '$value' exceeds "
          . "Excel's limit of 255 in set_custom_property()";
        return -1;
    }
    if ( length $value > 255 ) {
        carp "Length of custom name '$name' exceeds "
          . "Excel's limit of 255 in set_custom_property()";
        return -1;
    }

    push @{ $self->{_custom_properties} }, [ $name, $value, $type ];
}



###############################################################################
#
# add_vba_project()
#
# Add a vbaProject binary to the XLSX file.
#
sub add_vba_project {

    my $self        = shift;
    my $vba_project = shift;

    croak "No vbaProject.bin specified in add_vba_project()"
      if not $vba_project;

    croak "Couldn't locate $vba_project in add_vba_project(): $!"
      unless -e $vba_project;

    if ( !$self->{_vba_codemame} ) {
        $self->{_vba_codename} = 'ThisWorkbook';
    }

    $self->{_vba_project} = $vba_project;
}


###############################################################################
#
# set_vba_name()
#
# Set the VBA name for the workbook.
#
sub set_vba_name {

    my $self         = shift;
    my $vba_codemame = shift;

    if ( $vba_codemame ) {
        $self->{_vba_codename} = $vba_codemame;
    }
    else {
        $self->{_vba_codename} = 'ThisWorkbook';
    }
}


###############################################################################
#
# set_calc_mode()
#
# Set the Excel caclcuation mode for the workbook.
#
sub set_calc_mode {

    my $self    = shift;
    my $mode    = shift || 'auto';
    my $calc_id = shift;

    $self->{_calc_mode} = $mode;

    if ( $mode eq 'manual' ) {
        $self->{_calc_mode}    = 'manual';
        $self->{_calc_on_load} = 0;
    }
    elsif ( $mode eq 'auto_except_tables' ) {
        $self->{_calc_mode} = 'autoNoTable';
    }

    $self->{_calc_id} = $calc_id if defined $calc_id;
}


###############################################################################
#
# get_default_url_format()
#
# Get the default url format used when a user defined format isn't specified
# with write_url(). The format is the hyperlink style defined by Excel for the
# default theme.
#
sub get_default_url_format {

    my $self    = shift;

    return $self->{_default_url_format};
}


###############################################################################
#
# _store_workbook()
#
# Assemble worksheets into a workbook.
#
sub _store_workbook {

    my $self     = shift;
    my $tempdir  = File::Temp->newdir( DIR => $self->{_tempdir} );
    my $packager = Excel::Writer::XLSX::Package::Packager->new();
    my $zip      = Archive::Zip->new();


    # Add a default worksheet if non have been added.
    $self->add_worksheet() if not @{ $self->{_worksheets} };

    # Ensure that at least one worksheet has been selected.
    if ( $self->{_activesheet} == 0 ) {
        $self->{_worksheets}->[0]->{_selected} = 1;
        $self->{_worksheets}->[0]->{_hidden}   = 0;
    }

    # Set the active sheet.
    for my $sheet ( @{ $self->{_worksheets} } ) {
        $sheet->{_active} = 1 if $sheet->{_index} == $self->{_activesheet};
    }

    # Set the sheet vba_codename if the workbook has a vbaProject binary.
    if ( $self->{_vba_project} ) {
        for my $sheet ( @{ $self->{_worksheets} } ) {
            if ( !$sheet->{_vba_codename} ) {
                $sheet->set_vba_name();
            }
        }
    }

    # Convert the SST strings data structure.
    $self->_prepare_sst_string_data();

    # Prepare the worksheet VML elements such as comments and buttons.
    $self->_prepare_vml_objects();

    # Set the defined names for the worksheets such as Print Titles.
    $self->_prepare_defined_names();

    # Prepare the drawings, charts and images.
    $self->_prepare_drawings();

    # Add cached data to charts.
    $self->_add_chart_data();

    # Prepare the worksheet tables.
    $self->_prepare_tables();

    # Package the workbook.
    $packager->_add_workbook( $self );
    $packager->_set_package_dir( $tempdir );
    $packager->_create_package();

    # Free up the Packager object.
    $packager = undef;

    # Add the files to the zip archive. Due to issues with Archive::Zip in
    # taint mode we can't use addTree() so we have to build the file list
    # with File::Find and pass each one to addFile().
    my @xlsx_files;

    my $wanted = sub { push @xlsx_files, $File::Find::name if -f };

    File::Find::find(
        {
            wanted          => $wanted,
            untaint         => 1,
            untaint_pattern => qr|^(.+)$|
        },
        $tempdir
    );

    # Re-order the XML files before adding them to the Zip container to match
    # (mainly) the order used by Excel and thus satisfy mime-type heuristics
    # such as file(1) and magic.
    my @tmp     = grep {  m{/xl/} } @xlsx_files;
    @xlsx_files = grep { !m{/xl/} } @xlsx_files;
    @xlsx_files = ( @tmp, @xlsx_files );

    @tmp        = grep {  m{workbook\.xml$} } @xlsx_files;
    @xlsx_files = grep { !m{workbook\.xml$} } @xlsx_files;
    @xlsx_files = ( @tmp, @xlsx_files );

    @tmp        = grep {  m{_rels/workbook\.xml\.rels$} } @xlsx_files;
    @xlsx_files = grep { !m{_rels/workbook\.xml\.rels$} } @xlsx_files;
    @xlsx_files = ( @tmp, @xlsx_files );

    @tmp        = grep {  m{_rels/\.rels$} } @xlsx_files;
    @xlsx_files = grep { !m{_rels/\.rels$} } @xlsx_files;
    @xlsx_files = ( @tmp, @xlsx_files );

    @tmp        = grep {  m{\[Content_Types\]\.xml$} } @xlsx_files;
    @xlsx_files = grep { !m{\[Content_Types\]\.xml$} } @xlsx_files;
    @xlsx_files = ( @tmp, @xlsx_files );

    # Store the xlsx component files with the temp dir name removed.
    for my $filename ( @xlsx_files ) {
        my $short_name = $filename;
        $short_name =~ s{^\Q$tempdir\E/?}{};
        my $member = $zip->addFile( $filename, $short_name );

        # Set the file member datetime to 1980-01-01 00:00:00 like Excel so
        # that apps can produce a consistent binary file. Note, we don't use
        # the Archive::Zip::setLastModFileDateTimeFromUnix() function directly
        # since it doesn't allow the time 00:00:00 for this date.
        $member->{'lastModFileDateTime'} = 2162688;
    }

    if ( $self->{_internal_fh} ) {

        if ( $zip->writeToFileHandle( $self->{_filehandle} ) != 0 ) {
            carp 'Error writing zip container for xlsx file.';
        }
    }
    else {

        # Archive::Zip needs to rewind a filehandle to write the zip headers.
        # This won't work for arbitrary user defined filehandles so we use
        # a temp file based filehandle to create the zip archive and then
        # stream that to the filehandle.
        my $tmp_fh = tempfile( DIR => $self->{_tempdir} );
        my $is_seekable = 1;

        if ( $zip->writeToFileHandle( $tmp_fh, $is_seekable ) != 0 ) {
            carp 'Error writing zip container for xlsx file.';
        }

        my $buffer;
        seek $tmp_fh, 0, 0;

        while ( read( $tmp_fh, $buffer, 4_096 ) ) {
            local $\ = undef;    # Protect print from -l on commandline.
            print { $self->{_filehandle} } $buffer;
        }
    }
}


###############################################################################
#
# _prepare_sst_string_data()
#
# Convert the SST string data from a hash to an array.
#
sub _prepare_sst_string_data {

    my $self = shift;

    my @strings;
    $#strings = $self->{_str_unique} - 1;    # Pre-extend array

    while ( my $key = each %{ $self->{_str_table} } ) {
        $strings[ $self->{_str_table}->{$key} ] = $key;
    }

    # The SST data could be very large, free some memory (maybe).
    $self->{_str_table} = undef;
    $self->{_str_array} = \@strings;

}


###############################################################################
#
# _prepare_format_properties()
#
# Prepare all of the format properties prior to passing them to Styles.pm.
#
sub _prepare_format_properties {

    my $self = shift;

    # Separate format objects into XF and DXF formats.
    $self->_prepare_formats();

    # Set the font index for the format objects.
    $self->_prepare_fonts();

    # Set the number format index for the format objects.
    $self->_prepare_num_formats();

    # Set the border index for the format objects.
    $self->_prepare_borders();

    # Set the fill index for the format objects.
    $self->_prepare_fills();


}


###############################################################################
#
# _prepare_formats()
#
# Iterate through the XF Format objects and separate them into XF and DXF
# formats.
#
sub _prepare_formats {

    my $self = shift;

    for my $format ( @{ $self->{_formats} } ) {
        my $xf_index  = $format->{_xf_index};
        my $dxf_index = $format->{_dxf_index};

        if ( defined $xf_index ) {
            $self->{_xf_formats}->[$xf_index] = $format;
        }

        if ( defined $dxf_index ) {
            $self->{_dxf_formats}->[$dxf_index] = $format;
        }
    }
}


###############################################################################
#
# _set_default_xf_indices()
#
# Set the default index for each format. This is mainly used for testing.
#
sub _set_default_xf_indices {

    my $self = shift;

    # Delete the default url format.
    splice @{ $self->{_formats} }, 1, 1;

    for my $format ( @{ $self->{_formats} } ) {
        $format->get_xf_index();
    }
}


###############################################################################
#
# _prepare_fonts()
#
# Iterate through the XF Format objects and give them an index to non-default
# font elements.
#
sub _prepare_fonts {

    my $self = shift;

    my %fonts;
    my $index = 0;

    for my $format ( @{ $self->{_xf_formats} } ) {
        my $key = $format->get_font_key();

        if ( exists $fonts{$key} ) {

            # Font has already been used.
            $format->{_font_index} = $fonts{$key};
            $format->{_has_font}   = 0;
        }
        else {

            # This is a new font.
            $fonts{$key}           = $index;
            $format->{_font_index} = $index;
            $format->{_has_font}   = 1;
            $index++;
        }
    }

    $self->{_font_count} = $index;

    # For the DXF formats we only need to check if the properties have changed.
    for my $format ( @{ $self->{_dxf_formats} } ) {

        # The only font properties that can change for a DXF format are: color,
        # bold, italic, underline and strikethrough.
        if (   $format->{_color}
            || $format->{_bold}
            || $format->{_italic}
            || $format->{_underline}
            || $format->{_font_strikeout} )
        {
            $format->{_has_dxf_font} = 1;
        }
    }
}


###############################################################################
#
# _prepare_num_formats()
#
# Iterate through the XF Format objects and give them an index to non-default
# number format elements.
#
# User defined records start from index 0xA4.
#
sub _prepare_num_formats {

    my $self = shift;

    my %num_formats;
    my $index            = 164;
    my $num_format_count = 0;

    for my $format ( @{ $self->{_xf_formats} }, @{ $self->{_dxf_formats} } ) {
        my $num_format = $format->{_num_format};


        # Check if $num_format is an index to a built-in number format.
        # Also check for a string of zeros, which is a valid number format
        # string but would evaluate to zero.
        #
        if ( $num_format =~ m/^\d+$/ && $num_format !~ m/^0+\d/ ) {

            # Number format '0' is indexed as 1 in Excel.
            if ($num_format == 0) {
                $num_format = 1;
            }

            # Index to a built-in number format.
            $format->{_num_format_index} = $num_format;
            next;
        }
        elsif ( $num_format  eq 'General' ) {
            # The 'General' format has an number format index of 0.
            $format->{_num_format_index} = 0;
            next;
        }


        if ( exists( $num_formats{$num_format} ) ) {

            # Number format has already been used.
            $format->{_num_format_index} = $num_formats{$num_format};
        }
        else {

            # Add a new number format.
            $num_formats{$num_format} = $index;
            $format->{_num_format_index} = $index;
            $index++;

            # Only increase font count for XF formats (not for DXF formats).
            if ( $format->{_xf_index} ) {
                $num_format_count++;
            }
        }
    }

    $self->{_num_format_count} = $num_format_count;
}


###############################################################################
#
# _prepare_borders()
#
# Iterate through the XF Format objects and give them an index to non-default
# border elements.
#
sub _prepare_borders {

    my $self = shift;

    my %borders;
    my $index = 0;

    for my $format ( @{ $self->{_xf_formats} } ) {
        my $key = $format->get_border_key();

        if ( exists $borders{$key} ) {

            # Border has already been used.
            $format->{_border_index} = $borders{$key};
            $format->{_has_border}   = 0;
        }
        else {

            # This is a new border.
            $borders{$key}           = $index;
            $format->{_border_index} = $index;
            $format->{_has_border}   = 1;
            $index++;
        }
    }

    $self->{_border_count} = $index;

    # For the DXF formats we only need to check if the properties have changed.
    for my $format ( @{ $self->{_dxf_formats} } ) {
        my $key = $format->get_border_key();

        if ( $key =~ m/[^0:]/ ) {
            $format->{_has_dxf_border} = 1;
        }
    }

}


###############################################################################
#
# _prepare_fills()
#
# Iterate through the XF Format objects and give them an index to non-default
# fill elements.
#
# The user defined fill properties start from 2 since there are 2 default
# fills: patternType="none" and patternType="gray125".
#
sub _prepare_fills {

    my $self = shift;

    my %fills;
    my $index = 2;    # Start from 2. See above.

    # Add the default fills.
    $fills{'0:0:0'}  = 0;
    $fills{'17:0:0'} = 1;


    # Store the DXF colours separately since them may be reversed below.
    for my $format ( @{ $self->{_dxf_formats} } ) {
        if (   $format->{_pattern}
            || $format->{_bg_color}
            || $format->{_fg_color} )
        {
            $format->{_has_dxf_fill} = 1;
            $format->{_dxf_bg_color} = $format->{_bg_color};
            $format->{_dxf_fg_color} = $format->{_fg_color};
        }
    }


    for my $format ( @{ $self->{_xf_formats} } ) {

        # The following logical statements jointly take care of special cases
        # in relation to cell colours and patterns:
        # 1. For a solid fill (_pattern == 1) Excel reverses the role of
        #    foreground and background colours, and
        # 2. If the user specifies a foreground or background colour without
        #    a pattern they probably wanted a solid fill, so we fill in the
        #    defaults.
        #
        if (   $format->{_pattern} == 1
            && $format->{_bg_color} ne '0'
            && $format->{_fg_color} ne '0' )
        {
            my $tmp = $format->{_fg_color};
            $format->{_fg_color} = $format->{_bg_color};
            $format->{_bg_color} = $tmp;
        }

        if (   $format->{_pattern} <= 1
            && $format->{_bg_color} ne '0'
            && $format->{_fg_color} eq '0' )
        {
            $format->{_fg_color} = $format->{_bg_color};
            $format->{_bg_color} = 0;
            $format->{_pattern}  = 1;
        }

        if (   $format->{_pattern} <= 1
            && $format->{_bg_color} eq '0'
            && $format->{_fg_color} ne '0' )
        {
            $format->{_bg_color} = 0;
            $format->{_pattern}  = 1;
        }


        my $key = $format->get_fill_key();

        if ( exists $fills{$key} ) {

            # Fill has already been used.
            $format->{_fill_index} = $fills{$key};
            $format->{_has_fill}   = 0;
        }
        else {

            # This is a new fill.
            $fills{$key}           = $index;
            $format->{_fill_index} = $index;
            $format->{_has_fill}   = 1;
            $index++;
        }
    }

    $self->{_fill_count} = $index;


}


###############################################################################
#
# _prepare_defined_names()
#
# Iterate through the worksheets and store any defined names in addition to
# any user defined names. Stores the defined names for the Workbook.xml and
# the named ranges for App.xml.
#
sub _prepare_defined_names {

    my $self = shift;

    my @defined_names = @{ $self->{_defined_names} };

    for my $sheet ( @{ $self->{_worksheets} } ) {

        # Check for Print Area settings.
        if ( $sheet->{_autofilter} ) {

            my $range  = $sheet->{_autofilter};
            my $hidden = 1;

            # Store the defined names.
            push @defined_names,
              [ '_xlnm._FilterDatabase', $sheet->{_index}, $range, $hidden ];

        }

        # Check for Print Area settings.
        if ( $sheet->{_print_area} ) {

            my $range = $sheet->{_print_area};

            # Store the defined names.
            push @defined_names,
              [ '_xlnm.Print_Area', $sheet->{_index}, $range ];
        }

        # Check for repeat rows/cols. aka, Print Titles.
        if ( $sheet->{_repeat_cols} || $sheet->{_repeat_rows} ) {
            my $range = '';

            if ( $sheet->{_repeat_cols} && $sheet->{_repeat_rows} ) {
                $range = $sheet->{_repeat_cols} . ',' . $sheet->{_repeat_rows};
            }
            else {
                $range = $sheet->{_repeat_cols} . $sheet->{_repeat_rows};
            }

            # Store the defined names.
            push @defined_names,
              [ '_xlnm.Print_Titles', $sheet->{_index}, $range ];
        }

    }

    @defined_names          = _sort_defined_names( @defined_names );
    $self->{_defined_names} = \@defined_names;
    $self->{_named_ranges}  = _extract_named_ranges( @defined_names );
}


###############################################################################
#
# _sort_defined_names()
#
# Sort internal and user defined names in the same order as used by Excel.
# This may not be strictly necessary but unsorted elements caused a lot of
# issues in the Spreadsheet::WriteExcel binary version. Also makes
# comparison testing easier.
#
sub _sort_defined_names {

    my @names = @_;

    #<<< Perltidy ignore this.

    @names = sort {
        # Primary sort based on the defined name.
        _normalise_defined_name( $a->[0] )
        cmp
        _normalise_defined_name( $b->[0] )

        ||
        # Secondary sort based on the sheet name.
        _normalise_sheet_name( $a->[2] )
        cmp
        _normalise_sheet_name( $b->[2] )

    } @names;
    #>>>

    return @names;
}

# Used in the above sort routine to normalise the defined names. Removes any
# leading '_xmln.' from internal names and lowercases the strings.
sub _normalise_defined_name {
    my $name = shift;

    $name =~ s/^_xlnm.//;
    $name = lc $name;

    return $name;
}

# Used in the above sort routine to normalise the worksheet names for the
# secondary sort. Removes leading quote and lowercases the strings.
sub _normalise_sheet_name {
    my $name = shift;

    $name =~ s/^'//;
    $name = lc $name;

    return $name;
}


###############################################################################
#
# _extract_named_ranges()
#
# Extract the named ranges from the sorted list of defined names. These are
# used in the App.xml file.
#
sub _extract_named_ranges {

    my @defined_names = @_;
    my @named_ranges;

    NAME:
    for my $defined_name ( @defined_names ) {

        my $name  = $defined_name->[0];
        my $index = $defined_name->[1];
        my $range = $defined_name->[2];

        # Skip autoFilter ranges.
        next NAME if $name eq '_xlnm._FilterDatabase';

        # We are only interested in defined names with ranges.
        if ( $range =~ /^([^!]+)!/ ) {
            my $sheet_name = $1;

            # Match Print_Area and Print_Titles xlnm types.
            if ( $name =~ /^_xlnm\.(.*)$/ ) {
                my $xlnm_type = $1;
                $name = $sheet_name . '!' . $xlnm_type;
            }
            elsif ( $index != -1 ) {
                $name = $sheet_name . '!' . $name;
            }

            push @named_ranges, $name;
        }
    }

    return \@named_ranges;
}


###############################################################################
#
# _prepare_drawings()
#
# Iterate through the worksheets and set up any chart or image drawings.
#
sub _prepare_drawings {

    my $self             = shift;
    my $chart_ref_id     = 0;
    my $image_ref_id     = 0;
    my $drawing_id       = 0;
    my $ref_id           = 0;
    my %image_ids        = ();
    my %header_image_ids = ();

    for my $sheet ( @{ $self->{_worksheets} } ) {

        my $chart_count = scalar @{ $sheet->{_charts} };
        my $image_count = scalar @{ $sheet->{_images} };
        my $shape_count = scalar @{ $sheet->{_shapes} };

        my $header_image_count = scalar @{ $sheet->{_header_images} };
        my $footer_image_count = scalar @{ $sheet->{_footer_images} };
        my $has_drawing        = 0;


        # Check that some image or drawing needs to be processed.
        if (   !$chart_count
            && !$image_count
            && !$shape_count
            && !$header_image_count
            && !$footer_image_count )
        {
            next;
        }

        # Don't increase the drawing_id header/footer images.
        if ( $chart_count || $image_count || $shape_count ) {
            $drawing_id++;
            $has_drawing = 1;
        }

        # Prepare the worksheet images.
        for my $index ( 0 .. $image_count - 1 ) {

            my $filename = $sheet->{_images}->[$index]->[2];

            my ( $type, $width, $height, $name, $x_dpi, $y_dpi, $md5 ) =
              $self->_get_image_properties( $filename );

            if ( exists $image_ids{$md5} ) {
                $ref_id = $image_ids{$md5};
            }
            else {
                $ref_id = ++$image_ref_id;
                $image_ids{$md5} = $ref_id;
                push @{ $self->{_images} }, [ $filename, $type ];
            }

            $sheet->_prepare_image(
                $index, $ref_id, $drawing_id, $width, $height,
                $name,  $type,   $x_dpi,      $y_dpi, $md5
            );
        }

        # Prepare the worksheet charts.
        for my $index ( 0 .. $chart_count - 1 ) {
            $chart_ref_id++;
            $sheet->_prepare_chart( $index, $chart_ref_id, $drawing_id );
        }

        # Prepare the worksheet shapes.
        for my $index ( 0 .. $shape_count - 1 ) {
            $sheet->_prepare_shape( $index, $drawing_id );
        }

        # Prepare the header images.
        for my $index ( 0 .. $header_image_count - 1 ) {

            my $filename = $sheet->{_header_images}->[$index]->[0];
            my $position = $sheet->{_header_images}->[$index]->[1];

            my ( $type, $width, $height, $name, $x_dpi, $y_dpi, $md5 ) =
              $self->_get_image_properties( $filename );

            if ( exists $header_image_ids{$md5} ) {
                $ref_id = $header_image_ids{$md5};
            }
            else {
                $ref_id = ++$image_ref_id;
                $header_image_ids{$md5} = $ref_id;
                push @{ $self->{_images} }, [ $filename, $type ];
            }

            $sheet->_prepare_header_image(
                $ref_id,   $width, $height, $name, $type,
                $position, $x_dpi, $y_dpi,  $md5
            );
        }

        # Prepare the footer images.
        for my $index ( 0 .. $footer_image_count - 1 ) {

            my $filename = $sheet->{_footer_images}->[$index]->[0];
            my $position = $sheet->{_footer_images}->[$index]->[1];

            my ( $type, $width, $height, $name, $x_dpi, $y_dpi, $md5 ) =
              $self->_get_image_properties( $filename );

            if ( exists $header_image_ids{$md5} ) {
                $ref_id = $header_image_ids{$md5};
            }
            else {
                $ref_id = ++$image_ref_id;
                $header_image_ids{$md5} = $ref_id;
                push @{ $self->{_images} }, [ $filename, $type ];
            }

            $sheet->_prepare_header_image(
                $ref_id,   $width, $height, $name, $type,
                $position, $x_dpi, $y_dpi,  $md5
            );
        }


        if ( $has_drawing ) {
            my $drawing = $sheet->{_drawing};
            push @{ $self->{_drawings} }, $drawing;
        }
    }


    # Remove charts that were created but not inserted into worksheets.
    my @chart_data;

    for my $chart ( @{ $self->{_charts} } ) {
        if ( $chart->{_id} != -1 ) {
            push @chart_data, $chart;
        }
    }

    # Sort the workbook charts references into the order that the were
    # written from the worksheets above.
    @chart_data = sort { $a->{_id} <=> $b->{_id} } @chart_data;

    $self->{_charts}        = \@chart_data;
    $self->{_drawing_count} = $drawing_id;
}


###############################################################################
#
# _prepare_vml_objects()
#
# Iterate through the worksheets and set up the VML objects.
#
sub _prepare_vml_objects {

    my $self           = shift;
    my $comment_id     = 0;
    my $vml_drawing_id = 0;
    my $vml_data_id    = 1;
    my $vml_header_id  = 0;
    my $vml_shape_id   = 1024;
    my $vml_files      = 0;
    my $comment_files  = 0;

    for my $sheet ( @{ $self->{_worksheets} } ) {

        next if !$sheet->{_has_vml} and !$sheet->{_has_header_vml};
        $vml_files = 1;


        if ( $sheet->{_has_vml} ) {

            if ( $sheet->{_has_comments} ) {
                $comment_files++;
                $comment_id++;
                $self->{_has_comments} = 1;
            }

            $vml_drawing_id++;

            my $count =
              $sheet->_prepare_vml_objects( $vml_data_id, $vml_shape_id,
                $vml_drawing_id, $comment_id );

            # Each VML file should start with a shape id incremented by 1024.
            $vml_data_id  += 1 * int(    ( 1024 + $count ) / 1024 );
            $vml_shape_id += 1024 * int( ( 1024 + $count ) / 1024 );

        }

        if ( $sheet->{_has_header_vml} ) {
            $vml_header_id++;
            $vml_drawing_id++;
            $sheet->_prepare_header_vml_objects( $vml_header_id,
                $vml_drawing_id );
        }

    }

    $self->{_num_vml_files}     = $vml_files;
    $self->{_num_comment_files} = $comment_files;

}


###############################################################################
#
# _prepare_tables()
#
# Set the table ids for the worksheet tables.
#
sub _prepare_tables {

    my $self     = shift;
    my $table_id = 0;
    my $seen     = {};

    for my $sheet ( @{ $self->{_worksheets} } ) {

        my $table_count = scalar @{ $sheet->{_tables} };

        next unless $table_count;

        $sheet->_prepare_tables( $table_id + 1, $seen );

        $table_id += $table_count;
    }
}


###############################################################################
#
# _add_chart_data()
#
# Add "cached" data to charts to provide the numCache and strCache data for
# series and title/axis ranges.
#
sub _add_chart_data {

    my $self = shift;
    my %worksheets;
    my %seen_ranges;
    my @charts;

    # Map worksheet names to worksheet objects.
    for my $worksheet ( @{ $self->{_worksheets} } ) {
        $worksheets{ $worksheet->{_name} } = $worksheet;
    }

    # Build an array of the worksheet charts including any combined charts.
    for my $chart ( @{ $self->{_charts} } ) {
        push @charts, $chart;

        if ($chart->{_combined}) {
            push @charts, $chart->{_combined};
        }
    }


    CHART:
    for my $chart ( @charts ) {

        RANGE:
        while ( my ( $range, $id ) = each %{ $chart->{_formula_ids} } ) {

            # Skip if the series has user defined data.
            if ( defined $chart->{_formula_data}->[$id] ) {
                if (   !exists $seen_ranges{$range}
                    || !defined $seen_ranges{$range} )
                {
                    my $data = $chart->{_formula_data}->[$id];
                    $seen_ranges{$range} = $data;
                }
                next RANGE;
            }

            # Check to see if the data is already cached locally.
            if ( exists $seen_ranges{$range} ) {
                $chart->{_formula_data}->[$id] = $seen_ranges{$range};
                next RANGE;
            }

            # Convert the range formula to a sheet name and cell range.
            my ( $sheetname, @cells ) = $self->_get_chart_range( $range );

            # Skip if we couldn't parse the formula.
            next RANGE if !defined $sheetname;

            # Handle non-contiguous ranges: (Sheet1!$A$1:$A$2,Sheet1!$A$4:$A$5).
            # We don't try to parse the ranges. We just return an empty list.
            if ( $sheetname =~ m/^\([^,]+,/ ) {
                $chart->{_formula_data}->[$id] = [];
                $seen_ranges{$range} = [];
                next RANGE;
            }

            # Die if the name is unknown since it indicates a user error in
            # a chart series formula.
            if ( !exists $worksheets{$sheetname} ) {
                die "Unknown worksheet reference '$sheetname' in range "
                  . "'$range' passed to add_series().\n";
            }

            # Find the worksheet object based on the sheet name.
            my $worksheet = $worksheets{$sheetname};

            # Get the data from the worksheet table.
            my @data = $worksheet->_get_range_data( @cells );

            # Convert shared string indexes to strings.
            for my $token ( @data ) {
                if ( ref $token ) {
                    $token = $self->{_str_array}->[ $token->{sst_id} ];

                    # Ignore rich strings for now. Deparse later if necessary.
                    if ( $token =~ m{^<r>} && $token =~ m{</r>$} ) {
                        $token = '';
                    }
                }
            }

            # Add the data to the chart.
            $chart->{_formula_data}->[$id] = \@data;

            # Store range data locally to avoid lookup if seen again.
            $seen_ranges{$range} = \@data;
        }
    }
}


###############################################################################
#
# _get_chart_range()
#
# Convert a range formula such as Sheet1!$B$1:$B$5 into a sheet name and cell
# range such as ( 'Sheet1', 0, 1, 4, 1 ).
#
sub _get_chart_range {

    my $self  = shift;
    my $range = shift;
    my $cell_1;
    my $cell_2;
    my $sheetname;
    my $cells;

    # Split the range formula into sheetname and cells at the last '!'.
    my $pos = rindex $range, '!';
    if ( $pos > 0 ) {
        $sheetname = substr $range, 0, $pos;
        $cells = substr $range, $pos + 1;
    }
    else {
        return undef;
    }

    # Split the cell range into 2 cells or else use single cell for both.
    if ( $cells =~ ':' ) {
        ( $cell_1, $cell_2 ) = split /:/, $cells;
    }
    else {
        ( $cell_1, $cell_2 ) = ( $cells, $cells );
    }

    # Remove leading/trailing apostrophes and convert escaped quotes to single.
    $sheetname =~ s/^'//g;
    $sheetname =~ s/'$//g;
    $sheetname =~ s/''/'/g;

    my ( $row_start, $col_start ) = xl_cell_to_rowcol( $cell_1 );
    my ( $row_end,   $col_end )   = xl_cell_to_rowcol( $cell_2 );

    # Check that we have a 1D range only.
    if ( $row_start != $row_end && $col_start != $col_end ) {
        return undef;
    }

    return ( $sheetname, $row_start, $col_start, $row_end, $col_end );
}


###############################################################################
#
# _store_externs()
#
# Write the EXTERNCOUNT and EXTERNSHEET records. These are used as indexes for
# the NAME records.
#
sub _store_externs {

    my $self = shift;

}


###############################################################################
#
# _store_names()
#
# Write the NAME record to define the print area and the repeat rows and cols.
#
sub _store_names {

    my $self = shift;

}


###############################################################################
#
# _quote_sheetname()
#
# Sheetnames used in references should be quoted if they contain any spaces,
# special characters or if the look like something that isn't a sheet name.
# TODO. We need to handle more special cases.
#
sub _quote_sheetname {

    my $self      = shift;
    my $sheetname = $_[0];

    if ( $sheetname =~ /^Sheet\d+$/ ) {
        return $sheetname;
    }
    else {
        return qq('$sheetname');
    }
}


###############################################################################
#
# _get_image_properties()
#
# Extract information from the image file such as dimension, type, filename,
# and extension. Also keep track of previously seen images to optimise out
# any duplicates.
#
sub _get_image_properties {

    my $self     = shift;
    my $filename = shift;

    my $type;
    my $width;
    my $height;
    my $x_dpi = 96;
    my $y_dpi = 96;
    my $image_name;


    ( $image_name ) = fileparse( $filename );

    # Open the image file and import the data.
    my $fh = FileHandle->new( $filename );
    croak "Couldn't import $filename: $!" unless defined $fh;
    binmode $fh;

    # Slurp the file into a string and do some size calcs.
    my $data = do { local $/; <$fh> };
    my $size = length $data;
    my $md5  = md5_hex($data);


    if ( unpack( 'x A3', $data ) eq 'PNG' ) {

        # Test for PNGs.
        ( $type, $width, $height, $x_dpi, $y_dpi ) =
          $self->_process_png( $data, $filename );

        $self->{_image_types}->{png} = 1;
    }
    elsif ( unpack( 'n', $data ) == 0xFFD8 ) {

        # Test for JPEG files.
        ( $type, $width, $height, $x_dpi, $y_dpi ) =
          $self->_process_jpg( $data, $filename );

        $self->{_image_types}->{jpeg} = 1;
    }
    elsif ( unpack( 'A2', $data ) eq 'BM' ) {

        # Test for BMPs.
        ( $type, $width, $height ) = $self->_process_bmp( $data, $filename );

        $self->{_image_types}->{bmp} = 1;
    }
    else {
        croak "Unsupported image format for file: $filename\n";
    }

    # Set a default dpi for images with 0 dpi.
    $x_dpi = 96 if $x_dpi == 0;
    $y_dpi = 96 if $y_dpi == 0;

    $fh->close;

    return ( $type, $width, $height, $image_name, $x_dpi, $y_dpi, $md5 );
}


###############################################################################
#
# _process_png()
#
# Extract width and height information from a PNG file.
#
sub _process_png {

    my $self     = shift;
    my $data     = $_[0];
    my $filename = $_[1];

    my $type   = 'png';
    my $width  = 0;
    my $height = 0;
    my $x_dpi  = 96;
    my $y_dpi  = 96;

    my $offset      = 8;
    my $data_length = length $data;

    # Search through the image data to read the height and width in the
    # IHDR element. Also read the DPI in the pHYs element.
    while ( $offset < $data_length ) {

        my $length = unpack "N",  substr $data, $offset + 0, 4;
        my $type   = unpack "A4", substr $data, $offset + 4, 4;

        if ( $type eq "IHDR" ) {
            $width  = unpack "N", substr $data, $offset + 8,  4;
            $height = unpack "N", substr $data, $offset + 12, 4;
        }

        if ( $type eq "pHYs" ) {
            my $x_ppu = unpack "N", substr $data, $offset + 8,  4;
            my $y_ppu = unpack "N", substr $data, $offset + 12, 4;
            my $units = unpack "C", substr $data, $offset + 16, 1;

            if ( $units == 1 ) {
                $x_dpi = $x_ppu * 0.0254;
                $y_dpi = $y_ppu * 0.0254;
            }
        }

        $offset = $offset + $length + 12;

        last if $type eq "IEND";
    }

    if ( not defined $height ) {
        croak "$filename: no size data found in png image.\n";
    }

    return ( $type, $width, $height, $x_dpi, $y_dpi );
}


###############################################################################
#
# _process_bmp()
#
# Extract width and height information from a BMP file.
#
# Most of the checks came from old Spredsheet::WriteExcel code.
#
sub _process_bmp {

    my $self     = shift;
    my $data     = $_[0];
    my $filename = $_[1];
    my $type     = 'bmp';


    # Check that the file is big enough to be a bitmap.
    if ( length $data <= 0x36 ) {
        croak "$filename doesn't contain enough data.";
    }


    # Read the bitmap width and height. Verify the sizes.
    my ( $width, $height ) = unpack "x18 V2", $data;

    if ( $width > 0xFFFF ) {
        croak "$filename: largest image width $width supported is 65k.";
    }

    if ( $height > 0xFFFF ) {
        croak "$filename: largest image height supported is 65k.";
    }

    # Read the bitmap planes and bpp data. Verify them.
    my ( $planes, $bitcount ) = unpack "x26 v2", $data;

    if ( $bitcount != 24 ) {
        croak "$filename isn't a 24bit true color bitmap.";
    }

    if ( $planes != 1 ) {
        croak "$filename: only 1 plane supported in bitmap image.";
    }


    # Read the bitmap compression. Verify compression.
    my $compression = unpack "x30 V", $data;

    if ( $compression != 0 ) {
        croak "$filename: compression not supported in bitmap image.";
    }

    return ( $type, $width, $height );
}


###############################################################################
#
# _process_jpg()
#
# Extract width and height information from a JPEG file.
#
sub _process_jpg {

    my $self     = shift;
    my $data     = $_[0];
    my $filename = $_[1];
    my $type     = 'jpeg';
    my $x_dpi    = 96;
    my $y_dpi    = 96;
    my $width;
    my $height;

    my $offset      = 2;
    my $data_length = length $data;

    # Search through the image data to read the JPEG markers.
    while ( $offset < $data_length ) {

        my $marker = unpack "n", substr $data, $offset + 0, 2;
        my $length = unpack "n", substr $data, $offset + 2, 2;

        # Read the height and width in the 0xFFCn elements (except C4, C8 and
        # CC which aren't SOF markers).
        if (   ( $marker & 0xFFF0 ) == 0xFFC0
            && $marker != 0xFFC4
            && $marker != 0xFFCC )
        {
            $height = unpack "n", substr $data, $offset + 5, 2;
            $width  = unpack "n", substr $data, $offset + 7, 2;
        }

        # Read the DPI in the 0xFFE0 element.
        if ( $marker == 0xFFE0 ) {
            my $units     = unpack "C", substr $data, $offset + 11, 1;
            my $x_density = unpack "n", substr $data, $offset + 12, 2;
            my $y_density = unpack "n", substr $data, $offset + 14, 2;

            if ( $units == 1 ) {
                $x_dpi = $x_density;
                $y_dpi = $y_density;
            }

            if ( $units == 2 ) {
                $x_dpi = $x_density * 2.54;
                $y_dpi = $y_density * 2.54;
            }
        }

        $offset = $offset + $length + 2;
        last if $marker == 0xFFDA;
    }

    if ( not defined $height ) {
        croak "$filename: no size data found in jpeg image.\n";
    }

    return ( $type, $width, $height, $x_dpi, $y_dpi );
}


###############################################################################
#
# _get_sheet_index()
#
# Convert a sheet name to its index. Return undef otherwise.
#
sub _get_sheet_index {

    my $self        = shift;
    my $sheetname   = shift;
    my $sheet_index = undef;

    $sheetname =~ s/^'//;
    $sheetname =~ s/'$//;

    if ( exists $self->{_sheetnames}->{$sheetname} ) {
        return $self->{_sheetnames}->{$sheetname}->{_index};
    }
    else {
        return undef;
    }
}


###############################################################################
#
# set_optimization()
#
# Set the speed/memory optimisation level.
#
sub set_optimization {

    my $self = shift;
    my $level = defined $_[0] ? $_[0] : 1;

    croak "set_optimization() must be called before add_worksheet()"
      if $self->sheets();

    $self->{_optimization} = $level;
}


###############################################################################
#
# Deprecated methods for backwards compatibility.
#
###############################################################################

# No longer required by Excel::Writer::XLSX.
sub compatibility_mode { }
sub set_codepage       { }


###############################################################################
#
# XML writing methods.
#
###############################################################################


###############################################################################
#
# _write_workbook()
#
# Write <workbook> element.
#
sub _write_workbook {

    my $self    = shift;
    my $schema  = 'http://schemas.openxmlformats.org';
    my $xmlns   = $schema . '/spreadsheetml/2006/main';
    my $xmlns_r = $schema . '/officeDocument/2006/relationships';

    my @attributes = (
        'xmlns'   => $xmlns,
        'xmlns:r' => $xmlns_r,
    );

    $self->xml_start_tag( 'workbook', @attributes );
}


###############################################################################
#
# write_file_version()
#
# Write the <fileVersion> element.
#
sub _write_file_version {

    my $self          = shift;
    my $app_name      = 'xl';
    my $last_edited   = 4;
    my $lowest_edited = 4;
    my $rup_build     = 4505;

    my @attributes = (
        'appName'      => $app_name,
        'lastEdited'   => $last_edited,
        'lowestEdited' => $lowest_edited,
        'rupBuild'     => $rup_build,
    );

    if ( $self->{_vba_project} ) {
        push @attributes, codeName => '{37E998C4-C9E5-D4B9-71C8-EB1FF731991C}';
    }

    $self->xml_empty_tag( 'fileVersion', @attributes );
}


###############################################################################
#
# _write_workbook_pr()
#
# Write <workbookPr> element.
#
sub _write_workbook_pr {

    my $self                   = shift;
    my $date_1904              = $self->{_date_1904};
    my $show_ink_annotation    = 0;
    my $auto_compress_pictures = 0;
    my $default_theme_version  = 124226;
    my $codename               = $self->{_vba_codename};
    my @attributes;

    push @attributes, ( 'codeName' => $codename ) if $codename;
    push @attributes, ( 'date1904' => 1 )         if $date_1904;
    push @attributes, ( 'defaultThemeVersion' => $default_theme_version );

    $self->xml_empty_tag( 'workbookPr', @attributes );
}


###############################################################################
#
# _write_book_views()
#
# Write <bookViews> element.
#
sub _write_book_views {

    my $self = shift;

    $self->xml_start_tag( 'bookViews' );
    $self->_write_workbook_view();
    $self->xml_end_tag( 'bookViews' );
}

###############################################################################
#
# _write_workbook_view()
#
# Write <workbookView> element.
#
sub _write_workbook_view {

    my $self          = shift;
    my $x_window      = $self->{_x_window};
    my $y_window      = $self->{_y_window};
    my $window_width  = $self->{_window_width};
    my $window_height = $self->{_window_height};
    my $tab_ratio     = $self->{_tab_ratio};
    my $active_tab    = $self->{_activesheet};
    my $first_sheet   = $self->{_firstsheet};

    my @attributes = (
        'xWindow'      => $x_window,
        'yWindow'      => $y_window,
        'windowWidth'  => $window_width,
        'windowHeight' => $window_height,
    );

    # Store the tabRatio attribute when it isn't the default.
    push @attributes, ( tabRatio => $tab_ratio ) if $tab_ratio != 600;

    # Store the firstSheet attribute when it isn't the default.
    push @attributes, ( firstSheet => $first_sheet + 1 ) if $first_sheet > 0;

    # Store the activeTab attribute when it isn't the first sheet.
    push @attributes, ( activeTab => $active_tab ) if $active_tab > 0;

    $self->xml_empty_tag( 'workbookView', @attributes );
}

###############################################################################
#
# _write_sheets()
#
# Write <sheets> element.
#
sub _write_sheets {

    my $self   = shift;
    my $id_num = 1;

    $self->xml_start_tag( 'sheets' );

    for my $worksheet ( @{ $self->{_worksheets} } ) {
        $self->_write_sheet( $worksheet->{_name}, $id_num++,
            $worksheet->{_hidden} );
    }

    $self->xml_end_tag( 'sheets' );
}


###############################################################################
#
# _write_sheet()
#
# Write <sheet> element.
#
sub _write_sheet {

    my $self     = shift;
    my $name     = shift;
    my $sheet_id = shift;
    my $hidden   = shift;
    my $r_id     = 'rId' . $sheet_id;

    my @attributes = (
        'name'    => $name,
        'sheetId' => $sheet_id,
    );

    push @attributes, ( 'state' => 'hidden' ) if $hidden;
    push @attributes, ( 'r:id' => $r_id );


    $self->xml_empty_tag( 'sheet', @attributes );
}


###############################################################################
#
# _write_calc_pr()
#
# Write <calcPr> element.
#
sub _write_calc_pr {

    my $self            = shift;
    my $calc_id         = $self->{_calc_id};
    my $concurrent_calc = 0;

    my @attributes = ( calcId => $calc_id );

    if ( $self->{_calc_mode} eq 'manual' ) {
        push @attributes, 'calcMode'   => 'manual';
        push @attributes, 'calcOnSave' => 0;
    }
    elsif ( $self->{_calc_mode} eq 'autoNoTable' ) {
        push @attributes, calcMode => 'autoNoTable';
    }

    if ( $self->{_calc_on_load} ) {
        push @attributes, 'fullCalcOnLoad' => 1;
    }


    $self->xml_empty_tag( 'calcPr', @attributes );
}


###############################################################################
#
# _write_ext_lst()
#
# Write <extLst> element.
#
sub _write_ext_lst {

    my $self = shift;

    $self->xml_start_tag( 'extLst' );
    $self->_write_ext();
    $self->xml_end_tag( 'extLst' );
}


###############################################################################
#
# _write_ext()
#
# Write <ext> element.
#
sub _write_ext {

    my $self     = shift;
    my $xmlns_mx = 'http://schemas.microsoft.com/office/mac/excel/2008/main';
    my $uri      = 'http://schemas.microsoft.com/office/mac/excel/2008/main';

    my @attributes = (
        'xmlns:mx' => $xmlns_mx,
        'uri'      => $uri,
    );

    $self->xml_start_tag( 'ext', @attributes );
    $self->_write_mx_arch_id();
    $self->xml_end_tag( 'ext' );
}

###############################################################################
#
# _write_mx_arch_id()
#
# Write <mx:ArchID> element.
#
sub _write_mx_arch_id {

    my $self  = shift;
    my $Flags = 2;

    my @attributes = ( 'Flags' => $Flags, );

    $self->xml_empty_tag( 'mx:ArchID', @attributes );
}


##############################################################################
#
# _write_defined_names()
#
# Write the <definedNames> element.
#
sub _write_defined_names {

    my $self = shift;

    return unless @{ $self->{_defined_names} };

    $self->xml_start_tag( 'definedNames' );

    for my $aref ( @{ $self->{_defined_names} } ) {
        $self->_write_defined_name( $aref );
    }

    $self->xml_end_tag( 'definedNames' );
}


##############################################################################
#
# _write_defined_name()
#
# Write the <definedName> element.
#
sub _write_defined_name {

    my $self = shift;
    my $data = shift;

    my $name   = $data->[0];
    my $id     = $data->[1];
    my $range  = $data->[2];
    my $hidden = $data->[3];

    my @attributes = ( 'name' => $name );

    push @attributes, ( 'localSheetId' => $id ) if $id != -1;
    push @attributes, ( 'hidden'       => 1 )   if $hidden;

    $self->xml_data_element( 'definedName', $range, @attributes );
}


1;


__END__


=head1 NAME

Workbook - A class for writing Excel Workbooks.

=head1 SYNOPSIS

See the documentation for L<Excel::Writer::XLSX>

=head1 DESCRIPTION

This module is used in conjunction with L<Excel::Writer::XLSX>.

=head1 AUTHOR

John McNamara jmcnamara@cpan.org

=head1 COPYRIGHT

(c) MM-MMXX, John McNamara.

All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.

Youez - 2016 - github.com/yon3zu
LinuXploit