Properties.pm 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. # DISCLAIMER OF WARRANTY
  2. # Because this software is licensed free of charge, there is no warranty for the software,
  3. # to the extent permitted by applicable law. Except when otherwise stated in writing
  4. # the copyright holders and/or other parties provide the software "as is" without
  5. # warranty of any kind, either expressed or implied, including, but not limited to,
  6. # the implied warranties of merchantability and fitness for a particular purpose.
  7. # The entire risk as to the quality and performance of the software is with you.
  8. # Should the software prove defective, you assume the cost of all necessary
  9. # servicing, repair, or correction.
  10. # In no event unless required by applicable law or agreed to in writing will any
  11. # copyright holder, or any other party who may modify and/or redistribute the software
  12. # as permitted by the above licence, be liable to you for damages, including any general,
  13. # special, incidental, or consequential damages arising out of the use or inability
  14. # to use the software (including but not limited to loss of data or data being rendered
  15. # inaccurate or losses sustained by you or third parties or a failure of the software
  16. # to operate with any other software), even if such holder or other party
  17. # has been advised of the possibility of such damages.
  18. # AUTHOR
  19. # John McNamara jmcnamara@cpan.org
  20. # COPYRIGHT
  21. # Copyright MM-MMX, John McNamara.
  22. # All Rights Reserved. This module is free software. It may be used,
  23. # redistributed and/or modified under the terms of
  24. # the Artistic License(full text of the Artistic License http://dev.perl.org/licenses/artistic.html).
  25. package Spreadsheet::WriteExcel::Properties;
  26. ###############################################################################
  27. #
  28. # Properties - A module for creating Excel property sets.
  29. #
  30. #
  31. # Used in conjunction with Spreadsheet::WriteExcel
  32. #
  33. # Copyright 2000-2010, John McNamara.
  34. #
  35. # Documentation after __END__
  36. #
  37. use Exporter;
  38. use strict;
  39. use Carp;
  40. use POSIX 'fmod';
  41. use Time::Local 'timelocal';
  42. use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
  43. @ISA = qw(Exporter);
  44. $VERSION = '2.37';
  45. # Set up the exports.
  46. my @all_functions = qw(
  47. create_summary_property_set
  48. create_doc_summary_property_set
  49. _pack_property_data
  50. _pack_VT_I2
  51. _pack_VT_LPSTR
  52. _pack_VT_FILETIME
  53. );
  54. my @pps_summaries = qw(
  55. create_summary_property_set
  56. create_doc_summary_property_set
  57. );
  58. @EXPORT = ();
  59. @EXPORT_OK = (@all_functions);
  60. %EXPORT_TAGS = (testing => \@all_functions,
  61. property_sets => \@pps_summaries,
  62. );
  63. ###############################################################################
  64. #
  65. # create_summary_property_set().
  66. #
  67. # Create the SummaryInformation property set. This is mainly used for the
  68. # Title, Subject, Author, Keywords, Comments, Last author keywords and the
  69. # creation date.
  70. #
  71. sub create_summary_property_set {
  72. my @properties = @{$_[0]};
  73. my $byte_order = pack 'v', 0xFFFE;
  74. my $version = pack 'v', 0x0000;
  75. my $system_id = pack 'V', 0x00020105;
  76. my $class_id = pack 'H*', '00000000000000000000000000000000';
  77. my $num_property_sets = pack 'V', 0x0001;
  78. my $format_id = pack 'H*', 'E0859FF2F94F6810AB9108002B27B3D9';
  79. my $offset = pack 'V', 0x0030;
  80. my $num_property = pack 'V', scalar @properties;
  81. my $property_offsets = '';
  82. # Create the property set data block and calculate the offsets into it.
  83. my ($property_data, $offsets) = _pack_property_data(\@properties);
  84. # Create the property type and offsets based on the previous calculation.
  85. for my $i (0 .. @properties -1) {
  86. $property_offsets .= pack('VV', $properties[$i]->[0], $offsets->[$i]);
  87. }
  88. # Size of $size (4 bytes) + $num_property (4 bytes) + the data structures.
  89. my $size = 8 + length($property_offsets) + length($property_data);
  90. $size = pack 'V', $size;
  91. return $byte_order .
  92. $version .
  93. $system_id .
  94. $class_id .
  95. $num_property_sets .
  96. $format_id .
  97. $offset .
  98. $size .
  99. $num_property .
  100. $property_offsets .
  101. $property_data;
  102. }
  103. ###############################################################################
  104. #
  105. # Create the DocSummaryInformation property set. This is mainly used for the
  106. # Manager, Company and Category keywords.
  107. #
  108. # The DocSummary also contains a stream for user defined properties. However
  109. # this is a little arcane and probably not worth the implementation effort.
  110. #
  111. sub create_doc_summary_property_set {
  112. my @properties = @{$_[0]};
  113. my $byte_order = pack 'v', 0xFFFE;
  114. my $version = pack 'v', 0x0000;
  115. my $system_id = pack 'V', 0x00020105;
  116. my $class_id = pack 'H*', '00000000000000000000000000000000';
  117. my $num_property_sets = pack 'V', 0x0002;
  118. my $format_id_0 = pack 'H*', '02D5CDD59C2E1B10939708002B2CF9AE';
  119. my $format_id_1 = pack 'H*', '05D5CDD59C2E1B10939708002B2CF9AE';
  120. my $offset_0 = pack 'V', 0x0044;
  121. my $num_property_0 = pack 'V', scalar @properties;
  122. my $property_offsets_0 = '';
  123. # Create the property set data block and calculate the offsets into it.
  124. my ($property_data_0, $offsets) = _pack_property_data(\@properties);
  125. # Create the property type and offsets based on the previous calculation.
  126. for my $i (0 .. @properties -1) {
  127. $property_offsets_0 .= pack('VV', $properties[$i]->[0], $offsets->[$i]);
  128. }
  129. # Size of $size (4 bytes) + $num_property (4 bytes) + the data structures.
  130. my $data_len = 8 + length($property_offsets_0) + length($property_data_0);
  131. my $size_0 = pack 'V', $data_len;
  132. # The second property set offset is at the end of the first property set.
  133. my $offset_1 = pack 'V', 0x0044 + $data_len;
  134. # We will use a static property set stream rather than try to generate it.
  135. my $property_data_1 = pack 'H*', join '', qw (
  136. 98 00 00 00 03 00 00 00 00 00 00 00 20 00 00 00
  137. 01 00 00 00 36 00 00 00 02 00 00 00 3E 00 00 00
  138. 01 00 00 00 02 00 00 00 0A 00 00 00 5F 50 49 44
  139. 5F 47 55 49 44 00 02 00 00 00 E4 04 00 00 41 00
  140. 00 00 4E 00 00 00 7B 00 31 00 36 00 43 00 34 00
  141. 42 00 38 00 33 00 42 00 2D 00 39 00 36 00 35 00
  142. 46 00 2D 00 34 00 42 00 32 00 31 00 2D 00 39 00
  143. 30 00 33 00 44 00 2D 00 39 00 31 00 30 00 46 00
  144. 41 00 44 00 46 00 41 00 37 00 30 00 31 00 42 00
  145. 7D 00 00 00 00 00 00 00 2D 00 39 00 30 00 33 00
  146. );
  147. return $byte_order .
  148. $version .
  149. $system_id .
  150. $class_id .
  151. $num_property_sets .
  152. $format_id_0 .
  153. $offset_0 .
  154. $format_id_1 .
  155. $offset_1 .
  156. $size_0 .
  157. $num_property_0 .
  158. $property_offsets_0 .
  159. $property_data_0 .
  160. $property_data_1;
  161. }
  162. ###############################################################################
  163. #
  164. # _pack_property_data().
  165. #
  166. # Create a packed property set structure. Strings are null terminated and
  167. # padded to a 4 byte boundary. We also use this function to keep track of the
  168. # property offsets within the data structure. These offsets are used by the
  169. # calling functions. Currently we only need to handle 4 property types:
  170. # VT_I2, VT_LPSTR, VT_FILETIME.
  171. #
  172. sub _pack_property_data {
  173. my @properties = @{$_[0]};
  174. my $offset = $_[1] || 0;
  175. my $packed_property = '';
  176. my $data = '';
  177. my @offsets;
  178. # Get the strings codepage from the first property.
  179. my $codepage = $properties[0]->[2];
  180. # The properties start after 8 bytes for size + num_properties + 8 bytes
  181. # for each propety type/offset pair.
  182. $offset += 8 * (@properties + 1);
  183. for my $property (@properties) {
  184. push @offsets, $offset;
  185. my $property_type = $property->[1];
  186. if ($property_type eq 'VT_I2') {
  187. $packed_property = _pack_VT_I2($property->[2]);
  188. }
  189. elsif ($property_type eq 'VT_LPSTR') {
  190. $packed_property = _pack_VT_LPSTR($property->[2], $codepage);
  191. }
  192. elsif ($property_type eq 'VT_FILETIME') {
  193. $packed_property = _pack_VT_FILETIME($property->[2]);
  194. }
  195. else {
  196. croak "Unknown property type: $property_type\n";
  197. }
  198. $offset += length $packed_property;
  199. $data .= $packed_property;
  200. }
  201. return $data, \@offsets;
  202. }
  203. ###############################################################################
  204. #
  205. # _pack_VT_I2().
  206. #
  207. # Pack an OLE property type: VT_I2, 16-bit signed integer.
  208. #
  209. sub _pack_VT_I2 {
  210. my $type = 0x0002;
  211. my $value = $_[0];
  212. my $data = pack 'VV', $type, $value;
  213. return $data;
  214. }
  215. ###############################################################################
  216. #
  217. # _pack_VT_LPSTR().
  218. #
  219. # Pack an OLE property type: VT_LPSTR, String in the Codepage encoding.
  220. # The strings are null terminated and padded to a 4 byte boundary.
  221. #
  222. sub _pack_VT_LPSTR {
  223. my $type = 0x001E;
  224. my $string = $_[0] . "\0";
  225. my $codepage = $_[1];
  226. my $length;
  227. my $byte_string;
  228. if ($codepage == 0x04E4) {
  229. # Latin1
  230. $byte_string = $string;
  231. $length = length $byte_string;
  232. }
  233. elsif ($codepage == 0xFDE9) {
  234. # UTF-8
  235. if ( $] > 5.008 ) {
  236. require Encode;
  237. if (Encode::is_utf8($string)) {
  238. $byte_string = Encode::encode_utf8($string);
  239. }
  240. else {
  241. $byte_string = $string;
  242. }
  243. }
  244. else {
  245. $byte_string = $string;
  246. }
  247. $length = length $byte_string;
  248. }
  249. else {
  250. croak "Unknown codepage: $codepage\n";
  251. }
  252. # Pack the data.
  253. my $data = pack 'VV', $type, $length;
  254. $data .= $byte_string;
  255. # The packed data has to null padded to a 4 byte boundary.
  256. if (my $extra = $length % 4) {
  257. $data .= "\0" x (4 - $extra);
  258. }
  259. return $data;
  260. }
  261. ###############################################################################
  262. #
  263. # _pack_VT_FILETIME().
  264. #
  265. # Pack an OLE property type: VT_FILETIME.
  266. #
  267. sub _pack_VT_FILETIME {
  268. my $type = 0x0040;
  269. my $localtime = $_[0];
  270. # Convert from localtime to seconds.
  271. my $seconds = Time::Local::timelocal(@{$localtime});
  272. # Add the number of seconds between the 1601 and 1970 epochs.
  273. $seconds += 11644473600;
  274. # The FILETIME seconds are in units of 100 nanoseconds.
  275. my $nanoseconds = $seconds * 1E7;
  276. # Pack the total nanoseconds into 64 bits.
  277. my $time_hi = int($nanoseconds / 2**32);
  278. my $time_lo = POSIX::fmod($nanoseconds, 2**32);
  279. my $data = pack 'VVV', $type, $time_lo, $time_hi;
  280. return $data;
  281. }
  282. 1;
  283. __END__
  284. =head1 NAME
  285. Properties - A module for creating Excel property sets.
  286. =head1 SYNOPSIS
  287. See the C<set_properties()> method in the Spreadsheet::WriteExcel documentation.
  288. =head1 DESCRIPTION
  289. This module is used in conjunction with Spreadsheet::WriteExcel.
  290. =head1 AUTHOR
  291. John McNamara jmcnamara@cpan.org
  292. =head1 COPYRIGHT
  293. © MM-MMX, John McNamara.
  294. All Rights Reserved. This module is free software. It may be used, redistributed and/or modified under the same terms as Perl itself.