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