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