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