#----------------------------------------------------------------------------- # # $Id : Styles.pm 2.015 2005-10-10 JMG$ # # Initial developer: Jean-Marie Gouarne # Copyright 2005 by Genicorp, S.A. (www.genicorp.com) # License: # - Licence Publique Generale Genicorp v1.0 # - GNU Lesser General Public License v2.1 # #----------------------------------------------------------------------------- package OpenOffice::OODoc::Styles; use 5.006_001; our $VERSION = 2.015; use OpenOffice::OODoc::XPath 2.206; use File::Basename; require Exporter; our @ISA = qw ( Exporter OpenOffice::OODoc::XPath ); our @EXPORT = qw ( ooLoadColorMap oo2rgb rgb2oo ); #----------------------------------------------------------------------------- our $COLORMAP = undef; our %COLORMAP = ( 'red' => '255,0,0', 'green' => '0,255,0', 'blue' => '0,0,255', 'white' => '255,255,255', 'black' => '0,0,0', 'brown' => '165,42,42', 'cyan' => '0,255,255', 'grey' => '190,190,190', 'magenta' => '255,0,255', 'orange' => '255,165,0', 'pink' => '255,192,203', 'violet' => '238,130,238', 'yellow' => '255,255,0' ); #----------------------------------------------------------------------------- # loading a color map from an external file # the file format must be "%d %d %d %s" sub ooLoadColorMap { my $filename = shift || $COLORMAP or return undef; unless ( -e $filename && -r $filename ) { warn "[" . __PACKAGE__ . "::ooLoadColorMap] " . "Color map file non existent or unreadable\n"; return undef; } my $r = open COLORS, "<", $filename; unless ($r) { warn "[" . __PACKAGE__ . "::ooLoadColorMap] " . "Error opening $filename\n"; return undef; } while (my $line = ) { $line =~ s/^\s*//; $line =~ s/\s*$//; next unless $line =~ /^[0-9]/; $line =~ /(\d*)\s*(\d*)\s*(\d*)\s*(.*)/; my $name = $4; $COLORMAP{$name} = "$1,$2,$3" if $name; } close COLORS; return 1; } #----------------------------------------------------------------------------- # converting an hexadecimal OOo color code to decimal RGB sub oo2rgb { my $hexcolor = shift; return undef unless $hexcolor; return undef unless $hexcolor =~ /^#[0-9A-Fa-f]{6}$/; $hexcolor =~ /#(..)(..)(..)/; my ($red, $green, $blue) = ($1, $2, $3); my @rgb = (hex($red), hex($green), hex($blue)); if (wantarray) { return @rgb; } else { my $color = join(",", @rgb); foreach my $k (keys %COLORMAP) { return $k if ($COLORMAP{$k} eq $color); } return $color; } } #----------------------------------------------------------------------------- # converting a decimal RGB expression to an hexadecimal OOo color sub rgb2oo { my $colour = shift; my ($red, $green, $blue); if ($colour =~ /,/) { $colour =~ s/ //g; ($red, $green, $blue) = split(",", $colour); } elsif ($colour =~ /^[a-zA-Z]/) { if (defined $COLORMAP{$colour}) { ($red, $green, $blue) = split(",", $COLORMAP{$colour}); } else { return undef; } } elsif ($colour =~ /^#/) { my $rgb = oo2rgb($colour); return undef unless $rgb; my $hexrgb = rgb2oo($rgb); return undef unless $hexrgb; return (lc $hexrgb eq lc $colour) ? $colour : undef; } else { $red = $colour; ($green, $blue) = @_; } return sprintf("#%02x%02x%02x", $red, $green, $blue); } #----------------------------------------------------------------------------- package OpenOffice::OODoc::Element; #----------------------------------------------------------------------------- sub isStyle { my $element = shift; my $fullname = $element->getName; my ($prefix, $name) = split ':', $fullname; return ( $prefix && (($prefix eq 'style') || ($prefix eq 'number')) && $name && ($name ne 'properties') ) ? 1 : undef; } sub isMasterPage { my $element = shift; return ( $element->isElementNode && $element->getName eq 'style:master-page' ) ? 1 : undef; } #----------------------------------------------------------------------------- package OpenOffice::OODoc::Styles; #----------------------------------------------------------------------------- # constructor sub new { my $caller = shift; my $class = ref($caller) || $caller; my %options = ( member => 'styles', # XML member @_ ); my $object = $class->SUPER::new(%options); return $object ? bless $object, $class : undef; } #----------------------------------------------------------------------------- # get the tag name of the "properties" style sub-element sub _properties_tagname { my $self = shift; my $element = shift; my $part = shift; my $prefix = $element->getPrefix; if ($prefix eq 'number') { return 'number:number'; } elsif ($self->{'opendocument'}) { unless ($part) { $part = $element->att('style:family'); } return $part ? $prefix . ':' . $part . '-properties' : $element->name() . '-properties'; } else { return 'style:properties'; } } #----------------------------------------------------------------------------- # get the path of an individual style property node sub _get_property_path { my $self = shift; my $element = shift; my $nodename = shift; my $part = shift; if (($nodename eq 'header') || ($nodename eq 'footer')) { return 'style:' . $nodename . '-style/style:properties'; } my $path = $self->_properties_tagname($element, $part); $path .= ('/' . $nodename) if $nodename; return $path; } #----------------------------------------------------------------------------- # get a particular node in a main style element sub getStyleNode { my $self = shift; my $element = shift; my $nodename = shift; my $xpath = $self->_get_property_path($element, $nodename); return $self->getNodeByXPath($element, $xpath); } #----------------------------------------------------------------------------- # create the path for a particular node in a main style element sub setStyleNode { my $self = shift; my $element = shift; my $nodename = shift; my $xpath = $self->_get_property_path($element, $nodename); return $self->makeXPath($element, $xpath); } #----------------------------------------------------------------------------- # get named styles root element sub getNamedStyleRoot { my $self = shift; return $self->getElement($self->{'named_style_path'}, 0); } #----------------------------------------------------------------------------- # get automatic styles root element sub getAutoStyleRoot { my $self = shift; return $self->getElement($self->{'auto_style_path'}, 0); } #----------------------------------------------------------------------------- # get master styles root element sub getMasterStyleRoot { my $self = shift; return $self->getElement($self->{'master_style_path'}, 0); } #----------------------------------------------------------------------------- # get the root of font declarations sub getFontDeclarationBody { my $self = shift; my $path = $self->{'opendocument'} ? '//office:font-face-decls' : '//office:font-decls'; return $self->getElement($path, 0); } #----------------------------------------------------------------------------- # get a font declaration element sub getFontDeclaration { my $self = shift; my $font = shift or return undef; my $tag = $self->{'opendocument'} ? "style:font-face" : "style:font-decl"; if (ref $font) { my $n = $font->name; if ($n && ($n eq $tag)) { return $font; } else { warn "[" . __PACKAGE__ . "::getFontDeclaration] " . "Invalid font declaration element\n"; return undef; } } else { my $context = $self->getFontDeclarationBody; my $path = "//$tag\[\@style:name=\"$font\"]"; my $font_element = $self->getNodeByXPath($context, $path); unless ($font_element) { warn "[" . __PACKAGE__ . "::getFontDeclaration] " . "Unknown font name\n"; return undef; } return $font_element; } } #----------------------------------------------------------------------------- # imports a copy of an existing font declaration sub importFontDeclaration { my $self = shift; my $p1 = shift or return undef; my $font_element = undef; if (ref $p1) { my $e = undef; if ($p1->isa('OpenOffice::OODoc::Styles')) { # copy from another document my $font_name = shift; $e = $p1->getFontDeclaration($font_name); } else { # replicate from the same document $e = $self->getFontDeclaration($p1); } $font_element = $e->copy if $e; } else { # from anything (we hope XML) $font_element = OpenOffice::OODoc::XPath::new_element($p1); } # check the element type $font_element = $self->getFontDeclaration($font_element); $font_element->paste_last_child($self->getFontDeclarationBody); return $font_element; } #----------------------------------------------------------------------------- # select a list of style elements matching a given attribute, value pair # $path may be 'auto' or 'named' to search only in automatic or named styles # without args, returns the full style list sub selectStyleElementsByAttribute { my $self = shift; my $attribute = shift; my $value = shift; my %opt = ( namespace => 'style', type => 'style', @_ ); my $path = $opt{'category'}; return $self->getStyleList unless ($attribute && $value); unless ($path) { return ($self->selectElementsByAttribute ( $self->{'named_style_path'} . "/$opt{'namespace'}:$opt{'type'}", $attribute, $value ) , $self->selectElementsByAttribute ( $self->{'auto_style_path'} . "/$opt{'namespace'}:$opt{'type'}", $attribute, $value ) ); } else { $path = lc $path; if ($path =~ /^named/) { $path = $self->{'named_style_path'}; } elsif ($path =~ /^auto/) { $path = $self->{'auto_style_path'}; } else { return undef; } return $self->selectElementsByAttribute ( "$path/$opt{'namespace'}:$opt{'type'}", $attribute, $value ); } } #----------------------------------------------------------------------------- # select a style element by name # $path may be 'auto' or 'named' to search only in automatic or named styles sub selectStyleElementByAttribute { my $self = shift; my $attribute = shift; my $value = shift; my %opt = ( namespace => 'style', type => 'style', @_ ); unless ($attribute) { warn "[" . __PACKAGE__ . "::selectStyleElementByAttribute] Missing attribute\n"; return undef; } my $path = $opt{'category'}; unless ($path) { return $self->selectElementByAttribute ( $self->{'named_style_path'} . '/style:style', $attribute, $value ) || $self->selectElementByAttribute ( $self->{'auto_style_path'} . '/style:style', $attribute, $value ); } else { $path = lc $path; if ($path =~ /^named/) { $path = $self->{'named_style_path'}; } elsif ($path =~ /^auto/) { $path = $self->{'auto_style_path'}; } else { return undef; } return $self->selectElementByAttribute ( "$path/$opt{'namespace'}:$opt{'type'}", $attribute, $value ) } } #----------------------------------------------------------------------------- sub selectStyleElementByName { my $self = shift; return $self->selectStyleElementByAttribute('style:name', @_); } #----------------------------------------------------------------------------- sub selectStyleElementByFamily { my $self = shift; return $self->selectStyleElementByAttribute('style:family', @_); } #----------------------------------------------------------------------------- sub selectStyleElementsByName { my $self = shift; return $self->selectStyleElementsByAttribute('style:name', @_); } #----------------------------------------------------------------------------- sub selectStyleElementsByFamily { my $self = shift; return $self->selectStyleElementsByAttribute('style:family', @_); } #----------------------------------------------------------------------------- # get style element by exact name # search for any type of style element # parameters: # path => # type =>