package OpenOffice::OOBuilder;

# Copyright 2004, Stefan Loones
# More info can be found at
# This library is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.

use 5.008;                   # lower versions not tested
use strict;
use warnings;
no warnings 'uninitialized';  # don't want this, because we use strict

$VERSION=sprintf("%d.%02d", q$Revision: 0.6 $ =~ /(\d+)\.(\d+)/);
%COLORS=('red' => 'ff0000', 'green' => '00ff00', 'blue' => '0000ff', 
         'white' => 'ffffff', 'black' => '000000');

# - Object constructor
sub new {
  my ($proto, $class, $self, $doctype);
  $class=ref($proto) || $proto;
  $doctype='sxw' if (! $doctype);
  $self->{oooType}    = $doctype;
  $self->{contentxml} = undef;
  $self->{builddir}   = '.';
  $self->{tgt_file}   = 'oo_doc';
  $self->{meta}       = undef;
  $self->{log}        = 0;
  # - Init available fonts
  $self->{availfonts}{Arial}=q{<style:font-decl style:name="Arial" fo:font-family="Arial" style:font-family-generic="swiss" style:font-pitch="variable"/>};
  $self->{availfonts}{'Bitstream Vera Sans'}=q{<style:font-decl style:name="Bitstream Vera Sans" fo:font-family="\&apos;Bitstream Vera Sans\&apos;" style:font-pitch="variable"/>};
  $self->{availfonts}{'Bitstream Vera Serif'}=q{<style:font-decl style:name="Bitstream Vera Serif" fo:font-family="\&apos;Bitstream Vera Serif\&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>};
  $self->{availfonts}{Bookman}=q{<style:font-decl style:name="Bookman" fo:font-family="Bookman" style:font-pitch="variable"/>};
  $self->{availfonts}{Courier}=q{<style:font-decl style:name="Courier" fo:font-family="Courier" style:font-family-generic="modern" style:font-pitch="fixed"/>};
  $self->{availfonts}{'Courier 10 Pitch'}=q{<style:font-decl style:name="Courier 10 Pitch" fo:font-family="\&apos;Courier 10 Pitch\&apos;" style:font-pitch="fixed"/>};
  $self->{availfonts}{Helvetica}=q{<style:font-decl style:name="Helvetica" fo:font-family="Helvetica" style:font-family-generic="swiss" style:font-pitch="variable"/>};
  $self->{availfonts}{Lucidabright}=q{<style:font-decl style:name="Lucidabright" fo:font-family="Lucidabright" style:font-pitch="variable"/>};
  $self->{availfonts}{Lucidasans}=q{<style:font-decl style:name="Lucidasans" fo:font-family="Lucidasans" style:font-pitch="variable"/>};
  $self->{availfonts}{'Lucida Sans Unicode'}=q{<style:font-decl style:name="Lucida Sans Unicode" fo:font-family="\&apos;Lucida Sans Unicode\&apos;" style:font-pitch="variable"/>};
  $self->{availfonts}{Lucidatypewriter}=q{<style:font-decl style:name="Lucidatypewriter" fo:font-family="Lucidatypewriter" style:font-pitch="fixed"/>};
  $self->{availfonts}{'Luxi Mono'}=q{<style:font-decl style:name="Luxi Mono" fo:font-family="\&apos;Luxi Mono\&apos;" style:font-pitch="fixed" style:font-charset="x-symbol"/>};
  $self->{availfonts}{'Luxi Sans'}=q{<style:font-decl style:name="Luxi Sans" fo:font-family="\&apos;Luxi Sans\&apos;" style:font-pitch="variable" style:font-charset="x-symbol"/>};
  $self->{availfonts}{'Luxi Serif'}=q{<style:font-decl style:name="Luxi Serif" fo:font-family="\&apos;Luxi Serif\&apos;" style:font-pitch="variable" style:font-charset="x-symbol"/>};
  $self->{availfonts}{Symbol}=q{<style:font-decl style:name="Symbol" fo:font-family="Symbol" style:font-pitch="variable" style:font-charset="x-symbol"/>};
  $self->{availfonts}{Tahoma}=q{<style:font-decl style:name="Tahoma" fo:font-family="Tahoma" style:font-pitch="variable"/>};
  $self->{availfonts}{Times}=q{<style:font-decl style:name="Times" fo:font-family="Times" style:font-family-generic="roman" style:font-pitch="variable"/>};
  $self->{availfonts}{'Times New Roman'}=q{<style:font-decl style:name="Times New Roman" fo:font-family="\&apos;Times New Roman\&apos;" style:font-family-generic="roman" style:font-pitch="variable"/>};
  $self->{availfonts}{Utopia}=q{<style:font-decl style:name="Utopia" fo:font-family="Utopia" style:font-family-generic="roman" style:font-pitch="variable"/>};
  $self->{availfonts}{'Zapf Chancery'}=q{<style:font-decl style:name="Zapf Chancery" fo:font-family="\&apos;Zapf Chancery\&apos;" style:font-pitch="variable"/>};
  $self->{availfonts}{'Zapf Dingbats'}=q{<style:font-decl style:name="Zapf Dingbats" fo:font-family="\&apos;Zapf Dingbats\&apos;" style:font-pitch="variable" style:font-charset="x-symbol"/>};

  $self->{style}{bold}     = 0;
  $self->{style}{italic}   = 0;
  $self->{style}{underline}= 0;
  $self->{style}{align}    = 'left';
  $self->{style}{txtcolor} = '000000';
  $self->{style}{bgcolor}  = 'ffffff';
  $self->{style}{font}     = 'Luxi Sans';
  $self->{style}{size}     = '10';

  $self->{actstyle}=join('#',($self->{style}{bold}, $self->{style}{italic},

  bless ($self, $class);
  return $self;
}   # - - End new (Object constructor)

# - * - Setters/Getters Meta.xml data

sub set_title {
  my $self=shift;
  $self->{meta}{title}=$self->encode_data (shift);

sub get_title {
  my $self=shift;
  return $self->{meta}{title};

sub set_author {
  my $self=shift;
  $self->{meta}{author}=$self->encode_data (shift);

sub get_author {
  my $self=shift;
  return $self->{meta}{author};

sub set_subject {
  my $self=shift;
  $self->{meta}{subject}=$self->encode_data (shift);

sub get_subject {
  my $self=shift;
  return $self->{meta}{subject};

sub set_comments {
  my $self=shift;
  $self->{meta}{comments}=$self->encode_data (shift);

sub get_comments {
  my $self=shift;
  return $self->{meta}{comments};

sub set_keywords {
  my ($self, @keywords)=@_;
  @{$self->{meta}{keywords}}=map $self->encode_data($_), @keywords;

sub push_keywords {
  my ($self, @keywords)=@_;
  push @{$self->{meta}{keywords}}, map $self->encode_data($_), @keywords;

sub get_keywords {
  my $self=shift;
  return @{$self->{meta}{keywords}};

sub set_meta {
  my ($self, $nb, $name, $value)=@_;
  if ($nb < 1 || $nb > 4 || ! $name) {
    return 0;
  } else {
    delete $self->{meta}{"data$nb"} if (exists ($self->{meta}{"data$nb"}));
    $name=$self->encode_data ($name);
    $name="meta$nb" if (! $name);

# ** TODO get_meta : best way to return it: array, hash or ?

# - Setters for active style

# ** TODO getters for active style
# not yet implemented because in the future we will probably add 
# ways to get the style of a specifique cell or location
# maybe we also add the possibility to read an existing document, and make 
# changes to it. This all makes that we have to be careful about this.

sub set_bold {
  my ($self, $bold)=@_;
  if (! $bold) {
  } else {

sub set_italic {
  my ($self, $italic)=@_;
  if (! $italic) {
  } else {

sub set_underline {
  my ($self, $underline)=@_;
  if (! $underline) {
  } else {

sub set_align {
  my ($self, $align)=@_;
  if ($align eq 'right' || $align eq 'center' || $align eq 'justify' || 
      $align eq 'left') {
    return 1;
  } else {
    return 0;

sub set_txtcolor {
  my ($self, $txtcolor)=@_;
  return 0 unless ($txtcolor);

sub set_bgcolor {
  my ($self, $bgcolor)=@_;
  return 0 unless ($bgcolor);

sub set_font {
  my ($self, $font)=@_;
  return 0 unless (exists($self->{availfonts}{$font}));

sub set_fontsize {
  my ($self, $size)=@_;
  $size=~ s/[^0-9]//g;
  return 0 if ($size<$MINFONTSIZE || $size>$MAXFONTSIZE);

 # - * - Build-directory

sub set_builddir {
  my ($self, $builddir)=@_;
  if (-d $builddir) {
    return 1;
  } else {
    return 0;

sub get_builddir {
  my $self=shift;
  return $self->{builddir};

# - * - PrivateMethods
sub generate {
  my ($self, $tgtfile)=@_;
  # - check workdirectory & filename
  $self->{builddir}='.' unless (-d $self->{builddir});
  $tgtfile='oo_doc' unless ($tgtfile);
  # - Available Document types and their mime types
  my (%mimetype);
  # Text - oowriter - sxw - OOWBuilder
  # Spreadsheet - oocalc - sxc - OOCBuilder
  # Drawing - oodraw - sxd - OODBuilder
  # Presentation - ooimpress - sxi - OOIBuilder
  # Formula - oomath - OOMBuilder
# ** TODO  
  # Chart            application/vnd.sun.xml.chart
  # Master Document  application/ 
  # - Generate mimetype.xml
  open TGT, qq{>$self->{builddir}/mimetype};
  print TGT $mimetype{$self->{oooType}};
  close TGT;
  # - Generate content.xml 
  #   (must be build by the derived class and put into $self->{contentxml})
  open TGT, qq{>$self->{builddir}/content.xml};
  print TGT $self->{contentxml};
  close TGT;
  # - Generate meta.xml, styles.xml, settings.xml

  # - Generate Manifest
  mkdir(qq{$self->{builddir}/META-INF}) unless (-d qq{$self->{builddir}/META-INF});
  open TGT, qq{>$self->{builddir}/META-INF/manifest.xml};
  print TGT qq{<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE manifest:manifest PUBLIC "-// Manifest 1.0//EN" "Manifest.dtd">
<manifest:manifest xmlns:manifest="">
 <manifest:file-entry manifest:media-type="$mimetype{$self->{oooType}}" manifest:full-path="/" />
 <manifest:file-entry manifest:media-type="" manifest:full-path="Pictures/" />
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="content.xml" />
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="styles.xml" />
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="meta.xml" />
 <manifest:file-entry manifest:media-type="text/xml" manifest:full-path="settings.xml" />
  close (TGT);
  # - Build compressed target file
  system("cd $self->{builddir}; zip -r '$tgtfile' mimetype &> /dev/null");
  system("cd $self->{builddir}; zip -r '$tgtfile' content.xml &> /dev/null");
  system("cd $self->{builddir}; zip -r '$tgtfile' styles.xml &> /dev/null");
  system("cd $self->{builddir}; zip -r '$tgtfile' meta.xml &> /dev/null");
  system("cd $self->{builddir}; zip -r '$tgtfile' settings.xml &> /dev/null");
  system("cd $self->{builddir}; zip -r '$tgtfile' META-INF/manifest.xml &> /dev/null");
# ** from michael (to test): /dev/null 2>&1 &

  # - remove workfiles & directory


sub _generate_meta {
  my ($self);

  # - prepare data
  my ($timestamp, $keywords);
  $keywords=join('',map qq{<meta:keyword>$_</meta:keyword>}, @{$self->{meta}{keywords}});
  # - user defined vars
  my ($meta, $name, $value, @tmp);
  for (1 .. 4) {
    if ($self->{meta}{"data$_"}) {
      $name=shift @tmp;
    } else {
    if ($value) {
      $meta.=qq{<meta:user-defined meta:name="$name">$value</meta:user-defined>};
    } else {
      $meta.=qq{<meta:user-defined meta:name="$name"/>};
  open (TGT, qq{>$self->{builddir}/meta.xml});
  print TGT 
qq{<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE office:document-meta PUBLIC "-// OfficeDocument 1.0//EN" "office.dtd">
<office:document-meta xmlns:office="" xmlns:xlink="" xmlns:dc="" xmlns:meta="" office:version="1.0">
<meta:generator>oooBuilder $VERSION</meta:generator>
}   # - - End _generate_meta

sub _generate_styles {
  my ($self);
# ** TODO

}   # - - End _generate_styles

sub _generate_settings {
  my ($self);

# ** TODO
}   # - - End _generate_settings

sub _set_active_style {
  my ($self);
  $self->{actstyle}=join('#',($self->{style}{bold}, $self->{style}{italic},

sub _check_color {
  my ($self, $color)=@_;
  $color=$COLORS{$color} if (! ($color =~ /^[0-9a-f]{6}$/));
  return $color

# OpenOffice TimeStamp (form = yyyy-mm-ddThh:mm:ss)
sub _oo_timestamp {
  my ($self);
  my ($sec,$min,$hour,$mday,$mon,$year,@rest) = gmtime(time);
  return (sprintf("%04d-%02d-%02dT%02d:%02d:%02d",

sub encode_data {
  my ($self, $data)=@_;
  $data=~ s/</\&lt;/g;
  $data=~ s/>/\&gt;/g;
  $data=~ s/'/\&apos;/g;
  $data=~ s/"/\&quot;/g;
  $data=~ s/\&/\&amp;/g;
  $data=~ s/\t/<text:tab-stop\/>/g;
  $data=~ s/([^\x20-\x7F])/'&#' . ord($1) . ';'/gse; # from
  return $data;


=head1 NAME

OpenOffice::OOBuilder - Perl OO interface for creating OpenOffice 


See the documentation of the derived classes.


OOBuilder is a Perl OO interface for creating OpenOffice documents. 
OOBuilder is the base class for all OpenOffice documents. Depending 
on the type of document you want to create you have to use a
derived class. 

At this moment only spreadsheet (sxc) documents are supported. See
the documentation of the derived class OOCBuilder for more information.

=head1 METHODS


  Don't call this directly. Must be called through a derived class.

set_title ($title)

  Set $title as title of your document.


  Returns the title of your document

set_author ($author)

  Set $author as author of your document.


  Returns the author of your document.

set_subject ($subject)

  Set $subject as subject of your document.


  Returns the subject of your document.

set_comments ($comments)

  Set $comments as comments of your document.


  Returns the comments of your document.

set_keywords (@keywords)

  Set @keywords as all keywords of your document.

push_keywords (keywords)

  Add one (if scalar supplied) or more (if list supplied) new keywords
  to your document.


  Returns the keywords of your document in an array.

set_meta ($nb, $name, $value)

  Set meta data. $nb is the meta number (between 1 and 4). 
  $name is the name of your meta data. If ommited we'll take "meta$nb".
  $value is the new value of your meta data.


  not yet implemented

set_bold ($bold)

  Set active font: bold: 1 to active, 0 to deactivate.

set_italic ($italic)

  Set active font: italic: 1 to active, 0 to deactivate.

set_underline ($underline)

  Set active font: underline: 1 to active, 0 to deactivate.

set_align ($align)

  Set align: allowed values: right, center, justify or left
  Else returns 0.

set_txtcolor ($txtcolor)

  Set the text color. $txtcolor can be a predefined value like red,
  green, blue, white or black. Or it can be specified in RGB in the 
  form ff0000 (ie. as red). This returns 0 if the color can't be 

set_bgcolor ($bgcolor)

  Set the text color. $bgcolor can be a predefined value like red,
  green, blue, white or black. Or it can be specified in RGB in the 
  form ff0000 (ie. as red). This returns 0 if the color can't be 

set_font ($font)

  Available fonts are : Arial, Bitstream Vera Sans, Bitstream Vera Serif,
  Bookman, Courier, Courier 10 Pitch, Helvetica, Lucidabright, Lucidasans,
  Lucida Sans Unicode, Lucidatypewriter, Luxi Mono, Luxi Sans, Luxi Serif,
  Symbol, Tahoma, Times, Times New Roman, Utopia, Zapf Chancery, 
  Zapf Dingbats.
  Returns 0 if the font isn't available.

set_fontsize ($size)

  Set font size. Returns 0 if not succeeded.

set_builddir ($builddir)

  Set build directory. The target file will be placed in this directory.
  This directory will also be used for creating the temporary files, 
  needed for creating the target file. Builddir is '.' by default.
  Returns 0 if the $builddir doesn't exist.


  Returns the build directory.

generate ($tgtfile)

  Don't call this method directly. This method should be called from 
  the derived class.


Look at the examples directory supplied together with this distribution.

=head1 SEE ALSO

L<OpenOffice::OOCBuilder> for creating spreadsheets.

Bug reports and questions can be sent to <oobuilder(at)>. 
Attention: make sure the word <oobuilder> is in the subject or 
body of your e-mail. Otherwhise your e-mail will be taken as 
spam and will not be read.

=head1 AUTHOR

Stefan Loones


Copyright 2004 by Stefan Loones

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself.