summaryrefslogtreecommitdiff
path: root/LedgerSMB/Template/ODS.pm
blob: 7c3df2709df17e573db0987295a27d4e25e623e9 (plain)
  1. =head1 NAME
  2. LedgerSMB::Template::ODS Template support module for LedgerSMB
  3. =head1 SYNOPSIS
  4. OpenDocument Spreadsheet output.
  5. =head1 METHODS
  6. =over
  7. =item get_template ($name)
  8. Returns the appropriate template filename for this format. '.xlst' is the
  9. extension that was chosen for the templates.
  10. =item preprocess ($vars)
  11. Returns $vars.
  12. =item process ($parent, $cleanvars)
  13. Processes the template for text.
  14. =item postprocess ($parent)
  15. Returns the output filename.
  16. =back
  17. =head1 Copyright (C) 2007, The LedgerSMB core team.
  18. This work contains copyrighted information from a number of sources all used
  19. with permission.
  20. It is released under the GNU General Public License Version 2 or, at your
  21. option, any later version. See COPYRIGHT file for details. For a full list
  22. including contact information of contributors, maintainers, and copyright
  23. holders, see the CONTRIBUTORS file.
  24. =cut
  25. package LedgerSMB::Template::ODS;
  26. use Error qw(:try);
  27. use Data::Dumper;
  28. use CGI::Simple::Standard qw(:html);
  29. use Template;
  30. use XML::Twig;
  31. use OpenOffice::OODoc;
  32. use LedgerSMB::Template::TTI18N;
  33. sub get_template {
  34. my $name = shift;
  35. return "${name}.odst";
  36. }
  37. sub preprocess {
  38. my $rawvars = shift;
  39. my $vars;
  40. my $type = ref $rawvars;
  41. #XXX fix escaping function
  42. return $rawvars if $type =~ /^LedgerSMB::Locale/;
  43. if ( $type eq 'ARRAY' ) {
  44. for (@{$rawvars}) {
  45. push @{$vars}, preprocess( $_ );
  46. }
  47. } elsif (!$type) {
  48. return escapeHTML($rawvars);
  49. } else { # Hashes and objects
  50. for ( keys %{$rawvars} ) {
  51. $vars->{preprocess($_)} = preprocess( $rawvars->{$_} );
  52. }
  53. }
  54. return $vars;
  55. }
  56. sub _worksheet_handler {
  57. $rowcount = -1;
  58. $currcol = 0;
  59. $sheet = $ods->getTable(0, $_->{att}->{rows}, $_->{att}->{columns});
  60. $ods->renameTable($sheet, $_->{att}->{name});
  61. }
  62. sub _row_handler {
  63. $rowcount++;
  64. $currcol = 0;
  65. }
  66. sub _cell_handler {
  67. my $cell = $ods->getCell(-1, $rowcount, $currcol);
  68. if (@basestyle and $celltype{$basestyle[0][0]}) {
  69. $ods->cellValueType($cell, $celltype{$basestyle[0][0]}[0]);
  70. }
  71. $ods->cellValue($cell, $_->{att}->{text});
  72. if (@basestyle) {
  73. $ods->cellStyle($cell, $basestyle[0][0]);
  74. }
  75. $currcol++;
  76. }
  77. sub _border_set {
  78. my ($format, $properties, $border) = @_;
  79. my $edge = $border;
  80. $edge =~ s/^border-?//;
  81. my $val;
  82. if ($edge) {
  83. $val = $format->{att}{$edge};
  84. } else {
  85. $val = $format->{att}{'border'};
  86. }
  87. if ($properties->{cell}{"fo:$border"}){
  88. $properties->{cell}{"fo:$border"} =~ s/^.* (\#......)$/$val $1/;
  89. } else {
  90. $properties->{cell}{"fo:$border"} = "$width[$val] #000000";
  91. }
  92. if ($edge and $format->{att}->{"${edge}_color"}) {
  93. my $colour = $format->{att}->{"${edge}_color"};
  94. if ($colour =~ /^\d+$/) {
  95. $colour = $colour[$colour];
  96. } elsif ($color !~ /^\#......$/) {
  97. $colour = $colour_name{$colour};
  98. }
  99. $properties->{cell}{"fo:$border"} =~ s/^(.*) \#......$/$1 $colour/;
  100. } elsif ($format->{att}->{border_color}) {
  101. my $colour = $format->{att}->{"${edge}_color"};
  102. if ($colour =~ /^\d+$/) {
  103. $colour = $colour[$colour];
  104. } elsif ($color !~ /^\#......$/) {
  105. $colour = $colour_name{$colour};
  106. }
  107. $properties->{cell}{"fo:$border"} =~ s/^(.*) \#......$/$1 $colour/;
  108. }
  109. }
  110. sub _prepare_float {
  111. my ($style) = shift;
  112. my %properties;
  113. my @sides = split /\./, $style;
  114. if ($#sides == 1) { # decimal places
  115. $properties{'number:decimal-places'} = length $sides[1];
  116. }
  117. $properties{'number:min-integer-digits'} = length($sides[0] =~ /0+$/);
  118. $properties{'number:grouping'} = 'true' if $sides[0] =~ /.,...$/;
  119. \%properties;
  120. }
  121. sub _prepare_fraction {
  122. my ($style) = shift;
  123. my %properties;
  124. my @sides = split /[ \/]/, $style;
  125. $properties{'number:min-integer-digits'} = length($sides[0] =~ /0+$/);
  126. $properties{'number:min-numerator-digits'} = length($sides[1]);
  127. $properties{'number:min-denominator-digits'} = length($sides[2]);
  128. \%properties;
  129. }
  130. sub _create_positive_style {
  131. my ($name, $type, $base) = @_;
  132. my $pstyle = $ods->createStyle(
  133. $name,
  134. namespace => 'number',
  135. type => $type,
  136. properties => $base,
  137. references => {
  138. 'style:volatile' => 'true',
  139. },
  140. );
  141. $pstyle->insert_new_elt('last-child',
  142. 'number:text', {}, ' ');
  143. }
  144. sub _format_handler {
  145. my ($t, $format) = @_;
  146. my $style = "ce$stylecount";
  147. my @extras;
  148. local @width = ('none', '0.018cm solid', '0.035cm solid',
  149. '0.018cm dashed', '0.018cm dotted', '0.141cm solid',
  150. '0.039cm double', '0.002cm solid');
  151. # SC: Subtract 8 from the attribute to get the index
  152. # http://search.cpan.org/src/JMCNAMARA/Spreadsheet-WriteExcel-2.11/doc/palette.html
  153. local @colour = (odfColor(0, 0, 0), odfColor(255, 255, 255),
  154. odfColor(255, 0, 0), odfColor(0, 255, 0),
  155. odfColor(0, 0, 255), odfColor(255, 255, 0),
  156. odfColor(255, 0, 255), odfColor(0, 255, 255),
  157. odfColor(128, 0, 0), odfColor(0, 128, 0),
  158. odfColor(0, 0, 128), odfColor(128, 128, 0),
  159. odfColor(128, 0, 128), odfColor(0, 128, 128),
  160. odfColor(192, 192, 192), odfColor(128, 128, 128),
  161. odfColor(153, 153, 255), odfColor(153, 51, 102),
  162. odfColor(255, 255, 204), odfColor(204, 255, 255),
  163. odfColor(102, 0, 102), odfColor(255, 128, 128),
  164. odfColor(0, 102, 204), odfColor(204, 204, 255),
  165. odfColor(0, 0, 128), odfColor(255, 0, 255),
  166. odfColor(255, 255, 0), odfColor(0, 255, 255),
  167. odfColor(128, 0, 128), odfColor(120, 0, 0),
  168. odfColor(0, 128, 128), odfColor(0, 0, 255),
  169. odfColor(0, 204, 255), odfColor(204, 255, 255),
  170. odfColor(204, 255, 204), odfColor(255, 255, 153),
  171. odfColor(153, 204, 255), odfColor(255, 153, 204),
  172. odfColor(204, 153, 255), odfColor(192, 192, 192),
  173. odfColor(51, 102, 255), odfColor(51, 204, 204),
  174. odfColor(153, 204, 0), odfColor(255, 204, 0),
  175. odfColor(255, 153, 0), odfColor(255, 102, 0),
  176. odfColor(102, 102, 153), odfColor(150, 150, 150),
  177. odfColor(0, 51, 102), odfColor(51, 153, 102),
  178. odfColor(0, 51, 0), odfColor(51, 51, 0),
  179. odfColor(153, 51, 0), odfColor(153, 51, 102),
  180. odfColor(51, 51, 153), odfColor(51, 51, 51),
  181. );
  182. local %colour_name = ('black' => $colour[0], 'white' => $colour[1],
  183. 'red' => $colour[2], 'lime' => $colour[3],
  184. 'blue' => $colour[4], 'yellow' => $colour[5],
  185. 'magenta' => $colour[6], 'cyan' => $colour[7],
  186. 'brown' => $colour[8], 'green' => $colour[9],
  187. 'navy' => $colour[10], 'purple' => $colour[12],
  188. 'silver' => $colour[14], 'gray' => $colour[15],
  189. 'grey' => $colour[15], 'orange' => $colour[45],
  190. );
  191. # SC: There are multiple types of properties that can be associated
  192. # with a style. However, the OO::OOD style creation code appears
  193. # to only allow for a single type to be added to the style at a
  194. # time. As a result, %properties is split into property groupings
  195. # to allow for each group to get the correct type.
  196. my %properties;
  197. if (@basestyle) {
  198. %properties = %{$basestyle[0][1]};
  199. if ($celltype{$basestyle[0][0]}) {
  200. $celltype{$style} = $celltype{$basestyle[0][0]};
  201. @extras = ('references', {'style:data-style-name' => $celltype{$style}[1]});
  202. }
  203. }
  204. &_border_set(\%properties, $format, 'border') if $format->{att}->{border};
  205. while (my ($attr, $val) = each %{$format->{att}}) {
  206. if ($attr eq 'bottom') {
  207. &_border_set($format, \%properties, 'border-bottom');
  208. } elsif ($attr eq 'top') {
  209. &_border_set($format, \%properties, 'border-top');
  210. } elsif ($attr eq 'left') {
  211. &_border_set($format, \%properties, 'border-left');
  212. } elsif ($attr eq 'right') {
  213. &_border_set($format, \%properties, 'border-right');
  214. } elsif ($attr eq 'bg_color' or $attr eq 'bg_colour') {
  215. if ($val =~ /^\d+$/) {
  216. $properties{cell}{'fo:background-color'} =
  217. $colour[$val - 8];
  218. } elsif ($val =~ /^\#[0-9A-Fa-f]{6}$/) {
  219. $properties{cell}{'fo:background-color'} = $val;
  220. } else {
  221. $properties{cell}{'fo:background-color'} =
  222. $colour_name{$val};
  223. }
  224. } elsif ($attr eq 'color' or $attr eq 'colour') {
  225. if ($val =~ /^\d+$/) {
  226. $properties{text}{'fo:color'} =
  227. $colour[$val - 8];
  228. } elsif ($val =~ /^\#[0-9A-Fa-f]{6}$/) {
  229. $properties{text}{'fo:color'} = $val;
  230. } else {
  231. $properties{text}{'fo:color'} =
  232. $colour_name{$val};
  233. }
  234. } elsif ($attr eq 'align') {
  235. if (lc $val eq 'right') {
  236. $properties{paragraph}{'fo:text-align'} = 'end';
  237. } elsif (lc $val eq 'left') {
  238. $properties{paragraph}{'fo:text-align'} = 'start';
  239. } else {
  240. $properties{paragraph}{'fo:text-align'} = $val;
  241. }
  242. } elsif ($attr eq 'valign') {
  243. # takes top, vcenter, bottom, or vjustify
  244. # needs top, middle, or bottom
  245. if ($val =~ /^v/i) {
  246. $properties{paragraph}{'style:vertical-align'} = 'middle';
  247. } else {
  248. $properties{paragraph}{'style:vertical-align'} = $val;
  249. }
  250. } elsif ($attr eq 'hidden') {
  251. if ($properties{cell}{'style:cell-protect'} and !$val) {
  252. delete $properties{cell}{'style:cell-protect'};
  253. } elsif ($val) {
  254. $properties{cell}{'style:cell-protect'} = 'formula-hidden';
  255. }
  256. } elsif ($attr eq 'font') {
  257. $properties{text}{'style:font-name'} = $val;
  258. } elsif ($attr eq 'size') {
  259. $properties{text}{'fo:font-size'} = "${val}pt";
  260. } elsif ($attr eq 'bold') {
  261. if ($properties{text}{'fo:font-weight'} and !$val) {
  262. delete $properties{text}{'fo:font-weight'};
  263. } elsif ($val) {
  264. $properties{text}{'fo:font-weight'} = 'bold';
  265. }
  266. } elsif ($attr eq 'italic') {
  267. if ($properties{text}{'fo:font-style'} and !$val) {
  268. delete $properties{text}{'fo:font-style'};
  269. } elsif ($val) {
  270. $properties{text}{'fo:font-style'} = 'italic';
  271. }
  272. } elsif ($attr eq 'font_strikeout') {
  273. if (!$val) {
  274. $properties{text}{'style:text-line-through-type'} = 'none';
  275. } elsif ($val) {
  276. $properties{text}{'style:text-line-through-type'} = 'single';
  277. }
  278. } elsif ($attr eq 'font_shadow') {
  279. if ($properties{text}{'fo:text-shadow'} and !$val) {
  280. delete $properties{text}{'fo:text-shadow'};
  281. } elsif ($val) {
  282. $properties{text}{'fo:text-shadow'} = '2pt';
  283. }
  284. } elsif ($attr eq 'font_outline') {
  285. if ($properties{text}{'style:text-outline'} and !$val) {
  286. delete $properties{text}{'style:text-outline'};
  287. } elsif ($val) {
  288. $properties{text}{'style:text-outline'} = 'true';
  289. }
  290. } elsif ($attr eq 'shrink') {
  291. if ($properties{cell}{'style:shrink-to-fit'} and !$val) {
  292. delete $properties{cell}{'style:shrink-to-fit'};
  293. } elsif ($val) {
  294. $properties{cell}{'style:shrink-to-fit'} = 'true';
  295. }
  296. } elsif ($attr eq 'text_wrap') {
  297. if (!$val) {
  298. $properties{cell}{'style:wrap-option'} = 'no-wrap';
  299. } else {
  300. $properties{text}{'style:wrap-option'} = 'wrap';
  301. }
  302. } elsif ($attr eq 'text_justlast') {
  303. if ($properties{paragraph}{'fo:text-align-last'} and !$val) {
  304. delete $properties{paragraph}{'fo:text-align-last'};
  305. } elsif ($val) {
  306. $properties{paragraph}{'fo:text-align-last'} = 'justify';
  307. }
  308. } elsif ($attr eq 'num_format') {
  309. #SC: Number formatting is when I hit the point of,
  310. # "Screw the OO::OOD API, XML::Twig is simpler".
  311. # @children's elements are passed right into the
  312. # style via XML::Twig::Elt's insert_new_elt. The
  313. # OO:OOD API, while decent enough for the text
  314. # styles, is not so pleasant with complex number
  315. # styles.
  316. my @children;
  317. my %nproperties;
  318. my @nextras;
  319. my $nstyle;
  320. my $fval = sprintf 'N%02d', $val;
  321. @extras = ('references', {'style:data-style-name' => $fval});
  322. if ($sstyles{$fval}) {
  323. # pass through
  324. } elsif ($val == 0) {
  325. $celltype{$style} = 'float';
  326. } elsif ($val == 1) {
  327. $celltype{$style} = ['float', 'N01'];
  328. $nstyle = 'number-style';
  329. %nproperties = %{&_prepare_float('0')}
  330. } elsif ($val == 2) {
  331. $celltype{$style} = ['float', 'N02'];
  332. $nstyle = 'number-style';
  333. %nproperties = %{&_prepare_float('0.00')}
  334. } elsif ($val == 3) {
  335. $celltype{$style} = ['float', 'N03'];
  336. $nstyle = 'number-style';
  337. %nproperties = %{&_prepare_float('#,##0')}
  338. } elsif ($val == 4) {
  339. $celltype{$style} = ['float', "N04"];
  340. $nstyle = 'number-style';
  341. %nproperties = %{&_prepare_float('#,##0.00')}
  342. } elsif ($val == 5) { ## ($#,##0_);($#,##0)
  343. } elsif ($val == 6) {
  344. $celltype{$style} = 'currency';
  345. } elsif ($val == 7) {
  346. $celltype{$style} = 'currency';
  347. } elsif ($val == 8) {
  348. $celltype{$style} = 'currency';
  349. } elsif ($val == 9) { ## 0%
  350. $celltype{$style} = ['percentage', "N09"];
  351. $nstyle = 'percentage-style';
  352. %nproperties = %{&_prepare_float('0')};
  353. push @children, ['number:text', {}, '%'];
  354. } elsif ($val == 10) { ## 0.00%
  355. $celltype{$style} = ['percentage', "N10"];
  356. $nstyle = 'percentage-style';
  357. %nproperties = %{&_prepare_float('0.00')};
  358. push @children, ['number:text', {}, '%'];
  359. } elsif ($val == 11) { ## 0.00E+00
  360. $celltype{$style} = ['float', "N11"];
  361. $nstyle = 'number-style';
  362. push @children, ['number:scientific-number', {
  363. %{&_prepare_float('0.00')},
  364. 'number:min-exponent-digits' => 2
  365. }];
  366. } elsif ($val == 12) { ## # ?/?
  367. $celltype{$style} = ['float', "N12"];
  368. $nstyle = 'number-style';
  369. push @children, ['number:fraction',
  370. %{&_prepare_fraction('# ?/?')}];
  371. } elsif ($val == 13) { ## # ??/??
  372. $celltype{$style} = ['float', "N13"];
  373. $nstyle = 'number-style';
  374. push @children, ['number:fraction',
  375. %{&_prepare_fraction('# ??/??')}];
  376. } elsif ($val == 14) { ## m/d/yy
  377. $celltype{$style} = ['date', "N14"];
  378. $nstyle = 'date-style';
  379. @nextras = ('references' => {
  380. 'number:automatic-order' => 'true'});
  381. @children = (
  382. ['number:month'],
  383. ['number:text', {}, '/'],
  384. ['number:day'],
  385. ['number:text', {}, '/'],
  386. ['number:year'],
  387. );
  388. } elsif ($val == 15) { ## d-mmm-yy
  389. $celltype{$style} = ['date', "N15"];
  390. $nstyle = 'date-style';
  391. @nextras = ('references' => {
  392. 'number:automatic-order' => 'true'});
  393. @children = (
  394. ['number:day'],
  395. ['number:text', {}, '-'],
  396. ['number:month', {
  397. 'number:textual' => 'true'}],
  398. ['number:text', {}, '-'],
  399. ['number:year'],
  400. );
  401. } elsif ($val == 16) { ## d-mmm
  402. $celltype{$style} = ['date', "N16"];
  403. $nstyle = 'date-style';
  404. @nextras = ('references' => {
  405. 'number:automatic-order' => 'true'});
  406. @children = (
  407. ['number:day'],
  408. ['number:text', {}, '-'],
  409. ['number:month', {
  410. 'number:textual' => 'true'}],
  411. );
  412. } elsif ($val == 17) { ## mmm-yy
  413. $celltype{$style} = ['date', "N17"];
  414. $nstyle = 'date-style';
  415. @nextras = ('references' => {
  416. 'number:automatic-order' => 'true'});
  417. @children = (
  418. ['number:month', {
  419. 'number:textual' => 'true'}],
  420. ['number:text', {}, '-'],
  421. ['number:year'],
  422. );
  423. } elsif ($val == 18) { ## h:mm AM/PM
  424. $celltype{$style} = ['time', "N18"];
  425. $nstyle = 'time-style';
  426. @nextras = ('references' => {
  427. 'number:automatic-order' => 'true'});
  428. @children = (
  429. ['number:hours'],
  430. ['number:text', {}, ':'],
  431. ['number:minutes',
  432. {'number:style' => 'long'}],
  433. ['number:text', {}, ' '],
  434. ['number:am-pm']
  435. );
  436. } elsif ($val == 19) { ## h:mm:ss AM/PM
  437. $celltype{$style} = ['time', "N19"];
  438. $nstyle = 'time-style';
  439. @nextras = ('references' => {
  440. 'number:automatic-order' => 'true'});
  441. @children = (
  442. ['number:hours'],
  443. ['number:text', {}, ':'],
  444. ['number:minutes',
  445. {'number:style' => 'long'}],
  446. ['number:text', {}, ':'],
  447. ['number:seconds',
  448. {'number:style' => 'long'}],
  449. ['number:text', {}, ' '],
  450. ['number:am-pm']
  451. );
  452. } elsif ($val == 20) { ## h:mm
  453. $celltype{$style} = ['time', "N20"];
  454. $nstyle = 'time-style';
  455. @nextras = ('references' => {
  456. 'number:automatic-order' => 'true'});
  457. @children = (
  458. ['number:hours'],
  459. ['number:text', {}, ':'],
  460. ['number:minutes',
  461. {'number:style' => 'long'}],
  462. );
  463. } elsif ($val == 21) { ## h:mm:ss
  464. $celltype{$style} = ['time', "N21"];
  465. $nstyle = 'time-style';
  466. @nextras = ('references' => {
  467. 'number:automatic-order' => 'true'});
  468. @children = (
  469. ['number:hours'],
  470. ['number:text', {}, ':'],
  471. ['number:minutes',
  472. {'number:style' => 'long'}],
  473. ['number:text', {}, ':'],
  474. ['number:seconds',
  475. {'number:style' => 'long'}],
  476. );
  477. } elsif ($val == 22) { ## m/d/yy h:mm
  478. $celltype{$style} = ['date', "N22"];
  479. $nstyle = 'date-style';
  480. @nextras = ('references' => {
  481. 'number:automatic-order' => 'true'});
  482. @children = (
  483. ['number:month'],
  484. ['number:text', {}, '/'],
  485. ['number:day'],
  486. ['number:text', {}, '/'],
  487. ['number:year'],
  488. ['number:text', {}, ' '],
  489. ['number:hours'],
  490. ['number:text', {}, ':'],
  491. ['number:minutes',
  492. {'number:style' => 'long'}],
  493. );
  494. } elsif ($val == 37) { ## (#,##0_);(#,##0)
  495. $celltype{$style} = ['float', "N37"];
  496. $nstyle = 'number-style';
  497. my %base = (
  498. 'number:min-integer-digits' => 1,
  499. 'number:grouping' => 'true',
  500. );
  501. @children = (
  502. ['number:text', {}, '('],
  503. ['number:number', \%base],
  504. ['number:text', {}, ')'],
  505. ['style:map', {
  506. 'style:condition' => 'value()>=0',
  507. 'style:apply-style-name' => "NP37",
  508. }],
  509. );
  510. &_create_positive_style("NP37",
  511. $nstyle, \%base);
  512. } elsif ($val == 38) { ## (#,##0_);[Red](#,##0)
  513. $celltype{$style} = ['float', "N38"];
  514. $nstyle = 'number-style';
  515. my %base = %{&_prepare_float('#,##0')};
  516. @children = (
  517. ['style:text-properties',
  518. {'fo:color' => '#ff0000'}],
  519. ['number:text', {}, '('],
  520. ['number:number', \%base],
  521. ['number:text', {}, ')'],
  522. ['style:map',{
  523. 'style:condition' => 'value()>=0',
  524. 'style:apply-style-name' => "NP38",
  525. }]
  526. );
  527. &_create_positive_style("NP38",
  528. $nstyle, \%base);
  529. } elsif ($val == 39) { ## (#,##0.00_);(#,##0.00)
  530. $celltype{$style} = ['float', "N39"];
  531. $nstyle = 'number-style';
  532. my %base = %{&_prepare_float('#,##0.00')};
  533. @children = (
  534. ['number:text', {}, '('],
  535. ['number:number', \%base],
  536. ['number:text', {}, ')'],
  537. ['style:map',{
  538. 'style:condition' => 'value()>=0',
  539. 'style:apply-style-name' => "NP39",
  540. }]
  541. );
  542. &_create_positive_style("NP39",
  543. $nstyle, \%base);
  544. } elsif ($val == 40) { ## (#,##0.00_);[Red](#,##0.00)
  545. $celltype{$style} = ['float', "N40"];
  546. $nstyle = 'number-style';
  547. my %base = %{&_prepare_float('#,##0.00')};
  548. @children = (
  549. ['style:text-properties',
  550. {'fo:color' => '#ff0000'}],
  551. ['number:text', {}, '('],
  552. ['number:number', \%base],
  553. ['number:text', {}, ')'],
  554. ['style:map', {
  555. 'style:condition' => 'value()>=0',
  556. 'style:apply-style-name' => "NP40",
  557. }],
  558. );
  559. &_create_positive_style("NP40",
  560. $nstyle, \%base);
  561. } elsif ($val == 41) {
  562. $celltype{$style} = 'float';
  563. } elsif ($val == 42) {
  564. $celltype{$style} = 'currency';
  565. } elsif ($val == 43) {
  566. $celltype{$style} = 'float';
  567. } elsif ($val == 44) {
  568. $celltype{$style} = 'currency';
  569. } elsif ($val == 45) { ## mm:ss
  570. $celltype{$style} = ['time', "N45"];
  571. $nstyle = 'time-style';
  572. @nextras = ('references' => {
  573. 'number:automatic-order' => 'true'});
  574. @children = (
  575. ['number:minutes',
  576. {'number:style' => 'long'}],
  577. ['number:text', {}, ':'],
  578. ['number:seconds',
  579. {'number:style' => 'long'}],
  580. );
  581. } elsif ($val == 46) { ## [h]:mm:ss
  582. $celltype{$style} = ['time', "N46"];
  583. $nstyle = 'time-style';
  584. @nextras = ('references' => {
  585. 'number:automatic-order' => 'true',
  586. 'number:truncate-on-overflow' => 'false'});
  587. @children = (
  588. ['number:hours', {}],
  589. ['number:text', {}, ':'],
  590. ['number:minutes',
  591. {'number:style' => 'long'}],
  592. ['number:text', {}, ':'],
  593. ['number:seconds',
  594. {'number:style' => 'long'}],
  595. );
  596. } elsif ($val == 47) { ## mm:ss.0
  597. $celltype{$style} = ['time', "N47"];
  598. $nstyle = 'time-style';
  599. @nextras = ('references' => {
  600. 'number:automatic-order' => 'true'});
  601. @children = (
  602. ['number:minutes',
  603. {'number:style' => 'long'}],
  604. ['number:text', {}, ':'],
  605. ['number:seconds',
  606. {'number:style' => 'long',
  607. 'number:decimal-places' => 1}],
  608. );
  609. } elsif ($val == 48) { ## ##0.0E+0
  610. $celltype{$style} = ['float', "N48"];
  611. $nstyle = 'number-style';
  612. %nproperties = ();
  613. push @children, ['number:scientific-number',
  614. {%{&_prepare_float(0.0)},
  615. 'number:min-exponent-digits' => 1
  616. }];
  617. } elsif ($val == 49) {
  618. $celltype{$style} = 'string';
  619. }
  620. # $nstyle is set on new styles
  621. if ($nstyle) {
  622. my $cstyle = $ods->createStyle(
  623. $celltype{$style}[1],
  624. namespace => 'number',
  625. type => $nstyle,
  626. properties => \%nproperties,
  627. @nextras,
  628. );
  629. for my $child (@children) {
  630. $cstyle->insert_new_elt('last_child',
  631. @$child);
  632. }
  633. $sstyles{$fval} = 1;
  634. }
  635. }
  636. }
  637. # Maintain a hash table to keep the final style list size down
  638. $Data::Dumper::Sortkeys = 1;
  639. my $mystyle = Digest::MD5::md5_hex(Dumper(\%properties, \@extras));
  640. if (!$sstyles{$mystyle}) {
  641. $ods->createStyle(
  642. $style,
  643. family => 'table-cell',
  644. properties => $properties{cell},
  645. @extras,
  646. );
  647. $ods->updateStyle(
  648. $style,
  649. properties => {
  650. -area => 'text',
  651. %{$properties{text}}
  652. }
  653. );
  654. $ods->updateStyle(
  655. $style,
  656. properties => {
  657. -area => 'paragraph',
  658. %{$properties{paragraph}}
  659. }
  660. );
  661. $stylecount++;
  662. $sstyles{$mystyle} = [$style, \%properties];
  663. }
  664. unshift @basestyle, $sstyles{$mystyle};
  665. }
  666. sub _format_cleanup_handler {
  667. my ($t, $format) = @_;
  668. shift @basestyle;
  669. }
  670. sub _ods_process {
  671. my ($filename, $template, $user) = @_;
  672. # the handlers need these vars in common
  673. local $ods = ooDocument(file => $filename, create => 'spreadsheet');
  674. local $sheet;
  675. local $rowcount;
  676. local $currcol;
  677. local $stylecount = 1;
  678. local @basestyle;
  679. local %sstyles;
  680. local %celltype;
  681. my $parser = XML::Twig->new(
  682. start_tag_handlers => {
  683. worksheet => \&_worksheet_handler,
  684. row => \&_row_handler,
  685. cell => \&_cell_handler,
  686. format => \&_format_handler,
  687. },
  688. twig_handlers => {
  689. format => \&_format_cleanup_handler,
  690. }
  691. );
  692. $parser->parse($template);
  693. $parser->flush;
  694. $ods->save;
  695. }
  696. sub process {
  697. my $parent = shift;
  698. my $cleanvars = shift;
  699. my $template;
  700. my $source;
  701. my $tempdir = ${LedgerSMB::Sysconfig::tempdir};
  702. my $output = '';
  703. $parent->{outputfile} ||= "$tempdir/$parent->{template}-output-$$";
  704. if (ref $parent->{template} eq 'SCALAR') {
  705. $source = $parent->{template};
  706. } elsif (ref $parent->{template} eq 'ARRAY') {
  707. $source = join "\n", @{$parent->{template}};
  708. } else {
  709. $source = get_template($parent->{template});
  710. }
  711. $template = Template->new({
  712. INCLUDE_PATH => $parent->{include_path},
  713. START_TAG => quotemeta('<?lsmb'),
  714. END_TAG => quotemeta('?>'),
  715. DELIMITER => ';',
  716. DEBUG => ($parent->{debug})? 'dirs': undef,
  717. DEBUG_FORMAT => '',
  718. }) || throw Error::Simple Template->error();
  719. if (not $template->process(
  720. $source,
  721. {%$cleanvars, %$LedgerSMB::Template::TTI18N::ttfuncs,
  722. 'escape' => \&preprocess},
  723. \$output, binmode => ':utf8')) {
  724. throw Error::Simple $template->error();
  725. }
  726. &_ods_process("$parent->{outputfile}.ods", $output, $parent->{myconfig});
  727. parent->{mimetype} = 'application/vnd.oasis.opendocument.spreadsheet';
  728. }
  729. sub postprocess {
  730. my $parent = shift;
  731. $parent->{rendered} = "$parent->{outputfile}.ods";
  732. return $parent->{rendered};
  733. }
  734. 1;