package SXWWriter::Elt;
use XML::Twig;
use base "XML::Twig::Elt";
##########################################################
# Add to element
##########################################################
# 'public' functions
sub add_h { add_text($_[0],'h',$_[1],$_[2]);} # add a <text:h> elem
+ent
sub add_p { add_text($_[0],'p',$_[1],$_[2]);} # add a <text:p> elem
+ent
sub add { # insert an object into another (oo err missu
+s)
my ($self,$obj) = @_;
$obj->paste('last_child',$self);
$obj;
}
# internals
sub add_text { # add a <text:$type>
+ element
my ($self,$type,$text,$style) = @_;
$style ||= "Standard";
$text ||= '#EMPTY'; # Ensure an <empty/> tag
my @t;
foreach ((ref $text eq 'ARRAY') ? @$text : $text) {
while (s/^(.*?)\t//) { #replace tabs with <text:tab-stop />
push @t,$1;
push @t,SXWWriter::Elt->new('text:tab-stop','#EMPTY');
}
push @t,$_;
}
my $t = SXWWriter::Elt->new("text:$type"=>{'text:style-name'=>$sty
+le},@t);
$t->paste('last_child',$self);
}
sub insert_new { # 'cos insert_new_elt returns an XML::Twig::Elt
my $s = shift();
$s->add(SXWWriter::Elt->new(@_));
}
1;
package SXWWriter;
use XML::Twig;
use strict;
use warnings;
use File::Basename;
our $compression = 6; # default compression level
$XML::Twig::base_ent{'£'}='£';
sub new {
my ($class) = shift();
my %doc=(
'manifest'=>{fname=>"Meta-Inf\\manifest.xml",xml=>make_xml("ma
+nifest:manifest")},
'meta'=>{fname=>"meta.xml",xml=>make_xml("office:document-meta
+")},
'styles'=>{fname=>"styles.xml",xml=>make_xml("office:document-
+content")},
'content'=>{fname=>"content.xml",xml=>make_xml("office:documen
+t-styles")},
'index'=>1
);
my @ns = (['office:version','1.0'],['xmlns:office','http://openoff
+ice.org/2000/office'],
['xmlns:number','http://openoffice.org/2000/datastyle'],['xmln
+s:math','http://www.w3.org/1998/Math/MathML'],
['xmlns:xlink','http://www.w3.org/1999/xlink'],['xmlns:form','
+http://openoffice.org/2000/form'],
['xmlns:table','http://openoffice.org/2000/table'], ['xmlns:st
+yle','http://openoffice.org/2000/style'],
['xmlns:script','http://openoffice.org/2000/script'], ['xmlns:
+draw','http://openoffice.org/2000/drawing'],
['xmlns:svg','http://www.w3.org/2000/svg'], ['xmlns:fo','http:
+//www.w3.org/1999/XSL/Format'],
['xmlns:text','http://openoffice.org/2000/text'], ['xmlns:dr3d
+','http://openoffice.org/2000/dr3d'],
['xmlns:chart','http://openoffice.org/2000/chart']);
my $mroot = SXWWriter::Elt->new('manifest:manifest'=>{'xmlns:manif
+est'=>"http://openoffice.org/2001/manifest"});
$doc{manifest}{xml}->set_root($mroot);
my %props = map {$_->[0]=>$_->[1]} @ns[0..1];
my $nroot = SXWWriter::Elt->new('office:document-meta'=>{%props,
'xmlns:meta'=> 'http://openoffice.org/2000/meta',
'xmlns:xlink'=> 'http://www.w3.org/1999/xlink',
'xmlns:dc'=>'http://purl.org/dc/elements/1.1/'});
$nroot->insert_new("office:meta");
$doc{meta}{xml}->set_root($nroot);
%props = map {$_->[0]=>$_->[1]} @ns;
my $sroot = SXWWriter::Elt->new('office:document-styles'=>{%props,
+'office:class'=>'text'});
my $fd = $sroot->insert_new("office:font-decls");
my $ss = $sroot->insert_new("office:styles");
my $as = $sroot->insert_new("office:automatic-styles");
my $pm = $as->insert_new("style:page-master"=>{"style:name"=>"pm1"
+});
$pm->insert_new("style:header-style",'#EMPTY');
$pm->insert_new("style:footer-style",'#EMPTY');
my $psp = $pm->insert_new("style:properties"=>{
'style:print-orientation'=>'portrait','style:footnote-max-
+height'=>'0cm',
'fo:page-width'=>"21.001cm",'fo:page-height'=>"29.7cm",
'fo:margin-right'=>'2cm','fo:margin-bottom'=>'2cm','fo:mar
+gin-top' => '2cm','fo:margin-left'=>'2cm'});
$psp->insert_new('style:footnote-sep'=>{
'style:distance-before-sep'=>'0.101cm','style:adjustment'=>'le
+ft','style:width'=>'0.018cm',
'style:rel-width'=>'25%','style:distance-after-sep'=>'0.101cm'
+,'style:color' =>'#000000'});
$ss->insert_new("text:linenumbering-configuration"=>
{'text:increment'=>'5','text:number-lines'=>'false','text:numb
+er-position'=>'left','text:offset'=>'0.499cm','style:num-format'=>'1'
+},
'#EMPTY');
$ss->insert_new("text:footnotes-configuration"=>
{'style:num-format'=>"1",'text:start-value'=>"0",'text:footnot
+es-position'=>"page",'text:start-numbering-at'=>"document"},
'#EMPTY');
$ss->insert_new("text:endnotes-configuration"=>{'style:num-format'
+=>"i",'text:start-value'=>"0"},'#EMPTY');
my $tos = $ss->insert_new("text:outline-style");
$tos->insert_new('text:outline-level-style'=>{'style:num-format'=>
+'','text:level'=>$_},'#EMPTY') foreach (0..10);
my $ms = $sroot->insert_new("office:master-styles");
$ms->insert_new("style:master-page"=>{"style:name"=>"Standard","st
+yle:page-master-name"=>"pm1"});
$doc{styles}{xml}->set_root($sroot);
my $croot = SXWWriter::Elt->new('office:document-content'=>{%props
+,'office:class'=>'text'});
$croot->insert_new("office:script",'#EMPTY');
$croot->insert_new("office:font-decls");
$croot->insert_new("office:automatic-styles");
my $body = $croot->insert_new("office:body");
my $fds = $body->insert_new("text:sequence-decls");
foreach (qw/Illustration Table Text Drawing/) {
$fds->insert_new('text:sequence-decl'=>{'text:name'=>$_,'text:
+display-outline-level'=>'0'},'#EMPTY');
}
$doc{content}{xml}->set_root($croot);
$doc{body} = $body;
$doc{pageprops} = $psp; # save page props - saves us having to
+find them again
my $doc = bless \%doc ,$class;
$doc->add_dc('generator',"SWX Creator");
$doc->add_manifest('/','application/vnd.sun.xml.writer','Pictures/
+','','content.xml',
'text/xml','styles.xml','text/xml','meta.xml','text/xml');
$doc->add_font('font-family-generic'=>'roman','font-pitch'=>'varia
+ble','name' => 'Thorndale','font-family' => 'Thorndale');
$doc->add_font('font-family-generic'=>'swiss','font-pitch'=>'varia
+ble','name'=>'Arial','font-family' => 'Arial');
$doc->add_sstyle(family=>'paragraph',name=>'Standard');
$doc->add_sstyle('parent-style-name'=>'Standard','family'=>'paragr
+aph','name'=>'Text body',
'properties' => {'fo:margin-top'=>'0cm','fo:margin-bott
+om'=>'0.212cm'}
);
$doc;
}
sub save {
my ($self,$filename) = @_;
use Archive::Zip qw( :ERROR_CODES);
my $zip = Archive::Zip->new();
my $m = $self->{manifest}{xml}->root;
foreach ($m->children()) {
if ($_->att('manifest:media-type') =~/image/) {
my $file = $_->att('manifest:full-path');
my $filename = basename($file);
my $m = $zip->addFile($file,"Pictures\\$filename");
$_->set_att('manifest:full-path',"Pictures/$filename");
}
}
foreach (keys %$self) {
next if /index|body|page/;
my $m = $zip->addString($self->{$_}{xml}->sprint,$self->{$_}{f
+name});
$m->desiredCompressionLevel($compression);
}
die "Can't write file $!" unless $zip->writeToFileNamed($filename)
+ == AZ_OK;
}
##########################################################
# Add to Document
##########################################################
sub add_manifest {
my $self = shift;
while (@_) {
$self->{manifest}{xml}->root->insert_new(
'manifest:file-entry'=>{"manifest:full-path"=>shift,'manif
+est:media-type'=>shift},'#EMPTY'
);
}
}
sub add_meta {add_to_meta($_[0],"meta:".$_[1],$_[2]);}
sub add_dc {add_to_meta($_[0],"dc:".$_[1],$_[2]);}
sub add_to_meta {
$_[0]->{meta}{xml}->root->first_child->insert_new($_[1],$_[2]);
}
sub add_font {
my ($self,%properties) = @_;
my $style = make_style(%properties);
$self->{content}{xml}->root->first_child('office:font-decls')->ins
+ert_new('style:font-decl',$style,'#EMPTY');
$self->{styles}{xml}->root->first_child('office:font-decls')->inse
+rt_new('style:font-decl',$style,'#EMPTY');
}
sub add_pstyle {
my ($self,$name,%props) = @_;
$self->add_style('name'=>$name,'family'=>"paragraph",'parent-style
+-name'=>"Standard",'properties'=>\%props);
}
sub add_tstyle {
my ($self,$name,%prop) = @_;
$self->add_style('name'=>$name,'family'=>"text",'properties'=>\%pr
+op);
}
sub add_fstyle {
my ($self,$name,%prop) = @_;
$self->add_style('name'=>$name,'family'=>"frame",'properties'=>\%p
+rop);
}
sub add_style {my $self=shift();$self->add_to_styles("content",'office
+:automatic-styles',@_);}
sub add_sstyle {my $self=shift();$self->add_to_styles("styles",'office
+:styles',@_);}
sub add_to_styles {
my ($self,$doc,$stylename,%prop) = @_;
my $prop = SXWWriter::Elt->new('style:properties'=>$prop{properti
+es},'#EMPTY');
delete $prop{properties};
$self->{$doc}{xml}->root->first_child($stylename)->insert_new("sty
+le:style",make_style(%prop),$prop);
}
###################################################
# Helpers
###################################################
sub make_style {
my %opts = @_;
foreach (keys %opts){
$opts{(($_ eq 'font-family')?"fo":"style").":$_"}=$opts{$_};
delete $opts{$_};
}
\%opts;
}
sub make_xml {
my ($type) = @_;
my $dtd = ($type =~ /manifest/) ? "Manifest" : "office";
my $doc = ($dtd eq "Manifest") ? $dtd : "Office Document";
my $t = XML::Twig->new(pretty_print => 'indented','output_encodin
+g'=>'UTF-8');
$t->set_xml_version("1.0");
$t->set_doctype($type,$dtd.".dtd","-//OpenOffice.org//DTD $doc 1.0
+//EN");
$t;
}
###################################################
# Document Properties
###################################################
sub set_page_margin {$_[0]->set_page_margins($_[1],$_[1],$_[1],$_[1]);
+}
sub set_page_margins {
my ($self,$l,$r,$t,$b) = @_;
$self->{pageprops}->set_att("fo:margin-left",$l/10 . "cm");
$self->{pageprops}->set_att("fo:margin-right",$r/10 . "cm");
$self->{pageprops}->set_att("fo:margin-top",$t/10 . "cm");
$self->{pageprops}->set_att("fo:margin-bottom",$b/10 . "cm");
}
##########################################################
# Make Document Elements
##########################################################
sub make_span {
my ($self,$text,$style) = @_;
SXWWriter::Elt->new('text:span'=>{'text:style-name'=>$style},$text
+);
}
my %boxprops = ('style:vertical-pos'=>"from-top",'style:vertical-rel'=
+>"paragraph",
'style:horizontal-pos'=>"from-left",'style:horizontal-rel'=>"p
+aragraph",
"fo:border"=>"none");
sub make_frame {
my ($self,$x,$y,$w,$h,%prop) = @_;
my $index = $self->{'index'}++;
my %props = (%boxprops,%prop);
$self->add_style('properties'=>\%props,"name"=>"fr$index","family"
+=>"graphics","parent-style-name"=>"Frame");
SXWWriter::Elt->new("draw:text-box"=>{_box($x,$y,$w,$h,$index,"Fra
+me")});
}
sub make_picture {
my ($self,$filename,$x,$y,$w,$h,%prop) = @_;
my %props = (%boxprops,'draw:luminance'=>"0%",'draw:contrast'=>"0%
+",
'draw:red'=>"0%",'draw:green'=>"0%",'draw:blue'=>"0%",'draw:ga
+mma'=>"1",
'draw:color-inversion'=>"false",'draw:transparency'=>"-100%",
'draw:color-mode'=>"standard",'fo:clip'=>"rect(0cm 0cm 0cm 0cm
+)",%prop);
use File::MMagic;
my $m = new File::MMagic;
my $media = $m->checktype_filename($filename);
$self->add_manifest($filename,$media); #will replace pathname w
+hen saving
$filename = basename($filename);
my $index = $self->{'index'}++;
$self->add_style('properties'=>\%props,"name"=>"fr$index","family"
+=>"graphics","parent-style-name"=>"Graphics");
SXWWriter::Elt->new("draw:image"=>{_box($x,$y,$w,$h,$index,"Graphi
+c"),
'xlink:href'=>"#Pictures/$filename",'xlink:type'=>"simple",
'xlink:show'=>"embed",'xlink:actuate'=>"onLoad"});
}
sub _box {
my ($x,$y,$w,$h,$i,$n) = @_;
return('svg:x'=>($x/10)."cm",'svg:y'=>($y/10) ."cm",
'svg:width'=> ($w/10) ."cm",'svg:height' => ($h/10) ."cm",
'draw:style-name'=>"fr$i",'draw:z-index'=>"1",
'text:anchor-type'=>"page",'draw:name'=>"$n$i");
}
1;
__END__
=head1 NAME
SXWWriter -- create simple Open Office .sxw files
=head1 SYNOPSIS
use SXWWriter;
my $doc = new SXWWriter();
$doc->add_font('font-pitch'=>'variable', # Arial & Thorndal
+e predefined
'name'=>'MyFont','font-family' => 'Courier New');
$doc->add_pstyle('P1','style:font-name'=>"Arial", # a paragraph
+ style
'fo:color'=>"#0000ff",'fo:font-size'=>"36pt");
$doc->add_pstyle('P2','style:font-name'=>"MyFont",'fo:font-size'=>
+"11pt");
$doc->add_tstyle('T1','fo:font-weight'=>"bold"); # inline text
+style
$doc->add_dc('title',"My Document"); # dc:field=val
+ue
$doc->add_meta('initial-creator',"Me"); # meta:fiel
+d=value
$doc->set_page_margin(10); # ...or set_page_margins(l,r,t,b)
my $body = $doc->{body}; # A subclass of XML::T
+wig::Elt
my $frame = $doc->make_frame(136,10,57,50); # x y width height
+in mm
$body->add_p("A sentence in the default style");
$body->add_p(); # blank paragraph
$body->add_p("Some big text in style P1",'P1');
my $frame = $doc->make_frame(122,0,57,33); # x y width height
$frame->add_p("Text inside the frame",'P2');
$frame->add_p([$doc->make_span("embedded bold","T1")," Text"],'P2'
+);
$frame->add_p("Some text \t\twith tabs",'P2');
$body->add_p($frame);
my $picture = $doc->make_picture('picture.png',92,30,25,26); #name
+,x,y,w,h
$body->add_p($picture);
$s->save('mydocument.sxw');
=head1 DESCRIPTION
This is about the bare minimum needed to write out Open Office Tex
+t files.
All the 'public' methods are shown in the synopsis. Many style par
+ameters
are available, which can be found by downloading the definitive .s
+xw
specification from http://xml.openoffice.org/xml_specification.pdf
+ (1.4M),
or more simply by having a look in an existing .sxw :)
The body of the document, and all inserted elements, are a subclas
+s of
XML::Twig::Elt, so all its methods are available if needed. Frames
+ and
pictures need to be placed inside a paragraph to be displayed for
+reasons
not yet clear to me (but may be once I finish reading the 1.4M spe
+c :)) -
there *is* a simple 'add' method (which will paste() a Twig direct
+ly), but
for the moment, elements need to be add_p()'ed instead.
Adding a picture will copy the file into the .sxw file on saving
(a zip file, but no compression is done on pictures - it seemed
unnecessary). It uses File::MMagic to autodetect the MIME type for
+
simplicity's sake, but it'd be simple to remove this and replace w
+ith
a passed parameter if so wished.
=head1 TODO
Lots. It works, but that's about it :) There's a couple of methods
in there that are waiting for a use - add_h(),add_fstyle() etc. -
+they
all work, but I haven't inserted all the right document headers ye
+t to
utilise them, simply because I didn't need them yet. Damm this laz
+iness.
It only does single pages too atm...
Feel free to suggest improvements / send me bits of code / whateve
+r.
=head1 LICENSE
Same terms as Perl itself yada yada yada
=head1 AUTHOR
Ben Daglish
|